xref: /aosp_15_r20/tools/repohooks/rh/terminal_unittest.py (revision d68f33bc6fb0cc2476107c2af0573a2f5a63dfc1)
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