summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/testing
diff options
context:
space:
mode:
authorScott Dugas <scott.dugas@foundationdb.com>2014-11-03 14:54:51 -0500
committerScott Dugas <scott.dugas@foundationdb.com>2014-11-03 14:54:51 -0500
commitb31ab006897d2709442f9745faf0cac6e0de1713 (patch)
treea6b428e9ca7f1f67c5193581ecd82a83632eeb79 /lib/sqlalchemy/testing
parentebb9d57cb385f49becbf54c6f78647715ddd1c29 (diff)
parent7bf5ac9c1e814c999d4930941935e1d5cfd236bf (diff)
downloadsqlalchemy-b31ab006897d2709442f9745faf0cac6e0de1713.tar.gz
Merge branch 'master' into fdbsql-tests
Conflicts: lib/sqlalchemy/testing/exclusions.py
Diffstat (limited to 'lib/sqlalchemy/testing')
-rw-r--r--lib/sqlalchemy/testing/engines.py4
-rw-r--r--lib/sqlalchemy/testing/exclusions.py5
-rw-r--r--lib/sqlalchemy/testing/plugin/bootstrap.py44
-rw-r--r--lib/sqlalchemy/testing/plugin/noseplugin.py34
-rw-r--r--lib/sqlalchemy/testing/plugin/plugin_base.py56
-rw-r--r--lib/sqlalchemy/testing/plugin/pytestplugin.py14
-rw-r--r--lib/sqlalchemy/testing/provision.py (renamed from lib/sqlalchemy/testing/plugin/provision.py)16
-rw-r--r--lib/sqlalchemy/testing/requirements.py14
-rw-r--r--lib/sqlalchemy/testing/runner.py2
-rw-r--r--lib/sqlalchemy/testing/suite/test_insert.py37
-rw-r--r--lib/sqlalchemy/testing/suite/test_reflection.py95
11 files changed, 240 insertions, 81 deletions
diff --git a/lib/sqlalchemy/testing/engines.py b/lib/sqlalchemy/testing/engines.py
index 3a3f5be10..0f6f59401 100644
--- a/lib/sqlalchemy/testing/engines.py
+++ b/lib/sqlalchemy/testing/engines.py
@@ -37,8 +37,6 @@ class ConnectionKiller(object):
def _safe(self, fn):
try:
fn()
- except (SystemExit, KeyboardInterrupt):
- raise
except Exception as e:
warnings.warn(
"testing_reaper couldn't "
@@ -168,8 +166,6 @@ class ReconnectFixture(object):
def _safe(self, fn):
try:
fn()
- except (SystemExit, KeyboardInterrupt):
- raise
except Exception as e:
warnings.warn(
"ReconnectFixture couldn't "
diff --git a/lib/sqlalchemy/testing/exclusions.py b/lib/sqlalchemy/testing/exclusions.py
index c9f81c8b9..e3d91300d 100644
--- a/lib/sqlalchemy/testing/exclusions.py
+++ b/lib/sqlalchemy/testing/exclusions.py
@@ -135,7 +135,7 @@ class compound(object):
name, fail._as_string(config), str(exc_value))))
break
else:
- raise exc_type, exc_value, exc_traceback
+ util.raise_from_cause(ex)
def _expect_success(self, config, name='block'):
if not self.fails:
@@ -180,8 +180,7 @@ class Predicate(object):
@classmethod
def as_predicate(cls, predicate, description=None):
if isinstance(predicate, compound):
- return cls.as_predicate(predicate.fails.union(predicate.skips))
-
+ return cls.as_predicate(predicate.enabled_for_config, description)
elif isinstance(predicate, Predicate):
if description and predicate.description is None:
predicate.description = description
diff --git a/lib/sqlalchemy/testing/plugin/bootstrap.py b/lib/sqlalchemy/testing/plugin/bootstrap.py
new file mode 100644
index 000000000..497fcb7e5
--- /dev/null
+++ b/lib/sqlalchemy/testing/plugin/bootstrap.py
@@ -0,0 +1,44 @@
+"""
+Bootstrapper for nose/pytest plugins.
+
+The entire rationale for this system is to get the modules in plugin/
+imported without importing all of the supporting library, so that we can
+set up things for testing before coverage starts.
+
+The rationale for all of plugin/ being *in* the supporting library in the
+first place is so that the testing and plugin suite is available to other
+libraries, mainly external SQLAlchemy and Alembic dialects, to make use
+of the same test environment and standard suites available to
+SQLAlchemy/Alembic themselves without the need to ship/install a separate
+package outside of SQLAlchemy.
+
+NOTE: copied/adapted from SQLAlchemy master for backwards compatibility;
+this should be removable when Alembic targets SQLAlchemy 1.0.0.
+
+"""
+
+import os
+import sys
+
+bootstrap_file = locals()['bootstrap_file']
+to_bootstrap = locals()['to_bootstrap']
+
+
+def load_file_as_module(name):
+ path = os.path.join(os.path.dirname(bootstrap_file), "%s.py" % name)
+ if sys.version_info >= (3, 3):
+ from importlib import machinery
+ mod = machinery.SourceFileLoader(name, path).load_module()
+ else:
+ import imp
+ mod = imp.load_source(name, path)
+ return mod
+
+if to_bootstrap == "pytest":
+ sys.modules["sqla_plugin_base"] = load_file_as_module("plugin_base")
+ sys.modules["sqla_pytestplugin"] = load_file_as_module("pytestplugin")
+elif to_bootstrap == "nose":
+ sys.modules["sqla_plugin_base"] = load_file_as_module("plugin_base")
+ sys.modules["sqla_noseplugin"] = load_file_as_module("noseplugin")
+else:
+ raise Exception("unknown bootstrap: %s" % to_bootstrap) # noqa
diff --git a/lib/sqlalchemy/testing/plugin/noseplugin.py b/lib/sqlalchemy/testing/plugin/noseplugin.py
index 6ef539142..538087770 100644
--- a/lib/sqlalchemy/testing/plugin/noseplugin.py
+++ b/lib/sqlalchemy/testing/plugin/noseplugin.py
@@ -12,6 +12,14 @@ way (e.g. as a package-less import).
"""
+try:
+ # installed by bootstrap.py
+ import sqla_plugin_base as plugin_base
+except ImportError:
+ # assume we're a package, use traditional import
+ from . import plugin_base
+
+
import os
import sys
@@ -19,16 +27,6 @@ from nose.plugins import Plugin
fixtures = None
py3k = sys.version_info >= (3, 0)
-# no package imports yet! this prevents us from tripping coverage
-# too soon.
-path = os.path.join(os.path.dirname(__file__), "plugin_base.py")
-if sys.version_info >= (3, 3):
- from importlib import machinery
- plugin_base = machinery.SourceFileLoader(
- "plugin_base", path).load_module()
-else:
- import imp
- plugin_base = imp.load_source("plugin_base", path)
class NoseSQLAlchemy(Plugin):
@@ -58,10 +56,10 @@ class NoseSQLAlchemy(Plugin):
plugin_base.set_coverage_flag(options.enable_plugin_coverage)
+ def begin(self):
global fixtures
- from sqlalchemy.testing import fixtures
+ from sqlalchemy.testing import fixtures # noqa
- def begin(self):
plugin_base.post_begin()
def describeTest(self, test):
@@ -72,19 +70,23 @@ class NoseSQLAlchemy(Plugin):
def wantMethod(self, fn):
if py3k:
+ if not hasattr(fn.__self__, 'cls'):
+ return False
cls = fn.__self__.cls
else:
cls = fn.im_class
- print "METH:", fn, "CLS:", cls
return plugin_base.want_method(cls, fn)
def wantClass(self, cls):
return plugin_base.want_class(cls)
def beforeTest(self, test):
- plugin_base.before_test(test,
- test.test.cls.__module__,
- test.test.cls, test.test.method.__name__)
+ if not hasattr(test.test, 'cls'):
+ return
+ plugin_base.before_test(
+ test,
+ test.test.cls.__module__,
+ test.test.cls, test.test.method.__name__)
def afterTest(self, test):
plugin_base.after_test(test)
diff --git a/lib/sqlalchemy/testing/plugin/plugin_base.py b/lib/sqlalchemy/testing/plugin/plugin_base.py
index 7ba31d3e3..6696427dc 100644
--- a/lib/sqlalchemy/testing/plugin/plugin_base.py
+++ b/lib/sqlalchemy/testing/plugin/plugin_base.py
@@ -31,8 +31,6 @@ if py3k:
else:
import ConfigParser as configparser
-FOLLOWER_IDENT = None
-
# late imports
fixtures = None
engines = None
@@ -72,8 +70,6 @@ def setup_options(make_option):
help="Drop all tables in the target database first")
make_option("--backend-only", action="store_true", dest="backend_only",
help="Run only tests marked with __backend__")
- make_option("--mockpool", action="store_true", dest="mockpool",
- help="Use mock pool (asserts only one connection used)")
make_option("--low-connections", action="store_true",
dest="low_connections",
help="Use a low number of distinct connections - "
@@ -95,14 +91,6 @@ def setup_options(make_option):
make_option("--exclude-tag", action="callback", callback=_exclude_tag,
type="string",
help="Exclude tests with tag <tag>")
- make_option("--serverside", action="store_true",
- help="Turn on server side cursors for PG")
- make_option("--mysql-engine", action="store",
- dest="mysql_engine", default=None,
- help="Use the specified MySQL storage engine for all tables, "
- "default is a db-default/InnoDB combo.")
- make_option("--tableopts", action="append", dest="tableopts", default=[],
- help="Add a dialect-specific table option, key=value")
make_option("--write-profiles", action="store_true",
dest="write_profiles", default=False,
help="Write/update profiling data.")
@@ -115,8 +103,8 @@ def configure_follower(follower_ident):
database creation.
"""
- global FOLLOWER_IDENT
- FOLLOWER_IDENT = follower_ident
+ from sqlalchemy.testing import provision
+ provision.FOLLOWER_IDENT = follower_ident
def memoize_important_follower_config(dict_):
@@ -177,12 +165,14 @@ def post_begin():
global util, fixtures, engines, exclusions, \
assertions, warnings, profiling,\
config, testing
- from sqlalchemy import testing
- from sqlalchemy.testing import fixtures, engines, exclusions, \
- assertions, warnings, profiling, config
- from sqlalchemy import util
+ from sqlalchemy import testing # noqa
+ from sqlalchemy.testing import fixtures, engines, exclusions # noqa
+ from sqlalchemy.testing import assertions, warnings, profiling # noqa
+ from sqlalchemy.testing import config # noqa
+ from sqlalchemy import util # noqa
warnings.setup_filters()
+
def _log(opt_str, value, parser):
global logging
if not logging:
@@ -234,12 +224,6 @@ def _setup_options(opt, file_config):
@pre
-def _server_side_cursors(options, file_config):
- if options.serverside:
- db_opts['server_side_cursors'] = True
-
-
-@pre
def _monkeypatch_cdecimal(options, file_config):
if options.cdecimal:
import cdecimal
@@ -250,7 +234,7 @@ def _monkeypatch_cdecimal(options, file_config):
def _engine_uri(options, file_config):
from sqlalchemy.testing import config
from sqlalchemy import testing
- from sqlalchemy.testing.plugin import provision
+ from sqlalchemy.testing import provision
if options.dburi:
db_urls = list(options.dburi)
@@ -273,20 +257,13 @@ def _engine_uri(options, file_config):
for db_url in db_urls:
cfg = provision.setup_config(
- db_url, db_opts, options, file_config, FOLLOWER_IDENT)
+ db_url, db_opts, options, file_config, provision.FOLLOWER_IDENT)
if not config._current:
cfg.set_as_current(cfg, testing)
@post
-def _engine_pool(options, file_config):
- if options.mockpool:
- from sqlalchemy import pool
- db_opts['poolclass'] = pool.AssertionPool
-
-
-@post
def _requirements(options, file_config):
requirement_cls = file_config.get('sqla_testing', "requirement_cls")
@@ -369,19 +346,6 @@ def _prep_testing_database(options, file_config):
@post
-def _set_table_options(options, file_config):
- from sqlalchemy.testing import schema
-
- table_options = schema.table_options
- for spec in options.tableopts:
- key, value = spec.split('=')
- table_options[key] = value
-
- if options.mysql_engine:
- table_options['mysql_engine'] = options.mysql_engine
-
-
-@post
def _reverse_topological(options, file_config):
if options.reversetop:
from sqlalchemy.orm.util import randomize_unitofwork
diff --git a/lib/sqlalchemy/testing/plugin/pytestplugin.py b/lib/sqlalchemy/testing/plugin/pytestplugin.py
index 005942913..4bbc8ed9a 100644
--- a/lib/sqlalchemy/testing/plugin/pytestplugin.py
+++ b/lib/sqlalchemy/testing/plugin/pytestplugin.py
@@ -1,7 +1,13 @@
+try:
+ # installed by bootstrap.py
+ import sqla_plugin_base as plugin_base
+except ImportError:
+ # assume we're a package, use traditional import
+ from . import plugin_base
+
import pytest
import argparse
import inspect
-from . import plugin_base
import collections
import itertools
@@ -42,6 +48,8 @@ def pytest_configure(config):
plugin_base.set_coverage_flag(bool(getattr(config.option,
"cov_source", False)))
+
+def pytest_sessionstart(session):
plugin_base.post_begin()
if has_xdist:
@@ -54,11 +62,11 @@ if has_xdist:
plugin_base.memoize_important_follower_config(node.slaveinput)
node.slaveinput["follower_ident"] = "test_%s" % next(_follower_count)
- from . import provision
+ from sqlalchemy.testing import provision
provision.create_follower_db(node.slaveinput["follower_ident"])
def pytest_testnodedown(node, error):
- from . import provision
+ from sqlalchemy.testing import provision
provision.drop_follower_db(node.slaveinput["follower_ident"])
diff --git a/lib/sqlalchemy/testing/plugin/provision.py b/lib/sqlalchemy/testing/provision.py
index c6b9030f5..c8f7fdf30 100644
--- a/lib/sqlalchemy/testing/plugin/provision.py
+++ b/lib/sqlalchemy/testing/provision.py
@@ -1,8 +1,10 @@
from sqlalchemy.engine import url as sa_url
from sqlalchemy import text
from sqlalchemy.util import compat
-from .. import config, engines
-import os
+from . import config, engines
+
+
+FOLLOWER_IDENT = None
class register(object):
@@ -118,7 +120,7 @@ def _pg_create_db(cfg, eng, ident):
isolation_level="AUTOCOMMIT") as conn:
try:
_pg_drop_db(cfg, conn, ident)
- except:
+ except Exception:
pass
currentdb = conn.scalar("select current_database()")
conn.execute("CREATE DATABASE %s TEMPLATE %s" % (ident, currentdb))
@@ -129,7 +131,7 @@ def _mysql_create_db(cfg, eng, ident):
with eng.connect() as conn:
try:
_mysql_drop_db(cfg, conn, ident)
- except:
+ except Exception:
pass
conn.execute("CREATE DATABASE %s" % ident)
conn.execute("CREATE DATABASE %s_test_schema" % ident)
@@ -171,15 +173,15 @@ def _mysql_drop_db(cfg, eng, ident):
with eng.connect() as conn:
try:
conn.execute("DROP DATABASE %s_test_schema" % ident)
- except:
+ except Exception:
pass
try:
conn.execute("DROP DATABASE %s_test_schema_2" % ident)
- except:
+ except Exception:
pass
try:
conn.execute("DROP DATABASE %s" % ident)
- except:
+ except Exception:
pass
diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py
index a04bcbbdd..da3e3128a 100644
--- a/lib/sqlalchemy/testing/requirements.py
+++ b/lib/sqlalchemy/testing/requirements.py
@@ -314,6 +314,20 @@ class SuiteRequirements(Requirements):
return exclusions.open()
@property
+ def temp_table_reflection(self):
+ return exclusions.open()
+
+ @property
+ def temp_table_names(self):
+ """target dialect supports listing of temporary table names"""
+ return exclusions.closed()
+
+ @property
+ def temporary_views(self):
+ """target database supports temporary views"""
+ return exclusions.closed()
+
+ @property
def index_reflection(self):
return exclusions.open()
diff --git a/lib/sqlalchemy/testing/runner.py b/lib/sqlalchemy/testing/runner.py
index df254520b..23d7a0a91 100644
--- a/lib/sqlalchemy/testing/runner.py
+++ b/lib/sqlalchemy/testing/runner.py
@@ -30,7 +30,7 @@ SQLAlchemy itself is possible.
"""
-from sqlalchemy.testing.plugin.noseplugin import NoseSQLAlchemy
+from .plugin.noseplugin import NoseSQLAlchemy
import nose
diff --git a/lib/sqlalchemy/testing/suite/test_insert.py b/lib/sqlalchemy/testing/suite/test_insert.py
index 92d3d93e5..38519dfb9 100644
--- a/lib/sqlalchemy/testing/suite/test_insert.py
+++ b/lib/sqlalchemy/testing/suite/test_insert.py
@@ -4,7 +4,7 @@ from .. import exclusions
from ..assertions import eq_
from .. import engines
-from sqlalchemy import Integer, String, select, util
+from sqlalchemy import Integer, String, select, literal_column, literal
from ..schema import Table, Column
@@ -90,6 +90,13 @@ class InsertBehaviorTest(fixtures.TablesTest):
Column('id', Integer, primary_key=True, autoincrement=False),
Column('data', String(50))
)
+ Table('includes_defaults', metadata,
+ Column('id', Integer, primary_key=True,
+ test_needs_autoincrement=True),
+ Column('data', String(50)),
+ Column('x', Integer, default=5),
+ Column('y', Integer,
+ default=literal_column("2", type_=Integer) + literal(2)))
def test_autoclose_on_insert(self):
if requirements.returning.enabled:
@@ -158,6 +165,34 @@ class InsertBehaviorTest(fixtures.TablesTest):
("data3", ), ("data3", )]
)
+ @requirements.insert_from_select
+ def test_insert_from_select_with_defaults(self):
+ table = self.tables.includes_defaults
+ config.db.execute(
+ table.insert(),
+ [
+ dict(id=1, data="data1"),
+ dict(id=2, data="data2"),
+ dict(id=3, data="data3"),
+ ]
+ )
+
+ config.db.execute(
+ table.insert(inline=True).
+ from_select(("id", "data",),
+ select([table.c.id + 5, table.c.data]).
+ where(table.c.data.in_(["data2", "data3"]))
+ ),
+ )
+
+ eq_(
+ config.db.execute(
+ select([table]).order_by(table.c.data, table.c.id)
+ ).fetchall(),
+ [(1, 'data1', 5, 4), (2, 'data2', 5, 4),
+ (7, 'data2', 5, 4), (3, 'data3', 5, 4), (8, 'data3', 5, 4)]
+ )
+
class ReturningTest(fixtures.TablesTest):
run_create_tables = 'each'
diff --git a/lib/sqlalchemy/testing/suite/test_reflection.py b/lib/sqlalchemy/testing/suite/test_reflection.py
index 575a38db9..08b858b47 100644
--- a/lib/sqlalchemy/testing/suite/test_reflection.py
+++ b/lib/sqlalchemy/testing/suite/test_reflection.py
@@ -95,6 +95,39 @@ class ComponentReflectionTest(fixtures.TablesTest):
cls.define_index(metadata, users)
if testing.requires.view_column_reflection.enabled:
cls.define_views(metadata, schema)
+ if not schema and testing.requires.temp_table_reflection.enabled:
+ cls.define_temp_tables(metadata)
+
+ @classmethod
+ def define_temp_tables(cls, metadata):
+ # cheat a bit, we should fix this with some dialect-level
+ # temp table fixture
+ if testing.against("oracle"):
+ kw = {
+ 'prefixes': ["GLOBAL TEMPORARY"],
+ 'oracle_on_commit': 'PRESERVE ROWS'
+ }
+ else:
+ kw = {
+ 'prefixes': ["TEMPORARY"],
+ }
+
+ user_tmp = Table(
+ "user_tmp", metadata,
+ Column("id", sa.INT, primary_key=True),
+ Column('name', sa.VARCHAR(50)),
+ Column('foo', sa.INT),
+ sa.UniqueConstraint('name', name='user_tmp_uq'),
+ sa.Index("user_tmp_ix", "foo"),
+ **kw
+ )
+ if testing.requires.view_reflection.enabled and \
+ testing.requires.temporary_views.enabled:
+ event.listen(
+ user_tmp, "after_create",
+ DDL("create temporary view user_tmp_v as "
+ "select * from user_tmp")
+ )
@classmethod
def define_index(cls, metadata, users):
@@ -147,6 +180,7 @@ class ComponentReflectionTest(fixtures.TablesTest):
users, addresses, dingalings = self.tables.users, \
self.tables.email_addresses, self.tables.dingalings
insp = inspect(meta.bind)
+
if table_type == 'view':
table_names = insp.get_view_names(schema)
table_names.sort()
@@ -162,6 +196,20 @@ class ComponentReflectionTest(fixtures.TablesTest):
answer = ['dingalings', 'email_addresses', 'users']
eq_(sorted(table_names), answer)
+ @testing.requires.temp_table_names
+ def test_get_temp_table_names(self):
+ insp = inspect(testing.db)
+ temp_table_names = insp.get_temp_table_names()
+ eq_(sorted(temp_table_names), ['user_tmp'])
+
+ @testing.requires.view_reflection
+ @testing.requires.temp_table_names
+ @testing.requires.temporary_views
+ def test_get_temp_view_names(self):
+ insp = inspect(self.metadata.bind)
+ temp_table_names = insp.get_temp_view_names()
+ eq_(sorted(temp_table_names), ['user_tmp_v'])
+
@testing.requires.table_reflection
def test_get_table_names(self):
self._test_get_table_names()
@@ -294,6 +342,28 @@ class ComponentReflectionTest(fixtures.TablesTest):
def test_get_columns_with_schema(self):
self._test_get_columns(schema=testing.config.test_schema)
+ @testing.requires.temp_table_reflection
+ def test_get_temp_table_columns(self):
+ meta = MetaData(testing.db)
+ user_tmp = self.tables.user_tmp
+ insp = inspect(meta.bind)
+ cols = insp.get_columns('user_tmp')
+ self.assert_(len(cols) > 0, len(cols))
+
+ for i, col in enumerate(user_tmp.columns):
+ eq_(col.name, cols[i]['name'])
+
+ @testing.requires.temp_table_reflection
+ @testing.requires.view_column_reflection
+ @testing.requires.temporary_views
+ def test_get_temp_view_columns(self):
+ insp = inspect(self.metadata.bind)
+ cols = insp.get_columns('user_tmp_v')
+ eq_(
+ [col['name'] for col in cols],
+ ['id', 'name', 'foo']
+ )
+
@testing.requires.view_column_reflection
def test_get_view_columns(self):
self._test_get_columns(table_type='view')
@@ -426,6 +496,28 @@ class ComponentReflectionTest(fixtures.TablesTest):
def test_get_unique_constraints(self):
self._test_get_unique_constraints()
+ @testing.requires.temp_table_reflection
+ @testing.requires.unique_constraint_reflection
+ def test_get_temp_table_unique_constraints(self):
+ insp = inspect(self.metadata.bind)
+ reflected = insp.get_unique_constraints('user_tmp')
+ for refl in reflected:
+ # Different dialects handle duplicate index and constraints
+ # differently, so ignore this flag
+ refl.pop('duplicates_index', None)
+ eq_(reflected, [{'column_names': ['name'], 'name': 'user_tmp_uq'}])
+
+ @testing.requires.temp_table_reflection
+ def test_get_temp_table_indexes(self):
+ insp = inspect(self.metadata.bind)
+ indexes = insp.get_indexes('user_tmp')
+ eq_(
+ # TODO: we need to add better filtering for indexes/uq constraints
+ # that are doubled up
+ [idx for idx in indexes if idx['name'] == 'user_tmp_ix'],
+ [{'unique': False, 'column_names': ['foo'], 'name': 'user_tmp_ix'}]
+ )
+
@testing.requires.unique_constraint_reflection
@testing.requires.schemas
def test_get_unique_constraints_with_schema(self):
@@ -466,6 +558,9 @@ class ComponentReflectionTest(fixtures.TablesTest):
)
for orig, refl in zip(uniques, reflected):
+ # Different dialects handle duplicate index and constraints
+ # differently, so ignore this flag
+ refl.pop('duplicates_index', None)
eq_(orig, refl)
@testing.provide_metadata