diff --git a/HACKING.rst b/HACKING.rst index 42f3a76fa..3a741c73c 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -21,3 +21,4 @@ Neutron Library Specific Commandments - [N533] Validate that debug level logs are not translated - [N534] Exception messages should be translated - [N535] Usage of Python eventlet module not allowed +- [N536] Use assertIsNone/assertIsNotNone rather than assertEqual/assertIs to check None values. diff --git a/neutron_lib/hacking/checks.py b/neutron_lib/hacking/checks.py index f37a6fb17..a34056b60 100644 --- a/neutron_lib/hacking/checks.py +++ b/neutron_lib/hacking/checks.py @@ -37,6 +37,11 @@ namespace_imports_from_dot = re.compile(r"from[\s]+([\w]+)[.]") namespace_imports_from_root = re.compile(r"from[\s]+([\w]+)[\s]+import[\s]+") contextlib_nested = re.compile(r"^\s*with (contextlib\.)?nested\(") +assert_equal_none_re = re.compile( + r"assertEqual\(.*?,\s+None\)|assertEqual\(None,") +assert_is_none_re = re.compile( + r"assertIs(Not)?\(.*,\s+None\)|assertIs(Not)?\(None,") + def use_jsonutils(logical_line, filename): """N521 - jsonutils must be used instead of json. @@ -223,6 +228,19 @@ def check_no_eventlet_imports(logical_line): yield logical_line.index('eventlet'), msg +def assert_equal_none(logical_line): + """N536 - Use assertIsNone.""" + if assert_equal_none_re.search(logical_line): + msg = ("N536: Use assertIsNone rather than assertEqual " + "to check for None values") + yield logical_line.index('assert'), msg + + if assert_is_none_re.search(logical_line): + msg = ("N536: Use assertIsNone or assertIsNotNone rather than " + "assertIs or assertIsNone to check for None values.") + yield logical_line.index('assert'), msg + + def factory(register): """Hacking check factory for neutron-lib adopter compliant checks. @@ -255,6 +273,7 @@ def incubating_factory(register): :param register: The function to register the check functions with. :returns: None. """ + register(assert_equal_none) def _neutron_lib_factory(register): diff --git a/neutron_lib/tests/unit/hacking/test_checks.py b/neutron_lib/tests/unit/hacking/test_checks.py index 283b226f7..537608ac8 100644 --- a/neutron_lib/tests/unit/hacking/test_checks.py +++ b/neutron_lib/tests/unit/hacking/test_checks.py @@ -225,3 +225,25 @@ class HackingTestCase(base.BaseTestCase): self.assertLinePasses(f, 'print("eventlet not here")') self.assertLinePasses(f, 'print("eventlet.timeout")') self.assertLinePasses(f, "from mymod.timeout import (eventlet, X)") + + def test_assert_equal_none(self): + self.assertEqual(len(list(checks.assert_equal_none( + "self.assertEqual(A, None)"))), 1) + self.assertEqual(len(list(checks.assert_equal_none( + "self.assertEqual(A, None) # Comment"))), 1) + self.assertEqual(len(list(checks.assert_equal_none( + "self.assertEqual(None, A)"))), 1) + self.assertEqual(len(list(checks.assert_equal_none( + "self.assertEqual(None, A) # Comment"))), 1) + self.assertEqual(len(list(checks.assert_equal_none( + "assertIsNot(A, None)"))), 1) + self.assertEqual(len(list(checks.assert_equal_none( + "assertIsNot(A, None) # Comment"))), 1) + self.assertEqual(len(list(checks.assert_equal_none( + "assertIsNot(None, A)"))), 1) + self.assertEqual(len(list(checks.assert_equal_none( + "assertIsNot(None, A) # Comment"))), 1) + self.assertEqual( + len(list(checks.assert_equal_none("self.assertIsNone(A)"))), 0) + self.assertEqual( + len(list(checks.assert_equal_none("self.assertIsNotNone(A)"))), 0) diff --git a/releasenotes/notes/hacking-check-n537-280ec39c061d9dd7.yaml b/releasenotes/notes/hacking-check-n537-280ec39c061d9dd7.yaml new file mode 100644 index 000000000..258cfb65e --- /dev/null +++ b/releasenotes/notes/hacking-check-n537-280ec39c061d9dd7.yaml @@ -0,0 +1,4 @@ +--- +features: + - Added hacking check ``N536``. This hacking check is added to the + incubating checks.