1# Copyright 2017 The Abseil Authors. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Unittests for helpers module.""" 16 17import sys 18 19from absl.flags import _helpers 20from absl.flags.tests import module_bar 21from absl.flags.tests import module_foo 22from absl.testing import absltest 23 24 25class FlagSuggestionTest(absltest.TestCase): 26 27 def setUp(self): 28 self.longopts = [ 29 'fsplit-ivs-in-unroller=', 30 'fsplit-wide-types=', 31 'fstack-protector=', 32 'fstack-protector-all=', 33 'fstrict-aliasing=', 34 'fstrict-overflow=', 35 'fthread-jumps=', 36 'ftracer', 37 'ftree-bit-ccp', 38 'ftree-builtin-call-dce', 39 'ftree-ccp', 40 'ftree-ch'] 41 42 def test_damerau_levenshtein_id(self): 43 self.assertEqual(0, _helpers._damerau_levenshtein('asdf', 'asdf')) 44 45 def test_damerau_levenshtein_empty(self): 46 self.assertEqual(5, _helpers._damerau_levenshtein('', 'kites')) 47 self.assertEqual(6, _helpers._damerau_levenshtein('kitten', '')) 48 49 def test_damerau_levenshtein_commutative(self): 50 self.assertEqual(2, _helpers._damerau_levenshtein('kitten', 'kites')) 51 self.assertEqual(2, _helpers._damerau_levenshtein('kites', 'kitten')) 52 53 def test_damerau_levenshtein_transposition(self): 54 self.assertEqual(1, _helpers._damerau_levenshtein('kitten', 'ktiten')) 55 56 def test_mispelled_suggestions(self): 57 suggestions = _helpers.get_flag_suggestions('fstack_protector_all', 58 self.longopts) 59 self.assertEqual(['fstack-protector-all'], suggestions) 60 61 def test_ambiguous_prefix_suggestion(self): 62 suggestions = _helpers.get_flag_suggestions('fstack', self.longopts) 63 self.assertEqual(['fstack-protector', 'fstack-protector-all'], suggestions) 64 65 def test_misspelled_ambiguous_prefix_suggestion(self): 66 suggestions = _helpers.get_flag_suggestions('stack', self.longopts) 67 self.assertEqual(['fstack-protector', 'fstack-protector-all'], suggestions) 68 69 def test_crazy_suggestion(self): 70 suggestions = _helpers.get_flag_suggestions('asdfasdgasdfa', self.longopts) 71 self.assertEqual([], suggestions) 72 73 def test_suggestions_are_sorted(self): 74 sorted_flags = sorted(['aab', 'aac', 'aad']) 75 misspelt_flag = 'aaa' 76 suggestions = _helpers.get_flag_suggestions( 77 misspelt_flag, list(reversed(sorted_flags)) 78 ) 79 self.assertEqual(sorted_flags, suggestions) 80 81 82class GetCallingModuleTest(absltest.TestCase): 83 """Test whether we correctly determine the module which defines the flag.""" 84 85 def test_get_calling_module(self): 86 self.assertEqual(_helpers.get_calling_module(), sys.argv[0]) 87 self.assertEqual(module_foo.get_module_name(), 88 'absl.flags.tests.module_foo') 89 self.assertEqual(module_bar.get_module_name(), 90 'absl.flags.tests.module_bar') 91 92 # We execute the following exec statements for their side-effect 93 # (i.e., not raising an error). They emphasize the case that not 94 # all code resides in one of the imported modules: Python is a 95 # really dynamic language, where we can dynamically construct some 96 # code and execute it. 97 code = ('from absl.flags import _helpers\n' 98 'module_name = _helpers.get_calling_module()') 99 exec(code) # pylint: disable=exec-used 100 101 # Next two exec statements executes code with a global environment 102 # that is different from the global environment of any imported 103 # module. 104 exec(code, {}) # pylint: disable=exec-used 105 # vars(self) returns a dictionary corresponding to the symbol 106 # table of the self object. dict(...) makes a distinct copy of 107 # this dictionary, such that any new symbol definition by the 108 # exec-ed code (e.g., import flags, module_name = ...) does not 109 # affect the symbol table of self. 110 exec(code, dict(vars(self))) # pylint: disable=exec-used 111 112 # Next test is actually more involved: it checks not only that 113 # get_calling_module does not crash inside exec code, it also checks 114 # that it returns the expected value: the code executed via exec 115 # code is treated as being executed by the current module. We 116 # check it twice: first time by executing exec from the main 117 # module, second time by executing it from module_bar. 118 global_dict = {} 119 exec(code, global_dict) # pylint: disable=exec-used 120 self.assertEqual(global_dict['module_name'], 121 sys.argv[0]) 122 123 global_dict = {} 124 module_bar.execute_code(code, global_dict) 125 self.assertEqual(global_dict['module_name'], 126 'absl.flags.tests.module_bar') 127 128 def test_get_calling_module_with_iteritems_error(self): 129 # This test checks that get_calling_module is using 130 # sys.modules.items(), instead of .iteritems(). 131 orig_sys_modules = sys.modules 132 133 # Mock sys.modules: simulates error produced by importing a module 134 # in parallel with our iteration over sys.modules.iteritems(). 135 class SysModulesMock(dict): 136 137 def __init__(self, original_content): 138 dict.__init__(self, original_content) 139 140 def iteritems(self): 141 # Any dictionary method is fine, but not .iteritems(). 142 raise RuntimeError('dictionary changed size during iteration') 143 144 sys.modules = SysModulesMock(orig_sys_modules) 145 try: 146 # _get_calling_module should still work as expected: 147 self.assertEqual(_helpers.get_calling_module(), sys.argv[0]) 148 self.assertEqual(module_foo.get_module_name(), 149 'absl.flags.tests.module_foo') 150 finally: 151 sys.modules = orig_sys_modules 152 153 154if __name__ == '__main__': 155 absltest.main() 156