#!/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()