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
This commit is contained in:
parent
dd80045893
commit
4522a9728e
@ -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__)
|
||||
|
@ -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
|
||||
|
@ -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'))
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user