summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2016-07-15 16:31:09 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2016-11-16 15:41:01 -0500
commit4522a9728ee9ba5e7c55faf392721c340eef9f55 (patch)
tree9f3deb39eacb061ab4fb8da61b08f16f2aacb81c
parentdd800458937a4ccb0b12b0f760eb515d64e0be88 (diff)
downloadoslo-db-4522a9728ee9ba5e7c55faf392721c340eef9f55.tar.gz
Break optimize_db_test_loader into package and module level
This unittest-compatible load_tests function was not well organized for package-level tests and was broken for module-level usage; as in the case of module-level usage, the "pattern" parameter was being passed, which was unexpected since the Python 2.7 documentation for unittest claims it will be None. However, we are using subunit to run tests, which uses the unittest2 library that achieves compatibility with Python 3 - in Python 3, the documentation was changed here to note that "pattern" may be present (and in fact it is). Instead, break it into optimize_package_test_loader and optimize_module_test_loader. The former assumes placement inside of __init__.py only and the latter assumes placement inside of a specific module only. The issue with this function was observed when trying to get OptimisingTestSuite to work correctly with neutron; tests would be duplicated due to the double-search. In local testing it was shown that the function could cause recursion overflows. It has been observed that using this function at the module level is of great use when running subsets of tests, since the package-level load_tests function is not consulted unless the test run is package-wide. Change-Id: I267fd2fbc16504c11addef870fc8d4fc500da7d9
-rw-r--r--oslo_db/sqlalchemy/test_base.py43
-rw-r--r--oslo_db/sqlalchemy/test_fixtures.py78
-rw-r--r--oslo_db/tests/sqlalchemy/test_fixtures.py84
3 files changed, 166 insertions, 39 deletions
diff --git a/oslo_db/sqlalchemy/test_base.py b/oslo_db/sqlalchemy/test_base.py
index bda0d91..ab7dea2 100644
--- a/oslo_db/sqlalchemy/test_base.py
+++ b/oslo_db/sqlalchemy/test_base.py
@@ -14,9 +14,9 @@
# under the License.
import debtcollector
+import debtcollector.moves
import fixtures
import testresources
-import testscenarios
try:
from oslotest import base as test_base
@@ -25,8 +25,6 @@ except ImportError:
' test-requirements')
-import os
-
from oslo_utils import reflection
import six
@@ -34,6 +32,7 @@ from oslo_db import exception
from oslo_db.sqlalchemy import enginefacade
from oslo_db.sqlalchemy import provision
from oslo_db.sqlalchemy import session
+from oslo_db.sqlalchemy.test_fixtures import optimize_package_test_loader
@debtcollector.removals.removed_class("DbFixture")
@@ -245,39 +244,5 @@ class PostgreSQLOpportunisticTestCase(OpportunisticTestCase):
FIXTURE = PostgreSQLOpportunisticFixture
-def optimize_db_test_loader(file_):
- """Package level load_tests() function.
-
- Will apply an optimizing test suite to all sub-tests, which groups DB
- tests and other resources appropriately.
-
- Place this in an __init__.py package file within the root of the test
- suite, at the level where testresources loads it as a package::
-
- from oslo_db.sqlalchemy import test_base
-
- load_tests = test_base.optimize_db_test_loader(__file__)
-
- Alternatively, the directive can be placed into a test module directly.
-
- """
-
- this_dir = os.path.dirname(file_)
-
- def load_tests(loader, found_tests, pattern):
- # pattern is None if the directive is placed within
- # a test module directly, as well as within certain test
- # discovery patterns
-
- if pattern is not None:
- pkg_tests = loader.discover(start_dir=this_dir, pattern=pattern)
-
- result = testresources.OptimisingTestSuite()
- found_tests = testscenarios.load_tests_apply_scenarios(
- loader, found_tests, pattern)
- result.addTest(found_tests)
-
- if pattern is not None:
- result.addTest(pkg_tests)
- return result
- return load_tests
+optimize_db_test_loader = debtcollector.moves.moved_function(
+ optimize_package_test_loader, "optimize_db_test_loader", __name__)
diff --git a/oslo_db/sqlalchemy/test_fixtures.py b/oslo_db/sqlalchemy/test_fixtures.py
index 8b35ac3..fd954b3 100644
--- a/oslo_db/sqlalchemy/test_fixtures.py
+++ b/oslo_db/sqlalchemy/test_fixtures.py
@@ -15,7 +15,9 @@
import fixtures
import logging
+import os
import testresources
+import testscenarios
from oslo_db import exception
from oslo_db.sqlalchemy import enginefacade
@@ -544,3 +546,79 @@ class MySQLOpportunisticFixture(OpportunisticDbFixture):
class PostgresqlOpportunisticFixture(OpportunisticDbFixture):
DRIVER = 'postgresql'
+
+
+def optimize_package_test_loader(file_):
+ """Organize package-level tests into a testresources.OptimizingTestSuite.
+
+ This function provides a unittest-compatible load_tests hook
+ for a given package; for per-module, use the
+ :func:`.optimize_module_test_loader` function.
+
+ When a unitest or subunit style
+ test runner is used, the function will be called in order to
+ return a TestSuite containing the tests to run; this function
+ ensures that this suite is an OptimisingTestSuite, which will organize
+ the production of test resources across groups of tests at once.
+
+ The function is invoked as::
+
+ from oslo_db.sqlalchemy import test_base
+
+ load_tests = test_base.optimize_package_test_loader(__file__)
+
+ The loader *must* be present in the package level __init__.py.
+
+ The function also applies testscenarios expansion to all test collections.
+ This so that an existing test suite that already needs to build
+ TestScenarios from a load_tests call can still have this take place when
+ replaced with this function.
+
+ """
+
+ this_dir = os.path.dirname(file_)
+
+ def load_tests(loader, found_tests, pattern):
+ result = testresources.OptimisingTestSuite()
+ result.addTests(found_tests)
+ pkg_tests = loader.discover(start_dir=this_dir, pattern=pattern)
+ result.addTests(testscenarios.generate_scenarios(pkg_tests))
+
+ return result
+ return load_tests
+
+
+def optimize_module_test_loader():
+ """Organize module-level tests into a testresources.OptimizingTestSuite.
+
+ This function provides a unittest-compatible load_tests hook
+ for a given module; for per-package, use the
+ :func:`.optimize_package_test_loader` function.
+
+ When a unitest or subunit style
+ test runner is used, the function will be called in order to
+ return a TestSuite containing the tests to run; this function
+ ensures that this suite is an OptimisingTestSuite, which will organize
+ the production of test resources across groups of tests at once.
+
+ The function is invoked as::
+
+ from oslo_db.sqlalchemy import test_base
+
+ load_tests = test_base.optimize_module_test_loader()
+
+ The loader *must* be present in an individual module, and *not* the
+ package level __init__.py.
+
+ The function also applies testscenarios expansion to all test collections.
+ This so that an existing test suite that already needs to build
+ TestScenarios from a load_tests call can still have this take place when
+ replaced with this function.
+
+ """
+
+ def load_tests(loader, found_tests, pattern):
+ result = testresources.OptimisingTestSuite()
+ result.addTests(testscenarios.generate_scenarios(found_tests))
+ return result
+ return load_tests
diff --git a/oslo_db/tests/sqlalchemy/test_fixtures.py b/oslo_db/tests/sqlalchemy/test_fixtures.py
index 44fed1e..f256d30 100644
--- a/oslo_db/tests/sqlalchemy/test_fixtures.py
+++ b/oslo_db/tests/sqlalchemy/test_fixtures.py
@@ -11,7 +11,10 @@
# under the License.
import mock
+import os
import testresources
+import testscenarios
+import unittest
from oslo_db.sqlalchemy import enginefacade
from oslo_db.sqlalchemy import provision
@@ -19,6 +22,8 @@ from oslo_db.sqlalchemy import test_base as legacy_test_base
from oslo_db.sqlalchemy import test_fixtures
from oslotest import base as oslo_test_base
+start_dir = os.path.dirname(__file__)
+
class BackendSkipTest(oslo_test_base.BaseTestCase):
@@ -212,3 +217,82 @@ class LegacyBaseClassTest(oslo_test_base.BaseTestCase):
db_resource = dict(st.resources)['db']
self.assertTrue(db_resource.provision_new_database)
+
+
+class TestLoadHook(unittest.TestCase):
+ """Test the 'load_tests' hook supplied by test_base.
+
+ The purpose of this loader is to organize tests into an
+ OptimisingTestSuite using the standard unittest load_tests hook.
+ The hook needs to detect if it is being invoked at the module
+ level or at the package level. It has to behave completely differently
+ in these two cases.
+
+ """
+
+ def test_module_level(self):
+ load_tests = test_fixtures.optimize_module_test_loader()
+
+ loader = unittest.TestLoader()
+
+ found_tests = loader.discover(start_dir, pattern="test_fixtures.py")
+ new_loader = load_tests(loader, found_tests, "test_fixtures.py")
+
+ self.assertTrue(
+ isinstance(new_loader, testresources.OptimisingTestSuite)
+ )
+
+ actual_tests = unittest.TestSuite(
+ testscenarios.generate_scenarios(found_tests)
+ )
+
+ self.assertEqual(
+ new_loader.countTestCases(), actual_tests.countTestCases()
+ )
+
+ def test_package_level(self):
+ self._test_package_level(test_fixtures.optimize_package_test_loader)
+
+ def test_package_level_legacy(self):
+ self._test_package_level(legacy_test_base.optimize_db_test_loader)
+
+ def _test_package_level(self, fn):
+ load_tests = fn(
+ os.path.join(start_dir, "__init__.py"))
+
+ loader = unittest.TestLoader()
+
+ new_loader = load_tests(
+ loader, unittest.suite.TestSuite(), "test_fixtures.py")
+
+ self.assertTrue(
+ isinstance(new_loader, testresources.OptimisingTestSuite)
+ )
+
+ actual_tests = unittest.TestSuite(
+ testscenarios.generate_scenarios(
+ loader.discover(start_dir, pattern="test_fixtures.py"))
+ )
+
+ self.assertEqual(
+ new_loader.countTestCases(), actual_tests.countTestCases()
+ )
+
+
+class TestWScenarios(unittest.TestCase):
+ """a 'do nothing' test suite.
+
+ Should generate exactly four tests when testscenarios is used.
+
+ """
+
+ def test_one(self):
+ pass
+
+ def test_two(self):
+ pass
+
+ scenarios = [
+ ('scenario1', dict(scenario='scenario 1')),
+ ('scenario2', dict(scenario='scenario 2'))
+ ]