xref: /aosp_15_r20/tools/repohooks/rh/shell_unittest.py (revision d68f33bc6fb0cc2476107c2af0573a2f5a63dfc1)
1#!/usr/bin/env python3
2# Copyright 2016 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""Unittests for the shell module."""
17
18import difflib
19import os
20from pathlib import Path
21import sys
22import unittest
23
24_path = os.path.realpath(__file__ + '/../..')
25if sys.path[0] != _path:
26    sys.path.insert(0, _path)
27del _path
28
29# We have to import our local modules after the sys.path tweak.  We can't use
30# relative imports because this is an executable program, not a module.
31# pylint: disable=wrong-import-position
32import rh.shell
33
34
35class DiffTestCase(unittest.TestCase):
36    """Helper that includes diff output when failing."""
37
38    def setUp(self):
39        self.differ = difflib.Differ()
40
41    def _assertEqual(self, func, test_input, test_output, result):
42        """Like assertEqual but with built in diff support."""
43        diff = '\n'.join(list(self.differ.compare([test_output], [result])))
44        msg = (f'Expected {func} to translate {test_input!r} to '
45               f'{test_output!r}, but got {result!r}\n{diff}')
46        self.assertEqual(test_output, result, msg)
47
48    def _testData(self, functor, tests, check_type=True):
49        """Process a dict of test data."""
50        for test_output, test_input in tests.items():
51            result = functor(test_input)
52            self._assertEqual(functor.__name__, test_input, test_output, result)
53
54            if check_type:
55                # Also make sure the result is a string, otherwise the %r
56                # output will include a "u" prefix and that is not good for
57                # logging.
58                self.assertEqual(type(test_output), str)
59
60
61class ShellQuoteTest(DiffTestCase):
62    """Test the quote & unquote functions."""
63
64    def testShellQuote(self):
65        """Basic ShellQuote tests."""
66        # Dict of expected output strings to input lists.
67        tests_quote = {
68            "''": '',
69            'a': 'a',
70            "'a b c'": 'a b c',
71            "'a\tb'": 'a\tb',
72            "'/a$file'": '/a$file',
73            "'/a#file'": '/a#file',
74            """'b"c'""": 'b"c',
75            "'a@()b'": 'a@()b',
76            'j%k': 'j%k',
77            r'''"s'a\$va\\rs"''': r"s'a$va\rs",
78            r'''"\\'\\\""''': r'''\'\"''',
79            r'''"'\\\$"''': r"""'\$""",
80        }
81
82        # Expected input output specific to ShellUnquote.  This string cannot
83        # be produced by ShellQuote but is still a valid bash escaped string.
84        tests_unquote = {
85            r'''\$''': r'''"\\$"''',
86        }
87
88        def aux(s):
89            return rh.shell.unquote(rh.shell.quote(s))
90
91        self._testData(rh.shell.quote, tests_quote)
92        self._testData(rh.shell.unquote, tests_unquote)
93
94        # Test that the operations are reversible.
95        self._testData(aux, {k: k for k in tests_quote.values()}, False)
96        self._testData(aux, {k: k for k in tests_quote}, False)
97
98    def testPathlib(self):
99        """Verify pathlib is handled."""
100        self.assertEqual(rh.shell.quote(Path('/')), '/')
101
102    def testBadInputs(self):
103        """Verify bad inputs do not crash."""
104        for arg, exp in (
105            (1234, '1234'),
106            (Exception('hi'), "Exception('hi')"),
107        ):
108            self.assertEqual(rh.shell.quote(arg), exp)
109
110
111class CmdToStrTest(DiffTestCase):
112    """Test the cmd_to_str function."""
113
114    def testCmdToStr(self):
115        # Dict of expected output strings to input lists.
116        tests = {
117            r"a b": ['a', 'b'],
118            r"'a b' c": ['a b', 'c'],
119            r'''a "b'c"''': ['a', "b'c"],
120            r'''a "/'\$b" 'a b c' "xy'z"''':
121                ['a', "/'$b", 'a b c', "xy'z"],
122            '': [],
123        }
124        self._testData(rh.shell.cmd_to_str, tests)
125
126
127class BooleanShellTest(unittest.TestCase):
128    """Test the boolean_shell_value function."""
129
130    def testFull(self):
131        """Verify nputs work as expected"""
132        for v in (None,):
133            self.assertTrue(rh.shell.boolean_shell_value(v, True))
134            self.assertFalse(rh.shell.boolean_shell_value(v, False))
135
136        for v in (1234, '', 'akldjsf', '"'):
137            self.assertRaises(ValueError, rh.shell.boolean_shell_value, v, True)
138
139        for v in ('yes', 'YES', 'YeS', 'y', 'Y', '1', 'true', 'True', 'TRUE',):
140            self.assertTrue(rh.shell.boolean_shell_value(v, True))
141            self.assertTrue(rh.shell.boolean_shell_value(v, False))
142
143        for v in ('no', 'NO', 'nO', 'n', 'N', '0', 'false', 'False', 'FALSE',):
144            self.assertFalse(rh.shell.boolean_shell_value(v, True))
145            self.assertFalse(rh.shell.boolean_shell_value(v, False))
146
147
148if __name__ == '__main__':
149    unittest.main()
150