Merge "Restore MySQL and Postgresql functional testing"
This commit is contained in:
commit
fc02d5b7ea
|
@ -14,7 +14,6 @@
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
from neutron_lib import constants as n_const
|
from neutron_lib import constants as n_const
|
||||||
from oslo_db.sqlalchemy import test_base
|
|
||||||
import testtools.testcase
|
import testtools.testcase
|
||||||
import unittest2.case
|
import unittest2.case
|
||||||
|
|
||||||
|
@ -69,21 +68,3 @@ def no_skip_on_missing_deps(wrapped):
|
||||||
'is enabled, skip reason: %s' % (wrapped.__name__, e))
|
'is enabled, skip reason: %s' % (wrapped.__name__, e))
|
||||||
raise
|
raise
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
class MySQLTestCase(test_base.MySQLOpportunisticTestCase):
|
|
||||||
"""Base test class for MySQL tests.
|
|
||||||
|
|
||||||
If the MySQL db is unavailable then this test is skipped, unless
|
|
||||||
OS_FAIL_ON_MISSING_DEPS is enabled.
|
|
||||||
"""
|
|
||||||
SKIP_ON_UNAVAILABLE_DB = not base.bool_from_env('OS_FAIL_ON_MISSING_DEPS')
|
|
||||||
|
|
||||||
|
|
||||||
class PostgreSQLTestCase(test_base.PostgreSQLOpportunisticTestCase):
|
|
||||||
"""Base test class for PostgreSQL tests.
|
|
||||||
|
|
||||||
If the PostgreSQL db is unavailable then this test is skipped, unless
|
|
||||||
OS_FAIL_ON_MISSING_DEPS is enabled.
|
|
||||||
"""
|
|
||||||
SKIP_ON_UNAVAILABLE_DB = not base.bool_from_env('OS_FAIL_ON_MISSING_DEPS')
|
|
||||||
|
|
|
@ -15,14 +15,11 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_db.sqlalchemy import test_base
|
|
||||||
|
|
||||||
from neutron.db.migration import cli as migration
|
|
||||||
from neutron.tests import base as tests_base
|
|
||||||
from neutron.tests.common import base
|
|
||||||
from neutron.tests.common import helpers
|
from neutron.tests.common import helpers
|
||||||
from neutron.tests.fullstack.resources import client as client_resource
|
from neutron.tests.fullstack.resources import client as client_resource
|
||||||
from neutron.tests import tools
|
from neutron.tests import tools
|
||||||
|
from neutron.tests.unit import testlib_api
|
||||||
|
|
||||||
|
|
||||||
# This is the directory from which infra fetches log files for fullstack tests
|
# This is the directory from which infra fetches log files for fullstack tests
|
||||||
|
@ -30,20 +27,31 @@ DEFAULT_LOG_DIR = os.path.join(helpers.get_test_log_path(),
|
||||||
'dsvm-fullstack-logs')
|
'dsvm-fullstack-logs')
|
||||||
|
|
||||||
|
|
||||||
class BaseFullStackTestCase(base.MySQLTestCase):
|
class BaseFullStackTestCase(testlib_api.MySQLTestCaseMixin,
|
||||||
|
testlib_api.SqlTestCase):
|
||||||
"""Base test class for full-stack tests."""
|
"""Base test class for full-stack tests."""
|
||||||
|
|
||||||
|
BUILD_WITH_MIGRATIONS = True
|
||||||
|
|
||||||
def setUp(self, environment):
|
def setUp(self, environment):
|
||||||
super(BaseFullStackTestCase, self).setUp()
|
super(BaseFullStackTestCase, self).setUp()
|
||||||
|
|
||||||
tests_base.setup_test_logging(
|
# NOTE(zzzeek): the opportunistic DB fixtures have built for
|
||||||
cfg.CONF, DEFAULT_LOG_DIR, '%s.txt' % self.get_name())
|
# us a per-test (or per-process) database. Set the URL of this
|
||||||
|
# database in CONF as the full stack tests need to actually run a
|
||||||
|
# neutron server against this database.
|
||||||
|
_orig_db_url = cfg.CONF.database.connection
|
||||||
|
cfg.CONF.set_override(
|
||||||
|
'connection', str(self.engine.url), group='database')
|
||||||
|
self.addCleanup(
|
||||||
|
cfg.CONF.set_override,
|
||||||
|
"connection", _orig_db_url, group="database"
|
||||||
|
)
|
||||||
|
|
||||||
# NOTE(ihrachys): seed should be reset before environment fixture below
|
# NOTE(ihrachys): seed should be reset before environment fixture below
|
||||||
# since the latter starts services that may rely on generated port
|
# since the latter starts services that may rely on generated port
|
||||||
# numbers
|
# numbers
|
||||||
tools.reset_random_seed()
|
tools.reset_random_seed()
|
||||||
self.create_db_tables()
|
|
||||||
self.environment = environment
|
self.environment = environment
|
||||||
self.environment.test_name = self.get_name()
|
self.environment.test_name = self.get_name()
|
||||||
self.useFixture(self.environment)
|
self.useFixture(self.environment)
|
||||||
|
@ -54,35 +62,3 @@ class BaseFullStackTestCase(base.MySQLTestCase):
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
class_name, test_name = self.id().split(".")[-2:]
|
class_name, test_name = self.id().split(".")[-2:]
|
||||||
return "%s.%s" % (class_name, test_name)
|
return "%s.%s" % (class_name, test_name)
|
||||||
|
|
||||||
def create_db_tables(self):
|
|
||||||
"""Populate the new database.
|
|
||||||
|
|
||||||
MySQLTestCase creates a new database for each test, but these need to
|
|
||||||
be populated with the appropriate tables. Before we can do that, we
|
|
||||||
must change the 'connection' option which the Neutron code knows to
|
|
||||||
look at.
|
|
||||||
|
|
||||||
Currently, the username and password options are hard-coded by
|
|
||||||
oslo.db and neutron/tests/functional/contrib/gate_hook.sh. Also,
|
|
||||||
we only support MySQL for now, but the groundwork for adding Postgres
|
|
||||||
is already laid.
|
|
||||||
"""
|
|
||||||
conn = ("mysql+pymysql://%(username)s:%(password)s"
|
|
||||||
"@127.0.0.1/%(db_name)s" % {
|
|
||||||
'username': test_base.DbFixture.USERNAME,
|
|
||||||
'password': test_base.DbFixture.PASSWORD,
|
|
||||||
'db_name': self.engine.url.database})
|
|
||||||
|
|
||||||
alembic_config = migration.get_neutron_config()
|
|
||||||
alembic_config.neutron_config = cfg.CONF
|
|
||||||
self.original_conn = cfg.CONF.database.connection
|
|
||||||
self.addCleanup(self._revert_connection_address)
|
|
||||||
cfg.CONF.set_override('connection', conn, group='database')
|
|
||||||
|
|
||||||
migration.do_alembic_command(alembic_config, 'upgrade', 'heads')
|
|
||||||
|
|
||||||
def _revert_connection_address(self):
|
|
||||||
cfg.CONF.set_override('connection',
|
|
||||||
self.original_conn,
|
|
||||||
group='database')
|
|
||||||
|
|
|
@ -19,9 +19,9 @@ import testscenarios
|
||||||
from neutron.tests.fullstack import base
|
from neutron.tests.fullstack import base
|
||||||
from neutron.tests.fullstack.resources import environment
|
from neutron.tests.fullstack.resources import environment
|
||||||
from neutron.tests.fullstack.resources import machine
|
from neutron.tests.fullstack.resources import machine
|
||||||
|
from neutron.tests.unit import testlib_api
|
||||||
|
|
||||||
|
load_tests = testlib_api.module_load_tests
|
||||||
load_tests = testscenarios.load_tests_apply_scenarios
|
|
||||||
|
|
||||||
|
|
||||||
class BaseConnectivitySameNetworkTest(base.BaseFullStackTestCase):
|
class BaseConnectivitySameNetworkTest(base.BaseFullStackTestCase):
|
||||||
|
|
|
@ -27,6 +27,9 @@ from neutron.tests.common import machine_fixtures
|
||||||
from neutron.tests.fullstack import base
|
from neutron.tests.fullstack import base
|
||||||
from neutron.tests.fullstack.resources import environment
|
from neutron.tests.fullstack.resources import environment
|
||||||
from neutron.tests.fullstack.resources import machine
|
from neutron.tests.fullstack.resources import machine
|
||||||
|
from neutron.tests.unit import testlib_api
|
||||||
|
|
||||||
|
load_tests = testlib_api.module_load_tests
|
||||||
|
|
||||||
|
|
||||||
class TestL3Agent(base.BaseFullStackTestCase):
|
class TestL3Agent(base.BaseFullStackTestCase):
|
||||||
|
|
|
@ -16,7 +16,6 @@ import functools
|
||||||
|
|
||||||
from neutron_lib import constants
|
from neutron_lib import constants
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import testscenarios
|
|
||||||
|
|
||||||
from neutron.agent.common import ovs_lib
|
from neutron.agent.common import ovs_lib
|
||||||
from neutron.agent.linux import bridge_lib
|
from neutron.agent.linux import bridge_lib
|
||||||
|
@ -27,6 +26,7 @@ from neutron.tests.common.agents import l2_extensions
|
||||||
from neutron.tests.fullstack import base
|
from neutron.tests.fullstack import base
|
||||||
from neutron.tests.fullstack.resources import environment
|
from neutron.tests.fullstack.resources import environment
|
||||||
from neutron.tests.fullstack.resources import machine
|
from neutron.tests.fullstack.resources import machine
|
||||||
|
from neutron.tests.unit import testlib_api
|
||||||
|
|
||||||
from neutron.plugins.ml2.drivers.linuxbridge.agent.common import \
|
from neutron.plugins.ml2.drivers.linuxbridge.agent.common import \
|
||||||
config as linuxbridge_agent_config
|
config as linuxbridge_agent_config
|
||||||
|
@ -36,8 +36,7 @@ from neutron.plugins.ml2.drivers.openvswitch.mech_driver import \
|
||||||
mech_openvswitch as mech_ovs
|
mech_openvswitch as mech_ovs
|
||||||
|
|
||||||
|
|
||||||
load_tests = testscenarios.load_tests_apply_scenarios
|
load_tests = testlib_api.module_load_tests
|
||||||
|
|
||||||
|
|
||||||
BANDWIDTH_BURST = 100
|
BANDWIDTH_BURST = 100
|
||||||
BANDWIDTH_LIMIT = 500
|
BANDWIDTH_LIMIT = 500
|
||||||
|
|
|
@ -22,12 +22,18 @@ from neutron import context
|
||||||
from neutron.db import db_base_plugin_v2 as base_plugin
|
from neutron.db import db_base_plugin_v2 as base_plugin
|
||||||
from neutron.db import models_v2
|
from neutron.db import models_v2
|
||||||
from neutron.ipam.drivers.neutrondb_ipam import db_models as ipam_models
|
from neutron.ipam.drivers.neutrondb_ipam import db_models as ipam_models
|
||||||
from neutron.tests import base
|
|
||||||
from neutron.tests.common import base as common_base
|
|
||||||
from neutron.tests.unit import testlib_api
|
from neutron.tests.unit import testlib_api
|
||||||
|
|
||||||
|
|
||||||
class IpamTestCase(base.BaseTestCase):
|
# required in order for testresources to optimize same-backend
|
||||||
|
# tests together
|
||||||
|
load_tests = testlib_api.module_load_tests
|
||||||
|
# FIXME(zzzeek): needs to be provided by oslo.db, current version
|
||||||
|
# is not working
|
||||||
|
# load_tests = test_base.optimize_db_test_loader(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
class IpamTestCase(testlib_api.SqlTestCase):
|
||||||
"""
|
"""
|
||||||
Base class for tests that aim to test ip allocation.
|
Base class for tests that aim to test ip allocation.
|
||||||
"""
|
"""
|
||||||
|
@ -36,7 +42,6 @@ class IpamTestCase(base.BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(IpamTestCase, self).setUp()
|
super(IpamTestCase, self).setUp()
|
||||||
cfg.CONF.set_override('notify_nova_on_port_status_changes', False)
|
cfg.CONF.set_override('notify_nova_on_port_status_changes', False)
|
||||||
self.useFixture(testlib_api.SqlFixture())
|
|
||||||
if self.use_pluggable_ipam:
|
if self.use_pluggable_ipam:
|
||||||
self._turn_on_pluggable_ipam()
|
self._turn_on_pluggable_ipam()
|
||||||
else:
|
else:
|
||||||
|
@ -155,17 +160,17 @@ class IpamTestCase(base.BaseTestCase):
|
||||||
self._create_port(self.port_id)
|
self._create_port(self.port_id)
|
||||||
|
|
||||||
|
|
||||||
class TestIpamMySql(common_base.MySQLTestCase, IpamTestCase):
|
class TestIpamMySql(testlib_api.MySQLTestCaseMixin, IpamTestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestIpamPsql(common_base.PostgreSQLTestCase, IpamTestCase):
|
class TestIpamPsql(testlib_api.PostgreSQLTestCaseMixin, IpamTestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestPluggableIpamMySql(common_base.MySQLTestCase, IpamTestCase):
|
class TestPluggableIpamMySql(testlib_api.MySQLTestCaseMixin, IpamTestCase):
|
||||||
use_pluggable_ipam = True
|
use_pluggable_ipam = True
|
||||||
|
|
||||||
|
|
||||||
class TestPluggableIpamPsql(common_base.PostgreSQLTestCase, IpamTestCase):
|
class TestPluggableIpamPsql(testlib_api.PostgreSQLTestCaseMixin, IpamTestCase):
|
||||||
use_pluggable_ipam = True
|
use_pluggable_ipam = True
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
[migration_dbs]
|
|
||||||
# Migration DB details are listed separately as they can't be connected to
|
|
||||||
# concurrently. These databases can't be the same as above
|
|
||||||
|
|
||||||
# Note, sqlite:// is in-memory and unique each time it is spawned.
|
|
||||||
# However file sqlite's are not unique.
|
|
||||||
|
|
||||||
#sqlite=sqlite://
|
|
||||||
#sqlitefile=sqlite:///test_migrations.db
|
|
||||||
#mysql=mysql+mysqldb://user:pass@localhost/test_migrations
|
|
||||||
#postgresql=postgresql+psycopg2://user:pass@localhost/test_migrations
|
|
||||||
|
|
||||||
[walk_style]
|
|
||||||
snake_walk=yes
|
|
|
@ -14,20 +14,15 @@
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
import abc
|
|
||||||
from alembic.ddl import base as alembic_ddl
|
from alembic.ddl import base as alembic_ddl
|
||||||
from alembic import script as alembic_script
|
from alembic import script as alembic_script
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import os
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_config import fixture as config_fixture
|
from oslo_config import fixture as config_fixture
|
||||||
from oslo_db.sqlalchemy import session
|
|
||||||
from oslo_db.sqlalchemy import test_base
|
|
||||||
from oslo_db.sqlalchemy import test_migrations
|
from oslo_db.sqlalchemy import test_migrations
|
||||||
from oslo_db.sqlalchemy import utils as oslo_utils
|
from oslo_db.sqlalchemy import utils as oslo_utils
|
||||||
|
from oslotest import base as oslotest_base
|
||||||
import six
|
import six
|
||||||
from six.moves import configparser
|
|
||||||
from six.moves.urllib import parse
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from sqlalchemy import event
|
from sqlalchemy import event
|
||||||
from sqlalchemy.sql import ddl as sqla_ddl
|
from sqlalchemy.sql import ddl as sqla_ddl
|
||||||
|
@ -38,8 +33,7 @@ import subprocess
|
||||||
from neutron.db.migration.alembic_migrations import external
|
from neutron.db.migration.alembic_migrations import external
|
||||||
from neutron.db.migration import cli as migration
|
from neutron.db.migration import cli as migration
|
||||||
from neutron.db.migration.models import head as head_models
|
from neutron.db.migration.models import head as head_models
|
||||||
from neutron.tests import base as base_tests
|
from neutron.tests.unit import testlib_api
|
||||||
from neutron.tests.common import base
|
|
||||||
|
|
||||||
cfg.CONF.import_opt('core_plugin', 'neutron.common.config')
|
cfg.CONF.import_opt('core_plugin', 'neutron.common.config')
|
||||||
|
|
||||||
|
@ -129,6 +123,8 @@ class _TestModelsMigrations(test_migrations.ModelsMigrationsSync):
|
||||||
- wrong value.
|
- wrong value.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
BUILD_SCHEMA = False
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(_TestModelsMigrations, self).setUp()
|
super(_TestModelsMigrations, self).setUp()
|
||||||
self.cfg = self.useFixture(config_fixture.Config())
|
self.cfg = self.useFixture(config_fixture.Config())
|
||||||
|
@ -207,8 +203,10 @@ class _TestModelsMigrations(test_migrations.ModelsMigrationsSync):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class TestModelsMigrationsMysql(_TestModelsMigrations,
|
class TestModelsMigrationsMysql(testlib_api.MySQLTestCaseMixin,
|
||||||
base.MySQLTestCase):
|
_TestModelsMigrations,
|
||||||
|
testlib_api.SqlTestCaseLight):
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _listener(self, engine, listener_func):
|
def _listener(self, engine, listener_func):
|
||||||
try:
|
try:
|
||||||
|
@ -347,12 +345,14 @@ class TestModelsMigrationsMysql(_TestModelsMigrations,
|
||||||
self._test_has_offline_migrations('heads', False)
|
self._test_has_offline_migrations('heads', False)
|
||||||
|
|
||||||
|
|
||||||
class TestModelsMigrationsPsql(_TestModelsMigrations,
|
class TestModelsMigrationsPsql(testlib_api.PostgreSQLTestCaseMixin,
|
||||||
base.PostgreSQLTestCase):
|
_TestModelsMigrations,
|
||||||
|
testlib_api.SqlTestCaseLight):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestSanityCheck(test_base.DbTestCase):
|
class TestSanityCheck(testlib_api.SqlTestCaseLight):
|
||||||
|
BUILD_SCHEMA = False
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestSanityCheck, self).setUp()
|
super(TestSanityCheck, self).setUp()
|
||||||
|
@ -381,7 +381,7 @@ class TestSanityCheck(test_base.DbTestCase):
|
||||||
script.check_sanity, conn)
|
script.check_sanity, conn)
|
||||||
|
|
||||||
|
|
||||||
class TestWalkDowngrade(test_base.DbTestCase):
|
class TestWalkDowngrade(oslotest_base.BaseTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestWalkDowngrade, self).setUp()
|
super(TestWalkDowngrade, self).setUp()
|
||||||
|
@ -400,84 +400,16 @@ class TestWalkDowngrade(test_base.DbTestCase):
|
||||||
|
|
||||||
if failed_revisions:
|
if failed_revisions:
|
||||||
self.fail('Migrations %s have downgrade' % failed_revisions)
|
self.fail('Migrations %s have downgrade' % failed_revisions)
|
||||||
|
|
||||||
|
|
||||||
def _is_backend_avail(backend,
|
|
||||||
user="openstack_citest",
|
|
||||||
passwd="openstack_citest",
|
|
||||||
database="openstack_citest"):
|
|
||||||
# is_backend_avail will be soon deprecated from oslo_db
|
|
||||||
# thats why its added here
|
|
||||||
try:
|
|
||||||
connect_uri = oslo_utils.get_connect_string(backend, user=user,
|
|
||||||
passwd=passwd,
|
|
||||||
database=database)
|
|
||||||
engine = session.create_engine(connect_uri)
|
|
||||||
connection = engine.connect()
|
|
||||||
except Exception:
|
|
||||||
# intentionally catch all to handle exceptions even if we don't
|
|
||||||
# have any backend code loaded.
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
connection.close()
|
|
||||||
engine.dispose()
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
class _TestWalkMigrations(object):
|
||||||
class _TestWalkMigrations(base_tests.BaseTestCase, test_base.DbTestCase):
|
|
||||||
'''This will add framework for testing schema migarations
|
'''This will add framework for testing schema migarations
|
||||||
for different backends.
|
for different backends.
|
||||||
|
|
||||||
Right now it supports pymysql and postgresql backends. Pymysql
|
|
||||||
and postgresql commands are executed to walk between to do updates.
|
|
||||||
For upgrade and downgrade migrate_up and migrate down functions
|
|
||||||
have been added.
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
DEFAULT_CONFIG_FILE = os.path.join(os.path.dirname(__file__),
|
BUILD_SCHEMA = False
|
||||||
'test_migrations.conf')
|
|
||||||
CONFIG_FILE_PATH = os.environ.get('NEUTRON_TEST_MIGRATIONS_CONF',
|
|
||||||
DEFAULT_CONFIG_FILE)
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
if not _is_backend_avail(self.BACKEND):
|
|
||||||
self.skipTest("%s not available" % self.BACKEND)
|
|
||||||
|
|
||||||
super(_TestWalkMigrations, self).setUp()
|
|
||||||
|
|
||||||
self.snake_walk = False
|
|
||||||
self.test_databases = {}
|
|
||||||
|
|
||||||
if os.path.exists(self.CONFIG_FILE_PATH):
|
|
||||||
cp = configparser.RawConfigParser()
|
|
||||||
try:
|
|
||||||
cp.read(self.CONFIG_FILE_PATH)
|
|
||||||
options = cp.options('migration_dbs')
|
|
||||||
for key in options:
|
|
||||||
self.test_databases[key] = cp.get('migration_dbs', key)
|
|
||||||
self.snake_walk = cp.getboolean('walk_style', 'snake_walk')
|
|
||||||
except configparser.ParsingError as e:
|
|
||||||
self.fail("Failed to read test_migrations.conf config "
|
|
||||||
"file. Got error: %s" % e)
|
|
||||||
else:
|
|
||||||
self.fail("Failed to find test_migrations.conf config "
|
|
||||||
"file.")
|
|
||||||
|
|
||||||
self.engines = {}
|
|
||||||
for key, value in self.test_databases.items():
|
|
||||||
self.engines[key] = sqlalchemy.create_engine(value)
|
|
||||||
|
|
||||||
# We start each test case with a completely blank slate.
|
|
||||||
self._reset_databases()
|
|
||||||
|
|
||||||
def assertColumnInTable(self, engine, table_name, column):
|
|
||||||
table = oslo_utils.get_table(engine, table_name)
|
|
||||||
self.assertIn(column, table.columns)
|
|
||||||
|
|
||||||
def assertColumnNotInTables(self, engine, table_name, column):
|
|
||||||
table = oslo_utils.get_table(engine, table_name)
|
|
||||||
self.assertNotIn(column, table.columns)
|
|
||||||
|
|
||||||
def execute_cmd(self, cmd=None):
|
def execute_cmd(self, cmd=None):
|
||||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||||
|
@ -486,23 +418,6 @@ class _TestWalkMigrations(base_tests.BaseTestCase, test_base.DbTestCase):
|
||||||
self.assertEqual(0, proc.returncode, 'Command failed with '
|
self.assertEqual(0, proc.returncode, 'Command failed with '
|
||||||
'output:\n%s' % output)
|
'output:\n%s' % output)
|
||||||
|
|
||||||
@abc.abstractproperty
|
|
||||||
def BACKEND(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def _database_recreate(self, user, password, database, host):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _reset_databases(self):
|
|
||||||
for key, engine in self.engines.items():
|
|
||||||
conn_string = self.test_databases[key]
|
|
||||||
conn_pieces = parse.urlparse(conn_string)
|
|
||||||
engine.dispose()
|
|
||||||
user, password, database, host = oslo_utils.get_db_connection_info(
|
|
||||||
conn_pieces)
|
|
||||||
self._database_recreate(user, password, database, host)
|
|
||||||
|
|
||||||
def _get_alembic_config(self, uri):
|
def _get_alembic_config(self, uri):
|
||||||
db_config = migration.get_neutron_config()
|
db_config = migration.get_neutron_config()
|
||||||
self.script_dir = alembic_script.ScriptDirectory.from_config(db_config)
|
self.script_dir = alembic_script.ScriptDirectory.from_config(db_config)
|
||||||
|
@ -512,71 +427,18 @@ class _TestWalkMigrations(base_tests.BaseTestCase, test_base.DbTestCase):
|
||||||
group='database')
|
group='database')
|
||||||
return db_config
|
return db_config
|
||||||
|
|
||||||
def _revisions(self, downgrade=False):
|
def _revisions(self):
|
||||||
"""Provides revisions and its parent revisions.
|
"""Provides revisions and its parent revisions.
|
||||||
|
|
||||||
:param downgrade: whether to include downgrade behavior or not.
|
|
||||||
:type downgrade: Bool
|
|
||||||
:return: List of tuples. Every tuple contains revision and its parent
|
:return: List of tuples. Every tuple contains revision and its parent
|
||||||
revision.
|
revision.
|
||||||
"""
|
"""
|
||||||
revisions = list(self.script_dir.walk_revisions("base", "heads"))
|
revisions = list(self.script_dir.walk_revisions("base", "heads"))
|
||||||
if not downgrade:
|
revisions = list(reversed(revisions))
|
||||||
revisions = list(reversed(revisions))
|
|
||||||
|
|
||||||
for rev in revisions:
|
for rev in revisions:
|
||||||
if downgrade:
|
# Destination, current
|
||||||
# Destination, current
|
yield rev.revision, rev.down_revision
|
||||||
yield rev.down_revision, rev.revision
|
|
||||||
else:
|
|
||||||
# Destination, current
|
|
||||||
yield rev.revision, rev.down_revision
|
|
||||||
|
|
||||||
def _walk_versions(self, config, engine, downgrade=True, snake_walk=False):
|
|
||||||
"""Test migrations ability to upgrade and downgrade.
|
|
||||||
|
|
||||||
:param downgrade: whether to include downgrade behavior or not.
|
|
||||||
:type downgrade: Bool
|
|
||||||
:snake_walk: enable mode when at every upgrade revision will be
|
|
||||||
downgraded and upgraded in previous state at upgrade and backward at
|
|
||||||
downgrade.
|
|
||||||
:type snake_walk: Bool
|
|
||||||
"""
|
|
||||||
|
|
||||||
revisions = self._revisions()
|
|
||||||
for dest, curr in revisions:
|
|
||||||
self._migrate_up(config, engine, dest, curr, with_data=True)
|
|
||||||
|
|
||||||
if snake_walk and dest != 'None':
|
|
||||||
# NOTE(I159): Pass reversed arguments into `_migrate_down`
|
|
||||||
# method because we have been upgraded to a destination
|
|
||||||
# revision and now we going to downgrade back.
|
|
||||||
self._migrate_down(config, engine, curr, dest, with_data=True)
|
|
||||||
self._migrate_up(config, engine, dest, curr, with_data=True)
|
|
||||||
|
|
||||||
if downgrade:
|
|
||||||
revisions = self._revisions(downgrade)
|
|
||||||
for dest, curr in revisions:
|
|
||||||
self._migrate_down(config, engine, dest, curr, with_data=True)
|
|
||||||
if snake_walk:
|
|
||||||
self._migrate_up(config, engine, curr,
|
|
||||||
dest, with_data=True)
|
|
||||||
self._migrate_down(config, engine, dest,
|
|
||||||
curr, with_data=True)
|
|
||||||
|
|
||||||
def _migrate_down(self, config, engine, dest, curr, with_data=False):
|
|
||||||
# First upgrade it to current to do downgrade
|
|
||||||
if dest:
|
|
||||||
migration.do_alembic_command(config, 'downgrade', dest)
|
|
||||||
else:
|
|
||||||
meta = sqlalchemy.MetaData(bind=engine)
|
|
||||||
meta.drop_all()
|
|
||||||
|
|
||||||
if with_data:
|
|
||||||
post_downgrade = getattr(
|
|
||||||
self, "_post_downgrade_%s" % curr, None)
|
|
||||||
if post_downgrade:
|
|
||||||
post_downgrade(engine)
|
|
||||||
|
|
||||||
def _migrate_up(self, config, engine, dest, curr, with_data=False):
|
def _migrate_up(self, config, engine, dest, curr, with_data=False):
|
||||||
if with_data:
|
if with_data:
|
||||||
|
@ -591,71 +453,24 @@ class _TestWalkMigrations(base_tests.BaseTestCase, test_base.DbTestCase):
|
||||||
if check and data:
|
if check and data:
|
||||||
check(engine, data)
|
check(engine, data)
|
||||||
|
|
||||||
|
def test_walk_versions(self):
|
||||||
|
"""Test migrations ability to upgrade and downgrade.
|
||||||
|
|
||||||
class TestWalkMigrationsMysql(_TestWalkMigrations):
|
"""
|
||||||
|
engine = self.engine
|
||||||
BACKEND = 'mysql+pymysql'
|
config = self._get_alembic_config(engine.url)
|
||||||
|
revisions = self._revisions()
|
||||||
def _database_recreate(self, user, password, database, host):
|
for dest, curr in revisions:
|
||||||
# We can execute the MySQL client to destroy and re-create
|
self._migrate_up(config, engine, dest, curr, with_data=True)
|
||||||
# the MYSQL database, which is easier and less error-prone
|
|
||||||
# than using SQLAlchemy to do this via MetaData...trust me.
|
|
||||||
sql = ("drop database if exists %(database)s; create "
|
|
||||||
"database %(database)s;") % {'database': database}
|
|
||||||
cmd = ("mysql -u \"%(user)s\" -p%(password)s -h %(host)s "
|
|
||||||
"-e \"%(sql)s\"") % {'user': user, 'password': password,
|
|
||||||
'host': host, 'sql': sql}
|
|
||||||
self.execute_cmd(cmd)
|
|
||||||
|
|
||||||
def test_mysql_opportunistically(self):
|
|
||||||
connect_string = oslo_utils.get_connect_string(self.BACKEND,
|
|
||||||
"openstack_citest", user="openstack_citest",
|
|
||||||
passwd="openstack_citest")
|
|
||||||
engine = session.create_engine(connect_string)
|
|
||||||
config = self._get_alembic_config(connect_string)
|
|
||||||
self.engines["mysqlcitest"] = engine
|
|
||||||
self.test_databases["mysqlcitest"] = connect_string
|
|
||||||
|
|
||||||
# build a fully populated mysql database with all the tables
|
|
||||||
self._reset_databases()
|
|
||||||
self._walk_versions(config, engine, False, False)
|
|
||||||
|
|
||||||
|
|
||||||
class TestWalkMigrationsPsql(_TestWalkMigrations):
|
class TestWalkMigrationsMysql(testlib_api.MySQLTestCaseMixin,
|
||||||
|
_TestWalkMigrations,
|
||||||
|
testlib_api.SqlTestCaseLight):
|
||||||
|
pass
|
||||||
|
|
||||||
BACKEND = 'postgresql'
|
|
||||||
|
|
||||||
def _database_recreate(self, user, password, database, host):
|
class TestWalkMigrationsPsql(testlib_api.PostgreSQLTestCaseMixin,
|
||||||
os.environ['PGPASSWORD'] = password
|
_TestWalkMigrations,
|
||||||
os.environ['PGUSER'] = user
|
testlib_api.SqlTestCaseLight):
|
||||||
# note(boris-42): We must create and drop database, we can't
|
pass
|
||||||
# drop database which we have connected to, so for such
|
|
||||||
# operations there is a special database template1.
|
|
||||||
sqlcmd = ("psql -w -U %(user)s -h %(host)s -c"
|
|
||||||
" '%(sql)s' -d template1")
|
|
||||||
sql = "drop database if exists %(database)s;"
|
|
||||||
sql = sql % {'database': database}
|
|
||||||
droptable = sqlcmd % {'user': user, 'host': host,
|
|
||||||
'sql': sql}
|
|
||||||
self.execute_cmd(droptable)
|
|
||||||
sql = "create database %(database)s;"
|
|
||||||
sql = sql % {'database': database}
|
|
||||||
createtable = sqlcmd % {'user': user, 'host': host,
|
|
||||||
'sql': sql}
|
|
||||||
self.execute_cmd(createtable)
|
|
||||||
|
|
||||||
def test_postgresql_opportunistically(self):
|
|
||||||
# add this to the global lists to make reset work with it, it's removed
|
|
||||||
# automatically in tearDown so no need to clean it up here.
|
|
||||||
connect_string = oslo_utils.get_connect_string(self.BACKEND,
|
|
||||||
"openstack_citest",
|
|
||||||
"openstack_citest",
|
|
||||||
"openstack_citest")
|
|
||||||
engine = session.create_engine(connect_string)
|
|
||||||
config = self._get_alembic_config(connect_string)
|
|
||||||
self.engines["postgresqlcitest"] = engine
|
|
||||||
self.test_databases["postgresqlcitest"] = connect_string
|
|
||||||
|
|
||||||
# build a fully populated postgresql database with all the tables
|
|
||||||
self._reset_databases()
|
|
||||||
self._walk_versions(config, engine, False, False)
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ class PluginClientFixture(AbstractClientFixture):
|
||||||
|
|
||||||
def _setUp(self):
|
def _setUp(self):
|
||||||
super(PluginClientFixture, self)._setUp()
|
super(PluginClientFixture, self)._setUp()
|
||||||
self.useFixture(testlib_api.SqlFixture())
|
self.useFixture(testlib_api.StaticSqlFixture())
|
||||||
self.useFixture(self.plugin_conf)
|
self.useFixture(self.plugin_conf)
|
||||||
self.useFixture(base.PluginFixture(self.plugin_conf.plugin_name))
|
self.useFixture(base.PluginFixture(self.plugin_conf.plugin_name))
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,18 @@
|
||||||
|
|
||||||
import fixtures
|
import fixtures
|
||||||
import six
|
import six
|
||||||
|
import testresources
|
||||||
|
import testscenarios
|
||||||
import testtools
|
import testtools
|
||||||
|
|
||||||
|
from oslo_db import exception as oslodb_exception
|
||||||
|
from oslo_db.sqlalchemy import enginefacade
|
||||||
|
from oslo_db.sqlalchemy import provision
|
||||||
|
from oslo_db.sqlalchemy import session
|
||||||
|
|
||||||
from neutron.db import api as db_api
|
from neutron.db import api as db_api
|
||||||
# Import all data models
|
# Import all data models
|
||||||
|
from neutron.db.migration import cli as migration
|
||||||
from neutron.db.migration.models import head # noqa
|
from neutron.db.migration.models import head # noqa
|
||||||
from neutron.db import model_base
|
from neutron.db import model_base
|
||||||
from neutron.tests import base
|
from neutron.tests import base
|
||||||
|
@ -58,39 +66,323 @@ def create_request(path, body, content_type, method='GET',
|
||||||
|
|
||||||
|
|
||||||
class SqlFixture(fixtures.Fixture):
|
class SqlFixture(fixtures.Fixture):
|
||||||
|
"""Base of a fixture which can create a schema and delete from
|
||||||
|
its tables.
|
||||||
|
|
||||||
# flag to indicate that the models have been loaded
|
"""
|
||||||
_TABLES_ESTABLISHED = False
|
|
||||||
|
@classmethod
|
||||||
|
def _generate_schema(cls, engine):
|
||||||
|
model_base.BASEV2.metadata.create_all(engine)
|
||||||
|
|
||||||
|
def _delete_from_schema(self, engine):
|
||||||
|
with engine.begin() as conn:
|
||||||
|
for table in reversed(
|
||||||
|
model_base.BASEV2.metadata.sorted_tables):
|
||||||
|
conn.execute(table.delete())
|
||||||
|
|
||||||
|
def _init_resources(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _setUp(self):
|
def _setUp(self):
|
||||||
# Register all data models
|
self._init_resources()
|
||||||
engine = db_api.get_engine()
|
|
||||||
if not SqlFixture._TABLES_ESTABLISHED:
|
|
||||||
model_base.BASEV2.metadata.create_all(engine)
|
|
||||||
SqlFixture._TABLES_ESTABLISHED = True
|
|
||||||
|
|
||||||
def clear_tables():
|
# check if the fixtures failed to get
|
||||||
with engine.begin() as conn:
|
# an engine. The test setUp() itself should also be checking
|
||||||
for table in reversed(
|
# this and raising skipTest.
|
||||||
model_base.BASEV2.metadata.sorted_tables):
|
if not hasattr(self, 'engine'):
|
||||||
conn.execute(table.delete())
|
return
|
||||||
|
|
||||||
self.addCleanup(clear_tables)
|
engine = self.engine
|
||||||
|
self.addCleanup(lambda: self._delete_from_schema(engine))
|
||||||
|
|
||||||
|
self.sessionmaker = session.get_maker(engine)
|
||||||
|
|
||||||
|
self.enginefacade_factory = enginefacade._TestTransactionFactory(
|
||||||
|
self.engine, self.sessionmaker, apply_global=False,
|
||||||
|
synchronous_reader=True)
|
||||||
|
|
||||||
|
_restore_factory = db_api.context_manager._root_factory
|
||||||
|
_restore_facade = db_api._FACADE
|
||||||
|
|
||||||
|
db_api.context_manager._root_factory = self.enginefacade_factory
|
||||||
|
|
||||||
|
db_api._FACADE = self.enginefacade_factory.get_legacy_facade()
|
||||||
|
|
||||||
|
engine = db_api._FACADE.get_engine()
|
||||||
|
|
||||||
|
self.addCleanup(
|
||||||
|
lambda: setattr(
|
||||||
|
db_api.context_manager,
|
||||||
|
"_root_factory", _restore_factory))
|
||||||
|
self.addCleanup(
|
||||||
|
lambda: setattr(
|
||||||
|
db_api, "_FACADE", _restore_facade))
|
||||||
|
|
||||||
|
self.useFixture(EnableSQLiteFKsFixture(engine))
|
||||||
|
|
||||||
|
|
||||||
class SqlTestCaseLight(base.DietTestCase):
|
class EnableSQLiteFKsFixture(fixtures.Fixture):
|
||||||
|
"""Turn SQLite PRAGMA foreign keys on and off for tests.
|
||||||
|
|
||||||
|
FIXME(zzzeek): figure out some way to get oslo.db test_base to honor
|
||||||
|
oslo_db.engines.create_engine() arguments like sqlite_fks as well
|
||||||
|
as handling that it needs to be turned off during drops.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, engine):
|
||||||
|
self.engine = engine
|
||||||
|
|
||||||
|
def _setUp(self):
|
||||||
|
if self.engine.name == 'sqlite':
|
||||||
|
self.engine.execute("PRAGMA foreign_keys=ON")
|
||||||
|
|
||||||
|
def disable_fks():
|
||||||
|
with self.engine.connect() as conn:
|
||||||
|
conn.connection.rollback()
|
||||||
|
conn.execute("PRAGMA foreign_keys=OFF")
|
||||||
|
self.addCleanup(disable_fks)
|
||||||
|
|
||||||
|
|
||||||
|
class StaticSqlFixture(SqlFixture):
|
||||||
|
"""Fixture which keeps a single sqlite memory database at the global
|
||||||
|
scope.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
_GLOBAL_RESOURCES = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _init_resources(cls):
|
||||||
|
# this is a classlevel version of what testresources
|
||||||
|
# does w/ the resources attribute as well as the
|
||||||
|
# setUpResources() step (which requires a test instance, that
|
||||||
|
# SqlFixture does not have). Because this is a SQLite memory
|
||||||
|
# database, we don't actually tear it down, so we can keep
|
||||||
|
# it running throughout all tests.
|
||||||
|
if cls._GLOBAL_RESOURCES:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
cls._GLOBAL_RESOURCES = True
|
||||||
|
cls.schema_resource = provision.SchemaResource(
|
||||||
|
provision.DatabaseResource("sqlite"),
|
||||||
|
cls._generate_schema, teardown=False)
|
||||||
|
dependency_resources = {}
|
||||||
|
for name, resource in cls.schema_resource.resources:
|
||||||
|
dependency_resources[name] = resource.getResource()
|
||||||
|
cls.schema_resource.make(dependency_resources)
|
||||||
|
cls.engine = dependency_resources['database'].engine
|
||||||
|
|
||||||
|
|
||||||
|
class StaticSqlFixtureNoSchema(SqlFixture):
|
||||||
|
"""Fixture which keeps a single sqlite memory database at the global
|
||||||
|
scope
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
_GLOBAL_RESOURCES = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _init_resources(cls):
|
||||||
|
if cls._GLOBAL_RESOURCES:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
cls._GLOBAL_RESOURCES = True
|
||||||
|
cls.database_resource = provision.DatabaseResource("sqlite")
|
||||||
|
dependency_resources = {}
|
||||||
|
for name, resource in cls.database_resource.resources:
|
||||||
|
dependency_resources[name] = resource.getResource()
|
||||||
|
cls.engine = dependency_resources['backend'].engine
|
||||||
|
|
||||||
|
def _delete_from_schema(self, engine):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OpportunisticSqlFixture(SqlFixture):
|
||||||
|
"""Fixture which uses testresources with oslo_db provisioning to
|
||||||
|
check for available backends and optimize test runs.
|
||||||
|
|
||||||
|
Requires that the test itself implement the resources attribute.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
DRIVER = 'sqlite'
|
||||||
|
|
||||||
|
def __init__(self, test):
|
||||||
|
super(OpportunisticSqlFixture, self).__init__()
|
||||||
|
self.test = test
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _generate_schema_w_migrations(cls, engine):
|
||||||
|
alembic_config = migration.get_neutron_config()
|
||||||
|
with engine.connect() as conn:
|
||||||
|
alembic_config.attributes['connection'] = conn
|
||||||
|
migration.do_alembic_command(
|
||||||
|
alembic_config, 'upgrade', 'heads')
|
||||||
|
|
||||||
|
def _delete_from_schema(self, engine):
|
||||||
|
if self.test.BUILD_SCHEMA:
|
||||||
|
super(OpportunisticSqlFixture, self)._delete_from_schema(engine)
|
||||||
|
|
||||||
|
def _init_resources(self):
|
||||||
|
testresources.setUpResources(
|
||||||
|
self.test, self.test.resources, testresources._get_result())
|
||||||
|
self.addCleanup(
|
||||||
|
testresources.tearDownResources,
|
||||||
|
self.test, self.test.resources, testresources._get_result()
|
||||||
|
)
|
||||||
|
|
||||||
|
# unfortunately, fixtures won't let us call a skip() from
|
||||||
|
# here. So the test has to check this also.
|
||||||
|
# see https://github.com/testing-cabal/fixtures/issues/31
|
||||||
|
if hasattr(self.test, 'db'):
|
||||||
|
self.engine = self.test.engine = self.test.db.engine
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resources_collection(cls, test):
|
||||||
|
# reimplement current oslo.db code.
|
||||||
|
# FIXME(zzzeek) The patterns here are up in the air enough
|
||||||
|
# that I think keeping this totally separate will give us the
|
||||||
|
# most leverage in being able to fix oslo.db in an upcoming
|
||||||
|
# release, then port neutron back to the working version.
|
||||||
|
|
||||||
|
driver = test.DRIVER
|
||||||
|
|
||||||
|
if driver not in test._database_resources:
|
||||||
|
try:
|
||||||
|
test._database_resources[driver] = \
|
||||||
|
provision.DatabaseResource(driver)
|
||||||
|
except oslodb_exception.BackendNotAvailable:
|
||||||
|
test._database_resources[driver] = None
|
||||||
|
|
||||||
|
database_resource = test._database_resources[driver]
|
||||||
|
if database_resource is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
key = (driver, None)
|
||||||
|
if test.BUILD_SCHEMA:
|
||||||
|
if key not in test._schema_resources:
|
||||||
|
test._schema_resources[key] = provision.SchemaResource(
|
||||||
|
database_resource,
|
||||||
|
cls._generate_schema_w_migrations
|
||||||
|
if test.BUILD_WITH_MIGRATIONS
|
||||||
|
else cls._generate_schema, teardown=False)
|
||||||
|
|
||||||
|
schema_resource = test._schema_resources[key]
|
||||||
|
return [
|
||||||
|
('schema', schema_resource),
|
||||||
|
('db', database_resource)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
return [
|
||||||
|
('db', database_resource)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSqlTestCase(object):
|
||||||
|
BUILD_SCHEMA = True
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(BaseSqlTestCase, self).setUp()
|
||||||
|
|
||||||
|
self._setup_database_fixtures()
|
||||||
|
|
||||||
|
def _setup_database_fixtures(self):
|
||||||
|
if self.BUILD_SCHEMA:
|
||||||
|
fixture = StaticSqlFixture()
|
||||||
|
else:
|
||||||
|
fixture = StaticSqlFixtureNoSchema()
|
||||||
|
self.useFixture(fixture)
|
||||||
|
self.engine = fixture.engine
|
||||||
|
|
||||||
|
|
||||||
|
class SqlTestCaseLight(BaseSqlTestCase, base.DietTestCase):
|
||||||
"""All SQL taste, zero plugin/rpc sugar"""
|
"""All SQL taste, zero plugin/rpc sugar"""
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(SqlTestCaseLight, self).setUp()
|
class SqlTestCase(BaseSqlTestCase, base.BaseTestCase):
|
||||||
self.useFixture(SqlFixture())
|
"""regular sql test"""
|
||||||
|
|
||||||
|
|
||||||
class SqlTestCase(base.BaseTestCase):
|
class OpportunisticDBTestMixin(object):
|
||||||
|
"""Mixin that converts a BaseSqlTestCase to use the OpportunisticSqlFixture.
|
||||||
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
SKIP_ON_UNAVAILABLE_DB = not base.bool_from_env('OS_FAIL_ON_MISSING_DEPS')
|
||||||
super(SqlTestCase, self).setUp()
|
|
||||||
self.useFixture(SqlFixture())
|
FIXTURE = OpportunisticSqlFixture
|
||||||
|
|
||||||
|
BUILD_WITH_MIGRATIONS = False
|
||||||
|
|
||||||
|
def _setup_database_fixtures(self):
|
||||||
|
self.useFixture(self.FIXTURE(self))
|
||||||
|
|
||||||
|
if not hasattr(self, 'db'):
|
||||||
|
msg = "backend '%s' unavailable" % self.DRIVER
|
||||||
|
if self.SKIP_ON_UNAVAILABLE_DB:
|
||||||
|
self.skip(msg)
|
||||||
|
else:
|
||||||
|
self.fail(msg)
|
||||||
|
|
||||||
|
_schema_resources = {}
|
||||||
|
_database_resources = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def resources(self):
|
||||||
|
"""this attribute is used by testresources for optimized
|
||||||
|
sorting of tests.
|
||||||
|
|
||||||
|
This is the big requirement that allows testresources to sort
|
||||||
|
tests such that database "resources" can be kept open for
|
||||||
|
many tests at once.
|
||||||
|
|
||||||
|
IMO(zzzeek) "sorting" should not be needed; only that necessary
|
||||||
|
resources stay open as long as they are needed (or long enough to
|
||||||
|
reduce overhead). testresources would be improved to not depend on
|
||||||
|
custom, incompatible-with-pytest "suite classes", fixture information
|
||||||
|
leaking out of the Fixture classes themselves, and exotic sorting
|
||||||
|
schemes for something that can nearly always be handled "good enough"
|
||||||
|
with unittest-standard setupclass/setupmodule schemes.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.FIXTURE.resources_collection(self)
|
||||||
|
|
||||||
|
|
||||||
|
class MySQLTestCaseMixin(OpportunisticDBTestMixin):
|
||||||
|
"""Mixin that turns any BaseSqlTestCase into a MySQL test suite.
|
||||||
|
|
||||||
|
If the MySQL db is unavailable then this test is skipped, unless
|
||||||
|
OS_FAIL_ON_MISSING_DEPS is enabled.
|
||||||
|
"""
|
||||||
|
DRIVER = "mysql"
|
||||||
|
|
||||||
|
|
||||||
|
class PostgreSQLTestCaseMixin(OpportunisticDBTestMixin):
|
||||||
|
"""Mixin that turns any BaseSqlTestCase into a PostgresSQL test suite.
|
||||||
|
|
||||||
|
If the PostgreSQL db is unavailable then this test is skipped, unless
|
||||||
|
OS_FAIL_ON_MISSING_DEPS is enabled.
|
||||||
|
"""
|
||||||
|
DRIVER = "postgresql"
|
||||||
|
|
||||||
|
|
||||||
|
def module_load_tests(loader, found_tests, pattern):
|
||||||
|
"""Apply OptimisingTestSuite on a per-module basis.
|
||||||
|
|
||||||
|
FIXME(zzzeek): oslo.db provides this but the contract that
|
||||||
|
"pattern" should be None no longer seems to behave as it used
|
||||||
|
to at the module level, so this function needs to be added in this
|
||||||
|
form.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = testresources.OptimisingTestSuite()
|
||||||
|
found_tests = testscenarios.load_tests_apply_scenarios(
|
||||||
|
loader, found_tests, pattern)
|
||||||
|
result.addTest(found_tests)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class WebTestCase(SqlTestCase):
|
class WebTestCase(SqlTestCase):
|
||||||
|
|
Loading…
Reference in New Issue