From 58ba6586b754c2eccf08a6cb303e15ad6e09ad2b Mon Sep 17 00:00:00 2001 From: Ihar Hrachyshka Date: Wed, 20 Jan 2016 15:42:04 +0100 Subject: [PATCH] tests: register all objects before validating their hash versions The test validates that all object types registered in the object registry have expected object hashes. If they are not, it means that object API is changed, and it is used as an indicator to reviewers that object version conversion rules should be added to allow upgrading and downgrading objects between older and the new versions. The test was not importing all modules that contain objects though, so if no other code executed by the current testr process before the hasher validation test imported all objects to validate, the test would misbehave, claiming some expected object types not registered at all. To make sure all object types available in the tree are imported (and registered) before the test case is executed, we need to import all modules under neutron.objects. Instead of maintaining the list of modules with objects to import somewhere, inspect the list of those modules dynamically, assuming they are all located under neutron/objects/ subtree. Without the fix, the test may randomly fail depending on test case order for the current process. Note it's not an issue for QoS objects since they are implicitly imported by rpc callbacks resource manager that is initialized in the base test class. This becomes a problem when you start to introduce objects that are not part of rpc callbacks list of supported resources. Change-Id: Ice408faf10b75c508b9c5f5b7ab23b2fc3289eaa --- neutron/tests/tools.py | 33 +++++++++++++++++++ neutron/tests/unit/objects/test_objects.py | 8 +++++ neutron/tests/unit/tests/example/README | 2 ++ neutron/tests/unit/tests/example/__init__.py | 0 .../tests/unit/tests/example/dir/__init__.py | 0 .../unit/tests/example/dir/example_module.py | 0 neutron/tests/unit/tests/test_tools.py | 33 +++++++++++++++++++ 7 files changed, 76 insertions(+) create mode 100644 neutron/tests/unit/tests/example/README create mode 100644 neutron/tests/unit/tests/example/__init__.py create mode 100644 neutron/tests/unit/tests/example/dir/__init__.py create mode 100644 neutron/tests/unit/tests/example/dir/example_module.py create mode 100644 neutron/tests/unit/tests/test_tools.py diff --git a/neutron/tests/tools.py b/neutron/tests/tools.py index 980024c1bce..ffdfffe335c 100644 --- a/neutron/tests/tools.py +++ b/neutron/tests/tools.py @@ -13,10 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. +import importlib import os import platform import random import string +import sys import time import warnings @@ -24,6 +26,7 @@ import fixtures import mock import six +import neutron from neutron.api.v2 import attributes @@ -185,6 +188,36 @@ class UnorderedList(list): return not self == other +def import_modules_recursively(topdir): + '''Import and return all modules below the topdir directory.''' + modules = [] + for root, dirs, files in os.walk(topdir): + for file_ in files: + if file_[-3:] != '.py': + continue + + module = file_[:-3] + if module == '__init__': + continue + + import_base = root.replace('/', '.') + + # NOTE(ihrachys): in Python3, or when we are not located in the + # directory containing neutron code, __file__ is absolute, so we + # should truncate it to exclude PYTHONPATH prefix + prefixlen = len(os.path.dirname(neutron.__file__)) + import_base = 'neutron' + import_base[prefixlen:] + + module = '.'.join([import_base, module]) + if module not in sys.modules: + importlib.import_module(module) + modules.append(module) + + for dir_ in dirs: + modules.extend(import_modules_recursively(dir_)) + return modules + + def get_random_string(n=10): return ''.join(random.choice(string.ascii_lowercase) for _ in range(n)) diff --git a/neutron/tests/unit/objects/test_objects.py b/neutron/tests/unit/objects/test_objects.py index 341fc0fa26f..08257cf8f55 100644 --- a/neutron/tests/unit/objects/test_objects.py +++ b/neutron/tests/unit/objects/test_objects.py @@ -18,7 +18,9 @@ import pprint from oslo_versionedobjects import base as obj_base from oslo_versionedobjects import fixture +from neutron import objects from neutron.tests import base as test_base +from neutron.tests import tools # NOTE: The hashes in this list should only be changed if they come with a @@ -32,6 +34,12 @@ object_data = { class TestObjectVersions(test_base.BaseTestCase): + def setUp(self): + super(TestObjectVersions, self).setUp() + # NOTE(ihrachys): seed registry with all objects under neutron.objects + # before validating the hashes + tools.import_modules_recursively(os.path.dirname(objects.__file__)) + def test_versions(self): checker = fixture.ObjectVersionChecker( obj_base.VersionedObjectRegistry.obj_classes()) diff --git a/neutron/tests/unit/tests/example/README b/neutron/tests/unit/tests/example/README new file mode 100644 index 00000000000..4eebbde9962 --- /dev/null +++ b/neutron/tests/unit/tests/example/README @@ -0,0 +1,2 @@ +This directory is used by: +neutron.tests.unit.tests.test_tools.ImportModulesRecursivelyTestCase diff --git a/neutron/tests/unit/tests/example/__init__.py b/neutron/tests/unit/tests/example/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/tests/unit/tests/example/dir/__init__.py b/neutron/tests/unit/tests/example/dir/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/tests/unit/tests/example/dir/example_module.py b/neutron/tests/unit/tests/example/dir/example_module.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/tests/unit/tests/test_tools.py b/neutron/tests/unit/tests/test_tools.py new file mode 100644 index 00000000000..1ab3e939fda --- /dev/null +++ b/neutron/tests/unit/tests/test_tools.py @@ -0,0 +1,33 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import sys + +from neutron.tests import base +from neutron.tests import tools +from neutron.tests.unit import tests # noqa + + +EXAMPLE_MODULE = 'neutron.tests.unit.tests.example.dir.example_module' + + +class ImportModulesRecursivelyTestCase(base.BaseTestCase): + + def test_object_modules(self): + sys.modules.pop(EXAMPLE_MODULE, None) + modules = tools.import_modules_recursively( + os.path.dirname(tests.__file__)) + self.assertIn( + 'neutron.tests.unit.tests.example.dir.example_module', + modules) + self.assertIn(EXAMPLE_MODULE, sys.modules)