From 47858b85ec7139af83c2cd0aed1af8917d88ea3e Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 6 Feb 2013 17:50:11 -0500 Subject: [PATCH] - add support for pulling in an external requirements file - start filling in default versions of remaining requirements that are still only in test/ --- lib/sqlalchemy/testing/plugin/noseplugin.py | 19 +++++-- lib/sqlalchemy/testing/requirements.py | 55 +++++++++++++++++++-- test/requirements.py | 9 ++++ test/sql/test_query.py | 23 +++++---- 4 files changed, 91 insertions(+), 15 deletions(-) diff --git a/lib/sqlalchemy/testing/plugin/noseplugin.py b/lib/sqlalchemy/testing/plugin/noseplugin.py index 81e724bbe8..4ce76363e3 100644 --- a/lib/sqlalchemy/testing/plugin/noseplugin.py +++ b/lib/sqlalchemy/testing/plugin/noseplugin.py @@ -222,13 +222,23 @@ def _reverse_topological(options, file_config): dependency.set = RandomSet +def _requirements_opt(options, opt_str, value, parser): + _setup_requirements(value) + @post def _requirements(options, file_config): + + requirement_cls = file_config.get('sqla_testing', "requirement_cls") + _setup_requirements(requirement_cls) + +def _setup_requirements(argument): from sqlalchemy.testing import config from sqlalchemy import testing - requirement_cls = file_config.get('sqla_testing', "requirement_cls") - modname, clsname = requirement_cls.split(":") + if config.requirements is not None: + return + + modname, clsname = argument.split(":") # importlib.import_module() only introduced in 2.7, a little # late @@ -236,7 +246,7 @@ def _requirements(options, file_config): for component in modname.split(".")[1:]: mod = getattr(mod, component) req_cls = getattr(mod, clsname) - config.requirements = testing.requires = req_cls(db, config) + config.requirements = testing.requires = req_cls(config) @post @@ -290,6 +300,9 @@ class NoseSQLAlchemy(Plugin): opt("--reversetop", action="store_true", dest="reversetop", default=False, help="Use a random-ordering set implementation in the ORM (helps " "reveal dependency issues)") + opt("--requirements", action="callback", type="string", + callback=_requirements_opt, + help="requirements class for testing, overrides setup.cfg") opt("--with-cdecimal", action="store_true", dest="cdecimal", default=False, help="Monkeypatch the cdecimal library into Python 'decimal' for all tests") opt("--unhashable", action="store_true", dest="unhashable", default=False, diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py index e44a333beb..7228f38f9e 100644 --- a/lib/sqlalchemy/testing/requirements.py +++ b/lib/sqlalchemy/testing/requirements.py @@ -8,14 +8,16 @@ to provide specific inclusion/exlusions. """ -from . import exclusions +from . import exclusions, config class Requirements(object): - def __init__(self, db, config): - self.db = db + def __init__(self, config): self.config = config + @property + def db(self): + return config.db class SuiteRequirements(Requirements): @@ -61,6 +63,47 @@ class SuiteRequirements(Requirements): return exclusions.open() + @property + def offset(self): + """target database can render OFFSET, or an equivalent, in a SELECT.""" + + return exclusions.open() + + @property + def boolean_col_expressions(self): + """Target database must support boolean expressions as columns""" + + return exclusions.closed() + + @property + def nullsordering(self): + """Target backends that support nulls ordering.""" + + return exclusions.closed() + + @property + def standalone_binds(self): + """target database/driver supports bound parameters as column expressions + without being in the context of a typed column. + + """ + return exclusions.closed() + + @property + def intersect(self): + """Target database must support INTERSECT or equivalent.""" + return exclusions.closed() + + @property + def except_(self): + """Target database must support EXCEPT or equivalent (i.e. MINUS).""" + return exclusions.closed() + + @property + def window_functions(self): + """Target database must support window functions.""" + return exclusions.closed() + @property def autoincrement_insert(self): """target platform generates new surrogate integer primary key values @@ -300,3 +343,9 @@ class SuiteRequirements(Requirements): ) """ return exclusions.open() + + @property + def mod_operator_as_percent_sign(self): + """target database must use a plain percent '%' as the 'modulus' + operator.""" + return exclusions.closed() diff --git a/test/requirements.py b/test/requirements.py index e23a542250..525786f72a 100644 --- a/test/requirements.py +++ b/test/requirements.py @@ -237,6 +237,15 @@ class DefaultRequirements(SuiteRequirements): return skip_if(exclude('mysql', '<', (4, 1, 1)), 'no subquery support') + @property + def mod_operator_as_percent_sign(self): + """target database must use a plain percent '%' as the 'modulus' + operator.""" + + return only_if( + ['mysql', 'sqlite', 'postgresql+psycopg2', 'mssql'] + ) + @property def intersect(self): """Target database must support INTERSECT or equivalent.""" diff --git a/test/sql/test_query.py b/test/sql/test_query.py index 16b0f79fd9..b5f50aeea7 100644 --- a/test/sql/test_query.py +++ b/test/sql/test_query.py @@ -443,10 +443,7 @@ class QueryTest(fixtures.TestBase): ): eq_(expr.execute().fetchall(), result) - @testing.fails_on("firebird", "see dialect.test_firebird:MiscTest.test_percents_in_text") - @testing.fails_on("oracle", "neither % nor %% are accepted") - @testing.fails_on("informix", "neither % nor %% are accepted") - @testing.fails_on("+pg8000", "can't interpret result column from '%%'") + @testing.requires.mod_operator_as_percent_sign @testing.emits_warning('.*now automatically escapes.*') def test_percents_in_text(self): for expr, result in ( @@ -534,6 +531,7 @@ class QueryTest(fixtures.TestBase): a_eq(prep(r"(\:that$other)"), "(:that$other)") a_eq(prep(r".\:that$ :other."), ".:that$ ?.") + @testing.requires.standalone_binds def test_select_from_bindparam(self): """Test result row processing when selecting from a plain bind param.""" @@ -911,15 +909,23 @@ class QueryTest(fixtures.TestBase): eq_(users.select().execute().fetchall(), [(1, 'john')]) def test_result_as_args(self): - users.insert().execute([dict(user_id=1, user_name='john'), dict(user_id=2, user_name='ed')]) + users.insert().execute([ + dict(user_id=1, user_name='john'), + dict(user_id=2, user_name='ed')]) r = users.select().execute() users2.insert().execute(list(r)) - assert users2.select().execute().fetchall() == [(1, 'john'), (2, 'ed')] + eq_( + users2.select().order_by(users2.c.user_id).execute().fetchall(), + [(1, 'john'), (2, 'ed')] + ) users2.delete().execute() r = users.select().execute() users2.insert().execute(*list(r)) - assert users2.select().execute().fetchall() == [(1, 'john'), (2, 'ed')] + eq_( + users2.select().order_by(users2.c.user_id).execute().fetchall(), + [(1, 'john'), (2, 'ed')] + ) def test_ambiguous_column(self): users.insert().execute(user_id=1, user_name='john') @@ -1744,7 +1750,6 @@ class LimitTest(fixtures.TestBase): self.assert_(r == [(1, 'john'), (2, 'jack'), (3, 'ed')], repr(r)) @testing.requires.offset - @testing.fails_on('maxdb', 'FIXME: unknown') def test_select_limit_offset(self): """Test the interaction between limit and offset""" @@ -2369,7 +2374,7 @@ class OperatorTest(fixtures.TestBase): eq_( select([flds.c.intcol % 3], order_by=flds.c.idcol).execute().fetchall(), - [(2,),(1,)] + [(2,), (1,)] ) @testing.requires.window_functions -- 2.47.2