diff --git a/HACKING.rst b/HACKING.rst index 6678328fd276..76ff4b6c22d3 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -67,6 +67,7 @@ Nova Specific Commandments - [N363] Disallow ``(not_a_tuple)`` because you meant ``(a_tuple_of_one,)``. - [N364] Check non-existent mock assertion methods and attributes. - [N365] Check misuse of assertTrue/assertIsNone. +- [N366] The assert_has_calls is a method rather than a variable. Creating Unit Tests ------------------- diff --git a/nova/hacking/checks.py b/nova/hacking/checks.py index 65a63f1eb512..4bdba6991b90 100644 --- a/nova/hacking/checks.py +++ b/nova/hacking/checks.py @@ -127,6 +127,8 @@ mock_attribute_re = re.compile(r"[\.\(](retrun_value)[,=\s]") # Regex for useless assertions useless_assertion_re = re.compile( r"\.((assertIsNone)\(None|(assertTrue)\((True|\d+|'.+'|\".+\")),") +# Regex for misuse of assert_has_calls +mock_assert_has_calls_re = re.compile(r"\.assert_has_calls\s?=") class BaseASTChecker(ast.NodeVisitor): @@ -922,3 +924,18 @@ def useless_assertion(logical_line, filename): match = useless_assertion_re.search(logical_line) if match: yield (0, msg % (match.group(2) or match.group(3))) + + +@core.flake8ext +def check_assert_has_calls(logical_line, filename): + """Check misuse of assert_has_calls. + + Not correct: mock_method.assert_has_calls = [mock.call(0)] + Correct: mock_method.assert_has_calls([mock.call(0)]) + + N366 + """ + msg = "N366: The assert_has_calls is a method rather than a variable." + if ('nova/tests/' in filename and + mock_assert_has_calls_re.search(logical_line)): + yield (0, msg) diff --git a/nova/tests/unit/test_hacking.py b/nova/tests/unit/test_hacking.py index 9222daa451d1..78b948fbb278 100644 --- a/nova/tests/unit/test_hacking.py +++ b/nova/tests/unit/test_hacking.py @@ -909,3 +909,27 @@ class HackingTestCase(test.NoDBTestCase): self._assert_has_no_errors( code, checks.useless_assertion, filename="nova/tests/unit/test_context.py") + + def test_check_assert_has_calls(self): + code = """ + mock_method.assert_has_calls = [mock.call(1)] + mock_method2.assert_has_calls = [ + mock.call(1), mock.call(2)] + """ + errors = [(x + 1, 0, 'N366') for x in range(2)] + # Check errors in 'nova/tests' directory. + self._assert_has_errors( + code, checks.check_assert_has_calls, + expected_errors=errors, filename="nova/tests/unit/test_context.py") + # Check no errors in other than 'nova/tests' directory. + self._assert_has_no_errors( + code, checks.check_assert_has_calls, + filename="nova/compute/api.py") + code = """ + mock_method.assert_has_calls([mock.call(1)]) + mock_method2.assert_has_calls([ + mock.call(1), mock.call(2)]) + """ + self._assert_has_no_errors( + code, checks.check_assert_has_calls, + filename="nova/tests/unit/test_context.py") diff --git a/tox.ini b/tox.ini index 1bb4087b7af1..f2333b6e3050 100644 --- a/tox.ini +++ b/tox.ini @@ -314,6 +314,7 @@ extension = N363 = checks:did_you_mean_tuple N364 = checks:nonexistent_assertion_methods_and_attributes N365 = checks:useless_assertion + N366 = checks:check_assert_has_calls paths = ./nova/hacking