diff --git a/HACKING.rst b/HACKING.rst index 61785b2e25ab..5b769c19338d 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -51,6 +51,7 @@ Nova Specific Commandments assertIn/NotIn(A, B) - [N339] Check common raise_feature_not_supported() is used for v2.1 HTTPNotImplemented response. - [N340] Check nova.utils.spawn() is used instead of greenthread.spawn() and eventlet.spawn() +- [N341] contextlib.nested is deprecated Creating Unit Tests ------------------- diff --git a/nova/hacking/checks.py b/nova/hacking/checks.py index ddd9218c8204..4f1400ad1572 100644 --- a/nova/hacking/checks.py +++ b/nova/hacking/checks.py @@ -98,6 +98,7 @@ decorator_re = re.compile(r"@.*") http_not_implemented_re = re.compile(r"raise .*HTTPNotImplemented\(") spawn_re = re.compile( r".*(eventlet|greenthread)\.(?Pspawn(_n)?)\(.*\)") +contextlib_nested = re.compile(r"^with (contextlib\.)?nested\(") class BaseASTChecker(ast.NodeVisitor): @@ -539,6 +540,16 @@ def check_greenthread_spawns(logical_line, physical_line, filename): yield (0, msg % {'spawn': match.group('spawn_part')}) +def check_no_contextlib_nested(logical_line, filename): + msg = ("N341: contextlib.nested is deprecated. With Python 2.7 and later " + "the with-statement supports multiple nested objects. See https://" + "docs.python.org/2/library/contextlib.html#contextlib.nested for " + "more information. nova.test.nested() is an alternative as well.") + + if contextlib_nested.match(logical_line): + yield(0, msg) + + def factory(register): register(import_no_db_in_virt) register(no_db_session_in_public_api) @@ -565,4 +576,5 @@ def factory(register): register(dict_constructor_with_list_copy) register(assert_equal_in) register(check_http_not_implemented) + register(check_no_contextlib_nested) register(check_greenthread_spawns) diff --git a/nova/tests/unit/test_hacking.py b/nova/tests/unit/test_hacking.py index e4efdb044eaa..d8f3f8fac994 100644 --- a/nova/tests/unit/test_hacking.py +++ b/nova/tests/unit/test_hacking.py @@ -535,6 +535,31 @@ class HackingTestCase(test.NoDBTestCase): self._assert_has_no_errors(code, checks.check_http_not_implemented, filename=filename) + def test_check_contextlib_use(self): + code = """ + with test.nested( + mock.patch.object(network_model.NetworkInfo, 'hydrate'), + mock.patch.object(objects.InstanceInfoCache, 'save'), + ) as ( + hydrate_mock, save_mock + ) + """ + filename = "nova/api/openstack/compute/v21/test.py" + self._assert_has_no_errors(code, checks.check_no_contextlib_nested, + filename=filename) + code = """ + with contextlib.nested( + mock.patch.object(network_model.NetworkInfo, 'hydrate'), + mock.patch.object(objects.InstanceInfoCache, 'save'), + ) as ( + hydrate_mock, save_mock + ) + """ + filename = "nova/api/openstack/compute/legacy_v2/test.py" + errors = [(1, 0, 'N341')] + self._assert_has_errors(code, checks.check_no_contextlib_nested, + expected_errors=errors, filename=filename) + def test_check_greenthread_spawns(self): errors = [(1, 0, "N340")]