def test_missing_parameter_types(self): program = '\n'.join([ 'def function_with_excess_parameter(extra):', ' """We have an extra parameter below, extra.', '', ' Args:', ' extra: This shouldn\'t be here.', '', ' """', ' print(\'Hey!\')', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker(config=Configuration( ignore=[], message_template=None, style=DocstringStyle.GOOGLE, strictness=Strictness.FULL_DESCRIPTION, enable=['DAR104'] )) checker.run_checks(functions[0]) errors = checker.errors self.assertEqual(len(errors), 1) self.assertTrue(isinstance(errors[0], ParameterTypeMissingError))
def assertHasErrors(self, config, docstring): checker = IntegrityChecker(config) checker.run_checks(self.get_function_with(docstring)) errors = checker.errors self.assertTrue( len(errors) > 0 )
def test_doesnt_require_private_arguments(self): program = '\n'.join([ 'def reduce(fn, l, _curr=None):', ' """Reduce the list with the given function.', '', ' Parameters', ' ----------', ' fn', ' A function which takes two items and produces', ' one as a result.', ' l', ' The list to reduce.', '', ' Returns', ' -------', ' The final, reduced result of the list.', '', ' """', ' if not l:', ' return _curr', ' if not _curr:', ' return reduce(fn, l[1:], l[0])', ' return reduce(fn, l[1:], fn(l[0], _curr))', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker(self.config) checker.run_checks(functions[0]) errors = checker.errors self.assertEqual( len(errors), 0, [(x.message()) for x in errors], )
def test_ignore_private_methods(self): program = '\n'.join([ 'def function_with_missing_parameter(x):', ' """We\'re missing a description of x."""', ' print(x / 2)', '' 'def _same_error_but_private_method(x):', ' """We\'re missing a description of x."""', ' print(x / 2)', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker( config=Configuration( ignore=[], message_template=None, style=DocstringStyle.GOOGLE, strictness=Strictness.FULL_DESCRIPTION, ignore_regex=r'^_(.*)' ) ) checker.run_checks(functions[0]) checker.run_checks(functions[1]) errors = checker.errors self.assertEqual(len(errors), 1)
def test_runs_other_checks_on_private_arguments(self): program = '\n'.join([ 'def reduce(fn, l, _curr=None):', ' """Reduce the list with the given function.', '', ' :param fn: A function which takes two items and produces', ' one as a result.', ' :param l: The list to reduce.', ' :param _curr:', ' :return: The final, reduced result of the list.', '', ' """', ' if not l:', ' return _curr', ' if not _curr:', ' return reduce(fn, l[1:], l[0])', ' return reduce(fn, l[1:], fn(l[0], _curr))', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker(self.config) checker.run_checks(functions[0]) errors = checker.errors self.assertEqual( len(errors), 1, [(x.message()) for x in errors], ) self.assertTrue(isinstance(errors[0], EmptyDescriptionError), errors[0].__class__.__name__)
def test_try_block_no_excess_error(self): """Make sure the else and except blocks are checked. See Issue 20. """ program = '\n'.join([ 'def check_module_installed(name):', ' """Temp', ' ', ' Args:', ' name (str): module name', ' ', ' Returns:', ' bool: Whether the module can be imported', ' ', ' """', ' try:', ' __import__(name)', ' except ImportError:', ' return False', ' else:', ' return True', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0]) errors = checker.errors self.assertEqual(len(errors), 0)
def test_raises_style_error_if_no_content_after_colon(self): program_template = '\n'.join([ 'def hello_world():', ' """Tell the person hello.', '', ' {}:', ' {}:', '', ' """', ' person.hello()', ]) for section, item in [ ('Args', 'name'), ('Raises', 'Exception'), ]: program = program_template.format(section, item) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0]) errors = checker.errors self.assertTrue( len(errors) > 0, 'Failed to raise any errors for {}'.format(section), ) self.assertTrue( any([ isinstance(error, EmptyDescriptionError) for error in errors ]), 'Failed to raise EmptyDescriptionError for {}'.format(section), )
def test_empty_description_error(self): program_template = '\n'.join([ 'def f():', ' """Makes the thing scream.' ' ', ' :{}:', ' """', ' scream()', ]) for section in [ 'param x', 'return', 'var x', 'type x', 'vartype x', 'raises Exception', 'yield', 'ytype', 'rtype' ]: program = program_template.format(section) tree = ast.parse(program) function = get_function_descriptions(tree)[0] checker = IntegrityChecker(self.config) checker.run_checks(function) errors = checker.errors self.assertTrue( len(errors) > 0, 'EmptyDescriptionError not defined for {}'.format(section), ) self.assertTrue( any([ isinstance(error, EmptyDescriptionError) for error in errors ]), 'EmptyDescriptionError not defined for {}: {}'.format( section, errors, ), )
def test_extra_raises_added_to_error(self): program = '\n'.join([ 'def non_explicitly_errorful_function(x, y):', ' """Should not have a raises section.', '', ' Args:', ' x: The divisor.', ' y: The dividend.', '', ' Raises:', ' ZeroDivisionError: If y is zero.', '', ' Returns:', ' The quotient.', '', ' """', ' return x / y', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0]) self.assertEqual( len(checker.errors), 1, checker.errors ) error = checker.errors[0] self.assertTrue(isinstance(error, ExcessRaiseError)) self.assertEqual(error.name, 'ZeroDivisionError')
def assertHasNoErrors(self, config, docstring): checker = IntegrityChecker(config) checker.run_checks(self.get_function_with(docstring)) errors = checker.errors self.assertEqual( len(errors), 0, [(x.message()) for x in errors] )
def has_no_errors(self, program): tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0]) self.assertEqual( len(checker.errors), 0, 'Expected there to be no errors, but there were {}'.format( len(checker.errors)))
def test_skips_functions_without_docstrings(self): program = '\n'.join([ 'def function_without_docstring(arg1, arg2):', ' return 3', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0]) self.assertEqual(len(checker.errors), 0)
def get_single_error(self, src): tree = ast.parse(src) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0]) errors = checker.errors self.assertEqual( len(errors), 1, 'There should only be one error, but there were {}: {}.'.format( len(errors), ' '.join([x.__class__.__name__ for x in errors]))) return errors[0]
def test_yields_from_added_to_error(self): program = '\n'.join([ 'def function_with_yield():', ' """This should have a yields section."""', ' yield from (x for x in range(10))', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0]) self.assertEqual(len(checker.errors), 1) self.assertTrue(isinstance(checker.errors[0], MissingYieldError))
def get_n_errors(self, amount, src, config=dict()): tree = ast.parse(src) functions = get_function_descriptions(tree) with ConfigurationContext(**config): checker = IntegrityChecker() checker.run_checks(functions[0]) errors = checker.errors self.assertEqual(len(errors), amount, ('There should only be {} errors, ' 'but there were {}: {}.').format( amount, len(errors), ' '.join( [x.__class__.__name__ for x in errors]))) return errors
def test_missing_parameter_added(self): program = '\n'.join([ 'def function_with_missing_parameter(x):', ' """We\'re missing a description of x."""', ' print(x / 2)', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0]) errors = checker.errors self.assertEqual(len(errors), 1) self.assertTrue(isinstance(errors[0], MissingParameterError))
def test_return_incorrectly_has_parameter(self): """Make sure that a return with a parameter is parsed correctly.""" program = '\n'.join([ 'def f():', ' """Some fn', ' :return x: some value', ' """', ' return 3', ]) tree = ast.parse(program) function = get_function_descriptions(tree)[0] checker = IntegrityChecker(self.config) checker.run_checks(function)
def test_missing_return_parameter_added(self): program = '\n'.join([ 'def function_without_return():', ' """This should have a return in the docstring."""', ' global bad_number', ' return bad_number', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0]) errors = checker.errors self.assertEqual(len(errors), 1) self.assertTrue(isinstance(errors[0], MissingReturnError))
def test_incorrect_syntax_raises_exception_optionally(self): # example taken from https://github.com/deezer/html-linter program = '\n'.join([ 'def lint(html, exclude=None):', ' """Lints and HTML5 file.', '', ' Args:', ' html: str the contents of the file.', ' exclude: optional iterable with the Message classes', ' to be ommited from the output.', ' """', ' exclude = exclude or []', ' messages = [m.__unicode__() for m in HTML5Linter(html', ' ).messages', ' if not isinstance(m, tuple(exclude))]', ' return \'\\n\'.join(messages)', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker(raise_errors=True) with self.assertRaises(ParserException): checker.run_checks(functions[0]) # The default is to not raise exceptions. checker = IntegrityChecker() checker.run_checks(functions[0]) errors = checker.errors self.assertTrue(isinstance(errors[0], GenericSyntaxError))
def test_throws_assertion_if_no_colon_in_parameter_line(self): program = '\n'.join([ 'def hash_integer(value):', ' """Return the hash value of an integer.', '', ' Args:', ' value: The integer that we want', # This line should cause an error because it is at the # level for parameter identifiers. ' to make a hashed value of.', '', ' Returns:', ' The hashed value.', '', ' """', ' return value % 7', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker(raise_errors=True) with self.assertRaises(ParserException): checker.run_checks(functions[0]) checker = IntegrityChecker() checker.run_checks(functions[0]) errors = checker.errors self.assertTrue(isinstance(errors[0], GenericSyntaxError))
def test_missing_raises_added_to_error(self): program = '\n'.join([ 'def errorful_function():', ' """Should have a raises section here."""', ' raise AttributeError', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0]) self.assertEqual(len(checker.errors), 1) error = checker.errors[0] self.assertTrue(isinstance(error, MissingRaiseError)) self.assertEqual(error.name, 'AttributeError')
def test_raises_style_error_if_no_content_after_colon(self): program = '\n'.join([ 'def hello_world(name):', ' """Tell the person hello.', '', ' Args:', ' name:', '', ' """', ' print("Hello, {}".format(name))', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0])
def test_return_type_unchecked_if_not_defined_in_function(self): program = '\n'.join([ 'def foo():', ' """Just a foobar.', '', ' Returns:', ' str: bar', '', ' """', ' return "bar"', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0]) self.assertEqual(len(checker.errors), 0)
def test_excess_yield_added_to_errors(self): program = '\n'.join([ 'def function_with_yield():', ' """This should not have a yields section.', '', ' Yields:', ' A number.', '', ' """', ' print(\'Doesnt yield\')', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0]) self.assertEqual(len(checker.errors), 1) self.assertTrue(isinstance(checker.errors[0], ExcessYieldError))
def test_excess_parameter_added(self): program = '\n'.join([ 'def function_with_excess_parameter():', ' """We have an extra parameter below, extra.', '', ' Args:', ' extra: This shouldn\'t be here.', '', ' """', ' print(\'Hey!\')', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0]) errors = checker.errors self.assertEqual(len(errors), 1) self.assertTrue(isinstance(errors[0], ExcessParameterError))
def test_empty_type_section(self): program = '\n'.join([ 'def foo(bar):', ' """Foo.', '', ' Args:', ' bar (): A bar.', ' """', ' print(bar)', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker() checker.run_checks(functions[0]) errors = checker.errors self.assertEqual(len(errors), 1, [(x.message()) for x in errors]) self.assertTrue(isinstance(errors[0], EmptyTypeError), errors[0].__class__.__name__)
def test_missing_parameter(self): """Make sure we capture missing parameters.""" program = '\n'.join([ 'def cons(x, l):', ' """Add an item to the head of the list.', ' ', ' :param x: The item to add to the list.', ' :return: The list with the item attached.', ' ', ' """', ' return [x] + l', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker(self.config) checker.run_checks(functions[0]) errors = checker.errors self.assertEqual(len(errors), 1, [(x.message()) for x in errors]) self.assertTrue(isinstance(errors[0], MissingParameterError))
def test_variable_doesnt_exist(self): """Ensure described variables must exist in the function.""" program = '\n'.join([ 'def circle_area(r):', ' """Calculate the circle\'s area.', ' ', ' :param r: The radius of the circle.', ' :var pi: An estimate of PI.', ' :return: The area of the circle.', ' ', ' """', ' return 3.1415 * r**2', ]) tree = ast.parse(program) functions = get_function_descriptions(tree) checker = IntegrityChecker(self.config) checker.run_checks(functions[0]) errors = checker.errors self.assertEqual(len(errors), 1, [(x.message()) for x in errors]) self.assertTrue(isinstance(errors[0], ExcessVariableError)) self.assertEqual(errors[0].terse_message, '+v pi')
def test_catch_and_raise(self): program = '\n'.join([ 'def false_positive() -> None:', ' """summary', '', ' :raises ValueError: description', ' """', ' try:', ' raise ValueError("233")', ' except ValueError as e:', ' raise e from None', ]) tree = ast.parse(program) function = get_function_descriptions(tree)[0] checker = IntegrityChecker(self.config) checker.run_checks(function) self.assertEqual( len(checker.errors), 0, checker.errors, )
def test_empty_description_error(self): program_template = '\n'.join([ 'def f():', ' """Has arguments.', ' ', ' {}', ' {}', ' {}', '', ' """', ' scream()', ]) for section, item in [('Parameters', 'x')]: program = program_template.format( section, '-' * len(section), item, ) tree = ast.parse(program) function = get_function_descriptions(tree)[0] checker = IntegrityChecker(self.config) checker.run_checks(function) errors = checker.errors self.assertTrue( len(errors) > 0, 'EmptyDescriptionError not defined for {}'.format(section), ) self.assertTrue( any([ isinstance(error, EmptyDescriptionError) for error in errors ]), 'EmptyDescriptionError not defined for {}: {}'.format( section, errors, ), )