def test_discards_rewrite_error(self): # If we knew how to trigger a rewrite error, we'd just fix the bug, so let's # make one up. fx = fixer.CombiningPythonFixer([_search_replace_fixer('a', 'a')]) with mock.patch.object(syntactic_template.PythonExprTemplate, 'substitute_match', side_effect=formatting.RewriteError, autospec=True): self.assertEqual(list(search.find_iter(fx, 'a', 'foo.py')), [])
def test_fixedpoint_insignificant_only(self): fx = fixer.CombiningPythonFixer([ _search_replace_fixer('a', 'b', url='merged', significant=False), _search_replace_fixer('b', 'c', url='merged', significant=False), ]) self.assertEqual( list(search.find_iter(fx, 'a', 'foo.py', max_iterations=10)), [ _substitution(significant=False, category='refex.merged.not-significant') ])
def test_replace(self): fix = fixer.SimplePythonFixer( matcher=syntax_matchers.ExprPattern('$obj.attr'), replacement=syntactic_template.PythonTemplate(u'$obj.other'), ) searcher = fixer.CombiningPythonFixer([fix]) source = 'my_obj.attr + other_obj.attr' self.assertEqual('my_obj.other + other_obj.other', search.rewrite_string(searcher, source, 'example.py'))
def test_fixedpoint_disjoint_merge(self): """Disjoint rewrites should be merged together when it helps.""" fx = fixer.CombiningPythonFixer([ _search_replace_fixer('a1', 'a2'), _search_replace_fixer('b1', 'b2'), _search_replace_fixer('a2, b2', 'final'), ]) self.assertEqual( list(search.find_iter(fx, 'a1, b1', 'foo.py', max_iterations=10)), [_substitution(replacements={'fixedpoint': 'final'})])
def test_doesnt_discard_unparseable_compound_stmt(self): fx = fixer.CombiningPythonFixer([ fixer.SimplePythonFixer(message='ouch', matcher=ast_matchers.For(), replacement=formatting.ShTemplate('x x x'), url='') ]) self.assertEqual( list(search.find_iter(fx, 'for x in y: pass', 'foo.py')), [_substitution(message='ouch')])
def test_fixedpoint_merged_url(self): fx = fixer.CombiningPythonFixer([ _search_replace_fixer('a', 'b', url='merged'), _search_replace_fixer('b', 'c', url='merged'), ]) self.assertEqual( list(search.find_iter(fx, 'a', 'foo.py', max_iterations=10)), [ _substitution( message='There are a few findings here:\n\na\n\nb', url='merged') ])
def test_output_sorted(self): pyfixers = [ _search_replace_fixer('a', 'x'), _search_replace_fixer('b', 'x') ] for python_fixers in [pyfixers, pyfixers[::-1]]: fx = fixer.CombiningPythonFixer(python_fixers) with self.subTest(reversed=python_fixers != pyfixers): self.assertEqual( list(search.find_iter(fx, 'a + b', 'foo.py')), [_substitution(message='a'), _substitution(message='b')])
def test_discards_unparseable_expr(self): """Searching discards unparseable substitutions for expressions. (Note: this only happens during fixedpoint computation.) """ fx = fixer.CombiningPythonFixer([ fixer.SimplePythonFixer(message='', matcher=syntax_matchers.ExprPattern('a'), replacement=formatting.ShTemplate('x x x'), url='') ]) self.assertEqual( list(search.find_iter(fx, 'a', 'foo.py', max_iterations=10)), [])
def test_fixedpoint_drop_redundant_messages(self): fx = fixer.CombiningPythonFixer([ _search_replace_fixer('a', 'b', message='z', url='url_z'), _search_replace_fixer('b', 'c', message='z', url='url_z'), ]) self.assertEqual( list(search.find_iter(fx, 'a', 'foo.py', max_iterations=10)), [ _substitution( message='z', matched_spans={'fixedpoint': (0, 1)}, primary_label='fixedpoint', replacements={'fixedpoint': 'c'}, url='url_z', ) ])
def test_examples_real(self): """Tests that the fixers do actually give the example replacement.""" for fx in find_fixer.from_pattern('*').fixers: example = fx.example_fragment() example_replacement = fx.example_replacement() with self.subTest(fixer=type(fx).__name__, example=example, example_replacement=example_replacement): self.assertIsNotNone(example) substitutions = list( search.find_iter(fixer.CombiningPythonFixer([fx]), example, 'a.py')) self.assertLen(substitutions, 1) replaced = formatting.apply_substitutions( example, substitutions) self.assertEqual(replaced, example_replacement)
def test_fixedpoint_significant(self, ab_significant, bc_sigificant): fx = fixer.CombiningPythonFixer([ _search_replace_fixer('a', 'b', url='merged', significant=ab_significant), _search_replace_fixer('b', 'c', url='merged', significant=bc_sigificant), ]) self.assertEqual( list(search.find_iter(fx, 'a', 'foo.py', max_iterations=10)), [ _substitution(significant=True, category='refex.merged.significant') ])
def test_fixedpoint_drop_insignificant(self): fx = fixer.CombiningPythonFixer([ _search_replace_fixer('a', 'b', url='url_a', significant=False), _search_replace_fixer('b', 'c', url='url_b', significant=True), ]) self.assertEqual( list(search.find_iter(fx, 'a', 'foo.py', max_iterations=10)), [ substitution.Substitution( message='b', matched_spans={'fixedpoint': (0, 1)}, primary_label='fixedpoint', replacements={'fixedpoint': 'c'}, url='url_b', significant=True, category='refex.merged.significant', ) ])
def test_fixedpoint_nodrop_redundant_messages_with_different_urls(self): fx = fixer.CombiningPythonFixer([ _search_replace_fixer('a', 'b', message='z', url='url_a'), _search_replace_fixer('b', 'c', message='z', url='url_b'), ]) self.assertEqual( list(search.find_iter(fx, 'a', 'foo.py', max_iterations=10)), [ _substitution( message= 'There are a few findings here:\n\nz\n(url_a)\n\nz\n(url_b)', matched_spans={'fixedpoint': (0, 1)}, primary_label='fixedpoint', replacements={'fixedpoint': 'c'}, url= 'https://refex.readthedocs.io/en/latest/guide/fixers/merged.html', ) ])
def test_fixedpoint_keep_insignificant(self): fx = fixer.CombiningPythonFixer([ _search_replace_fixer('a', 'b', url='url_a', significant=False), _search_replace_fixer('b', 'c', url='url_b', significant=False), ]) self.assertEqual( list(search.find_iter(fx, 'a', 'foo.py', max_iterations=10)), [ substitution.Substitution( message= 'There are a few findings here:\n\na\n(url_a)\n\nb\n(url_b)', matched_spans={'fixedpoint': (0, 1)}, primary_label='fixedpoint', replacements={'fixedpoint': 'c'}, url= 'https://refex.readthedocs.io/en/latest/guide/fixers/merged.html', significant=False, category='refex.merged.not-significant', ) ])
class SimpleFixersTest(parameterized.TestCase): fixers = fixer.CombiningPythonFixer( correctness_fixers.SIMPLE_PYTHON_FIXERS) def test_skips_number_mod(self): before = 'y = 3 % (x)' self.assertEqual(before, _rewrite(self.fixers, before)) @parameterized.parameters('(\nfoo)', '(foo\n)', '(foo\n.bar)') def test_skips_multiline_rhs(self, rhs): before = 'y = "hello %s" % {rhs}'.format(rhs=rhs) self.assertEqual(before, _rewrite(self.fixers, before)) def test_skips_formatting_when_already_using_tuple(self): before = "y = 'hello %s' % (world,)" self.assertEqual(before, _rewrite(self.fixers, before)) @parameterized.parameters('u', 'b', '') def test_changes_superfluous_parens_to_tuple_when_formatting( self, string_prefix): before = textwrap.dedent(""" y = ( {}'hello: %s\\n' % (thing.world)) """).format(string_prefix) after = textwrap.dedent(""" y = ( {}'hello: %s\\n' % (thing.world,)) """).format(string_prefix) self.assertEqual(after, _rewrite(self.fixers, before)) @parameterized.parameters('None', 'True', 'False') def test_is_named_constant(self, constant): """Named constants aren't fixed by the is check: identity is guaranteed.""" before = f'x is {constant}' self.assertEqual(before, _rewrite(self.fixers, before)) @parameterized.parameters('42', '0x42', '0b01', '6.6', '1e1', '1j', '"s"', 'u"s"', 'b"s"') def test_is_unnamed_constant(self, constant): before = f'x is {constant}' after = f'x == {constant}' self.assertEqual(after, _rewrite(self.fixers, before))
class MutableConstantFixers(parameterized.TestCase): mutable_constant_fixers = fixer.CombiningPythonFixer( idiom_fixers._MUTABLE_CONSTANT_FIXERS) @parameterized.named_parameters( ('set_literal', 'foo = {1}'), ('set_constructor', '_bar = set([1, 2])'), ('setcomp', 'mymod.bar = {x for x in y()}'), ) def test_skips_non_constants(self, example): self.assertEqual(example, _rewrite(self.mutable_constant_fixers, example)) def test_multiline_fix(self): before = textwrap.dedent(""" _MYCONST = { 1, # Thing1 2, # Thing2 } """) after = textwrap.dedent(""" _MYCONST = frozenset({ 1, # Thing1 2, # Thing2 }) """) self.assertEqual(after, _rewrite(self.mutable_constant_fixers, before)) def test_does_not_recurse(self): before = textwrap.dedent(""" _MYCONST = frozenset( (a, b) for a in y() for b in set([1, 2, 3]) ) """) self.assertEqual(before, _rewrite(self.mutable_constant_fixers, before))
def from_pattern(fixer_pattern: str) -> fixer.CombiningPythonFixer: """Provide a fixer that combines all the fixers specified in `fixer_pattern`. To get all the default fixers, pass '*'. Otherwise, to get a group of fixers by name, specify that name. (See _default_fixers & _extra_fixers). Args: fixer_pattern: The pattern of fixers to load. Returns: A PythonFixer. Raises: ValueError: The fixer pattern was not recognized. """ # TODO: Allow you to do set operations like '*,-FixerNameHere', etc. # or something along those lines. if fixer_pattern in _extra_fixers: return fixer.CombiningPythonFixer(_extra_fixers[fixer_pattern]) else: raise ValueError('Unknown fixer pattern %r: must provide one of: %s' % (fixer_pattern, ', '.join(_extra_fixers.keys())))
class NoneReturnFixerTest(absltest.TestCase): none_fixers = fixer.CombiningPythonFixer(idiom_fixers._NONE_RETURNS_FIXERS) def test_fix_non_void_function_with_bare_return(self): before = textwrap.dedent(""" def func(a, b): if a: return 5 elif b: print('a') return # Nothing to return """) after = textwrap.dedent(""" def func(a, b): if a: return 5 elif b: print('a') return None # Nothing to return """) self.assertEqual(after, _rewrite(self.none_fixers, before)) def test_fix_void_function_with_return_none_and_bare_returns(self): before = textwrap.dedent(""" def func(a, b): if a: return elif b: print('a') return None else: raise NotImplementedError """) after = textwrap.dedent(""" def func(a, b): if a: return elif b: print('a') return else: raise NotImplementedError """) self.assertEqual(after, _rewrite(self.none_fixers, before)) def test_fix_void_function_with_return_none_only(self): before = textwrap.dedent(""" def func(a, b): if a: return None elif b: print('a') return None else: raise NotImplementedError """) after = textwrap.dedent(""" def func(a, b): if a: return elif b: print('a') return else: raise NotImplementedError """) self.assertEqual(after, _rewrite(self.none_fixers, before)) def test_skips_bad_void_functions_containing_other_functions(self): code = textwrap.dedent(""" def func(a, b): if a: return None elif b: print('a') def inner(): return 5 return """) self.assertEmpty( list(search.find_iter(self.none_fixers, code, 'example.py'))) def test_skips_bad_non_void_functions_containing_other_functions(self): code = textwrap.dedent(""" def func(a, b): if a: return 5 elif b: print('a') return def inner(): return return inner """) self.assertEmpty( list(search.find_iter(self.none_fixers, code, 'example.py'))) def test_skips_ok_funcs(self): code = textwrap.dedent(""" def func1(a, b): if a: return 5 elif b: print('a') return None def func2(a, b): if a: return elif b: print('a') return else: raise NotImplementedError """) self.assertEmpty( list(search.find_iter(self.none_fixers, code, 'example.py'))) def test_single_return_none(self): before = textwrap.dedent(""" def func(a, b): del a, b if 1: # do something here return None """) after = textwrap.dedent(""" def func(a, b): del a, b if 1: # do something here return """) self.assertEqual(after, _rewrite(self.none_fixers, before)) def test_fixes_methods(self): before = textwrap.dedent(""" class A(object): def foo(self): if use_random(): return None if random_bool() else 5 elif something_else(): return raise RuntimeError """) after = textwrap.dedent(""" class A(object): def foo(self): if use_random(): return None if random_bool() else 5 elif something_else(): return None raise RuntimeError """) self.assertEqual(after, _rewrite(self.none_fixers, before)) @unittest.skipIf(six.PY2, 'Testing async functions') def test_fixes_async_functions(self): before = textwrap.dedent(""" async def foo(self): if use_random(): await asynio.sleep(2) return None if random_bool() else 5 elif something_else(): return else: return 6 """) after = textwrap.dedent(""" async def foo(self): if use_random(): await asynio.sleep(2) return None if random_bool() else 5 elif something_else(): return None else: return 6 """) self.assertEqual(after, _rewrite(self.none_fixers, before)) def test_optional_return(self): """return None is fine if the function returns Optional[T].""" # TODO(b/117351081): port this test to work on vanilla Python. example = textwrap.dedent(""" import typing from typing import Optional def func() -> typing.Optional[int]: return None def func2() -> Optional[int]: return None """) self.assertEqual(example, _rewrite(self.none_fixers, example))
class LoggingExceptionFixerTest(parameterized.TestCase): fixers = fixer.CombiningPythonFixer(idiom_fixers._LOGGING_FIXERS) @parameterized.named_parameters( ('function_call_without_args', 'dangerous_func()', 'dangerous_func'), ('function_call_with_args', 'dangerous_func(1, b=2)', 'dangerous_func'), ('attribute_call_without_args', 'myobject.dangerous_func()', 'myobject.dangerous_func'), ('attribute_call_with_args', 'myobject.dangerous_func(1, b=2)', 'myobject.dangerous_func'), ('call_on_result_of_call', "myfunc().otherfunc(x, 'hello').dangerous_func()", "myfunc().otherfunc(x, 'hello').dangerous_func"), ('assignment_to_function_call', 'a = dangerous_func()', 'dangerous_func'), ('assignment_to_attribute_call', 'a = a.b.c.dangerous_func()', 'a.b.c.dangerous_func'), ('returned_function_call_without_args', 'return dangerous_func()', 'dangerous_func'), ) def test_rewrites_redundant_logging_exception_for(self, try_body, failing_name): before = textwrap.dedent(""" f = open('/tmp/myfile', 'w') try: %s except (ValueError, KeyError) as exc: logging.exception(exc) _record_error(exc) else: f.write('...') finally: f.close() """ % try_body) after = before.replace( 'logging.exception(exc)', 'logging.exception(\'Call to "%s" resulted in an error\')' % failing_name) self.assertEqual(after, _rewrite(self.fixers, before)) @parameterized.named_parameters(( 'same_exception_id', 'e', ), ( 'different_exception_id', 'e2', )) def test_fixes_multiple_except_clauses(self, second_exception_id): before = textwrap.dedent(""" try: dangerous_func() except KeyError as e: if 'mykey' in str(e): logging.exception(e) raise except Exception as {second_exception_id}: logging.exception({second_exception_id}) """.format(second_exception_id=second_exception_id)) after = textwrap.dedent(""" try: dangerous_func() except KeyError as e: if 'mykey' in str(e): logging.exception('Call to "dangerous_func" resulted in an error') raise except Exception as {second_exception_id}: logging.exception('Call to "dangerous_func" resulted in an error') """.format(second_exception_id=second_exception_id)) self.assertEqual(after, _rewrite(self.fixers, before)) def test_nested_try_except(self): before = textwrap.dedent(""" def foo(): try: return dangerous_func() except Exception as e: try: f = e.foo except AttributeError as e: logging.exception(e) """) self.assertEqual(before, _rewrite(self.fixers, before)) def test_skips_ambiguous_try_body(self): before = textwrap.dedent(""" def foo(): try: return dangerous_func() other_dangerous_func() except Exception as e: logging.exception(e) """) self.assertEqual(before, _rewrite(self.fixers, before)) def test_skips_non_redundant_logging_exception_call(self): before = textwrap.dedent(""" try: dangerous_func() except Exception as e: msg = "Something bad happened" logging.exception(msg) """) self.assertEqual(before, _rewrite(self.fixers, before)) def test_skips_replacement_requiring_escaping(self): before = textwrap.dedent(""" try: foo("abc", "xyz").dangerous_func() except Exception as e: logging.exception(e) """) self.assertEqual(before, _rewrite(self.fixers, before))
class LoggingErrorFixerTest(parameterized.TestCase): fixers = fixer.CombiningPythonFixer(idiom_fixers._LOGGING_FIXERS) def test_nested_try_except(self): before = textwrap.dedent(""" def foo(x, y): try: a = dangerous_func(x) b = other_func(y) except Exception as e: logging.error('Bad stuff: (%s, %d): %r', x, y, e) try: f = e.foo except AttributeError as e: logging.error('Error: %r', e) except IndexError as e2: logging.error('What happened? %s', e2) """) after = textwrap.dedent(""" def foo(x, y): try: a = dangerous_func(x) b = other_func(y) except Exception as e: logging.exception('Bad stuff: (%s, %d): %r', x, y, e) try: f = e.foo except AttributeError as e: logging.exception('Error: %r', e) except IndexError as e2: logging.exception('What happened? %s', e2) """) self.assertEqual(after, _rewrite(self.fixers, before)) def test_skips_exc_info(self): before = textwrap.dedent(""" try: foo() except Exception as e: logging.error('Error: %r', e, exc_info=True) """) self.assertEqual(before, _rewrite(self.fixers, before)) def test_skips_inner_unnamed_exception(self): before = textwrap.dedent(""" try: foo() except Exception as e: try: bar() except: logging.error('bar() failed after foo() failed with: %r', e) """) self.assertEqual(before, _rewrite(self.fixers, before)) def test_fixes_exception_as_message(self): before = textwrap.dedent(""" try: foo() except Exception as e: logging.error(e) """) # NOTE(nmarrow): The replacement here is suboptimal and should be corrected # by the `logging.exception` fixer. after = textwrap.dedent(""" try: foo() except Exception as e: logging.exception(e) """) self.assertEqual(after, _rewrite(self.fixers, before))
def _rewrite(fx, source): searcher = fixer.CombiningPythonFixer([fx]) return search.rewrite_string(searcher, source, 'example.py')
def test_empty_fixers(self): fx = fixer.CombiningPythonFixer([]) self.assertEqual(list(search.find_iter(fx, 'b', 'foo.py')), [])
def test_empty_results(self): fx = fixer.CombiningPythonFixer([_search_replace_fixer('a', 'x')]) self.assertEqual(list(search.find_iter(fx, 'b', 'foo.py')), [])