# Copyright 2017 The Abseil Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unittests for helpers module.""" import sys from absl.flags import _helpers from absl.flags.tests import module_bar from absl.flags.tests import module_foo from absl.testing import absltest class FlagSuggestionTest(absltest.TestCase): def setUp(self): self.longopts = [ 'fsplit-ivs-in-unroller=', 'fsplit-wide-types=', 'fstack-protector=', 'fstack-protector-all=', 'fstrict-aliasing=', 'fstrict-overflow=', 'fthread-jumps=', 'ftracer', 'ftree-bit-ccp', 'ftree-builtin-call-dce', 'ftree-ccp', 'ftree-ch'] def test_damerau_levenshtein_id(self): self.assertEqual(0, _helpers._damerau_levenshtein('asdf', 'asdf')) def test_damerau_levenshtein_empty(self): self.assertEqual(5, _helpers._damerau_levenshtein('', 'kites')) self.assertEqual(6, _helpers._damerau_levenshtein('kitten', '')) def test_damerau_levenshtein_commutative(self): self.assertEqual(2, _helpers._damerau_levenshtein('kitten', 'kites')) self.assertEqual(2, _helpers._damerau_levenshtein('kites', 'kitten')) def test_damerau_levenshtein_transposition(self): self.assertEqual(1, _helpers._damerau_levenshtein('kitten', 'ktiten')) def test_mispelled_suggestions(self): suggestions = _helpers.get_flag_suggestions('fstack_protector_all', self.longopts) self.assertEqual(['fstack-protector-all'], suggestions) def test_ambiguous_prefix_suggestion(self): suggestions = _helpers.get_flag_suggestions('fstack', self.longopts) self.assertEqual(['fstack-protector', 'fstack-protector-all'], suggestions) def test_misspelled_ambiguous_prefix_suggestion(self): suggestions = _helpers.get_flag_suggestions('stack', self.longopts) self.assertEqual(['fstack-protector', 'fstack-protector-all'], suggestions) def test_crazy_suggestion(self): suggestions = _helpers.get_flag_suggestions('asdfasdgasdfa', self.longopts) self.assertEqual([], suggestions) def test_suggestions_are_sorted(self): sorted_flags = sorted(['aab', 'aac', 'aad']) misspelt_flag = 'aaa' suggestions = _helpers.get_flag_suggestions(misspelt_flag, reversed(sorted_flags)) self.assertEqual(sorted_flags, suggestions) class GetCallingModuleTest(absltest.TestCase): """Test whether we correctly determine the module which defines the flag.""" def test_get_calling_module(self): self.assertEqual(_helpers.get_calling_module(), sys.argv[0]) self.assertEqual(module_foo.get_module_name(), 'absl.flags.tests.module_foo') self.assertEqual(module_bar.get_module_name(), 'absl.flags.tests.module_bar') # We execute the following exec statements for their side-effect # (i.e., not raising an error). They emphasize the case that not # all code resides in one of the imported modules: Python is a # really dynamic language, where we can dynamically construct some # code and execute it. code = ('from absl.flags import _helpers\n' 'module_name = _helpers.get_calling_module()') exec(code) # pylint: disable=exec-used # Next two exec statements executes code with a global environment # that is different from the global environment of any imported # module. exec(code, {}) # pylint: disable=exec-used # vars(self) returns a dictionary corresponding to the symbol # table of the self object. dict(...) makes a distinct copy of # this dictionary, such that any new symbol definition by the # exec-ed code (e.g., import flags, module_name = ...) does not # affect the symbol table of self. exec(code, dict(vars(self))) # pylint: disable=exec-used # Next test is actually more involved: it checks not only that # get_calling_module does not crash inside exec code, it also checks # that it returns the expected value: the code executed via exec # code is treated as being executed by the current module. We # check it twice: first time by executing exec from the main # module, second time by executing it from module_bar. global_dict = {} exec(code, global_dict) # pylint: disable=exec-used self.assertEqual(global_dict['module_name'], sys.argv[0]) global_dict = {} module_bar.execute_code(code, global_dict) self.assertEqual(global_dict['module_name'], 'absl.flags.tests.module_bar') def test_get_calling_module_with_iteritems_error(self): # This test checks that get_calling_module is using # sys.modules.items(), instead of .iteritems(). orig_sys_modules = sys.modules # Mock sys.modules: simulates error produced by importing a module # in parallel with our iteration over sys.modules.iteritems(). class SysModulesMock(dict): def __init__(self, original_content): dict.__init__(self, original_content) def iteritems(self): # Any dictionary method is fine, but not .iteritems(). raise RuntimeError('dictionary changed size during iteration') sys.modules = SysModulesMock(orig_sys_modules) try: # _get_calling_module should still work as expected: self.assertEqual(_helpers.get_calling_module(), sys.argv[0]) self.assertEqual(module_foo.get_module_name(), 'absl.flags.tests.module_foo') finally: sys.modules = orig_sys_modules if __name__ == '__main__': absltest.main()