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(misspelt_flag, 77 reversed(sorted_flags)) 78 self.assertEqual(sorted_flags, suggestions) 79 80 81class GetCallingModuleTest(absltest.TestCase): 82 """Test whether we correctly determine the module which defines the flag.""" 83 84 def test_get_calling_module(self): 85 self.assertEqual(_helpers.get_calling_module(), sys.argv[0]) 86 self.assertEqual(module_foo.get_module_name(), 87 'absl.flags.tests.module_foo') 88 self.assertEqual(module_bar.get_module_name(), 89 'absl.flags.tests.module_bar') 90 91 # We execute the following exec statements for their side-effect 92 # (i.e., not raising an error). They emphasize the case that not 93 # all code resides in one of the imported modules: Python is a 94 # really dynamic language, where we can dynamically construct some 95 # code and execute it. 96 code = ('from absl.flags import _helpers\n' 97 'module_name = _helpers.get_calling_module()') 98 exec(code) # pylint: disable=exec-used 99 100 # Next two exec statements executes code with a global environment 101 # that is different from the global environment of any imported 102 # module. 103 exec(code, {}) # pylint: disable=exec-used 104 # vars(self) returns a dictionary corresponding to the symbol 105 # table of the self object. dict(...) makes a distinct copy of 106 # this dictionary, such that any new symbol definition by the 107 # exec-ed code (e.g., import flags, module_name = ...) does not 108 # affect the symbol table of self. 109 exec(code, dict(vars(self))) # pylint: disable=exec-used 110 111 # Next test is actually more involved: it checks not only that 112 # get_calling_module does not crash inside exec code, it also checks 113 # that it returns the expected value: the code executed via exec 114 # code is treated as being executed by the current module. We 115 # check it twice: first time by executing exec from the main 116 # module, second time by executing it from module_bar. 117 global_dict = {} 118 exec(code, global_dict) # pylint: disable=exec-used 119 self.assertEqual(global_dict['module_name'], 120 sys.argv[0]) 121 122 global_dict = {} 123 module_bar.execute_code(code, global_dict) 124 self.assertEqual(global_dict['module_name'], 125 'absl.flags.tests.module_bar') 126 127 def test_get_calling_module_with_iteritems_error(self): 128 # This test checks that get_calling_module is using 129 # sys.modules.items(), instead of .iteritems(). 130 orig_sys_modules = sys.modules 131 132 # Mock sys.modules: simulates error produced by importing a module 133 # in parallel with our iteration over sys.modules.iteritems(). 134 class SysModulesMock(dict): 135 136 def __init__(self, original_content): 137 dict.__init__(self, original_content) 138 139 def iteritems(self): 140 # Any dictionary method is fine, but not .iteritems(). 141 raise RuntimeError('dictionary changed size during iteration') 142 143 sys.modules = SysModulesMock(orig_sys_modules) 144 try: 145 # _get_calling_module should still work as expected: 146 self.assertEqual(_helpers.get_calling_module(), sys.argv[0]) 147 self.assertEqual(module_foo.get_module_name(), 148 'absl.flags.tests.module_foo') 149 finally: 150 sys.modules = orig_sys_modules 151 152 153if __name__ == '__main__': 154 absltest.main() 155