1*d68f33bcSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*d68f33bcSAndroid Build Coastguard Worker# Copyright 2023 The Android Open Source Project 3*d68f33bcSAndroid Build Coastguard Worker# 4*d68f33bcSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 5*d68f33bcSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 6*d68f33bcSAndroid Build Coastguard Worker# You may obtain a copy of the License at 7*d68f33bcSAndroid Build Coastguard Worker# 8*d68f33bcSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 9*d68f33bcSAndroid Build Coastguard Worker# 10*d68f33bcSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 11*d68f33bcSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 12*d68f33bcSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*d68f33bcSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 14*d68f33bcSAndroid Build Coastguard Worker# limitations under the License. 15*d68f33bcSAndroid Build Coastguard Worker 16*d68f33bcSAndroid Build Coastguard Worker"""Unittests for the terminal module.""" 17*d68f33bcSAndroid Build Coastguard Worker 18*d68f33bcSAndroid Build Coastguard Workerimport contextlib 19*d68f33bcSAndroid Build Coastguard Workerimport io 20*d68f33bcSAndroid Build Coastguard Workerimport os 21*d68f33bcSAndroid Build Coastguard Workerimport sys 22*d68f33bcSAndroid Build Coastguard Workerimport unittest 23*d68f33bcSAndroid Build Coastguard Worker 24*d68f33bcSAndroid Build Coastguard Worker_path = os.path.realpath(__file__ + '/../..') 25*d68f33bcSAndroid Build Coastguard Workerif sys.path[0] != _path: 26*d68f33bcSAndroid Build Coastguard Worker sys.path.insert(0, _path) 27*d68f33bcSAndroid Build Coastguard Workerdel _path 28*d68f33bcSAndroid Build Coastguard Worker 29*d68f33bcSAndroid Build Coastguard Worker# We have to import our local modules after the sys.path tweak. We can't use 30*d68f33bcSAndroid Build Coastguard Worker# relative imports because this is an executable program, not a module. 31*d68f33bcSAndroid Build Coastguard Worker# pylint: disable=wrong-import-position 32*d68f33bcSAndroid Build Coastguard Workerimport rh.terminal 33*d68f33bcSAndroid Build Coastguard Worker 34*d68f33bcSAndroid Build Coastguard Worker 35*d68f33bcSAndroid Build Coastguard Workerclass ColorTests(unittest.TestCase): 36*d68f33bcSAndroid Build Coastguard Worker """Verify behavior of Color class.""" 37*d68f33bcSAndroid Build Coastguard Worker 38*d68f33bcSAndroid Build Coastguard Worker def setUp(self): 39*d68f33bcSAndroid Build Coastguard Worker os.environ.pop('NOCOLOR', None) 40*d68f33bcSAndroid Build Coastguard Worker 41*d68f33bcSAndroid Build Coastguard Worker def test_enabled_auto_tty(self): 42*d68f33bcSAndroid Build Coastguard Worker """Test automatic enable behavior based on tty.""" 43*d68f33bcSAndroid Build Coastguard Worker stderr = io.StringIO() 44*d68f33bcSAndroid Build Coastguard Worker with contextlib.redirect_stderr(stderr): 45*d68f33bcSAndroid Build Coastguard Worker c = rh.terminal.Color() 46*d68f33bcSAndroid Build Coastguard Worker self.assertFalse(c.enabled) 47*d68f33bcSAndroid Build Coastguard Worker 48*d68f33bcSAndroid Build Coastguard Worker stderr.isatty = lambda: True 49*d68f33bcSAndroid Build Coastguard Worker c = rh.terminal.Color() 50*d68f33bcSAndroid Build Coastguard Worker self.assertTrue(c.enabled) 51*d68f33bcSAndroid Build Coastguard Worker 52*d68f33bcSAndroid Build Coastguard Worker def test_enabled_auto_env(self): 53*d68f33bcSAndroid Build Coastguard Worker """Test automatic enable behavior based on $NOCOLOR.""" 54*d68f33bcSAndroid Build Coastguard Worker stderr = io.StringIO() 55*d68f33bcSAndroid Build Coastguard Worker with contextlib.redirect_stderr(stderr): 56*d68f33bcSAndroid Build Coastguard Worker os.environ['NOCOLOR'] = 'yes' 57*d68f33bcSAndroid Build Coastguard Worker c = rh.terminal.Color() 58*d68f33bcSAndroid Build Coastguard Worker self.assertFalse(c.enabled) 59*d68f33bcSAndroid Build Coastguard Worker 60*d68f33bcSAndroid Build Coastguard Worker os.environ['NOCOLOR'] = 'no' 61*d68f33bcSAndroid Build Coastguard Worker c = rh.terminal.Color() 62*d68f33bcSAndroid Build Coastguard Worker self.assertTrue(c.enabled) 63*d68f33bcSAndroid Build Coastguard Worker 64*d68f33bcSAndroid Build Coastguard Worker def test_enabled_override(self): 65*d68f33bcSAndroid Build Coastguard Worker """Test explicit enable behavior.""" 66*d68f33bcSAndroid Build Coastguard Worker stderr = io.StringIO() 67*d68f33bcSAndroid Build Coastguard Worker with contextlib.redirect_stderr(stderr): 68*d68f33bcSAndroid Build Coastguard Worker stderr.isatty = lambda: True 69*d68f33bcSAndroid Build Coastguard Worker os.environ['NOCOLOR'] = 'no' 70*d68f33bcSAndroid Build Coastguard Worker c = rh.terminal.Color() 71*d68f33bcSAndroid Build Coastguard Worker self.assertTrue(c.enabled) 72*d68f33bcSAndroid Build Coastguard Worker c = rh.terminal.Color(False) 73*d68f33bcSAndroid Build Coastguard Worker self.assertFalse(c.enabled) 74*d68f33bcSAndroid Build Coastguard Worker 75*d68f33bcSAndroid Build Coastguard Worker stderr.isatty = lambda: False 76*d68f33bcSAndroid Build Coastguard Worker os.environ['NOCOLOR'] = 'yes' 77*d68f33bcSAndroid Build Coastguard Worker c = rh.terminal.Color() 78*d68f33bcSAndroid Build Coastguard Worker self.assertFalse(c.enabled) 79*d68f33bcSAndroid Build Coastguard Worker c = rh.terminal.Color(True) 80*d68f33bcSAndroid Build Coastguard Worker self.assertTrue(c.enabled) 81*d68f33bcSAndroid Build Coastguard Worker 82*d68f33bcSAndroid Build Coastguard Worker def test_output_disabled(self): 83*d68f33bcSAndroid Build Coastguard Worker """Test output when coloring is disabled.""" 84*d68f33bcSAndroid Build Coastguard Worker c = rh.terminal.Color(False) 85*d68f33bcSAndroid Build Coastguard Worker self.assertEqual(c.start(rh.terminal.Color.BLACK), '') 86*d68f33bcSAndroid Build Coastguard Worker self.assertEqual(c.color(rh.terminal.Color.BLACK, 'foo'), 'foo') 87*d68f33bcSAndroid Build Coastguard Worker self.assertEqual(c.stop(), '') 88*d68f33bcSAndroid Build Coastguard Worker 89*d68f33bcSAndroid Build Coastguard Worker def test_output_enabled(self): 90*d68f33bcSAndroid Build Coastguard Worker """Test output when coloring is enabled.""" 91*d68f33bcSAndroid Build Coastguard Worker c = rh.terminal.Color(True) 92*d68f33bcSAndroid Build Coastguard Worker self.assertEqual(c.start(rh.terminal.Color.BLACK), '\x1b[1;30m') 93*d68f33bcSAndroid Build Coastguard Worker self.assertEqual(c.color(rh.terminal.Color.BLACK, 'foo'), 94*d68f33bcSAndroid Build Coastguard Worker '\x1b[1;30mfoo\x1b[m') 95*d68f33bcSAndroid Build Coastguard Worker self.assertEqual(c.stop(), '\x1b[m') 96*d68f33bcSAndroid Build Coastguard Worker 97*d68f33bcSAndroid Build Coastguard Worker 98*d68f33bcSAndroid Build Coastguard Workerclass PrintStatusLine(unittest.TestCase): 99*d68f33bcSAndroid Build Coastguard Worker """Verify behavior of print_status_line.""" 100*d68f33bcSAndroid Build Coastguard Worker 101*d68f33bcSAndroid Build Coastguard Worker def test_terminal(self): 102*d68f33bcSAndroid Build Coastguard Worker """Check tty behavior.""" 103*d68f33bcSAndroid Build Coastguard Worker stderr = io.StringIO() 104*d68f33bcSAndroid Build Coastguard Worker stderr.isatty = lambda: True 105*d68f33bcSAndroid Build Coastguard Worker with contextlib.redirect_stderr(stderr): 106*d68f33bcSAndroid Build Coastguard Worker rh.terminal.print_status_line('foo') 107*d68f33bcSAndroid Build Coastguard Worker rh.terminal.print_status_line('bar', print_newline=True) 108*d68f33bcSAndroid Build Coastguard Worker csi = rh.terminal.CSI_ERASE_LINE_AFTER 109*d68f33bcSAndroid Build Coastguard Worker self.assertEqual(stderr.getvalue(), f'\rfoo{csi}\rbar{csi}\n') 110*d68f33bcSAndroid Build Coastguard Worker 111*d68f33bcSAndroid Build Coastguard Worker def test_no_terminal(self): 112*d68f33bcSAndroid Build Coastguard Worker """Check tty-less behavior.""" 113*d68f33bcSAndroid Build Coastguard Worker stderr = io.StringIO() 114*d68f33bcSAndroid Build Coastguard Worker with contextlib.redirect_stderr(stderr): 115*d68f33bcSAndroid Build Coastguard Worker rh.terminal.print_status_line('foo') 116*d68f33bcSAndroid Build Coastguard Worker rh.terminal.print_status_line('bar', print_newline=True) 117*d68f33bcSAndroid Build Coastguard Worker self.assertEqual(stderr.getvalue(), 'foo\nbar\n') 118*d68f33bcSAndroid Build Coastguard Worker 119*d68f33bcSAndroid Build Coastguard Worker 120*d68f33bcSAndroid Build Coastguard Worker@contextlib.contextmanager 121*d68f33bcSAndroid Build Coastguard Workerdef redirect_stdin(new_target): 122*d68f33bcSAndroid Build Coastguard Worker """Temporarily switch sys.stdin to |new_target|.""" 123*d68f33bcSAndroid Build Coastguard Worker old = sys.stdin 124*d68f33bcSAndroid Build Coastguard Worker try: 125*d68f33bcSAndroid Build Coastguard Worker sys.stdin = new_target 126*d68f33bcSAndroid Build Coastguard Worker yield 127*d68f33bcSAndroid Build Coastguard Worker finally: 128*d68f33bcSAndroid Build Coastguard Worker sys.stdin = old 129*d68f33bcSAndroid Build Coastguard Worker 130*d68f33bcSAndroid Build Coastguard Worker 131*d68f33bcSAndroid Build Coastguard Workerclass StringPromptTests(unittest.TestCase): 132*d68f33bcSAndroid Build Coastguard Worker """Verify behavior of str_prompt.""" 133*d68f33bcSAndroid Build Coastguard Worker 134*d68f33bcSAndroid Build Coastguard Worker def setUp(self): 135*d68f33bcSAndroid Build Coastguard Worker self.stdin = io.StringIO() 136*d68f33bcSAndroid Build Coastguard Worker 137*d68f33bcSAndroid Build Coastguard Worker def set_stdin(self, value: str) -> None: 138*d68f33bcSAndroid Build Coastguard Worker """Set stdin wrapper to a string.""" 139*d68f33bcSAndroid Build Coastguard Worker self.stdin.seek(0) 140*d68f33bcSAndroid Build Coastguard Worker self.stdin.write(value) 141*d68f33bcSAndroid Build Coastguard Worker self.stdin.truncate() 142*d68f33bcSAndroid Build Coastguard Worker self.stdin.seek(0) 143*d68f33bcSAndroid Build Coastguard Worker 144*d68f33bcSAndroid Build Coastguard Worker def test_defaults(self): 145*d68f33bcSAndroid Build Coastguard Worker """Test default behavior.""" 146*d68f33bcSAndroid Build Coastguard Worker stdout = io.StringIO() 147*d68f33bcSAndroid Build Coastguard Worker with redirect_stdin(self.stdin), contextlib.redirect_stdout(stdout): 148*d68f33bcSAndroid Build Coastguard Worker # Test EOF behavior. 149*d68f33bcSAndroid Build Coastguard Worker self.assertIsNone(rh.terminal.str_prompt('foo', ('a', 'b'))) 150*d68f33bcSAndroid Build Coastguard Worker 151*d68f33bcSAndroid Build Coastguard Worker # Test enter behavior. 152*d68f33bcSAndroid Build Coastguard Worker self.set_stdin('\n') 153*d68f33bcSAndroid Build Coastguard Worker self.assertEqual(rh.terminal.str_prompt('foo', ('a', 'b')), '') 154*d68f33bcSAndroid Build Coastguard Worker 155*d68f33bcSAndroid Build Coastguard Worker # Lowercase inputs. 156*d68f33bcSAndroid Build Coastguard Worker self.set_stdin('Ok') 157*d68f33bcSAndroid Build Coastguard Worker self.assertEqual(rh.terminal.str_prompt('foo', ('a', 'b')), 'ok') 158*d68f33bcSAndroid Build Coastguard Worker 159*d68f33bcSAndroid Build Coastguard Worker # Don't lowercase inputs. 160*d68f33bcSAndroid Build Coastguard Worker self.set_stdin('Ok') 161*d68f33bcSAndroid Build Coastguard Worker self.assertEqual( 162*d68f33bcSAndroid Build Coastguard Worker rh.terminal.str_prompt('foo', ('a', 'b'), lower=False), 'Ok') 163*d68f33bcSAndroid Build Coastguard Worker 164*d68f33bcSAndroid Build Coastguard Worker 165*d68f33bcSAndroid Build Coastguard Workerclass BooleanPromptTests(unittest.TestCase): 166*d68f33bcSAndroid Build Coastguard Worker """Verify behavior of boolean_prompt.""" 167*d68f33bcSAndroid Build Coastguard Worker 168*d68f33bcSAndroid Build Coastguard Worker def setUp(self): 169*d68f33bcSAndroid Build Coastguard Worker self.stdin = io.StringIO() 170*d68f33bcSAndroid Build Coastguard Worker 171*d68f33bcSAndroid Build Coastguard Worker def set_stdin(self, value: str) -> None: 172*d68f33bcSAndroid Build Coastguard Worker """Set stdin wrapper to a string.""" 173*d68f33bcSAndroid Build Coastguard Worker self.stdin.seek(0) 174*d68f33bcSAndroid Build Coastguard Worker self.stdin.write(value) 175*d68f33bcSAndroid Build Coastguard Worker self.stdin.truncate() 176*d68f33bcSAndroid Build Coastguard Worker self.stdin.seek(0) 177*d68f33bcSAndroid Build Coastguard Worker 178*d68f33bcSAndroid Build Coastguard Worker def test_defaults(self): 179*d68f33bcSAndroid Build Coastguard Worker """Test default behavior.""" 180*d68f33bcSAndroid Build Coastguard Worker stdout = io.StringIO() 181*d68f33bcSAndroid Build Coastguard Worker with redirect_stdin(self.stdin), contextlib.redirect_stdout(stdout): 182*d68f33bcSAndroid Build Coastguard Worker # Default values. Will loop to EOF when it doesn't match anything. 183*d68f33bcSAndroid Build Coastguard Worker for v in ('', '\n', 'oops'): 184*d68f33bcSAndroid Build Coastguard Worker self.set_stdin(v) 185*d68f33bcSAndroid Build Coastguard Worker self.assertTrue(rh.terminal.boolean_prompt()) 186*d68f33bcSAndroid Build Coastguard Worker 187*d68f33bcSAndroid Build Coastguard Worker # False values. 188*d68f33bcSAndroid Build Coastguard Worker for v in ('n', 'N', 'no', 'NO'): 189*d68f33bcSAndroid Build Coastguard Worker self.set_stdin(v) 190*d68f33bcSAndroid Build Coastguard Worker self.assertFalse(rh.terminal.boolean_prompt()) 191*d68f33bcSAndroid Build Coastguard Worker 192*d68f33bcSAndroid Build Coastguard Worker # True values. 193*d68f33bcSAndroid Build Coastguard Worker for v in ('y', 'Y', 'ye', 'yes', 'YES'): 194*d68f33bcSAndroid Build Coastguard Worker self.set_stdin(v) 195*d68f33bcSAndroid Build Coastguard Worker self.assertTrue(rh.terminal.boolean_prompt()) 196*d68f33bcSAndroid Build Coastguard Worker 197*d68f33bcSAndroid Build Coastguard Worker 198*d68f33bcSAndroid Build Coastguard Workerif __name__ == '__main__': 199*d68f33bcSAndroid Build Coastguard Worker unittest.main() 200