blob: 9f67bba4a3b7778c3c4f36a404afecc9312da699 [file] [log] [blame] [edit]
#!/usr/bin/env python3
# Copyright 2023 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittests for the terminal module."""
import contextlib
import io
import os
import sys
import unittest
_path = os.path.realpath(__file__ + "/../..")
if sys.path[0] != _path:
sys.path.insert(0, _path)
del _path
# We have to import our local modules after the sys.path tweak. We can't use
# relative imports because this is an executable program, not a module.
# pylint: disable=wrong-import-position
import rh.terminal
class ColorTests(unittest.TestCase):
"""Verify behavior of Color class."""
def setUp(self):
os.environ.pop("NOCOLOR", None)
def test_enabled_auto_tty(self):
"""Test automatic enable behavior based on tty."""
stderr = io.StringIO()
with contextlib.redirect_stderr(stderr):
c = rh.terminal.Color()
self.assertFalse(c.enabled)
stderr.isatty = lambda: True
c = rh.terminal.Color()
self.assertTrue(c.enabled)
def test_enabled_auto_env(self):
"""Test automatic enable behavior based on $NOCOLOR."""
stderr = io.StringIO()
with contextlib.redirect_stderr(stderr):
os.environ["NOCOLOR"] = "yes"
c = rh.terminal.Color()
self.assertFalse(c.enabled)
os.environ["NOCOLOR"] = "no"
c = rh.terminal.Color()
self.assertTrue(c.enabled)
def test_enabled_override(self):
"""Test explicit enable behavior."""
stderr = io.StringIO()
with contextlib.redirect_stderr(stderr):
stderr.isatty = lambda: True
os.environ["NOCOLOR"] = "no"
c = rh.terminal.Color()
self.assertTrue(c.enabled)
c = rh.terminal.Color(False)
self.assertFalse(c.enabled)
stderr.isatty = lambda: False
os.environ["NOCOLOR"] = "yes"
c = rh.terminal.Color()
self.assertFalse(c.enabled)
c = rh.terminal.Color(True)
self.assertTrue(c.enabled)
def test_output_disabled(self):
"""Test output when coloring is disabled."""
c = rh.terminal.Color(False)
self.assertEqual(c.start(rh.terminal.Color.BLACK), "")
self.assertEqual(c.color(rh.terminal.Color.BLACK, "foo"), "foo")
self.assertEqual(c.stop(), "")
def test_output_enabled(self):
"""Test output when coloring is enabled."""
c = rh.terminal.Color(True)
self.assertEqual(c.start(rh.terminal.Color.BLACK), "\x1b[1;30m")
self.assertEqual(
c.color(rh.terminal.Color.BLACK, "foo"), "\x1b[1;30mfoo\x1b[m"
)
self.assertEqual(c.stop(), "\x1b[m")
class PrintStatusLine(unittest.TestCase):
"""Verify behavior of print_status_line."""
def test_terminal(self):
"""Check tty behavior."""
stderr = io.StringIO()
stderr.isatty = lambda: True
with contextlib.redirect_stderr(stderr):
rh.terminal.print_status_line("foo")
rh.terminal.print_status_line("bar", print_newline=True)
csi = rh.terminal.CSI_ERASE_LINE_AFTER
self.assertEqual(stderr.getvalue(), f"\rfoo{csi}\rbar{csi}\n")
def test_no_terminal(self):
"""Check tty-less behavior."""
stderr = io.StringIO()
with contextlib.redirect_stderr(stderr):
rh.terminal.print_status_line("foo")
rh.terminal.print_status_line("bar", print_newline=True)
self.assertEqual(stderr.getvalue(), "foo\nbar\n")
@contextlib.contextmanager
def redirect_stdin(new_target):
"""Temporarily switch sys.stdin to |new_target|."""
old = sys.stdin
try:
sys.stdin = new_target
yield
finally:
sys.stdin = old
class StringPromptTests(unittest.TestCase):
"""Verify behavior of str_prompt."""
def setUp(self):
self.stdin = io.StringIO()
def set_stdin(self, value: str) -> None:
"""Set stdin wrapper to a string."""
self.stdin.seek(0)
self.stdin.write(value)
self.stdin.truncate()
self.stdin.seek(0)
def test_defaults(self):
"""Test default behavior."""
stdout = io.StringIO()
with redirect_stdin(self.stdin), contextlib.redirect_stdout(stdout):
# Test EOF behavior.
self.assertIsNone(rh.terminal.str_prompt("foo", ("a", "b")))
# Test enter behavior.
self.set_stdin("\n")
self.assertEqual(rh.terminal.str_prompt("foo", ("a", "b")), "")
# Lowercase inputs.
self.set_stdin("Ok")
self.assertEqual(rh.terminal.str_prompt("foo", ("a", "b")), "ok")
# Don't lowercase inputs.
self.set_stdin("Ok")
self.assertEqual(
rh.terminal.str_prompt("foo", ("a", "b"), lower=False), "Ok"
)
class BooleanPromptTests(unittest.TestCase):
"""Verify behavior of boolean_prompt."""
def setUp(self):
self.stdin = io.StringIO()
def set_stdin(self, value: str) -> None:
"""Set stdin wrapper to a string."""
self.stdin.seek(0)
self.stdin.write(value)
self.stdin.truncate()
self.stdin.seek(0)
def test_defaults(self):
"""Test default behavior."""
stdout = io.StringIO()
with redirect_stdin(self.stdin), contextlib.redirect_stdout(stdout):
# Default values. Will loop to EOF when it doesn't match anything.
for v in ("", "\n", "oops"):
self.set_stdin(v)
self.assertTrue(rh.terminal.boolean_prompt())
# False values.
for v in ("n", "N", "no", "NO"):
self.set_stdin(v)
self.assertFalse(rh.terminal.boolean_prompt())
# True values.
for v in ("y", "Y", "ye", "yes", "YES"):
self.set_stdin(v)
self.assertTrue(rh.terminal.boolean_prompt())
if __name__ == "__main__":
unittest.main()