From a0ef9edc1908adb823ec788eee1974900bca4bac Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 6 Feb 2013 19:06:09 -0500 Subject: [PATCH] - adding in requirements - get test_naturalpks to be more generalized --- lib/sqlalchemy/testing/exclusions.py | 4 +- lib/sqlalchemy/testing/requirements.py | 42 ++++++++++ .../testing/suite/test_reflection.py | 30 +++++++ lib/sqlalchemy/testing/suite/test_types.py | 37 ++++++++- test/engine/test_reflection.py | 6 +- test/orm/test_naturalpks.py | 78 ++++++++----------- test/orm/test_query.py | 23 ++++-- test/requirements.py | 20 ++++- 8 files changed, 178 insertions(+), 62 deletions(-) diff --git a/lib/sqlalchemy/testing/exclusions.py b/lib/sqlalchemy/testing/exclusions.py index f105c8b6a2..2c0679e1d3 100644 --- a/lib/sqlalchemy/testing/exclusions.py +++ b/lib/sqlalchemy/testing/exclusions.py @@ -84,7 +84,9 @@ def succeeds_if(predicate, reason=None): class Predicate(object): @classmethod def as_predicate(cls, predicate): - if isinstance(predicate, Predicate): + if isinstance(predicate, skip_if): + return predicate.predicate + elif isinstance(predicate, Predicate): return predicate elif isinstance(predicate, list): return OrPredicate([cls.as_predicate(pred) for pred in predicate]) diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py index 7228f38f9e..e6e21d2cb7 100644 --- a/lib/sqlalchemy/testing/requirements.py +++ b/lib/sqlalchemy/testing/requirements.py @@ -39,6 +39,27 @@ class SuiteRequirements(Requirements): return exclusions.open() + @property + def on_update_cascade(self): + """"target database must support ON UPDATE..CASCADE behavior in + foreign keys.""" + + return exclusions.open() + + @property + def deferrable_fks(self): + return exclusions.closed() + + @property + def on_update_or_deferrable_fks(self): + # TODO: exclusions should be composable, + # somehow only_if([x, y]) isn't working here, negation/conjunctions + # getting confused. + return exclusions.only_if( + lambda: self.on_update_cascade.enabled or self.deferrable_fks.enabled + ) + + @property def self_referential_foreign_keys(self): """Target database must support self-referential foreign keys.""" @@ -253,6 +274,11 @@ class SuiteRequirements(Requirements): """ return exclusions.open() + @property + def unicode_ddl(self): + """Target driver must support some degree of non-ascii symbol names.""" + return exclusions.closed() + @property def datetime(self): """target dialect supports representation of Python @@ -349,3 +375,19 @@ class SuiteRequirements(Requirements): """target database must use a plain percent '%' as the 'modulus' operator.""" return exclusions.closed() + + @property + def unicode_connections(self): + """Target driver must support non-ASCII characters being passed at all.""" + return exclusions.open() + + @property + def skip_mysql_on_windows(self): + """Catchall for a large variety of MySQL on Windows failures""" + return exclusions.open() + + def _has_mysql_on_windows(self): + return False + + def _has_mysql_fully_case_sensitive(self): + return False diff --git a/lib/sqlalchemy/testing/suite/test_reflection.py b/lib/sqlalchemy/testing/suite/test_reflection.py index 89e233295e..5beed6aad7 100644 --- a/lib/sqlalchemy/testing/suite/test_reflection.py +++ b/lib/sqlalchemy/testing/suite/test_reflection.py @@ -218,6 +218,9 @@ class ComponentReflectionTest(fixtures.TablesTest): ])) > 0, '%s(%s), %s(%s)' % (col.name, col.type, cols[i]['name'], ctype)) + if not col.primary_key: + assert cols[i]['default'] is None + @testing.requires.table_reflection def test_get_columns(self): self._test_get_columns() @@ -386,5 +389,32 @@ class ComponentReflectionTest(fixtures.TablesTest): def test_get_table_oid_with_schema(self): self._test_get_table_oid('users', schema='test_schema') + @testing.provide_metadata + def test_autoincrement_col(self): + """test that 'autoincrement' is reflected according to sqla's policy. + + Don't mark this test as unsupported for any backend ! + + (technically it fails with MySQL InnoDB since "id" comes before "id2") + + A backend is better off not returning "autoincrement" at all, + instead of potentially returning "False" for an auto-incrementing + primary key column. + + """ + + meta = self.metadata + insp = inspect(meta.bind) + + for tname, cname in [ + ('users', 'user_id'), + ('email_addresses', 'address_id'), + ('dingalings', 'dingaling_id'), + ]: + cols = insp.get_columns(tname) + id_ = dict((c['name'], c) for c in cols)[cname] + assert id_.get('autoincrement', True) + + __all__ = ('ComponentReflectionTest', 'HasTableTest') diff --git a/lib/sqlalchemy/testing/suite/test_types.py b/lib/sqlalchemy/testing/suite/test_types.py index 8d0500d71d..5ad26c2f2f 100644 --- a/lib/sqlalchemy/testing/suite/test_types.py +++ b/lib/sqlalchemy/testing/suite/test_types.py @@ -4,7 +4,7 @@ from .. import fixtures, config from ..assertions import eq_ from ..config import requirements from sqlalchemy import Integer, Unicode, UnicodeText, select -from sqlalchemy import Date, DateTime, Time, MetaData, String +from sqlalchemy import Date, DateTime, Time, MetaData, String, Text from ..schema import Table, Column import datetime @@ -103,6 +103,39 @@ class UnicodeTextTest(_UnicodeFixture, fixtures.TablesTest): def test_empty_strings_text(self): self._test_empty_strings() +class TextTest(fixtures.TablesTest): + @classmethod + def define_tables(cls, metadata): + Table('text_table', metadata, + Column('id', Integer, primary_key=True, + test_needs_autoincrement=True), + Column('text_data', Text), + ) + + def test_text_roundtrip(self): + text_table = self.tables.text_table + + config.db.execute( + text_table.insert(), + {"text_data": 'some text'} + ) + row = config.db.execute( + select([text_table.c.text_data]) + ).first() + eq_(row, ('some text',)) + + def test_text_empty_strings(self): + text_table = self.tables.text_table + + config.db.execute( + text_table.insert(), + {"text_data": ''} + ) + row = config.db.execute( + select([text_table.c.text_data]) + ).first() + eq_(row, ('',)) + class StringTest(fixtures.TestBase): @requirements.unbounded_varchar @@ -212,7 +245,7 @@ class DateHistoricTest(_DateFixture, fixtures.TablesTest): __all__ = ('UnicodeVarcharTest', 'UnicodeTextTest', - 'DateTest', 'DateTimeTest', + 'DateTest', 'DateTimeTest', 'TextTest', 'DateTimeHistoricTest', 'DateTimeCoercedToDateTimeTest', 'TimeMicrosecondsTest', 'TimeTest', 'DateTimeMicrosecondsTest', 'DateHistoricTest', 'StringTest') diff --git a/test/engine/test_reflection.py b/test/engine/test_reflection.py index 2ff3140a56..86df929873 100644 --- a/test/engine/test_reflection.py +++ b/test/engine/test_reflection.py @@ -645,8 +645,8 @@ class ReflectionTest(fixtures.TestBase, ComparesTables): id INTEGER NOT NULL, isbn VARCHAR(50) NOT NULL, title VARCHAR(100) NOT NULL, - series INTEGER, - series_id INTEGER, + series INTEGER NOT NULL, + series_id INTEGER NOT NULL, UNIQUE(series, series_id), PRIMARY KEY(id, isbn) )""") @@ -1060,7 +1060,7 @@ class UnicodeReflectionTest(fixtures.TestBase): # are really limited unless you're on PG or SQLite # forget about it on these backends - if testing.against('sybase', 'maxdb', 'oracle'): + if not testing.requires.unicode_ddl.enabled: names = no_multibyte_period # mysql can't handle casing usually elif testing.against("mysql") and \ diff --git a/test/orm/test_naturalpks.py b/test/orm/test_naturalpks.py index 9ad54fd10c..d30cdc5989 100644 --- a/test/orm/test_naturalpks.py +++ b/test/orm/test_naturalpks.py @@ -15,17 +15,23 @@ from sqlalchemy.testing import eq_ from sqlalchemy.testing import fixtures from test.orm import _fixtures +def _backend_specific_fk_args(): + if testing.requires.deferrable_fks.enabled: + fk_args = dict(deferrable=True, initially='deferred') + elif not testing.requires.on_update_cascade.enabled: + fk_args = dict() + else: + fk_args = dict(onupdate='cascade') + return fk_args + class NaturalPKTest(fixtures.MappedTest): # MySQL 5.5 on Windows crashes (the entire server, not the client) # if you screw around with ON UPDATE CASCADE type of stuff. - __requires__ = 'skip_mysql_on_windows', + __requires__ = 'skip_mysql_on_windows', 'on_update_or_deferrable_fks' @classmethod def define_tables(cls, metadata): - if testing.against('oracle'): - fk_args = dict(deferrable=True, initially='deferred') - else: - fk_args = dict(onupdate='cascade') + fk_args = _backend_specific_fk_args() users = Table('users', metadata, Column('username', String(50), primary_key=True), @@ -128,8 +134,7 @@ class NaturalPKTest(fixtures.MappedTest): assert sess.query(User).get('ed').fullname == 'jack' - @testing.fails_on('sqlite', 'sqlite doesnt support ON UPDATE CASCADE') - @testing.fails_on('oracle', 'oracle doesnt support ON UPDATE CASCADE') + @testing.requires.on_update_cascade def test_onetomany_passive(self): self._test_onetomany(True) @@ -190,8 +195,7 @@ class NaturalPKTest(fixtures.MappedTest): u1 = sess.query(User).get('fred') eq_(User(username='fred', fullname='jack'), u1) - @testing.fails_on('sqlite', 'sqlite doesnt support ON UPDATE CASCADE') - @testing.fails_on('oracle', 'oracle doesnt support ON UPDATE CASCADE') + @testing.requires.on_update_cascade def test_manytoone_passive(self): self._test_manytoone(True) @@ -276,8 +280,7 @@ class NaturalPKTest(fixtures.MappedTest): sess.query(Address).all()) - @testing.fails_on('sqlite', 'sqlite doesnt support ON UPDATE CASCADE') - @testing.fails_on('oracle', 'oracle doesnt support ON UPDATE CASCADE') + @testing.requires.on_update_cascade def test_onetoone_passive(self): self._test_onetoone(True) @@ -323,8 +326,7 @@ class NaturalPKTest(fixtures.MappedTest): sess.expunge_all() eq_([Address(username='ed')], sess.query(Address).all()) - @testing.fails_on('sqlite', 'sqlite doesnt support ON UPDATE CASCADE') - @testing.fails_on('oracle', 'oracle doesnt support ON UPDATE CASCADE') + @testing.requires.on_update_cascade def test_bidirectional_passive(self): self._test_bidirectional(True) @@ -339,7 +341,7 @@ class NaturalPKTest(fixtures.MappedTest): mapper(User, users) mapper(Address, addresses, properties={ - 'user':relationship(User, passive_updates=passive_updates, + 'user': relationship(User, passive_updates=passive_updates, backref='addresses')}) sess = create_session() @@ -382,8 +384,7 @@ class NaturalPKTest(fixtures.MappedTest): sess.query(Address).all()) - @testing.fails_on('sqlite', 'sqlite doesnt support ON UPDATE CASCADE') - @testing.fails_on('oracle', 'oracle doesnt support ON UPDATE CASCADE') + @testing.requires.on_update_cascade def test_manytomany_passive(self): self._test_manytomany(True) @@ -549,14 +550,13 @@ class ReversePKsTest(fixtures.MappedTest): class SelfReferentialTest(fixtures.MappedTest): # mssql, mysql don't allow # ON UPDATE on self-referential keys - __unsupported_on__ = ('mssql','mysql') + __unsupported_on__ = ('mssql', 'mysql') + + __requires__ = 'on_update_or_deferrable_fks', @classmethod def define_tables(cls, metadata): - if testing.against('oracle'): - fk_args = dict(deferrable=True, initially='deferred') - else: - fk_args = dict(onupdate='cascade') + fk_args = _backend_specific_fk_args() Table('nodes', metadata, Column('name', String(50), primary_key=True), @@ -622,8 +622,7 @@ class SelfReferentialTest(fixtures.MappedTest): for n in sess.query(Node).filter( Node.name.in_(['n11', 'n12', 'n13']))]) - @testing.fails_on('sqlite', 'sqlite doesnt support ON UPDATE CASCADE') - @testing.fails_on('oracle', 'oracle doesnt support ON UPDATE CASCADE') + @testing.requires.on_update_cascade def test_many_to_one_passive(self): self._test_many_to_one(True) @@ -657,14 +656,11 @@ class SelfReferentialTest(fixtures.MappedTest): class NonPKCascadeTest(fixtures.MappedTest): - __requires__ = 'skip_mysql_on_windows', + __requires__ = 'skip_mysql_on_windows', 'on_update_or_deferrable_fks' @classmethod def define_tables(cls, metadata): - if testing.against('oracle'): - fk_args = dict(deferrable=True, initially='deferred') - else: - fk_args = dict(onupdate='cascade') + fk_args = _backend_specific_fk_args() Table('users', metadata, Column('id', Integer, primary_key=True, @@ -689,8 +685,7 @@ class NonPKCascadeTest(fixtures.MappedTest): class Address(cls.Comparable): pass - @testing.fails_on('sqlite', 'sqlite doesnt support ON UPDATE CASCADE') - @testing.fails_on('oracle', 'oracle doesnt support ON UPDATE CASCADE') + @testing.requires.on_update_cascade def test_onetomany_passive(self): self._test_onetomany(True) @@ -770,10 +765,7 @@ class CascadeToFKPKTest(fixtures.MappedTest, testing.AssertsCompiledSQL): @classmethod def define_tables(cls, metadata): - if testing.against('oracle'): - fk_args = dict(deferrable=True, initially='deferred') - else: - fk_args = dict(onupdate='cascade') + fk_args = _backend_specific_fk_args() Table('users', metadata, Column('username', String(50), primary_key=True), @@ -796,8 +788,7 @@ class CascadeToFKPKTest(fixtures.MappedTest, testing.AssertsCompiledSQL): class Address(cls.Comparable): pass - @testing.fails_on('sqlite', 'sqlite doesnt support ON UPDATE CASCADE') - @testing.fails_on('oracle', 'oracle doesnt support ON UPDATE CASCADE') + @testing.requires.on_update_cascade def test_onetomany_passive(self): self._test_onetomany(True) @@ -875,9 +866,7 @@ class CascadeToFKPKTest(fixtures.MappedTest, testing.AssertsCompiledSQL): u2.addresses.append(a1) sess.flush() - @testing.fails_on('oracle', 'oracle doesnt support ON UPDATE CASCADE ' - 'but requires referential integrity') - @testing.fails_on('sqlite', 'sqlite doesnt support ON UPDATE CASCADE') + @testing.requires.on_update_cascade def test_change_m2o_passive(self): self._test_change_m2o(True) @@ -1030,10 +1019,7 @@ class JoinedInheritanceTest(fixtures.MappedTest): @classmethod def define_tables(cls, metadata): - if testing.against('oracle'): - fk_args = dict(deferrable=True, initially='deferred') - else: - fk_args = dict(onupdate='cascade') + fk_args = _backend_specific_fk_args() Table('person', metadata, Column('name', String(50), primary_key=True), @@ -1066,8 +1052,7 @@ class JoinedInheritanceTest(fixtures.MappedTest): class Manager(Person): pass - @testing.fails_on('sqlite', 'sqlite doesnt support ON UPDATE CASCADE') - @testing.fails_on('oracle', 'oracle doesnt support ON UPDATE CASCADE') + @testing.requires.on_update_cascade def test_pk_passive(self): self._test_pk(True) @@ -1076,8 +1061,7 @@ class JoinedInheritanceTest(fixtures.MappedTest): def test_pk_nonpassive(self): self._test_pk(False) - @testing.fails_on('sqlite', 'sqlite doesnt support ON UPDATE CASCADE') - @testing.fails_on('oracle', 'oracle doesnt support ON UPDATE CASCADE') + @testing.requires.on_update_cascade def test_fk_passive(self): self._test_fk(True) diff --git a/test/orm/test_query.py b/test/orm/test_query.py index 9aad19579d..05a13c3c15 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -1099,20 +1099,27 @@ class FilterTest(QueryTest, AssertsCompiledSQL): def test_basic(self): User = self.classes.User - assert [User(id=7), User(id=8), User(id=9),User(id=10)] == create_session().query(User).all() + users = create_session().query(User).all() + eq_( + [User(id=7), User(id=8), User(id=9),User(id=10)], + users + ) - @testing.fails_on('maxdb', 'FIXME: unknown') - def test_limit(self): + @testing.requires.offset + def test_limit_offset(self): User = self.classes.User - assert [User(id=8), User(id=9)] == create_session().query(User).order_by(User.id).limit(2).offset(1).all() + sess = create_session() + + assert [User(id=8), User(id=9)] == sess.query(User).order_by(User.id).limit(2).offset(1).all() + + assert [User(id=8), User(id=9)] == list(sess.query(User).order_by(User.id)[1:3]) - assert [User(id=8), User(id=9)] == list(create_session().query(User).order_by(User.id)[1:3]) + assert User(id=8) == sess.query(User).order_by(User.id)[1] - assert User(id=8) == create_session().query(User).order_by(User.id)[1] + assert [] == sess.query(User).order_by(User.id)[3:3] + assert [] == sess.query(User).order_by(User.id)[0:0] - assert [] == create_session().query(User).order_by(User.id)[3:3] - assert [] == create_session().query(User).order_by(User.id)[0:0] @testing.requires.boolean_col_expressions def test_exists(self): diff --git a/test/requirements.py b/test/requirements.py index 525786f72a..8dde55d6af 100644 --- a/test/requirements.py +++ b/test/requirements.py @@ -51,6 +51,23 @@ class DefaultRequirements(SuiteRequirements): no_support('sqlite', 'not supported by database') ) + @property + def on_update_cascade(self): + """target database must support ON UPDATE..CASCADE behavior in + foreign keys.""" + + return skip_if( + ['sqlite', 'oracle'], + 'target backend does not support ON UPDATE CASCADE' + ) + + @property + def deferrable_fks(self): + """target database must support deferrable fks""" + + return only_on(['oracle']) + + @property def unbounded_varchar(self): """Target database must support VARCHAR with no length""" @@ -316,6 +333,7 @@ class DefaultRequirements(SuiteRequirements): @property def unicode_data(self): + """target drive must support unicode data stored in columns.""" return skip_if([ no_support("sybase", "no unicode driver support") ]) @@ -330,7 +348,7 @@ class DefaultRequirements(SuiteRequirements): @property def unicode_ddl(self): - """Target driver must support some encoding of Unicode across the wire.""" + """Target driver must support some degree of non-ascii symbol names.""" # TODO: expand to exclude MySQLdb versions w/ broken unicode return skip_if([ no_support('maxdb', 'database support flakey'), -- 2.47.2