#!/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
from pathlib
import Path
import sys
import unittest
THIS_FILE = Path(__file__).resolve()
THIS_DIR = THIS_FILE.parent
sys.path.insert(
0, str(THIS_DIR.parent))
# 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()