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