diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-07-25 18:33:04 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-07-25 18:33:04 -0400 |
commit | 9e6624c0496e22ec1139e0fb54cfefc14f660352 (patch) | |
tree | 673048a2c63d3f59b94fa1326c6b9adfb636bfe1 | |
parent | fe878f5aff1cb17fdf1f13aba1d13f008da0ef4e (diff) | |
download | sqlalchemy-9e6624c0496e22ec1139e0fb54cfefc14f660352.tar.gz |
- proof of concept for parallel testing
-rw-r--r-- | lib/sqlalchemy/engine/url.py | 6 | ||||
-rw-r--r-- | lib/sqlalchemy/testing/config.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/testing/fixtures.py | 18 | ||||
-rw-r--r-- | lib/sqlalchemy/testing/plugin/plugin_base.py | 12 | ||||
-rw-r--r-- | lib/sqlalchemy/testing/plugin/provision.py | 62 | ||||
-rw-r--r-- | lib/sqlalchemy/testing/plugin/pytestplugin.py | 16 |
6 files changed, 102 insertions, 13 deletions
diff --git a/lib/sqlalchemy/engine/url.py b/lib/sqlalchemy/engine/url.py index e3629613f..116a3a343 100644 --- a/lib/sqlalchemy/engine/url.py +++ b/lib/sqlalchemy/engine/url.py @@ -105,6 +105,12 @@ class URL(object): self.database == other.database and \ self.query == other.query + def get_backend_name(self): + if '+' not in self.drivername: + return self.drivername + else: + return self.drivername.split('+')[0] + def get_dialect(self): """Return the SQLAlchemy database dialect class corresponding to this URL's driver name. diff --git a/lib/sqlalchemy/testing/config.py b/lib/sqlalchemy/testing/config.py index c914434b4..84344eb31 100644 --- a/lib/sqlalchemy/testing/config.py +++ b/lib/sqlalchemy/testing/config.py @@ -78,3 +78,4 @@ class Config(object): def all_dbs(cls): for cfg in cls.all_configs(): yield cfg.db + diff --git a/lib/sqlalchemy/testing/fixtures.py b/lib/sqlalchemy/testing/fixtures.py index 7c7b00998..d86049da7 100644 --- a/lib/sqlalchemy/testing/fixtures.py +++ b/lib/sqlalchemy/testing/fixtures.py @@ -91,20 +91,12 @@ class TablesTest(TestBase): cls.run_create_tables = 'each' assert cls.run_inserts in ('each', None) - if cls.other is None: - cls.other = adict() + cls.other = adict() + cls.tables = adict() - if cls.tables is None: - cls.tables = adict() - - if cls.bind is None: - setattr(cls, 'bind', cls.setup_bind()) - - if cls.metadata is None: - setattr(cls, 'metadata', sa.MetaData()) - - if cls.metadata.bind is None: - cls.metadata.bind = cls.bind + cls.bind = cls.setup_bind() + cls.metadata = sa.MetaData() + cls.metadata.bind = cls.bind @classmethod def _setup_once_inserts(cls): diff --git a/lib/sqlalchemy/testing/plugin/plugin_base.py b/lib/sqlalchemy/testing/plugin/plugin_base.py index 2590f3b1e..47d297b2e 100644 --- a/lib/sqlalchemy/testing/plugin/plugin_base.py +++ b/lib/sqlalchemy/testing/plugin/plugin_base.py @@ -31,6 +31,7 @@ if py3k: else: import ConfigParser as configparser +FOLLOWER_IDENT = None # late imports fixtures = None @@ -100,6 +101,11 @@ def setup_options(make_option): help="Write/update profiling data.") +def configure_follower(follower_ident): + global FOLLOWER_IDENT + FOLLOWER_IDENT = "test_%s" % follower_ident + + def read_config(): global file_config file_config = configparser.ConfigParser() @@ -135,6 +141,7 @@ def post_begin(): from sqlalchemy import util + def _log(opt_str, value, parser): global logging if not logging: @@ -176,6 +183,7 @@ def post(fn): return fn + @pre def _setup_options(opt, file_config): global options @@ -214,6 +222,10 @@ def _engine_uri(options, file_config): db_urls.append(file_config.get('db', 'default')) for db_url in db_urls: + if FOLLOWER_IDENT: + from sqlalchemy.engine import url + db_url = url.make_url(db_url) + db_url.database = FOLLOWER_IDENT eng = engines.testing_engine(db_url, db_opts) eng.connect().close() config.Config.register(eng, db_opts, options, file_config, testing) diff --git a/lib/sqlalchemy/testing/plugin/provision.py b/lib/sqlalchemy/testing/plugin/provision.py new file mode 100644 index 000000000..d665727f1 --- /dev/null +++ b/lib/sqlalchemy/testing/plugin/provision.py @@ -0,0 +1,62 @@ +from sqlalchemy.engine import url as sa_url + +def create_follower_db(follower_ident): + from .. import config, engines + + follower_ident = "test_%s" % follower_ident + + hosts = set() + + for cfg in config.Config.all_configs(): + url = cfg.db.url + backend = url.get_backend_name() + host_conf = ( + backend, + url.username, url.host, url.database) + + if host_conf not in hosts: + if backend.startswith("postgresql"): + _pg_create_db(cfg.db, follower_ident) + elif backend.startswith("mysql"): + _mysql_create_db(cfg.db, follower_ident) + + new_url = sa_url.make_url(str(url)) + + new_url.database = follower_ident + eng = engines.testing_engine(new_url, cfg.db_opts) + + if backend.startswith("postgresql"): + _pg_init_db(eng) + elif backend.startswith("mysql"): + _mysql_init_db(eng) + + hosts.add(host_conf) + + +def _pg_create_db(eng, ident): + with eng.connect().execution_options( + isolation_level="AUTOCOMMIT") as conn: + try: + conn.execute("DROP DATABASE %s" % ident) + except: + pass + conn.execute("CREATE DATABASE %s" % ident) + + +def _pg_init_db(eng): + with eng.connect() as conn: + conn.execute("CREATE SCHEMA test_schema") + conn.execute("CREATE SCHEMA test_schema_2") + + +def _mysql_create_db(eng, ident): + with eng.connect() as conn: + try: + conn.execute("DROP DATABASE %s" % ident) + except: + pass + conn.execute("CREATE DATABASE %s" % ident) + + +def _mysql_init_db(eng): + pass diff --git a/lib/sqlalchemy/testing/plugin/pytestplugin.py b/lib/sqlalchemy/testing/plugin/pytestplugin.py index 11238bbac..7bef644d9 100644 --- a/lib/sqlalchemy/testing/plugin/pytestplugin.py +++ b/lib/sqlalchemy/testing/plugin/pytestplugin.py @@ -3,6 +3,7 @@ import argparse import inspect from . import plugin_base import collections +import itertools def pytest_addoption(parser): @@ -24,6 +25,11 @@ def pytest_addoption(parser): def pytest_configure(config): + if hasattr(config, "slaveinput"): + plugin_base.configure_follower( + config.slaveinput["follower_ident"] + ) + plugin_base.pre_begin(config.option) plugin_base.set_coverage_flag(bool(getattr(config.option, @@ -31,6 +37,16 @@ def pytest_configure(config): plugin_base.post_begin() +_follower_count = itertools.count(1) + + +def pytest_configure_node(node): + # the master for each node fills slaveinput dictionary + # which pytest-xdist will transfer to the subprocess + node.slaveinput["follower_ident"] = next(_follower_count) + from . import provision + provision.create_follower_db(node.slaveinput["follower_ident"]) + def pytest_collection_modifyitems(session, config, items): # look for all those classes that specify __backend__ and |