diff options
Diffstat (limited to 'test')
136 files changed, 3136 insertions, 349 deletions
diff --git a/test/aaa_profiling/test_compiler.py b/test/aaa_profiling/test_compiler.py index 160385e95..ea33b96dc 100644 --- a/test/aaa_profiling/test_compiler.py +++ b/test/aaa_profiling/test_compiler.py @@ -1,5 +1,5 @@ from sqlalchemy import * -from sqlalchemy.test import * +from test.lib import * class CompileTest(TestBase, AssertsExecutionResults): diff --git a/test/aaa_profiling/test_memusage.py b/test/aaa_profiling/test_memusage.py index 7c0979d57..f6de1fa2f 100644 --- a/test/aaa_profiling/test_memusage.py +++ b/test/aaa_profiling/test_memusage.py @@ -1,17 +1,17 @@ -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from sqlalchemy.orm import mapper, relationship, create_session, \ clear_mappers, sessionmaker, class_mapper from sqlalchemy.orm.mapper import _mapper_registry from sqlalchemy.orm.session import _sessions from sqlalchemy.util import jython import operator -from sqlalchemy.test import testing, engines +from test.lib import testing, engines from sqlalchemy import MetaData, Integer, String, ForeignKey, \ PickleType, create_engine, Unicode -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column import sqlalchemy as sa from sqlalchemy.sql import column -from sqlalchemy.test.util import gc_collect +from test.lib.util import gc_collect import gc import weakref from test.orm import _base diff --git a/test/aaa_profiling/test_orm.py b/test/aaa_profiling/test_orm.py index d7e7b8c00..f2edebe06 100644 --- a/test/aaa_profiling/test_orm.py +++ b/test/aaa_profiling/test_orm.py @@ -1,11 +1,11 @@ -from sqlalchemy.test.testing import eq_, assert_raises, \ +from test.lib.testing import eq_, assert_raises, \ assert_raises_message from sqlalchemy import exc as sa_exc, util, Integer, String, ForeignKey from sqlalchemy.orm import exc as orm_exc, mapper, relationship, \ sessionmaker -from sqlalchemy.test import testing, profiling +from test.lib import testing, profiling from test.orm import _base -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column class MergeTest(_base.MappedTest): diff --git a/test/aaa_profiling/test_pool.py b/test/aaa_profiling/test_pool.py index b34144feb..1df93d980 100644 --- a/test/aaa_profiling/test_pool.py +++ b/test/aaa_profiling/test_pool.py @@ -1,5 +1,5 @@ from sqlalchemy import * -from sqlalchemy.test import * +from test.lib import * from sqlalchemy.pool import QueuePool diff --git a/test/aaa_profiling/test_resultset.py b/test/aaa_profiling/test_resultset.py index bd9d3ae50..9904267dc 100644 --- a/test/aaa_profiling/test_resultset.py +++ b/test/aaa_profiling/test_resultset.py @@ -1,5 +1,5 @@ from sqlalchemy import * -from sqlalchemy.test import * +from test.lib import * NUM_FIELDS = 10 NUM_RECORDS = 1000 diff --git a/test/aaa_profiling/test_zoomark.py b/test/aaa_profiling/test_zoomark.py index dc990e983..ec489beb1 100644 --- a/test/aaa_profiling/test_zoomark.py +++ b/test/aaa_profiling/test_zoomark.py @@ -7,7 +7,7 @@ import datetime import sys import time from sqlalchemy import * -from sqlalchemy.test import * +from test.lib import * ITERATIONS = 1 dbapi_session = engines.ReplayableSession() metadata = None diff --git a/test/aaa_profiling/test_zoomark_orm.py b/test/aaa_profiling/test_zoomark_orm.py index 623ec67ba..11285f972 100644 --- a/test/aaa_profiling/test_zoomark_orm.py +++ b/test/aaa_profiling/test_zoomark_orm.py @@ -8,7 +8,7 @@ import sys import time from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test import * +from test.lib import * ITERATIONS = 1 dbapi_session = engines.ReplayableSession() metadata = None diff --git a/test/base/test_dependency.py b/test/base/test_dependency.py index 605a16cc3..6d4662be5 100644 --- a/test/base/test_dependency.py +++ b/test/base/test_dependency.py @@ -1,7 +1,7 @@ import sqlalchemy.topological as topological -from sqlalchemy.test import TestBase -from sqlalchemy.test.testing import assert_raises, eq_ -from sqlalchemy.test.util import conforms_partial_ordering +from test.lib import TestBase +from test.lib.testing import assert_raises, eq_ +from test.lib.util import conforms_partial_ordering from sqlalchemy import exc, util diff --git a/test/base/test_events.py b/test/base/test_events.py index 292059877..1baed241b 100644 --- a/test/base/test_events.py +++ b/test/base/test_events.py @@ -1,6 +1,6 @@ """Test event registration and listening.""" -from sqlalchemy.test.testing import TestBase, eq_, assert_raises +from test.lib.testing import TestBase, eq_, assert_raises from sqlalchemy import event, exc, util class TestEvents(TestBase): diff --git a/test/base/test_except.py b/test/base/test_except.py index 78a534e67..f02ca988b 100644 --- a/test/base/test_except.py +++ b/test/base/test_except.py @@ -2,7 +2,7 @@ from sqlalchemy import exc as sa_exceptions -from sqlalchemy.test import TestBase +from test.lib import TestBase # Py3K #StandardError = BaseException diff --git a/test/base/test_utils.py b/test/base/test_utils.py index d083a8458..4f02d3811 100644 --- a/test/base/test_utils.py +++ b/test/base/test_utils.py @@ -1,9 +1,9 @@ -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message import copy, threading from sqlalchemy import util, sql, exc -from sqlalchemy.test import TestBase -from sqlalchemy.test.testing import eq_, is_, ne_ -from sqlalchemy.test.util import gc_collect, picklers +from test.lib import TestBase +from test.lib.testing import eq_, is_, ne_ +from test.lib.util import gc_collect, picklers from sqlalchemy.util import classproperty diff --git a/test/bootstrap/__init__.py b/test/bootstrap/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/test/bootstrap/__init__.py diff --git a/test/bootstrap/config.py b/test/bootstrap/config.py new file mode 100644 index 000000000..ef37e4f20 --- /dev/null +++ b/test/bootstrap/config.py @@ -0,0 +1,173 @@ +import optparse, os, sys, re, ConfigParser, time, warnings + +# 2to3 +import StringIO + +logging = None + +__all__ = 'parser', 'configure', 'options', + +db = None +db_label, db_url, db_opts = None, None, {} + +options = None +file_config = None + +base_config = """ +[db] +sqlite=sqlite:///:memory: +sqlite_file=sqlite:///querytest.db +postgresql=postgresql://scott:tiger@127.0.0.1:5432/test +postgres=postgresql://scott:tiger@127.0.0.1:5432/test +pg8000=postgresql+pg8000://scott:tiger@127.0.0.1:5432/test +postgresql_jython=postgresql+zxjdbc://scott:tiger@127.0.0.1:5432/test +mysql_jython=mysql+zxjdbc://scott:tiger@127.0.0.1:5432/test +mysql=mysql://scott:tiger@127.0.0.1:3306/test +oracle=oracle://scott:tiger@127.0.0.1:1521 +oracle8=oracle://scott:tiger@127.0.0.1:1521/?use_ansi=0 +mssql=mssql://scott:tiger@SQUAWK\\SQLEXPRESS/test +firebird=firebird://sysdba:masterkey@localhost//tmp/test.fdb +maxdb=maxdb://MONA:RED@/maxdb1 +""" + +def _log(option, opt_str, value, parser): + global logging + if not logging: + import logging + logging.basicConfig() + + if opt_str.endswith('-info'): + logging.getLogger(value).setLevel(logging.INFO) + elif opt_str.endswith('-debug'): + logging.getLogger(value).setLevel(logging.DEBUG) + + +def _list_dbs(*args): + print "Available --db options (use --dburi to override)" + for macro in sorted(file_config.options('db')): + print "%20s\t%s" % (macro, file_config.get('db', macro)) + sys.exit(0) + +def _server_side_cursors(options, opt_str, value, parser): + db_opts['server_side_cursors'] = True + +def _engine_strategy(options, opt_str, value, parser): + if value: + db_opts['strategy'] = value + +class _ordered_map(object): + def __init__(self): + self._keys = list() + self._data = dict() + + def __setitem__(self, key, value): + if key not in self._keys: + self._keys.append(key) + self._data[key] = value + + def __iter__(self): + for key in self._keys: + yield self._data[key] + +# at one point in refactoring, modules were injecting into the config +# process. this could probably just become a list now. +post_configure = _ordered_map() + +def _engine_uri(options, file_config): + global db_label, db_url + db_label = 'sqlite' + if options.dburi: + db_url = options.dburi + db_label = db_url[:db_url.index(':')] + elif options.db: + db_label = options.db + db_url = None + + if db_url is None: + if db_label not in file_config.options('db'): + raise RuntimeError( + "Unknown engine. Specify --dbs for known engines.") + db_url = file_config.get('db', db_label) +post_configure['engine_uri'] = _engine_uri + +def _require(options, file_config): + if not(options.require or + (file_config.has_section('require') and + file_config.items('require'))): + return + + try: + import pkg_resources + except ImportError: + raise RuntimeError("setuptools is required for version requirements") + + cmdline = [] + for requirement in options.require: + pkg_resources.require(requirement) + cmdline.append(re.split('\s*(<!>=)', requirement, 1)[0]) + + if file_config.has_section('require'): + for label, requirement in file_config.items('require'): + if not label == db_label or label.startswith('%s.' % db_label): + continue + seen = [c for c in cmdline if requirement.startswith(c)] + if seen: + continue + pkg_resources.require(requirement) +post_configure['require'] = _require + +def _engine_pool(options, file_config): + if options.mockpool: + from sqlalchemy import pool + db_opts['poolclass'] = pool.AssertionPool +post_configure['engine_pool'] = _engine_pool + +def _create_testing_engine(options, file_config): + from test.lib import engines, testing + global db + db = engines.testing_engine(db_url, db_opts) + testing.db = db +post_configure['create_engine'] = _create_testing_engine + +def _prep_testing_database(options, file_config): + from test.lib import engines + from sqlalchemy import schema + + # also create alt schemas etc. here? + if options.dropfirst: + e = engines.utf8_engine() + existing = e.table_names() + if existing: + print "Dropping existing tables in database: " + db_url + try: + print "Tables: %s" % ', '.join(existing) + except: + pass + print "Abort within 5 seconds..." + time.sleep(5) + md = schema.MetaData(e, reflect=True) + md.drop_all() + e.dispose() + +post_configure['prep_db'] = _prep_testing_database + +def _set_table_options(options, file_config): + from test.lib 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_configure['table_options'] = _set_table_options + +def _reverse_topological(options, file_config): + if options.reversetop: + from sqlalchemy.orm import unitofwork, session, mapper, dependency + from sqlalchemy import topological + from test.lib.util import RandomSet + topological.set = unitofwork.set = session.set = mapper.set = dependency.set = RandomSet +post_configure['topological'] = _reverse_topological + diff --git a/test/bootstrap/noseplugin.py b/test/bootstrap/noseplugin.py new file mode 100644 index 000000000..c2a152aa6 --- /dev/null +++ b/test/bootstrap/noseplugin.py @@ -0,0 +1,169 @@ +import logging +import os +import re +import sys +import time +import warnings +import ConfigParser +import StringIO + +import nose.case +from nose.plugins import Plugin + +from test.bootstrap import config + +from test.bootstrap.config import ( + _create_testing_engine, _engine_pool, _engine_strategy, _engine_uri, _list_dbs, _log, + _prep_testing_database, _require, _reverse_topological, _server_side_cursors, + _set_table_options, base_config, db, db_label, db_url, file_config, post_configure) + +log = logging.getLogger('nose.plugins.sqlalchemy') + +class NoseSQLAlchemy(Plugin): + """ + Handles the setup and extra properties required for testing SQLAlchemy + """ + enabled = True + + # nose 1.0 will allow us to replace the old "sqlalchemy" plugin, + # if installed, using the same name, but nose 1.0 isn't released yet... + name = '_sqlalchemy' + score = 100 + + def options(self, parser, env=os.environ): + Plugin.options(self, parser, env) + opt = parser.add_option + opt("--log-info", action="callback", type="string", callback=_log, + help="turn on info logging for <LOG> (multiple OK)") + opt("--log-debug", action="callback", type="string", callback=_log, + help="turn on debug logging for <LOG> (multiple OK)") + opt("--require", action="append", dest="require", default=[], + help="require a particular driver or module version (multiple OK)") + opt("--db", action="store", dest="db", default="sqlite", + help="Use prefab database uri") + opt('--dbs', action='callback', callback=_list_dbs, + help="List available prefab dbs") + opt("--dburi", action="store", dest="dburi", + help="Database uri (overrides --db)") + opt("--dropfirst", action="store_true", dest="dropfirst", + help="Drop all tables in the target database first (use with caution on Oracle, " + "MS-SQL)") + opt("--mockpool", action="store_true", dest="mockpool", + help="Use mock pool (asserts only one connection used)") + opt("--enginestrategy", action="callback", type="string", + callback=_engine_strategy, + help="Engine strategy (plain or threadlocal, defaults to plain)") + opt("--reversetop", action="store_true", dest="reversetop", default=False, + help="Use a random-ordering set implementation in the ORM (helps " + "reveal dependency issues)") + opt("--unhashable", action="store_true", dest="unhashable", default=False, + help="Disallow SQLAlchemy from performing a hash() on mapped test objects.") + opt("--noncomparable", action="store_true", dest="noncomparable", default=False, + help="Disallow SQLAlchemy from performing == on mapped test objects.") + opt("--truthless", action="store_true", dest="truthless", default=False, + help="Disallow SQLAlchemy from truth-evaluating mapped test objects.") + opt("--serverside", action="callback", callback=_server_side_cursors, + help="Turn on server side cursors for PG") + opt("--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.") + opt("--table-option", action="append", dest="tableopts", default=[], + help="Add a dialect-specific table option, key=value") + + global file_config + file_config = ConfigParser.ConfigParser() + file_config.readfp(StringIO.StringIO(base_config)) + file_config.read(['test.cfg', os.path.expanduser('~/.satest.cfg')]) + config.file_config = file_config + + def configure(self, options, conf): + Plugin.configure(self, options, conf) + self.options = options + + def begin(self): + global testing, requires, util + from test.lib import testing, requires + from sqlalchemy import util + + testing.db = db + testing.requires = requires + + # Lazy setup of other options (post coverage) + for fn in post_configure: + fn(self.options, file_config) + + def describeTest(self, test): + return "" + + def wantClass(self, cls): + """Return true if you want the main test selector to collect + tests from this class, false if you don't, and None if you don't + care. + + :Parameters: + cls : class + The class being examined by the selector + + """ + + if not issubclass(cls, testing.TestBase): + return False + else: + if (hasattr(cls, '__whitelist__') and testing.db.name in cls.__whitelist__): + return True + else: + return not self.__should_skip_for(cls) + + def __should_skip_for(self, cls): + if hasattr(cls, '__requires__'): + def test_suite(): return 'ok' + test_suite.__name__ = cls.__name__ + for requirement in cls.__requires__: + check = getattr(requires, requirement) + if check(test_suite)() != 'ok': + # The requirement will perform messaging. + return True + + if cls.__unsupported_on__: + spec = testing.db_spec(*cls.__unsupported_on__) + if spec(testing.db): + print "'%s' unsupported on DB implementation '%s'" % ( + cls.__class__.__name__, testing.db.name) + return True + + if getattr(cls, '__only_on__', None): + spec = testing.db_spec(*util.to_list(cls.__only_on__)) + if not spec(testing.db): + print "'%s' unsupported on DB implementation '%s'" % ( + cls.__class__.__name__, testing.db.name) + return True + + if getattr(cls, '__skip_if__', False): + for c in getattr(cls, '__skip_if__'): + if c(): + print "'%s' skipped by %s" % ( + cls.__class__.__name__, c.__name__) + return True + + for rule in getattr(cls, '__excluded_on__', ()): + if testing._is_excluded(*rule): + print "'%s' unsupported on DB %s version %s" % ( + cls.__class__.__name__, testing.db.name, + _server_version()) + return True + return False + + def beforeTest(self, test): + testing.resetwarnings() + + def afterTest(self, test): + testing.resetwarnings() + + def afterContext(self): + testing.global_cleanup_assertions() + + #def handleError(self, test, err): + #pass + + #def finalize(self, result=None): + #pass diff --git a/test/dialect/test_access.py b/test/dialect/test_access.py index 0ea8d9a61..bd0d7c22a 100644 --- a/test/dialect/test_access.py +++ b/test/dialect/test_access.py @@ -1,7 +1,7 @@ from sqlalchemy import * from sqlalchemy import sql from sqlalchemy.databases import access -from sqlalchemy.test import * +from test.lib import * class CompileTest(TestBase, AssertsCompiledSQL): diff --git a/test/dialect/test_firebird.py b/test/dialect/test_firebird.py index 41a50e6a3..814c267b5 100644 --- a/test/dialect/test_firebird.py +++ b/test/dialect/test_firebird.py @@ -1,9 +1,9 @@ -from sqlalchemy.test.testing import eq_, assert_raises +from test.lib.testing import eq_, assert_raises from sqlalchemy import * from sqlalchemy.databases import firebird from sqlalchemy.exc import ProgrammingError from sqlalchemy.sql import table, column -from sqlalchemy.test import * +from test.lib import * class DomainReflectionTest(TestBase, AssertsExecutionResults): diff --git a/test/dialect/test_informix.py b/test/dialect/test_informix.py index ceec587d9..ea74dcbe4 100644 --- a/test/dialect/test_informix.py +++ b/test/dialect/test_informix.py @@ -1,6 +1,6 @@ from sqlalchemy import * from sqlalchemy.databases import informix -from sqlalchemy.test import * +from test.lib import * class CompileTest(TestBase, AssertsCompiledSQL): diff --git a/test/dialect/test_maxdb.py b/test/dialect/test_maxdb.py index 4df049030..7d43d594b 100644 --- a/test/dialect/test_maxdb.py +++ b/test/dialect/test_maxdb.py @@ -1,12 +1,12 @@ """MaxDB-specific tests.""" -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ import StringIO, sys from sqlalchemy import * from sqlalchemy import exc, sql from decimal import Decimal from sqlalchemy.databases import maxdb -from sqlalchemy.test import * +from test.lib import * # TODO diff --git a/test/dialect/test_mssql.py b/test/dialect/test_mssql.py index e766a8301..26c53298c 100644 --- a/test/dialect/test_mssql.py +++ b/test/dialect/test_mssql.py @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ import datetime import os import re @@ -11,8 +11,8 @@ from sqlalchemy.sql import table, column from sqlalchemy.databases import mssql from sqlalchemy.dialects.mssql import pyodbc, mxodbc, pymssql from sqlalchemy.engine import url -from sqlalchemy.test import * -from sqlalchemy.test.testing import eq_, emits_warning_on, \ +from test.lib import * +from test.lib.testing import eq_, emits_warning_on, \ assert_raises_message class CompileTest(TestBase, AssertsCompiledSQL): diff --git a/test/dialect/test_mxodbc.py b/test/dialect/test_mxodbc.py index f574177dd..36cfc9b08 100644 --- a/test/dialect/test_mxodbc.py +++ b/test/dialect/test_mxodbc.py @@ -1,6 +1,6 @@ from sqlalchemy import * -from sqlalchemy.test.testing import eq_, TestBase -from sqlalchemy.test import engines +from test.lib.testing import eq_, TestBase +from test.lib import engines # TODO: we should probably build mock bases for # these to share with test_reconnect, test_parseconnect diff --git a/test/dialect/test_mysql.py b/test/dialect/test_mysql.py index 499fd7bd2..7b06f412a 100644 --- a/test/dialect/test_mysql.py +++ b/test/dialect/test_mysql.py @@ -1,6 +1,6 @@ # coding: utf-8 -from sqlalchemy.test.testing import eq_, assert_raises +from test.lib.testing import eq_, assert_raises # Py2K import sets @@ -9,9 +9,9 @@ import sets from sqlalchemy import * from sqlalchemy import sql, exc, schema, types as sqltypes, event from sqlalchemy.dialects.mysql import base as mysql -from sqlalchemy.test.testing import eq_ -from sqlalchemy.test import * -from sqlalchemy.test.engines import utf8_engine +from test.lib.testing import eq_ +from test.lib import * +from test.lib.engines import utf8_engine import datetime class TypesTest(TestBase, AssertsExecutionResults, AssertsCompiledSQL): diff --git a/test/dialect/test_oracle.py b/test/dialect/test_oracle.py index 6627015b9..3a0fbac9a 100644 --- a/test/dialect/test_oracle.py +++ b/test/dialect/test_oracle.py @@ -1,12 +1,12 @@ # coding: utf-8 -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from sqlalchemy import * from sqlalchemy import types as sqltypes, exc from sqlalchemy.sql import table, column -from sqlalchemy.test import * -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message -from sqlalchemy.test.engines import testing_engine +from test.lib import * +from test.lib.testing import eq_, assert_raises, assert_raises_message +from test.lib.engines import testing_engine from sqlalchemy.dialects.oracle import cx_oracle, base as oracle from sqlalchemy.engine import default from sqlalchemy.util import jython diff --git a/test/dialect/test_postgresql.py b/test/dialect/test_postgresql.py index 2baa65823..06a39ec26 100644 --- a/test/dialect/test_postgresql.py +++ b/test/dialect/test_postgresql.py @@ -1,6 +1,6 @@ # coding: utf-8 -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message -from sqlalchemy.test import engines +from test.lib.testing import eq_, assert_raises, assert_raises_message +from test.lib import engines import datetime import decimal from sqlalchemy import * @@ -8,10 +8,10 @@ from sqlalchemy.orm import * from sqlalchemy import exc, schema, types from sqlalchemy.dialects.postgresql import base as postgresql from sqlalchemy.engine.strategies import MockEngineStrategy -from sqlalchemy.test import * -from sqlalchemy.test.util import round_decimal +from test.lib import * +from test.lib.util import round_decimal from sqlalchemy.sql import table, column -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from test.engine._base import TablesTest import logging diff --git a/test/dialect/test_sqlite.py b/test/dialect/test_sqlite.py index d42e8dde9..34f5927ed 100644 --- a/test/dialect/test_sqlite.py +++ b/test/dialect/test_sqlite.py @@ -1,13 +1,13 @@ """SQLite-specific tests.""" -from sqlalchemy.test.testing import eq_, assert_raises, \ +from test.lib.testing import eq_, assert_raises, \ assert_raises_message import datetime from sqlalchemy import * from sqlalchemy import exc, sql, schema, pool from sqlalchemy.dialects.sqlite import base as sqlite, \ pysqlite as pysqlite_dialect -from sqlalchemy.test import * +from test.lib import * class TestTypes(TestBase, AssertsExecutionResults): diff --git a/test/dialect/test_sybase.py b/test/dialect/test_sybase.py index 37de91d1c..54e4f32e1 100644 --- a/test/dialect/test_sybase.py +++ b/test/dialect/test_sybase.py @@ -1,7 +1,7 @@ from sqlalchemy import * from sqlalchemy import sql from sqlalchemy.databases import sybase -from sqlalchemy.test import * +from test.lib import * class CompileTest(TestBase, AssertsCompiledSQL): diff --git a/test/engine/_base.py b/test/engine/_base.py index ec91243d2..773fa2fea 100644 --- a/test/engine/_base.py +++ b/test/engine/_base.py @@ -1,6 +1,6 @@ import sqlalchemy as sa -from sqlalchemy.test import testing -from sqlalchemy.test.testing import adict +from test.lib import testing +from test.lib.testing import adict class TablesTest(testing.TestBase): diff --git a/test/engine/test_bind.py b/test/engine/test_bind.py index dfcc5e172..855c3611e 100644 --- a/test/engine/test_bind.py +++ b/test/engine/test_bind.py @@ -1,14 +1,14 @@ """tests the "bind" attribute/argument across schema and SQL, including the deprecated versions of these arguments""" -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from sqlalchemy import engine, exc from sqlalchemy import MetaData, ThreadLocalMetaData from sqlalchemy import Integer, text -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column +from test.lib.schema import Table +from test.lib.schema import Column import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing class BindTest(testing.TestBase): diff --git a/test/engine/test_ddlevents.py b/test/engine/test_ddlevents.py index d0e8af81d..733cc1fcf 100644 --- a/test/engine/test_ddlevents.py +++ b/test/engine/test_ddlevents.py @@ -1,13 +1,13 @@ -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message from sqlalchemy.schema import DDL, CheckConstraint, AddConstraint, \ DropConstraint from sqlalchemy import create_engine from sqlalchemy import MetaData, Integer, String, event, exc, text -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column +from test.lib.schema import Table +from test.lib.schema import Column import sqlalchemy as tsa -from sqlalchemy.test import TestBase, testing, engines -from sqlalchemy.test.testing import AssertsCompiledSQL, eq_ +from test.lib import TestBase, testing, engines +from test.lib.testing import AssertsCompiledSQL, eq_ from nose import SkipTest class DDLEventTest(TestBase): diff --git a/test/engine/test_execute.py b/test/engine/test_execute.py index 7334f755a..6b0e86e2f 100644 --- a/test/engine/test_execute.py +++ b/test/engine/test_execute.py @@ -1,11 +1,11 @@ -from sqlalchemy.test.testing import eq_, assert_raises +from test.lib.testing import eq_, assert_raises import re from sqlalchemy.interfaces import ConnectionProxy from sqlalchemy import MetaData, Integer, String, INT, VARCHAR, func, \ bindparam, select, event -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column import sqlalchemy as tsa -from sqlalchemy.test import TestBase, testing, engines +from test.lib import TestBase, testing, engines import logging from sqlalchemy.dialects.oracle.zxjdbc import ReturningParam diff --git a/test/engine/test_metadata.py b/test/engine/test_metadata.py index 1feb4d9e2..6534f67be 100644 --- a/test/engine/test_metadata.py +++ b/test/engine/test_metadata.py @@ -1,17 +1,17 @@ -from sqlalchemy.test.testing import assert_raises -from sqlalchemy.test.testing import assert_raises_message -from sqlalchemy.test.testing import emits_warning +from test.lib.testing import assert_raises +from test.lib.testing import assert_raises_message +from test.lib.testing import emits_warning import pickle from sqlalchemy import Integer, String, UniqueConstraint, \ CheckConstraint, ForeignKey, MetaData, Sequence, \ ForeignKeyConstraint, ColumnDefault, Index -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy import schema, exc import sqlalchemy as tsa -from sqlalchemy.test import TestBase, ComparesTables, \ +from test.lib import TestBase, ComparesTables, \ AssertsCompiledSQL, testing, engines -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ class MetaDataTest(TestBase, ComparesTables): def test_metadata_connect(self): diff --git a/test/engine/test_parseconnect.py b/test/engine/test_parseconnect.py index 78b75ad2f..7000549e7 100644 --- a/test/engine/test_parseconnect.py +++ b/test/engine/test_parseconnect.py @@ -1,11 +1,11 @@ -from sqlalchemy.test.testing import assert_raises, assert_raises_message, eq_ +from test.lib.testing import assert_raises, assert_raises_message, eq_ import ConfigParser import StringIO import sqlalchemy.engine.url as url from sqlalchemy import create_engine, engine_from_config from sqlalchemy.engine import _coerce_config import sqlalchemy as tsa -from sqlalchemy.test import TestBase +from test.lib import TestBase class ParseConnectTest(TestBase): diff --git a/test/engine/test_pool.py b/test/engine/test_pool.py index 06c9ac065..4da3a08e3 100644 --- a/test/engine/test_pool.py +++ b/test/engine/test_pool.py @@ -1,9 +1,9 @@ import threading, time from sqlalchemy import pool, interfaces, create_engine, select, event import sqlalchemy as tsa -from sqlalchemy.test import TestBase, testing -from sqlalchemy.test.util import gc_collect, lazy_gc -from sqlalchemy.test.testing import eq_ +from test.lib import TestBase, testing +from test.lib.util import gc_collect, lazy_gc +from test.lib.testing import eq_ mcid = 1 class MockDBAPI(object): diff --git a/test/engine/test_reconnect.py b/test/engine/test_reconnect.py index 7675de9c3..e28985801 100644 --- a/test/engine/test_reconnect.py +++ b/test/engine/test_reconnect.py @@ -1,11 +1,11 @@ -from sqlalchemy.test.testing import eq_, assert_raises +from test.lib.testing import eq_, assert_raises import time import weakref from sqlalchemy import select, MetaData, Integer, String, pool -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column import sqlalchemy as tsa -from sqlalchemy.test import TestBase, testing, engines -from sqlalchemy.test.util import gc_collect +from test.lib import TestBase, testing, engines +from test.lib.util import gc_collect from sqlalchemy import exc class MockDisconnect(Exception): diff --git a/test/engine/test_reflection.py b/test/engine/test_reflection.py index 91e73d4f2..5fd18dc81 100644 --- a/test/engine/test_reflection.py +++ b/test/engine/test_reflection.py @@ -1,12 +1,12 @@ -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message +from test.lib.testing import eq_, assert_raises, assert_raises_message import StringIO, unicodedata from sqlalchemy import types as sql_types from sqlalchemy import schema from sqlalchemy.engine.reflection import Inspector from sqlalchemy import MetaData -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column import sqlalchemy as sa -from sqlalchemy.test import TestBase, ComparesTables, \ +from test.lib import TestBase, ComparesTables, \ testing, engines, AssertsCompiledSQL create_inspector = Inspector.from_engine diff --git a/test/engine/test_transaction.py b/test/engine/test_transaction.py index 6f3186add..14d8bfd1b 100644 --- a/test/engine/test_transaction.py +++ b/test/engine/test_transaction.py @@ -1,13 +1,13 @@ -from sqlalchemy.test.testing import eq_, assert_raises, \ +from test.lib.testing import eq_, assert_raises, \ assert_raises_message import sys import time import threading from sqlalchemy import create_engine, MetaData, INT, VARCHAR, Sequence, \ select, Integer, String, func, text, exc -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column -from sqlalchemy.test import TestBase, testing +from test.lib.schema import Table +from test.lib.schema import Column +from test.lib import TestBase, testing users, metadata = None, None diff --git a/test/ex/test_examples.py b/test/ex/test_examples.py index 9c68f1825..311eab041 100644 --- a/test/ex/test_examples.py +++ b/test/ex/test_examples.py @@ -1,4 +1,4 @@ -from sqlalchemy.test import * +from test.lib import * import os import re diff --git a/test/ext/test_associationproxy.py b/test/ext/test_associationproxy.py index d22e45796..fb2f60ff1 100644 --- a/test/ext/test_associationproxy.py +++ b/test/ext/test_associationproxy.py @@ -1,4 +1,4 @@ -from sqlalchemy.test.testing import eq_, assert_raises +from test.lib.testing import eq_, assert_raises import copy import pickle @@ -7,8 +7,8 @@ from sqlalchemy.orm import * from sqlalchemy.orm.collections import collection from sqlalchemy.ext.associationproxy import * from sqlalchemy.ext.associationproxy import _AssociationList -from sqlalchemy.test import * -from sqlalchemy.test.util import gc_collect +from test.lib import * +from test.lib.util import gc_collect from sqlalchemy.sql import not_ from test.orm import _base diff --git a/test/ext/test_compiler.py b/test/ext/test_compiler.py index 3ed84fe61..31b893b48 100644 --- a/test/ext/test_compiler.py +++ b/test/ext/test_compiler.py @@ -5,7 +5,7 @@ from sqlalchemy.sql.expression import ClauseElement, ColumnClause,\ from sqlalchemy.schema import DDLElement from sqlalchemy.ext.compiler import compiles from sqlalchemy.sql import table, column -from sqlalchemy.test import * +from test.lib import * class UserDefinedTest(TestBase, AssertsCompiledSQL): diff --git a/test/ext/test_declarative.py b/test/ext/test_declarative.py index 32173cdc1..85692161b 100644 --- a/test/ext/test_declarative.py +++ b/test/ext/test_declarative.py @@ -1,17 +1,17 @@ -from sqlalchemy.test.testing import eq_, assert_raises, \ +from test.lib.testing import eq_, assert_raises, \ assert_raises_message from sqlalchemy.ext import declarative as decl from sqlalchemy import exc import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import MetaData, Integer, String, ForeignKey, \ ForeignKeyConstraint, asc, Index -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import relationship, create_session, class_mapper, \ joinedload, configure_mappers, backref, clear_mappers, \ polymorphic_union, deferred, column_property -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from sqlalchemy.util import classproperty from test.orm._base import ComparableEntity, MappedTest from sqlalchemy.ext.declarative import declared_attr diff --git a/test/ext/test_horizontal_shard.py b/test/ext/test_horizontal_shard.py index a5cee5cad..b4f60519c 100644 --- a/test/ext/test_horizontal_shard.py +++ b/test/ext/test_horizontal_shard.py @@ -4,8 +4,8 @@ from sqlalchemy import sql from sqlalchemy.orm import * from sqlalchemy.ext.horizontal_shard import ShardedSession from sqlalchemy.sql import operators -from sqlalchemy.test import * -from sqlalchemy.test.testing import eq_ +from test.lib import * +from test.lib.testing import eq_ from nose import SkipTest # TODO: ShardTest can be turned into a base for further subclasses diff --git a/test/ext/test_orderinglist.py b/test/ext/test_orderinglist.py index 559aefd1d..f7f8f7fa7 100644 --- a/test/ext/test_orderinglist.py +++ b/test/ext/test_orderinglist.py @@ -1,8 +1,8 @@ from sqlalchemy import * from sqlalchemy.orm import * from sqlalchemy.ext.orderinglist import * -from sqlalchemy.test.testing import eq_ -from sqlalchemy.test import * +from test.lib.testing import eq_ +from test.lib import * metadata = None diff --git a/test/ext/test_serializer.py b/test/ext/test_serializer.py index 24626bc8e..862a871af 100644 --- a/test/ext/test_serializer.py +++ b/test/ext/test_serializer.py @@ -2,14 +2,14 @@ from sqlalchemy.ext import serializer from sqlalchemy import exc import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import MetaData, Integer, String, ForeignKey, select, \ desc, func, util -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column +from test.lib.schema import Table +from test.lib.schema import Column from sqlalchemy.orm import relationship, sessionmaker, scoped_session, \ class_mapper, mapper, joinedload, configure_mappers, aliased -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from test.orm._base import ComparableEntity, MappedTest diff --git a/test/ext/test_sqlsoup.py b/test/ext/test_sqlsoup.py index 7fe8ab178..f0ac6cbed 100644 --- a/test/ext/test_sqlsoup.py +++ b/test/ext/test_sqlsoup.py @@ -1,5 +1,5 @@ from sqlalchemy.ext import sqlsoup -from sqlalchemy.test.testing import TestBase, eq_, assert_raises +from test.lib.testing import TestBase, eq_, assert_raises from sqlalchemy import create_engine, or_, desc, select, func, exc, \ Table, util from sqlalchemy.orm import scoped_session, sessionmaker diff --git a/test/lib/__init__.py b/test/lib/__init__.py new file mode 100644 index 000000000..452848aff --- /dev/null +++ b/test/lib/__init__.py @@ -0,0 +1,27 @@ +"""Testing environment and utilities. + +This package contains base classes and routines used by +the unit tests. Tests are based on Nose and bootstrapped +by noseplugin.NoseSQLAlchemy. + +""" + +from test.bootstrap import config +from test.lib import testing, engines, requires, profiling, pickleable +from test.lib.schema import Column, Table +from test.lib.testing import \ + AssertsCompiledSQL, \ + AssertsExecutionResults, \ + ComparesTables, \ + TestBase, \ + rowset + + +__all__ = ('testing', + 'Column', 'Table', + 'rowset', + 'TestBase', 'AssertsExecutionResults', + 'AssertsCompiledSQL', 'ComparesTables', + 'engines', 'profiling', 'pickleable') + + diff --git a/test/lib/assertsql.py b/test/lib/assertsql.py new file mode 100644 index 000000000..b206f91fc --- /dev/null +++ b/test/lib/assertsql.py @@ -0,0 +1,314 @@ + +from sqlalchemy.interfaces import ConnectionProxy +from sqlalchemy.engine.default import DefaultDialect +from sqlalchemy.engine.base import Connection +from sqlalchemy import util +import re + +class AssertRule(object): + + def process_execute(self, clauseelement, *multiparams, **params): + pass + + def process_cursor_execute(self, statement, parameters, context, + executemany): + pass + + def is_consumed(self): + """Return True if this rule has been consumed, False if not. + + Should raise an AssertionError if this rule's condition has + definitely failed. + + """ + + raise NotImplementedError() + + def rule_passed(self): + """Return True if the last test of this rule passed, False if + failed, None if no test was applied.""" + + raise NotImplementedError() + + def consume_final(self): + """Return True if this rule has been consumed. + + Should raise an AssertionError if this rule's condition has not + been consumed or has failed. + + """ + + if self._result is None: + assert False, 'Rule has not been consumed' + return self.is_consumed() + +class SQLMatchRule(AssertRule): + def __init__(self): + self._result = None + self._errmsg = "" + + def rule_passed(self): + return self._result + + def is_consumed(self): + if self._result is None: + return False + + assert self._result, self._errmsg + + return True + +class ExactSQL(SQLMatchRule): + + def __init__(self, sql, params=None): + SQLMatchRule.__init__(self) + self.sql = sql + self.params = params + + def process_cursor_execute(self, statement, parameters, context, + executemany): + if not context: + return + _received_statement = \ + _process_engine_statement(context.unicode_statement, + context) + _received_parameters = context.compiled_parameters + + # TODO: remove this step once all unit tests are migrated, as + # ExactSQL should really be *exact* SQL + + sql = _process_assertion_statement(self.sql, context) + equivalent = _received_statement == sql + if self.params: + if util.callable(self.params): + params = self.params(context) + else: + params = self.params + if not isinstance(params, list): + params = [params] + equivalent = equivalent and params \ + == context.compiled_parameters + else: + params = {} + self._result = equivalent + if not self._result: + self._errmsg = \ + 'Testing for exact statement %r exact params %r, '\ + 'received %r with params %r' % (sql, params, + _received_statement, _received_parameters) + + +class RegexSQL(SQLMatchRule): + + def __init__(self, regex, params=None): + SQLMatchRule.__init__(self) + self.regex = re.compile(regex) + self.orig_regex = regex + self.params = params + + def process_cursor_execute(self, statement, parameters, context, + executemany): + if not context: + return + _received_statement = \ + _process_engine_statement(context.unicode_statement, + context) + _received_parameters = context.compiled_parameters + equivalent = bool(self.regex.match(_received_statement)) + if self.params: + if util.callable(self.params): + params = self.params(context) + else: + params = self.params + if not isinstance(params, list): + params = [params] + + # do a positive compare only + + for param, received in zip(params, _received_parameters): + for k, v in param.iteritems(): + if k not in received or received[k] != v: + equivalent = False + break + else: + params = {} + self._result = equivalent + if not self._result: + self._errmsg = \ + 'Testing for regex %r partial params %r, received %r '\ + 'with params %r' % (self.orig_regex, params, + _received_statement, + _received_parameters) + +class CompiledSQL(SQLMatchRule): + + def __init__(self, statement, params): + SQLMatchRule.__init__(self) + self.statement = statement + self.params = params + + def process_cursor_execute(self, statement, parameters, context, + executemany): + if not context: + return + _received_parameters = list(context.compiled_parameters) + + # recompile from the context, using the default dialect + + compiled = \ + context.compiled.statement.compile(dialect=DefaultDialect(), + column_keys=context.compiled.column_keys) + _received_statement = re.sub(r'\n', '', str(compiled)) + equivalent = self.statement == _received_statement + if self.params: + if util.callable(self.params): + params = self.params(context) + else: + params = self.params + if not isinstance(params, list): + params = [params] + all_params = list(params) + all_received = list(_received_parameters) + while params: + param = dict(params.pop(0)) + for k, v in context.compiled.params.iteritems(): + param.setdefault(k, v) + if param not in _received_parameters: + equivalent = False + break + else: + _received_parameters.remove(param) + if _received_parameters: + equivalent = False + else: + params = {} + self._result = equivalent + if not self._result: + print 'Testing for compiled statement %r partial params '\ + '%r, received %r with params %r' % (self.statement, + all_params, _received_statement, all_received) + self._errmsg = \ + 'Testing for compiled statement %r partial params %r, '\ + 'received %r with params %r' % (self.statement, + all_params, _received_statement, all_received) + + + # print self._errmsg + +class CountStatements(AssertRule): + + def __init__(self, count): + self.count = count + self._statement_count = 0 + + def process_execute(self, clauseelement, *multiparams, **params): + self._statement_count += 1 + + def process_cursor_execute(self, statement, parameters, context, + executemany): + pass + + def is_consumed(self): + return False + + def consume_final(self): + assert self.count == self._statement_count, \ + 'desired statement count %d does not match %d' \ + % (self.count, self._statement_count) + return True + +class AllOf(AssertRule): + + def __init__(self, *rules): + self.rules = set(rules) + + def process_execute(self, clauseelement, *multiparams, **params): + for rule in self.rules: + rule.process_execute(clauseelement, *multiparams, **params) + + def process_cursor_execute(self, statement, parameters, context, + executemany): + for rule in self.rules: + rule.process_cursor_execute(statement, parameters, context, + executemany) + + def is_consumed(self): + if not self.rules: + return True + for rule in list(self.rules): + if rule.rule_passed(): # a rule passed, move on + self.rules.remove(rule) + return len(self.rules) == 0 + assert False, 'No assertion rules were satisfied for statement' + + def consume_final(self): + return len(self.rules) == 0 + +def _process_engine_statement(query, context): + if util.jython: + + # oracle+zxjdbc passes a PyStatement when returning into + + query = unicode(query) + if context.engine.name == 'mssql' \ + and query.endswith('; select scope_identity()'): + query = query[:-25] + query = re.sub(r'\n', '', query) + return query + +def _process_assertion_statement(query, context): + paramstyle = context.dialect.paramstyle + if paramstyle == 'named': + pass + elif paramstyle =='pyformat': + query = re.sub(r':([\w_]+)', r"%(\1)s", query) + else: + # positional params + repl = None + if paramstyle=='qmark': + repl = "?" + elif paramstyle=='format': + repl = r"%s" + elif paramstyle=='numeric': + repl = None + query = re.sub(r':([\w_]+)', repl, query) + + return query + +class SQLAssert(object): + + rules = None + + def add_rules(self, rules): + self.rules = list(rules) + + def statement_complete(self): + for rule in self.rules: + if not rule.consume_final(): + assert False, \ + 'All statements are complete, but pending '\ + 'assertion rules remain' + + def clear_rules(self): + del self.rules + + def execute(self, conn, clauseelement, multiparams, params, result): + if self.rules is not None: + if not self.rules: + assert False, \ + 'All rules have been exhausted, but further '\ + 'statements remain' + rule = self.rules[0] + rule.process_execute(clauseelement, *multiparams, **params) + if rule.is_consumed(): + self.rules.pop(0) + + def cursor_execute(self, conn, cursor, statement, parameters, + context, executemany): + if self.rules: + rule = self.rules[0] + rule.process_cursor_execute(statement, parameters, context, + executemany) + +asserter = SQLAssert() + diff --git a/test/lib/engines.py b/test/lib/engines.py new file mode 100644 index 000000000..fdf4163c8 --- /dev/null +++ b/test/lib/engines.py @@ -0,0 +1,304 @@ +import sys, types, weakref +from collections import deque +from test.bootstrap import config +from sqlalchemy.util import function_named, callable +from sqlalchemy import event +import re +import warnings + +class ConnectionKiller(object): + def __init__(self): + self.proxy_refs = weakref.WeakKeyDictionary() + + def checkout(self, dbapi_con, con_record, con_proxy): + self.proxy_refs[con_proxy] = True + + def _apply_all(self, methods): + # must copy keys atomically + for rec in self.proxy_refs.keys(): + if rec is not None and rec.is_valid: + try: + for name in methods: + if callable(name): + name(rec) + else: + getattr(rec, name)() + except (SystemExit, KeyboardInterrupt): + raise + except Exception, e: + warnings.warn("testing_reaper couldn't close connection: %s" % e) + + def rollback_all(self): + self._apply_all(('rollback',)) + + def close_all(self): + self._apply_all(('rollback', 'close')) + + def assert_all_closed(self): + for rec in self.proxy_refs: + if rec.is_valid: + assert False + +testing_reaper = ConnectionKiller() + +def drop_all_tables(metadata): + testing_reaper.close_all() + metadata.drop_all() + +def assert_conns_closed(fn): + def decorated(*args, **kw): + try: + fn(*args, **kw) + finally: + testing_reaper.assert_all_closed() + return function_named(decorated, fn.__name__) + +def rollback_open_connections(fn): + """Decorator that rolls back all open connections after fn execution.""" + + def decorated(*args, **kw): + try: + fn(*args, **kw) + finally: + testing_reaper.rollback_all() + return function_named(decorated, fn.__name__) + +def close_first(fn): + """Decorator that closes all connections before fn execution.""" + def decorated(*args, **kw): + testing_reaper.close_all() + fn(*args, **kw) + return function_named(decorated, fn.__name__) + + +def close_open_connections(fn): + """Decorator that closes all connections after fn execution.""" + + def decorated(*args, **kw): + try: + fn(*args, **kw) + finally: + testing_reaper.close_all() + return function_named(decorated, fn.__name__) + +def all_dialects(exclude=None): + import sqlalchemy.databases as d + for name in d.__all__: + # TEMPORARY + if exclude and name in exclude: + continue + mod = getattr(d, name, None) + if not mod: + mod = getattr(__import__('sqlalchemy.databases.%s' % name).databases, name) + yield mod.dialect() + +class ReconnectFixture(object): + def __init__(self, dbapi): + self.dbapi = dbapi + self.connections = [] + + def __getattr__(self, key): + return getattr(self.dbapi, key) + + def connect(self, *args, **kwargs): + conn = self.dbapi.connect(*args, **kwargs) + self.connections.append(conn) + return conn + + def shutdown(self): + # TODO: this doesn't cover all cases + # as nicely as we'd like, namely MySQLdb. + # would need to implement R. Brewer's + # proxy server idea to get better + # coverage. + for c in list(self.connections): + c.close() + self.connections = [] + +def reconnecting_engine(url=None, options=None): + url = url or config.db_url + dbapi = config.db.dialect.dbapi + if not options: + options = {} + options['module'] = ReconnectFixture(dbapi) + engine = testing_engine(url, options) + engine.test_shutdown = engine.dialect.dbapi.shutdown + return engine + +def testing_engine(url=None, options=None): + """Produce an engine configured by --options with optional overrides.""" + + from sqlalchemy import create_engine + from test.lib.assertsql import asserter + + url = url or config.db_url + options = options or config.db_opts + + engine = create_engine(url, **options) + event.listen(asserter.execute, 'on_after_execute', engine) + event.listen(asserter.cursor_execute, 'on_after_cursor_execute', engine) + event.listen(testing_reaper.checkout, 'on_checkout', engine.pool) + + # may want to call this, results + # in first-connect initializers + #engine.connect() + + return engine + +def utf8_engine(url=None, options=None): + """Hook for dialects or drivers that don't handle utf8 by default.""" + + from sqlalchemy.engine import url as engine_url + + if config.db.driver == 'mysqldb': + dbapi_ver = config.db.dialect.dbapi.version_info + if (dbapi_ver < (1, 2, 1) or + dbapi_ver in ((1, 2, 1, 'gamma', 1), (1, 2, 1, 'gamma', 2), + (1, 2, 1, 'gamma', 3), (1, 2, 1, 'gamma', 5))): + raise RuntimeError('Character set support unavailable with this ' + 'driver version: %s' % repr(dbapi_ver)) + else: + url = url or config.db_url + url = engine_url.make_url(url) + url.query['charset'] = 'utf8' + url.query['use_unicode'] = '0' + url = str(url) + + return testing_engine(url, options) + +def mock_engine(dialect_name=None): + """Provides a mocking engine based on the current testing.db. + + This is normally used to test DDL generation flow as emitted + by an Engine. + + It should not be used in other cases, as assert_compile() and + assert_sql_execution() are much better choices with fewer + moving parts. + + """ + + from sqlalchemy import create_engine + + if not dialect_name: + dialect_name = config.db.name + + buffer = [] + def executor(sql, *a, **kw): + buffer.append(sql) + def assert_sql(stmts): + recv = [re.sub(r'[\n\t]', '', str(s)) for s in buffer] + assert recv == stmts, recv + + engine = create_engine(dialect_name + '://', + strategy='mock', executor=executor) + assert not hasattr(engine, 'mock') + engine.mock = buffer + engine.assert_sql = assert_sql + return engine + +class ReplayableSession(object): + """A simple record/playback tool. + + This is *not* a mock testing class. It only records a session for later + playback and makes no assertions on call consistency whatsoever. It's + unlikely to be suitable for anything other than DB-API recording. + + """ + + Callable = object() + NoAttribute = object() + Natives = set([getattr(types, t) + for t in dir(types) if not t.startswith('_')]). \ + difference([getattr(types, t) + # Py3K + #for t in ('FunctionType', 'BuiltinFunctionType', + # 'MethodType', 'BuiltinMethodType', + # 'LambdaType', )]) + + # Py2K + for t in ('FunctionType', 'BuiltinFunctionType', + 'MethodType', 'BuiltinMethodType', + 'LambdaType', 'UnboundMethodType',)]) + # end Py2K + def __init__(self): + self.buffer = deque() + + def recorder(self, base): + return self.Recorder(self.buffer, base) + + def player(self): + return self.Player(self.buffer) + + class Recorder(object): + def __init__(self, buffer, subject): + self._buffer = buffer + self._subject = subject + + def __call__(self, *args, **kw): + subject, buffer = [object.__getattribute__(self, x) + for x in ('_subject', '_buffer')] + + result = subject(*args, **kw) + if type(result) not in ReplayableSession.Natives: + buffer.append(ReplayableSession.Callable) + return type(self)(buffer, result) + else: + buffer.append(result) + return result + + @property + def _sqla_unwrap(self): + return self._subject + + def __getattribute__(self, key): + try: + return object.__getattribute__(self, key) + except AttributeError: + pass + + subject, buffer = [object.__getattribute__(self, x) + for x in ('_subject', '_buffer')] + try: + result = type(subject).__getattribute__(subject, key) + except AttributeError: + buffer.append(ReplayableSession.NoAttribute) + raise + else: + if type(result) not in ReplayableSession.Natives: + buffer.append(ReplayableSession.Callable) + return type(self)(buffer, result) + else: + buffer.append(result) + return result + + class Player(object): + def __init__(self, buffer): + self._buffer = buffer + + def __call__(self, *args, **kw): + buffer = object.__getattribute__(self, '_buffer') + result = buffer.popleft() + if result is ReplayableSession.Callable: + return self + else: + return result + + @property + def _sqla_unwrap(self): + return None + + def __getattribute__(self, key): + try: + return object.__getattribute__(self, key) + except AttributeError: + pass + buffer = object.__getattribute__(self, '_buffer') + result = buffer.popleft() + if result is ReplayableSession.Callable: + return self + elif result is ReplayableSession.NoAttribute: + raise AttributeError(key) + else: + return result + diff --git a/test/lib/entities.py b/test/lib/entities.py new file mode 100644 index 000000000..0ec677eea --- /dev/null +++ b/test/lib/entities.py @@ -0,0 +1,83 @@ +import sqlalchemy as sa +from sqlalchemy import exc as sa_exc + +_repr_stack = set() +class BasicEntity(object): + def __init__(self, **kw): + for key, value in kw.iteritems(): + setattr(self, key, value) + + def __repr__(self): + if id(self) in _repr_stack: + return object.__repr__(self) + _repr_stack.add(id(self)) + try: + return "%s(%s)" % ( + (self.__class__.__name__), + ', '.join(["%s=%r" % (key, getattr(self, key)) + for key in sorted(self.__dict__.keys()) + if not key.startswith('_')])) + finally: + _repr_stack.remove(id(self)) + +_recursion_stack = set() +class ComparableEntity(BasicEntity): + def __hash__(self): + return hash(self.__class__) + + def __ne__(self, other): + return not self.__eq__(other) + + def __eq__(self, other): + """'Deep, sparse compare. + + Deeply compare two entities, following the non-None attributes of the + non-persisted object, if possible. + + """ + if other is self: + return True + elif not self.__class__ == other.__class__: + return False + + if id(self) in _recursion_stack: + return True + _recursion_stack.add(id(self)) + + try: + # pick the entity thats not SA persisted as the source + try: + self_key = sa.orm.attributes.instance_state(self).key + except sa.orm.exc.NO_STATE: + self_key = None + + if other is None: + a = self + b = other + elif self_key is not None: + a = other + b = self + else: + a = self + b = other + + for attr in a.__dict__.keys(): + if attr.startswith('_'): + continue + value = getattr(a, attr) + + try: + # handle lazy loader errors + battr = getattr(b, attr) + except (AttributeError, sa_exc.UnboundExecutionError): + return False + + if hasattr(value, '__iter__'): + if list(value) != list(battr): + return False + else: + if value is not None and value != battr: + return False + return True + finally: + _recursion_stack.remove(id(self)) diff --git a/test/lib/orm.py b/test/lib/orm.py new file mode 100644 index 000000000..7ec13c555 --- /dev/null +++ b/test/lib/orm.py @@ -0,0 +1,111 @@ +import inspect, re +import config, testing +from sqlalchemy import orm + +__all__ = 'mapper', + + +_whitespace = re.compile(r'^(\s+)') + +def _find_pragma(lines, current): + m = _whitespace.match(lines[current]) + basis = m and m.group() or '' + + for line in reversed(lines[0:current]): + if 'testlib.pragma' in line: + return line + m = _whitespace.match(line) + indent = m and m.group() or '' + + # simplistic detection: + + # >> # testlib.pragma foo + # >> center_line() + if indent == basis: + break + # >> # testlib.pragma foo + # >> if fleem: + # >> center_line() + if line.endswith(':'): + break + return None + +def _make_blocker(method_name, fallback): + """Creates tripwired variant of a method, raising when called. + + To excempt an invocation from blockage, there are two options. + + 1) add a pragma in a comment:: + + # testlib.pragma exempt:methodname + offending_line() + + 2) add a magic cookie to the function's namespace:: + __sa_baremethodname_exempt__ = True + ... + offending_line() + another_offending_lines() + + The second is useful for testing and development. + """ + + if method_name.startswith('__') and method_name.endswith('__'): + frame_marker = '__sa_%s_exempt__' % method_name[2:-2] + else: + frame_marker = '__sa_%s_exempt__' % method_name + pragma_marker = 'exempt:' + method_name + + def method(self, *args, **kw): + frame_r = None + try: + frame = inspect.stack()[1][0] + frame_r = inspect.getframeinfo(frame, 9) + + module = frame.f_globals.get('__name__', '') + + type_ = type(self) + + pragma = _find_pragma(*frame_r[3:5]) + + exempt = ( + (not module.startswith('sqlalchemy')) or + (pragma and pragma_marker in pragma) or + (frame_marker in frame.f_locals) or + ('self' in frame.f_locals and + getattr(frame.f_locals['self'], frame_marker, False))) + + if exempt: + supermeth = getattr(super(type_, self), method_name, None) + if (supermeth is None or + getattr(supermeth, 'im_func', None) is method): + return fallback(self, *args, **kw) + else: + return supermeth(*args, **kw) + else: + raise AssertionError( + "%s.%s called in %s, line %s in %s" % ( + type_.__name__, method_name, module, frame_r[1], frame_r[2])) + finally: + del frame + method.__name__ = method_name + return method + +def mapper(type_, *args, **kw): + forbidden = [ + ('__hash__', 'unhashable', lambda s: id(s)), + ('__eq__', 'noncomparable', lambda s, o: s is o), + ('__ne__', 'noncomparable', lambda s, o: s is not o), + ('__cmp__', 'noncomparable', lambda s, o: object.__cmp__(s, o)), + ('__le__', 'noncomparable', lambda s, o: object.__le__(s, o)), + ('__lt__', 'noncomparable', lambda s, o: object.__lt__(s, o)), + ('__ge__', 'noncomparable', lambda s, o: object.__ge__(s, o)), + ('__gt__', 'noncomparable', lambda s, o: object.__gt__(s, o)), + ('__nonzero__', 'truthless', lambda s: 1), ] + + if isinstance(type_, type) and type_.__bases__ == (object,): + for method_name, option, fallback in forbidden: + if (getattr(config.options, option, False) and + method_name not in type_.__dict__): + setattr(type_, method_name, _make_blocker(method_name, fallback)) + + return orm.mapper(type_, *args, **kw) diff --git a/test/lib/pickleable.py b/test/lib/pickleable.py new file mode 100644 index 000000000..9794e424d --- /dev/null +++ b/test/lib/pickleable.py @@ -0,0 +1,75 @@ +""" + +some objects used for pickle tests, declared in their own module so that they +are easily pickleable. + +""" + + +class Foo(object): + def __init__(self, moredata): + self.data = 'im data' + self.stuff = 'im stuff' + self.moredata = moredata + __hash__ = object.__hash__ + def __eq__(self, other): + return other.data == self.data and other.stuff == self.stuff and other.moredata==self.moredata + + +class Bar(object): + def __init__(self, x, y): + self.x = x + self.y = y + __hash__ = object.__hash__ + def __eq__(self, other): + return other.__class__ is self.__class__ and other.x==self.x and other.y==self.y + def __str__(self): + return "Bar(%d, %d)" % (self.x, self.y) + +class OldSchool: + def __init__(self, x, y): + self.x = x + self.y = y + def __eq__(self, other): + return other.__class__ is self.__class__ and other.x==self.x and other.y==self.y + +class OldSchoolWithoutCompare: + def __init__(self, x, y): + self.x = x + self.y = y + +class BarWithoutCompare(object): + def __init__(self, x, y): + self.x = x + self.y = y + def __str__(self): + return "Bar(%d, %d)" % (self.x, self.y) + + +class NotComparable(object): + def __init__(self, data): + self.data = data + + def __hash__(self): + return id(self) + + def __eq__(self, other): + return NotImplemented + + def __ne__(self, other): + return NotImplemented + + +class BrokenComparable(object): + def __init__(self, data): + self.data = data + + def __hash__(self): + return id(self) + + def __eq__(self, other): + raise NotImplementedError + + def __ne__(self, other): + raise NotImplementedError + diff --git a/test/lib/profiling.py b/test/lib/profiling.py new file mode 100644 index 000000000..f6c21bde8 --- /dev/null +++ b/test/lib/profiling.py @@ -0,0 +1,221 @@ +"""Profiling support for unit and performance tests. + +These are special purpose profiling methods which operate +in a more fine-grained way than nose's profiling plugin. + +""" + +import os, sys +from test.lib.util import function_named, gc_collect +from nose import SkipTest + +__all__ = 'profiled', 'function_call_count', 'conditional_call_count' + +all_targets = set() +profile_config = { 'targets': set(), + 'report': True, + 'sort': ('time', 'calls'), + 'limit': None } +profiler = None + +def profiled(target=None, **target_opts): + """Optional function profiling. + + @profiled('label') + or + @profiled('label', report=True, sort=('calls',), limit=20) + + Enables profiling for a function when 'label' is targetted for + profiling. Report options can be supplied, and override the global + configuration and command-line options. + """ + + # manual or automatic namespacing by module would remove conflict issues + if target is None: + target = 'anonymous_target' + elif target in all_targets: + print "Warning: redefining profile target '%s'" % target + all_targets.add(target) + + filename = "%s.prof" % target + + def decorator(fn): + def profiled(*args, **kw): + if (target not in profile_config['targets'] and + not target_opts.get('always', None)): + return fn(*args, **kw) + + elapsed, load_stats, result = _profile( + filename, fn, *args, **kw) + + report = target_opts.get('report', profile_config['report']) + if report: + sort_ = target_opts.get('sort', profile_config['sort']) + limit = target_opts.get('limit', profile_config['limit']) + print "Profile report for target '%s' (%s)" % ( + target, filename) + + stats = load_stats() + stats.sort_stats(*sort_) + if limit: + stats.print_stats(limit) + else: + stats.print_stats() + #stats.print_callers() + os.unlink(filename) + return result + return function_named(profiled, fn.__name__) + return decorator + +def function_call_count(count=None, versions={}, variance=0.05): + """Assert a target for a test case's function call count. + + count + Optional, general target function call count. + + versions + Optional, a dictionary of Python version strings to counts, + for example:: + + { '2.5.1': 110, + '2.5': 100, + '2.4': 150 } + + The best match for the current running python will be used. + If none match, 'count' will be used as the fallback. + + variance + An +/- deviation percentage, defaults to 5%. + """ + + # this could easily dump the profile report if --verbose is in effect + + version_info = list(sys.version_info) + py_version = '.'.join([str(v) for v in sys.version_info]) + try: + from sqlalchemy.cprocessors import to_float + cextension = True + except ImportError: + cextension = False + + while version_info: + version = '.'.join([str(v) for v in version_info]) + if cextension: + version += "+cextension" + if version in versions: + count = versions[version] + break + version_info.pop() + + if count is None: + return lambda fn: fn + + def decorator(fn): + def counted(*args, **kw): + try: + filename = "%s.prof" % fn.__name__ + + elapsed, stat_loader, result = _profile( + filename, fn, *args, **kw) + + stats = stat_loader() + calls = stats.total_calls + + stats.sort_stats('calls', 'cumulative') + stats.print_stats() + #stats.print_callers() + deviance = int(count * variance) + if (calls < (count - deviance) or + calls > (count + deviance)): + raise AssertionError( + "Function call count %s not within %s%% " + "of expected %s. (Python version %s)" % ( + calls, (variance * 100), count, py_version)) + + return result + finally: + if os.path.exists(filename): + os.unlink(filename) + return function_named(counted, fn.__name__) + return decorator + +def conditional_call_count(discriminator, categories): + """Apply a function call count conditionally at runtime. + + Takes two arguments, a callable that returns a key value, and a dict + mapping key values to a tuple of arguments to function_call_count. + + The callable is not evaluated until the decorated function is actually + invoked. If the `discriminator` returns a key not present in the + `categories` dictionary, no call count assertion is applied. + + Useful for integration tests, where running a named test in isolation may + have a function count penalty not seen in the full suite, due to lazy + initialization in the DB-API, SA, etc. + """ + + def decorator(fn): + def at_runtime(*args, **kw): + criteria = categories.get(discriminator(), None) + if criteria is None: + return fn(*args, **kw) + + rewrapped = function_call_count(*criteria)(fn) + return rewrapped(*args, **kw) + return function_named(at_runtime, fn.__name__) + return decorator + + +def _profile(filename, fn, *args, **kw): + global profiler + if not profiler: + if sys.version_info > (2, 5): + try: + import cProfile + profiler = 'cProfile' + except ImportError: + pass + if not profiler: + try: + import hotshot + profiler = 'hotshot' + except ImportError: + profiler = 'skip' + + if profiler == 'skip': + raise SkipTest('Profiling not supported on this platform') + elif profiler == 'cProfile': + return _profile_cProfile(filename, fn, *args, **kw) + else: + return _profile_hotshot(filename, fn, *args, **kw) + +def _profile_cProfile(filename, fn, *args, **kw): + import cProfile, gc, pstats, time + + load_stats = lambda: pstats.Stats(filename) + gc_collect() + + began = time.time() + cProfile.runctx('result = fn(*args, **kw)', globals(), locals(), + filename=filename) + ended = time.time() + + return ended - began, load_stats, locals()['result'] + +def _profile_hotshot(filename, fn, *args, **kw): + import gc, hotshot, hotshot.stats, time + load_stats = lambda: hotshot.stats.load(filename) + + gc_collect() + prof = hotshot.Profile(filename) + began = time.time() + prof.start() + try: + result = fn(*args, **kw) + finally: + prof.stop() + ended = time.time() + prof.close() + + return ended - began, load_stats, result + diff --git a/test/lib/requires.py b/test/lib/requires.py new file mode 100644 index 000000000..08fde66c3 --- /dev/null +++ b/test/lib/requires.py @@ -0,0 +1,327 @@ +"""Global database feature support policy. + +Provides decorators to mark tests requiring specific feature support from the +target database. + +""" + +from testing import \ + _block_unconditionally as no_support, \ + _chain_decorators_on, \ + exclude, \ + emits_warning_on,\ + skip_if,\ + fails_on,\ + fails_on_everything_except + +import testing +import sys + +def deferrable_constraints(fn): + """Target database must support derferable constraints.""" + return _chain_decorators_on( + fn, + no_support('firebird', 'not supported by database'), + no_support('mysql', 'not supported by database'), + no_support('mssql', 'not supported by database'), + ) + +def foreign_keys(fn): + """Target database must support foreign keys.""" + return _chain_decorators_on( + fn, + no_support('sqlite', 'not supported by database'), + ) + + +def unbounded_varchar(fn): + """Target database must support VARCHAR with no length""" + return _chain_decorators_on( + fn, + no_support('firebird', 'not supported by database'), + no_support('oracle', 'not supported by database'), + no_support('mysql', 'not supported by database'), + ) + +def boolean_col_expressions(fn): + """Target database must support boolean expressions as columns""" + return _chain_decorators_on( + fn, + no_support('firebird', 'not supported by database'), + no_support('oracle', 'not supported by database'), + no_support('mssql', 'not supported by database'), + no_support('sybase', 'not supported by database'), + no_support('maxdb', 'FIXME: verify not supported by database'), + no_support('informix', 'not supported by database'), + ) + +def identity(fn): + """Target database must support GENERATED AS IDENTITY or a facsimile. + + Includes GENERATED AS IDENTITY, AUTOINCREMENT, AUTO_INCREMENT, or other + column DDL feature that fills in a DB-generated identifier at INSERT-time + without requiring pre-execution of a SEQUENCE or other artifact. + + """ + return _chain_decorators_on( + fn, + no_support('firebird', 'not supported by database'), + no_support('oracle', 'not supported by database'), + no_support('postgresql', 'not supported by database'), + no_support('sybase', 'not supported by database'), + ) + +def independent_cursors(fn): + """Target must support simultaneous, independent database cursors on a single connection.""" + + return _chain_decorators_on( + fn, + no_support('mssql+pyodbc', 'no driver support'), + no_support('mssql+mxodbc', 'no driver support'), + ) + +def independent_connections(fn): + """Target must support simultaneous, independent database connections.""" + + # This is also true of some configurations of UnixODBC and probably win32 + # ODBC as well. + return _chain_decorators_on( + fn, + no_support('sqlite', 'no driver support'), + exclude('mssql', '<', (9, 0, 0), + 'SQL Server 2005+ is required for independent connections'), + ) + +def row_triggers(fn): + """Target must support standard statement-running EACH ROW triggers.""" + return _chain_decorators_on( + fn, + # no access to same table + no_support('mysql', 'requires SUPER priv'), + exclude('mysql', '<', (5, 0, 10), 'not supported by database'), + + # huh? TODO: implement triggers for PG tests, remove this + no_support('postgresql', 'PG triggers need to be implemented for tests'), + ) + +def correlated_outer_joins(fn): + """Target must support an outer join to a subquery which correlates to the parent.""" + + return _chain_decorators_on( + fn, + no_support('oracle', 'Raises "ORA-01799: a column may not be outer-joined to a subquery"') + ) + +def savepoints(fn): + """Target database must support savepoints.""" + return _chain_decorators_on( + fn, + emits_warning_on('mssql', 'Savepoint support in mssql is experimental and may lead to data loss.'), + no_support('access', 'not supported by database'), + no_support('sqlite', 'not supported by database'), + no_support('sybase', 'FIXME: guessing, needs confirmation'), + exclude('mysql', '<', (5, 0, 3), 'not supported by database'), + exclude('informix', '<', (11, 55, 'xC3'), 'not supported by database'), + ) + +def denormalized_names(fn): + """Target database must have 'denormalized', i.e. UPPERCASE as case insensitive names.""" + + return skip_if( + lambda: not testing.db.dialect.requires_name_normalize, + "Backend does not require denomralized names." + )(fn) + +def schemas(fn): + """Target database must support external schemas, and have one named 'test_schema'.""" + + return _chain_decorators_on( + fn, + no_support('sqlite', 'no schema support'), + no_support('firebird', 'no schema support') + ) + +def sequences(fn): + """Target database must support SEQUENCEs.""" + return _chain_decorators_on( + fn, + no_support('access', 'no SEQUENCE support'), + no_support('mssql', 'no SEQUENCE support'), + no_support('mysql', 'no SEQUENCE support'), + no_support('sqlite', 'no SEQUENCE support'), + no_support('sybase', 'no SEQUENCE support'), + no_support('informix', 'no SEQUENCE support'), + ) + +def update_nowait(fn): + """Target database must support SELECT...FOR UPDATE NOWAIT""" + return _chain_decorators_on( + fn, + no_support('access', 'no FOR UPDATE NOWAIT support'), + no_support('firebird', 'no FOR UPDATE NOWAIT support'), + no_support('mssql', 'no FOR UPDATE NOWAIT support'), + no_support('mysql', 'no FOR UPDATE NOWAIT support'), + no_support('sqlite', 'no FOR UPDATE NOWAIT support'), + no_support('sybase', 'no FOR UPDATE NOWAIT support'), + ) + +def subqueries(fn): + """Target database must support subqueries.""" + return _chain_decorators_on( + fn, + exclude('mysql', '<', (4, 1, 1), 'no subquery support'), + ) + +def intersect(fn): + """Target database must support INTERSECT or equivlaent.""" + return _chain_decorators_on( + fn, + fails_on('firebird', 'no support for INTERSECT'), + fails_on('mysql', 'no support for INTERSECT'), + fails_on('sybase', 'no support for INTERSECT'), + fails_on('informix', 'no support for INTERSECT'), + ) + +def except_(fn): + """Target database must support EXCEPT or equivlaent (i.e. MINUS).""" + return _chain_decorators_on( + fn, + fails_on('firebird', 'no support for EXCEPT'), + fails_on('mysql', 'no support for EXCEPT'), + fails_on('sybase', 'no support for EXCEPT'), + fails_on('informix', 'no support for EXCEPT'), + ) + +def offset(fn): + """Target database must support some method of adding OFFSET or equivalent to a result set.""" + return _chain_decorators_on( + fn, + fails_on('sybase', 'no support for OFFSET or equivalent'), + ) + +def returning(fn): + return _chain_decorators_on( + fn, + no_support('access', 'not supported by database'), + no_support('sqlite', 'not supported by database'), + no_support('mysql', 'not supported by database'), + no_support('maxdb', 'not supported by database'), + no_support('sybase', 'not supported by database'), + no_support('informix', 'not supported by database'), + ) + +def two_phase_transactions(fn): + """Target database must support two-phase transactions.""" + return _chain_decorators_on( + fn, + no_support('access', 'not supported by database'), + no_support('firebird', 'no SA implementation'), + no_support('maxdb', 'not supported by database'), + no_support('mssql', 'FIXME: guessing, needs confirmation'), + no_support('oracle', 'no SA implementation'), + no_support('sqlite', 'not supported by database'), + no_support('sybase', 'FIXME: guessing, needs confirmation'), + no_support('postgresql+zxjdbc', 'FIXME: JDBC driver confuses the transaction state, may ' + 'need separate XA implementation'), + exclude('mysql', '<', (5, 0, 3), 'not supported by database'), + ) + +def unicode_connections(fn): + """Target driver must support some encoding of Unicode across the wire.""" + # TODO: expand to exclude MySQLdb versions w/ broken unicode + return _chain_decorators_on( + fn, + exclude('mysql', '<', (4, 1, 1), 'no unicode connection support'), + ) + +def unicode_ddl(fn): + """Target driver must support some encoding of Unicode across the wire.""" + # TODO: expand to exclude MySQLdb versions w/ broken unicode + return _chain_decorators_on( + fn, + no_support('maxdb', 'database support flakey'), + no_support('oracle', 'FIXME: no support in database?'), + no_support('sybase', 'FIXME: guessing, needs confirmation'), + no_support('mssql+pymssql', 'no FreeTDS support'), + exclude('mysql', '<', (4, 1, 1), 'no unicode connection support'), + ) + +def sane_rowcount(fn): + return _chain_decorators_on( + fn, + skip_if(lambda: not testing.db.dialect.supports_sane_rowcount) + ) + +def cextensions(fn): + return _chain_decorators_on( + fn, + skip_if(lambda: not _has_cextensions(), "C extensions not installed") + ) + +def dbapi_lastrowid(fn): + return _chain_decorators_on( + fn, + fails_on_everything_except('mysql+mysqldb', 'mysql+oursql', 'sqlite+pysqlite') + ) + +def sane_multi_rowcount(fn): + return _chain_decorators_on( + fn, + skip_if(lambda: not testing.db.dialect.supports_sane_multi_rowcount) + ) + +def reflects_pk_names(fn): + """Target driver reflects the name of primary key constraints.""" + return _chain_decorators_on( + fn, + fails_on_everything_except('postgresql', 'oracle') + ) + +def python2(fn): + return _chain_decorators_on( + fn, + skip_if( + lambda: sys.version_info >= (3,), + "Python version 2.xx is required." + ) + ) + +def python26(fn): + return _chain_decorators_on( + fn, + skip_if( + lambda: sys.version_info < (2, 6), + "Python version 2.6 or greater is required" + ) + ) + +def python25(fn): + return _chain_decorators_on( + fn, + skip_if( + lambda: sys.version_info < (2, 5), + "Python version 2.5 or greater is required" + ) + ) + +def _has_cextensions(): + try: + from sqlalchemy import cresultproxy, cprocessors + return True + except ImportError: + return False + +def _has_sqlite(): + from sqlalchemy import create_engine + try: + e = create_engine('sqlite://') + return True + except ImportError: + return False + +def sqlite(fn): + return _chain_decorators_on( + fn, + skip_if(lambda: not _has_sqlite()) + ) + diff --git a/test/lib/schema.py b/test/lib/schema.py new file mode 100644 index 000000000..614e5863e --- /dev/null +++ b/test/lib/schema.py @@ -0,0 +1,79 @@ +"""Enhanced versions of schema.Table and schema.Column which establish +desired state for different backends. +""" + +from test.lib import testing +from sqlalchemy import schema + +__all__ = 'Table', 'Column', + +table_options = {} + +def Table(*args, **kw): + """A schema.Table wrapper/hook for dialect-specific tweaks.""" + + test_opts = dict([(k,kw.pop(k)) for k in kw.keys() + if k.startswith('test_')]) + + kw.update(table_options) + + if testing.against('mysql'): + if 'mysql_engine' not in kw and 'mysql_type' not in kw: + if 'test_needs_fk' in test_opts or 'test_needs_acid' in test_opts: + kw['mysql_engine'] = 'InnoDB' + + # Apply some default cascading rules for self-referential foreign keys. + # MySQL InnoDB has some issues around seleting self-refs too. + if testing.against('firebird'): + table_name = args[0] + unpack = (testing.config.db.dialect. + identifier_preparer.unformat_identifiers) + + # Only going after ForeignKeys in Columns. May need to + # expand to ForeignKeyConstraint too. + fks = [fk + for col in args if isinstance(col, schema.Column) + for fk in col.foreign_keys] + + for fk in fks: + # root around in raw spec + ref = fk._colspec + if isinstance(ref, schema.Column): + name = ref.table.name + else: + # take just the table name: on FB there cannot be + # a schema, so the first element is always the + # table name, possibly followed by the field name + name = unpack(ref)[0] + if name == table_name: + if fk.ondelete is None: + fk.ondelete = 'CASCADE' + if fk.onupdate is None: + fk.onupdate = 'CASCADE' + + return schema.Table(*args, **kw) + + +def Column(*args, **kw): + """A schema.Column wrapper/hook for dialect-specific tweaks.""" + + test_opts = dict([(k,kw.pop(k)) for k in kw.keys() + if k.startswith('test_')]) + + col = schema.Column(*args, **kw) + if 'test_needs_autoincrement' in test_opts and \ + kw.get('primary_key', False) and \ + testing.against('firebird', 'oracle'): + def add_seq(tbl, c): + c._init_items( + schema.Sequence(_truncate_name(testing.db.dialect, tbl.name + '_' + c.name + '_seq'), optional=True) + ) + col._on_table_attach(add_seq) + return col + +def _truncate_name(dialect, name): + if len(name) > dialect.max_identifier_length: + return name[0:max(dialect.max_identifier_length - 6, 0)] + "_" + hex(hash(name) % 64)[2:] + else: + return name + diff --git a/test/lib/testing.py b/test/lib/testing.py new file mode 100644 index 000000000..da2a3dfd3 --- /dev/null +++ b/test/lib/testing.py @@ -0,0 +1,797 @@ +"""TestCase and TestSuite artifacts and testing decorators.""" + +import itertools +import operator +import re +import sys +import types +import warnings +from cStringIO import StringIO + +from test.bootstrap import config +from test.lib import assertsql, util as testutil +from sqlalchemy.util import function_named, py3k +from engines import drop_all_tables + +from sqlalchemy import exc as sa_exc, util, types as sqltypes, schema, pool, orm +from sqlalchemy.engine import default +from nose import SkipTest + + +_ops = { '<': operator.lt, + '>': operator.gt, + '==': operator.eq, + '!=': operator.ne, + '<=': operator.le, + '>=': operator.ge, + 'in': operator.contains, + 'between': lambda val, pair: val >= pair[0] and val <= pair[1], + } + +# sugar ('testing.db'); set here by config() at runtime +db = None + +# more sugar, installed by __init__ +requires = None + +def fails_if(callable_, reason=None): + """Mark a test as expected to fail if callable_ returns True. + + If the callable returns false, the test is run and reported as normal. + However if the callable returns true, the test is expected to fail and the + unit test logic is inverted: if the test fails, a success is reported. If + the test succeeds, a failure is reported. + """ + + docstring = getattr(callable_, '__doc__', None) or callable_.__name__ + description = docstring.split('\n')[0] + + def decorate(fn): + fn_name = fn.__name__ + def maybe(*args, **kw): + if not callable_(): + return fn(*args, **kw) + else: + try: + fn(*args, **kw) + except Exception, ex: + print ("'%s' failed as expected (condition: %s): %s " % ( + fn_name, description, str(ex))) + return True + else: + raise AssertionError( + "Unexpected success for '%s' (condition: %s)" % + (fn_name, description)) + return function_named(maybe, fn_name) + return decorate + + +def future(fn): + """Mark a test as expected to unconditionally fail. + + Takes no arguments, omit parens when using as a decorator. + """ + + fn_name = fn.__name__ + def decorated(*args, **kw): + try: + fn(*args, **kw) + except Exception, ex: + print ("Future test '%s' failed as expected: %s " % ( + fn_name, str(ex))) + return True + else: + raise AssertionError( + "Unexpected success for future test '%s'" % fn_name) + return function_named(decorated, fn_name) + +def db_spec(*dbs): + dialects = set([x for x in dbs if '+' not in x]) + drivers = set([x[1:] for x in dbs if x.startswith('+')]) + specs = set([tuple(x.split('+')) for x in dbs if '+' in x and x not in drivers]) + + def check(engine): + return engine.name in dialects or \ + engine.driver in drivers or \ + (engine.name, engine.driver) in specs + + return check + + +def fails_on(dbs, reason): + """Mark a test as expected to fail on the specified database + implementation. + + Unlike ``crashes``, tests marked as ``fails_on`` will be run + for the named databases. The test is expected to fail and the unit test + logic is inverted: if the test fails, a success is reported. If the test + succeeds, a failure is reported. + """ + + spec = db_spec(dbs) + + def decorate(fn): + fn_name = fn.__name__ + def maybe(*args, **kw): + if not spec(config.db): + return fn(*args, **kw) + else: + try: + fn(*args, **kw) + except Exception, ex: + print ("'%s' failed as expected on DB implementation " + "'%s+%s': %s" % ( + fn_name, config.db.name, config.db.driver, reason)) + return True + else: + raise AssertionError( + "Unexpected success for '%s' on DB implementation '%s+%s'" % + (fn_name, config.db.name, config.db.driver)) + return function_named(maybe, fn_name) + return decorate + +def fails_on_everything_except(*dbs): + """Mark a test as expected to fail on most database implementations. + + Like ``fails_on``, except failure is the expected outcome on all + databases except those listed. + """ + + spec = db_spec(*dbs) + + def decorate(fn): + fn_name = fn.__name__ + def maybe(*args, **kw): + if spec(config.db): + return fn(*args, **kw) + else: + try: + fn(*args, **kw) + except Exception, ex: + print ("'%s' failed as expected on DB implementation " + "'%s+%s': %s" % ( + fn_name, config.db.name, config.db.driver, str(ex))) + return True + else: + raise AssertionError( + "Unexpected success for '%s' on DB implementation '%s+%s'" % + (fn_name, config.db.name, config.db.driver)) + return function_named(maybe, fn_name) + return decorate + +def crashes(db, reason): + """Mark a test as unsupported by a database implementation. + + ``crashes`` tests will be skipped unconditionally. Use for feature tests + that cause deadlocks or other fatal problems. + + """ + carp = _should_carp_about_exclusion(reason) + spec = db_spec(db) + def decorate(fn): + fn_name = fn.__name__ + def maybe(*args, **kw): + if spec(config.db): + msg = "'%s' unsupported on DB implementation '%s+%s': %s" % ( + fn_name, config.db.name, config.db.driver, reason) + print msg + if carp: + print >> sys.stderr, msg + return True + else: + return fn(*args, **kw) + return function_named(maybe, fn_name) + return decorate + +def _block_unconditionally(db, reason): + """Mark a test as unsupported by a database implementation. + + Will never run the test against any version of the given database, ever, + no matter what. Use when your assumptions are infallible; past, present + and future. + + """ + carp = _should_carp_about_exclusion(reason) + spec = db_spec(db) + def decorate(fn): + fn_name = fn.__name__ + def maybe(*args, **kw): + if spec(config.db): + msg = "'%s' unsupported on DB implementation '%s+%s': %s" % ( + fn_name, config.db.name, config.db.driver, reason) + print msg + if carp: + print >> sys.stderr, msg + return True + else: + return fn(*args, **kw) + return function_named(maybe, fn_name) + return decorate + +def only_on(dbs, reason): + carp = _should_carp_about_exclusion(reason) + spec = db_spec(*util.to_list(dbs)) + def decorate(fn): + fn_name = fn.__name__ + def maybe(*args, **kw): + if spec(config.db): + return fn(*args, **kw) + else: + msg = "'%s' unsupported on DB implementation '%s+%s': %s" % ( + fn_name, config.db.name, config.db.driver, reason) + print msg + if carp: + print >> sys.stderr, msg + return True + return function_named(maybe, fn_name) + return decorate + +def exclude(db, op, spec, reason): + """Mark a test as unsupported by specific database server versions. + + Stackable, both with other excludes and other decorators. Examples:: + + # Not supported by mydb versions less than 1, 0 + @exclude('mydb', '<', (1,0)) + # Other operators work too + @exclude('bigdb', '==', (9,0,9)) + @exclude('yikesdb', 'in', ((0, 3, 'alpha2'), (0, 3, 'alpha3'))) + + """ + carp = _should_carp_about_exclusion(reason) + + def decorate(fn): + fn_name = fn.__name__ + def maybe(*args, **kw): + if _is_excluded(db, op, spec): + msg = "'%s' unsupported on DB %s version '%s': %s" % ( + fn_name, config.db.name, _server_version(), reason) + print msg + if carp: + print >> sys.stderr, msg + return True + else: + return fn(*args, **kw) + return function_named(maybe, fn_name) + return decorate + +def _should_carp_about_exclusion(reason): + """Guard against forgotten exclusions.""" + assert reason + for _ in ('todo', 'fixme', 'xxx'): + if _ in reason.lower(): + return True + else: + if len(reason) < 4: + return True + +def _is_excluded(db, op, spec): + """Return True if the configured db matches an exclusion specification. + + db: + A dialect name + op: + An operator or stringified operator, such as '==' + spec: + A value that will be compared to the dialect's server_version_info + using the supplied operator. + + Examples:: + # Not supported by mydb versions less than 1, 0 + _is_excluded('mydb', '<', (1,0)) + # Other operators work too + _is_excluded('bigdb', '==', (9,0,9)) + _is_excluded('yikesdb', 'in', ((0, 3, 'alpha2'), (0, 3, 'alpha3'))) + """ + + vendor_spec = db_spec(db) + + if not vendor_spec(config.db): + return False + + version = _server_version() + + oper = hasattr(op, '__call__') and op or _ops[op] + return oper(version, spec) + +def _server_version(bind=None): + """Return a server_version_info tuple.""" + + if bind is None: + bind = config.db + + # force metadata to be retrieved + conn = bind.connect() + version = getattr(bind.dialect, 'server_version_info', ()) + conn.close() + return version + +def skip_if(predicate, reason=None): + """Skip a test if predicate is true.""" + reason = reason or predicate.__name__ + carp = _should_carp_about_exclusion(reason) + + def decorate(fn): + fn_name = fn.__name__ + def maybe(*args, **kw): + if predicate(): + msg = "'%s' skipped on DB %s version '%s': %s" % ( + fn_name, config.db.name, _server_version(), reason) + print msg + if carp: + print >> sys.stderr, msg + return True + else: + return fn(*args, **kw) + return function_named(maybe, fn_name) + return decorate + +def emits_warning(*messages): + """Mark a test as emitting a warning. + + With no arguments, squelches all SAWarning failures. Or pass one or more + strings; these will be matched to the root of the warning description by + warnings.filterwarnings(). + """ + + # TODO: it would be nice to assert that a named warning was + # emitted. should work with some monkeypatching of warnings, + # and may work on non-CPython if they keep to the spirit of + # warnings.showwarning's docstring. + # - update: jython looks ok, it uses cpython's module + def decorate(fn): + def safe(*args, **kw): + # todo: should probably be strict about this, too + filters = [dict(action='ignore', + category=sa_exc.SAPendingDeprecationWarning)] + if not messages: + filters.append(dict(action='ignore', + category=sa_exc.SAWarning)) + else: + filters.extend(dict(action='ignore', + message=message, + category=sa_exc.SAWarning) + for message in messages) + for f in filters: + warnings.filterwarnings(**f) + try: + return fn(*args, **kw) + finally: + resetwarnings() + return function_named(safe, fn.__name__) + return decorate + +def emits_warning_on(db, *warnings): + """Mark a test as emitting a warning on a specific dialect. + + With no arguments, squelches all SAWarning failures. Or pass one or more + strings; these will be matched to the root of the warning description by + warnings.filterwarnings(). + """ + spec = db_spec(db) + + def decorate(fn): + def maybe(*args, **kw): + if isinstance(db, basestring): + if not spec(config.db): + return fn(*args, **kw) + else: + wrapped = emits_warning(*warnings)(fn) + return wrapped(*args, **kw) + else: + if not _is_excluded(*db): + return fn(*args, **kw) + else: + wrapped = emits_warning(*warnings)(fn) + return wrapped(*args, **kw) + return function_named(maybe, fn.__name__) + return decorate + +def uses_deprecated(*messages): + """Mark a test as immune from fatal deprecation warnings. + + With no arguments, squelches all SADeprecationWarning failures. + Or pass one or more strings; these will be matched to the root + of the warning description by warnings.filterwarnings(). + + As a special case, you may pass a function name prefixed with // + and it will be re-written as needed to match the standard warning + verbiage emitted by the sqlalchemy.util.deprecated decorator. + """ + + + def decorate(fn): + def safe(*args, **kw): + # todo: should probably be strict about this, too + filters = [dict(action='ignore', + category=sa_exc.SAPendingDeprecationWarning)] + if not messages: + filters.append(dict(action='ignore', + category=sa_exc.SADeprecationWarning)) + else: + filters.extend( + [dict(action='ignore', + message=message, + category=sa_exc.SADeprecationWarning) + for message in + [ (m.startswith('//') and + ('Call to deprecated function ' + m[2:]) or m) + for m in messages] ]) + + for f in filters: + warnings.filterwarnings(**f) + try: + return fn(*args, **kw) + finally: + resetwarnings() + return function_named(safe, fn.__name__) + return decorate + +def resetwarnings(): + """Reset warning behavior to testing defaults.""" + + warnings.filterwarnings('ignore', + category=sa_exc.SAPendingDeprecationWarning) + warnings.filterwarnings('error', category=sa_exc.SADeprecationWarning) + warnings.filterwarnings('error', category=sa_exc.SAWarning) + +# warnings.simplefilter('error') + + +def global_cleanup_assertions(): + """Check things that have to be finalized at the end of a test suite. + + Hardcoded at the moment, a modular system can be built here + to support things like PG prepared transactions, tables all + dropped, etc. + + """ + + testutil.lazy_gc() + assert not pool._refs + + + +def against(*queries): + """Boolean predicate, compares to testing database configuration. + + Given one or more dialect names, returns True if one is the configured + database engine. + + Also supports comparison to database version when provided with one or + more 3-tuples of dialect name, operator, and version specification:: + + testing.against('mysql', 'postgresql') + testing.against(('mysql', '>=', (5, 0, 0)) + """ + + for query in queries: + if isinstance(query, basestring): + if db_spec(query)(config.db): + return True + else: + name, op, spec = query + if not db_spec(name)(config.db): + continue + + have = _server_version() + + oper = hasattr(op, '__call__') and op or _ops[op] + if oper(have, spec): + return True + return False + +def _chain_decorators_on(fn, *decorators): + """Apply a series of decorators to fn, returning a decorated function.""" + for decorator in reversed(decorators): + fn = decorator(fn) + return fn + +def rowset(results): + """Converts the results of sql execution into a plain set of column tuples. + + Useful for asserting the results of an unordered query. + """ + + return set([tuple(row) for row in results]) + + +def eq_(a, b, msg=None): + """Assert a == b, with repr messaging on failure.""" + assert a == b, msg or "%r != %r" % (a, b) + +def ne_(a, b, msg=None): + """Assert a != b, with repr messaging on failure.""" + assert a != b, msg or "%r == %r" % (a, b) + +def is_(a, b, msg=None): + """Assert a is b, with repr messaging on failure.""" + assert a is b, msg or "%r is not %r" % (a, b) + +def is_not_(a, b, msg=None): + """Assert a is not b, with repr messaging on failure.""" + assert a is not b, msg or "%r is %r" % (a, b) + +def startswith_(a, fragment, msg=None): + """Assert a.startswith(fragment), with repr messaging on failure.""" + assert a.startswith(fragment), msg or "%r does not start with %r" % ( + a, fragment) + +def assert_raises(except_cls, callable_, *args, **kw): + try: + callable_(*args, **kw) + success = False + except except_cls, e: + success = True + + # assert outside the block so it works for AssertionError too ! + assert success, "Callable did not raise an exception" + +def assert_raises_message(except_cls, msg, callable_, *args, **kwargs): + try: + callable_(*args, **kwargs) + assert False, "Callable did not raise an exception" + except except_cls, e: + assert re.search(msg, str(e)), "%r !~ %s" % (msg, e) + print str(e) + +def fail(msg): + assert False, msg + +def fixture(table, columns, *rows): + """Insert data into table after creation.""" + def onload(event, schema_item, connection): + insert = table.insert() + column_names = [col.key for col in columns] + connection.execute(insert, [dict(zip(column_names, column_values)) + for column_values in rows]) + table.append_ddl_listener('after-create', onload) + +def provide_metadata(fn): + """Provides a bound MetaData object for a single test, + drops it afterwards.""" + def maybe(*args, **kw): + metadata = schema.MetaData(db) + context = dict(fn.func_globals) + context['metadata'] = metadata + # jython bug #1034 + rebound = types.FunctionType( + fn.func_code, context, fn.func_name, fn.func_defaults, + fn.func_closure) + try: + return rebound(*args, **kw) + finally: + metadata.drop_all() + return function_named(maybe, fn.__name__) + +def resolve_artifact_names(fn): + """Decorator, augment function globals with tables and classes. + + Swaps out the function's globals at execution time. The 'global' statement + will not work as expected inside a decorated function. + + """ + # This could be automatically applied to framework and test_ methods in + # the MappedTest-derived test suites but... *some* explicitness for this + # magic is probably good. Especially as 'global' won't work- these + # rebound functions aren't regular Python.. + # + # Also: it's lame that CPython accepts a dict-subclass for globals, but + # only calls dict methods. That would allow 'global' to pass through to + # the func_globals. + def resolved(*args, **kwargs): + self = args[0] + context = dict(fn.func_globals) + for source in self._artifact_registries: + context.update(getattr(self, source)) + # jython bug #1034 + rebound = types.FunctionType( + fn.func_code, context, fn.func_name, fn.func_defaults, + fn.func_closure) + return rebound(*args, **kwargs) + return function_named(resolved, fn.func_name) + +class adict(dict): + """Dict keys available as attributes. Shadows.""" + def __getattribute__(self, key): + try: + return self[key] + except KeyError: + return dict.__getattribute__(self, key) + + def get_all(self, *keys): + return tuple([self[key] for key in keys]) + + +class TestBase(object): + # A sequence of database names to always run, regardless of the + # constraints below. + __whitelist__ = () + + # A sequence of requirement names matching testing.requires decorators + __requires__ = () + + # A sequence of dialect names to exclude from the test class. + __unsupported_on__ = () + + # If present, test class is only runnable for the *single* specified + # dialect. If you need multiple, use __unsupported_on__ and invert. + __only_on__ = None + + # A sequence of no-arg callables. If any are True, the entire testcase is + # skipped. + __skip_if__ = None + + _artifact_registries = () + + def assert_(self, val, msg=None): + assert val, msg + +class AssertsCompiledSQL(object): + def assert_compile(self, clause, result, params=None, checkparams=None, dialect=None, use_default_dialect=False): + if use_default_dialect: + dialect = default.DefaultDialect() + + if dialect is None: + dialect = getattr(self, '__dialect__', None) + + kw = {} + if params is not None: + kw['column_keys'] = params.keys() + + if isinstance(clause, orm.Query): + context = clause._compile_context() + context.statement.use_labels = True + clause = context.statement + + c = clause.compile(dialect=dialect, **kw) + + param_str = repr(getattr(c, 'params', {})) + # Py3K + #param_str = param_str.encode('utf-8').decode('ascii', 'ignore') + + print "\nSQL String:\n" + str(c) + param_str + + cc = re.sub(r'[\n\t]', '', str(c)) + + eq_(cc, result, "%r != %r on dialect %r" % (cc, result, dialect)) + + if checkparams is not None: + eq_(c.construct_params(params), checkparams) + +class ComparesTables(object): + def assert_tables_equal(self, table, reflected_table, strict_types=False): + assert len(table.c) == len(reflected_table.c) + for c, reflected_c in zip(table.c, reflected_table.c): + eq_(c.name, reflected_c.name) + assert reflected_c is reflected_table.c[c.name] + eq_(c.primary_key, reflected_c.primary_key) + eq_(c.nullable, reflected_c.nullable) + + if strict_types: + assert type(reflected_c.type) is type(c.type), \ + "Type '%s' doesn't correspond to type '%s'" % (reflected_c.type, c.type) + else: + self.assert_types_base(reflected_c, c) + + if isinstance(c.type, sqltypes.String): + eq_(c.type.length, reflected_c.type.length) + + eq_(set([f.column.name for f in c.foreign_keys]), set([f.column.name for f in reflected_c.foreign_keys])) + if c.server_default: + assert isinstance(reflected_c.server_default, + schema.FetchedValue) + + assert len(table.primary_key) == len(reflected_table.primary_key) + for c in table.primary_key: + assert reflected_table.primary_key.columns[c.name] is not None + + def assert_types_base(self, c1, c2): + assert c1.type._compare_type_affinity(c2.type),\ + "On column %r, type '%s' doesn't correspond to type '%s'" % \ + (c1.name, c1.type, c2.type) + +class AssertsExecutionResults(object): + def assert_result(self, result, class_, *objects): + result = list(result) + print repr(result) + self.assert_list(result, class_, objects) + + def assert_list(self, result, class_, list): + self.assert_(len(result) == len(list), + "result list is not the same size as test list, " + + "for class " + class_.__name__) + for i in range(0, len(list)): + self.assert_row(class_, result[i], list[i]) + + def assert_row(self, class_, rowobj, desc): + self.assert_(rowobj.__class__ is class_, + "item class is not " + repr(class_)) + for key, value in desc.iteritems(): + if isinstance(value, tuple): + if isinstance(value[1], list): + self.assert_list(getattr(rowobj, key), value[0], value[1]) + else: + self.assert_row(value[0], getattr(rowobj, key), value[1]) + else: + self.assert_(getattr(rowobj, key) == value, + "attribute %s value %s does not match %s" % ( + key, getattr(rowobj, key), value)) + + def assert_unordered_result(self, result, cls, *expected): + """As assert_result, but the order of objects is not considered. + + The algorithm is very expensive but not a big deal for the small + numbers of rows that the test suite manipulates. + """ + + class frozendict(dict): + def __hash__(self): + return id(self) + + found = util.IdentitySet(result) + expected = set([frozendict(e) for e in expected]) + + for wrong in itertools.ifilterfalse(lambda o: type(o) == cls, found): + fail('Unexpected type "%s", expected "%s"' % ( + type(wrong).__name__, cls.__name__)) + + if len(found) != len(expected): + fail('Unexpected object count "%s", expected "%s"' % ( + len(found), len(expected))) + + NOVALUE = object() + def _compare_item(obj, spec): + for key, value in spec.iteritems(): + if isinstance(value, tuple): + try: + self.assert_unordered_result( + getattr(obj, key), value[0], *value[1]) + except AssertionError: + return False + else: + if getattr(obj, key, NOVALUE) != value: + return False + return True + + for expected_item in expected: + for found_item in found: + if _compare_item(found_item, expected_item): + found.remove(found_item) + break + else: + fail( + "Expected %s instance with attributes %s not found." % ( + cls.__name__, repr(expected_item))) + return True + + def assert_sql_execution(self, db, callable_, *rules): + assertsql.asserter.add_rules(rules) + try: + callable_() + assertsql.asserter.statement_complete() + finally: + assertsql.asserter.clear_rules() + + def assert_sql(self, db, callable_, list_, with_sequences=None): + if with_sequences is not None and config.db.name in ('firebird', 'oracle', 'postgresql'): + rules = with_sequences + else: + rules = list_ + + newrules = [] + for rule in rules: + if isinstance(rule, dict): + newrule = assertsql.AllOf(*[ + assertsql.ExactSQL(k, v) for k, v in rule.iteritems() + ]) + else: + newrule = assertsql.ExactSQL(*rule) + newrules.append(newrule) + + self.assert_sql_execution(db, callable_, *newrules) + + def assert_sql_count(self, db, callable_, count): + self.assert_sql_execution(db, callable_, assertsql.CountStatements(count)) + + diff --git a/test/lib/util.py b/test/lib/util.py new file mode 100644 index 000000000..e5277f076 --- /dev/null +++ b/test/lib/util.py @@ -0,0 +1,107 @@ +from sqlalchemy.util import jython, function_named, defaultdict + +import gc +import time +import random + +if jython: + def gc_collect(*args): + """aggressive gc.collect for tests.""" + gc.collect() + time.sleep(0.1) + gc.collect() + gc.collect() + return 0 + + # "lazy" gc, for VM's that don't GC on refcount == 0 + lazy_gc = gc_collect + +else: + # assume CPython - straight gc.collect, lazy_gc() is a pass + gc_collect = gc.collect + def lazy_gc(): + pass + +def picklers(): + picklers = set() + # Py2K + try: + import cPickle + picklers.add(cPickle) + except ImportError: + pass + # end Py2K + import pickle + picklers.add(pickle) + + # yes, this thing needs this much testing + for pickle in picklers: + for protocol in -1, 0, 1, 2: + yield pickle.loads, lambda d:pickle.dumps(d, protocol) + + +def round_decimal(value, prec): + if isinstance(value, float): + return round(value, prec) + + import decimal + + # can also use shift() here but that is 2.6 only + return (value * decimal.Decimal("1" + "0" * prec)).to_integral(decimal.ROUND_FLOOR) / \ + pow(10, prec) + +class RandomSet(set): + def __iter__(self): + l = list(set.__iter__(self)) + random.shuffle(l) + return iter(l) + + def pop(self): + index = random.randint(0, len(self) - 1) + item = list(set.__iter__(self))[index] + self.remove(item) + return item + + def union(self, other): + return RandomSet(set.union(self, other)) + + def difference(self, other): + return RandomSet(set.difference(self, other)) + + def intersection(self, other): + return RandomSet(set.intersection(self, other)) + + def copy(self): + return RandomSet(self) + +def conforms_partial_ordering(tuples, sorted_elements): + """True if the given sorting conforms to the given partial ordering.""" + + deps = defaultdict(set) + for parent, child in tuples: + deps[parent].add(child) + for i, node in enumerate(sorted_elements): + for n in sorted_elements[i:]: + if node in deps[n]: + return False + else: + return True + +def all_partial_orderings(tuples, elements): + edges = defaultdict(set) + for parent, child in tuples: + edges[child].add(parent) + + def _all_orderings(elements): + + if len(elements) == 1: + yield list(elements) + else: + for elem in elements: + subset = set(elements).difference([elem]) + if not subset.intersection(edges[elem]): + for sub_ordering in _all_orderings(subset): + yield [elem] + sub_ordering + + return iter(_all_orderings(elements)) + diff --git a/test/orm/_base.py b/test/orm/_base.py index 4d0031f5a..4ccc10157 100644 --- a/test/orm/_base.py +++ b/test/orm/_base.py @@ -3,11 +3,11 @@ import sys import types import sqlalchemy as sa import sqlalchemy.exceptions as sa_exc -from sqlalchemy.test import config, testing -from sqlalchemy.test.testing import resolve_artifact_names, adict -from sqlalchemy.test.engines import drop_all_tables +from test.lib import config, testing +from test.lib.testing import resolve_artifact_names, adict +from test.lib.engines import drop_all_tables from sqlalchemy.util import function_named -from sqlalchemy.test.entities import BasicEntity, ComparableEntity +from test.lib.entities import BasicEntity, ComparableEntity Entity = BasicEntity diff --git a/test/orm/_fixtures.py b/test/orm/_fixtures.py index a8df63b4a..8f128c287 100644 --- a/test/orm/_fixtures.py +++ b/test/orm/_fixtures.py @@ -1,8 +1,8 @@ from sqlalchemy import MetaData, Integer, String, ForeignKey -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column +from test.lib.schema import Table +from test.lib.schema import Column from sqlalchemy.orm import attributes -from sqlalchemy.test.testing import fixture +from test.lib.testing import fixture from test.orm import _base __all__ = () diff --git a/test/orm/inheritance/test_abc_inheritance.py b/test/orm/inheritance/test_abc_inheritance.py index edbd476ec..08ab28a08 100644 --- a/test/orm/inheritance/test_abc_inheritance.py +++ b/test/orm/inheritance/test_abc_inheritance.py @@ -2,8 +2,8 @@ from sqlalchemy import * from sqlalchemy.orm import * from sqlalchemy.orm.interfaces import ONETOMANY, MANYTOONE -from sqlalchemy.test import testing -from sqlalchemy.test.schema import Table, Column +from test.lib import testing +from test.lib.schema import Table, Column from test.orm import _base diff --git a/test/orm/inheritance/test_abc_polymorphic.py b/test/orm/inheritance/test_abc_polymorphic.py index 2dab59bb2..fb229003b 100644 --- a/test/orm/inheritance/test_abc_polymorphic.py +++ b/test/orm/inheritance/test_abc_polymorphic.py @@ -4,7 +4,7 @@ from sqlalchemy.orm import * from sqlalchemy.util import function_named from test.orm import _base, _fixtures -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column class ABCTest(_base.MappedTest): @classmethod diff --git a/test/orm/inheritance/test_basic.py b/test/orm/inheritance/test_basic.py index c57f1d095..5892b3c89 100644 --- a/test/orm/inheritance/test_basic.py +++ b/test/orm/inheritance/test_basic.py @@ -1,14 +1,14 @@ import warnings -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message +from test.lib.testing import eq_, assert_raises, assert_raises_message from sqlalchemy import * from sqlalchemy import exc as sa_exc, util from sqlalchemy.orm import * from sqlalchemy.orm import exc as orm_exc -from sqlalchemy.test import testing, engines +from test.lib import testing, engines from sqlalchemy.util import function_named from test.orm import _base, _fixtures -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column class O2MTest(_base.MappedTest): """deals with inheritance and one-to-many relationships""" diff --git a/test/orm/inheritance/test_concrete.py b/test/orm/inheritance/test_concrete.py index a2d79284c..43ba36ace 100644 --- a/test/orm/inheritance/test_concrete.py +++ b/test/orm/inheritance/test_concrete.py @@ -1,15 +1,15 @@ -from sqlalchemy.test.testing import eq_, assert_raises, \ +from test.lib.testing import eq_, assert_raises, \ assert_raises_message from sqlalchemy import * from sqlalchemy.orm import * from sqlalchemy.orm import exc as orm_exc -from sqlalchemy.test import * +from test.lib import * import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from test.orm import _base from sqlalchemy.orm import attributes -from sqlalchemy.test.testing import eq_ -from sqlalchemy.test.schema import Table, Column +from test.lib.testing import eq_ +from test.lib.schema import Table, Column class Employee(object): diff --git a/test/orm/inheritance/test_magazine.py b/test/orm/inheritance/test_magazine.py index 125a5629c..307c54a9c 100644 --- a/test/orm/inheritance/test_magazine.py +++ b/test/orm/inheritance/test_magazine.py @@ -1,10 +1,10 @@ from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy.util import function_named from test.orm import _base -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column class BaseObject(object): def __init__(self, *args, **kwargs): diff --git a/test/orm/inheritance/test_manytomany.py b/test/orm/inheritance/test_manytomany.py index 8390e2a1b..f5e3e63a1 100644 --- a/test/orm/inheritance/test_manytomany.py +++ b/test/orm/inheritance/test_manytomany.py @@ -1,8 +1,8 @@ -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test import testing +from test.lib import testing from test.orm import _base diff --git a/test/orm/inheritance/test_poly_linked_list.py b/test/orm/inheritance/test_poly_linked_list.py index 01dad72e4..8b300f06a 100644 --- a/test/orm/inheritance/test_poly_linked_list.py +++ b/test/orm/inheritance/test_poly_linked_list.py @@ -2,8 +2,8 @@ from sqlalchemy import * from sqlalchemy.orm import * from test.orm import _base -from sqlalchemy.test import testing -from sqlalchemy.test.schema import Table, Column +from test.lib import testing +from test.lib.schema import Table, Column class PolymorphicCircularTest(_base.MappedTest): diff --git a/test/orm/inheritance/test_polymorph.py b/test/orm/inheritance/test_polymorph.py index 33646c922..1f82834d9 100644 --- a/test/orm/inheritance/test_polymorph.py +++ b/test/orm/inheritance/test_polymorph.py @@ -1,11 +1,11 @@ """tests basic polymorphic mapper loading/saving, minimal relationships""" -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message +from test.lib.testing import eq_, assert_raises, assert_raises_message from sqlalchemy import * from sqlalchemy.orm import * from sqlalchemy.orm import exc as orm_exc from sqlalchemy import exc as sa_exc -from sqlalchemy.test import Column, testing +from test.lib import Column, testing from sqlalchemy.util import function_named from test.orm import _fixtures, _base diff --git a/test/orm/inheritance/test_polymorph2.py b/test/orm/inheritance/test_polymorph2.py index 9852e8b09..030b931a5 100644 --- a/test/orm/inheritance/test_polymorph2.py +++ b/test/orm/inheritance/test_polymorph2.py @@ -2,16 +2,16 @@ inheritance setups for which we maintain compatibility. """ -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from sqlalchemy import * from sqlalchemy import util from sqlalchemy.orm import * -from sqlalchemy.test import TestBase, AssertsExecutionResults, testing +from test.lib import TestBase, AssertsExecutionResults, testing from sqlalchemy.util import function_named from test.orm import _base, _fixtures -from sqlalchemy.test.testing import eq_ -from sqlalchemy.test.schema import Table, Column +from test.lib.testing import eq_ +from test.lib.schema import Table, Column class AttrSettable(object): def __init__(self, **kwargs): diff --git a/test/orm/inheritance/test_productspec.py b/test/orm/inheritance/test_productspec.py index dc81d9245..a1ecf2562 100644 --- a/test/orm/inheritance/test_productspec.py +++ b/test/orm/inheritance/test_productspec.py @@ -2,9 +2,9 @@ from datetime import datetime from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test import testing +from test.lib import testing from test.orm import _base -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column class InheritTest(_base.MappedTest): """tests some various inheritance round trips involving a particular set of polymorphic inheritance relationships""" diff --git a/test/orm/inheritance/test_query.py b/test/orm/inheritance/test_query.py index 45b3e9da1..36a23204d 100644 --- a/test/orm/inheritance/test_query.py +++ b/test/orm/inheritance/test_query.py @@ -1,4 +1,4 @@ -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message +from test.lib.testing import eq_, assert_raises, assert_raises_message from sqlalchemy import * from sqlalchemy.orm import * from sqlalchemy.orm import interfaces @@ -6,10 +6,10 @@ from sqlalchemy import exc as sa_exc from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.engine import default -from sqlalchemy.test import AssertsCompiledSQL, testing +from test.lib import AssertsCompiledSQL, testing from test.orm import _base, _fixtures -from sqlalchemy.test.testing import eq_ -from sqlalchemy.test.schema import Table, Column +from test.lib.testing import eq_ +from test.lib.schema import Table, Column class Company(_fixtures.Base): pass diff --git a/test/orm/inheritance/test_selects.py b/test/orm/inheritance/test_selects.py index 7c9920f6f..5a7389386 100644 --- a/test/orm/inheritance/test_selects.py +++ b/test/orm/inheritance/test_selects.py @@ -1,7 +1,7 @@ from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test import testing +from test.lib import testing from test.orm._fixtures import Base from test.orm._base import MappedTest diff --git a/test/orm/inheritance/test_single.py b/test/orm/inheritance/test_single.py index 2efde2b32..a65851a5f 100644 --- a/test/orm/inheritance/test_single.py +++ b/test/orm/inheritance/test_single.py @@ -1,11 +1,11 @@ -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test import testing +from test.lib import testing from test.orm import _fixtures from test.orm._base import MappedTest, ComparableEntity -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column class SingleInheritanceTest(testing.AssertsCompiledSQL, MappedTest): diff --git a/test/orm/test_association.py b/test/orm/test_association.py index c9b1584bb..e681099c5 100644 --- a/test/orm/test_association.py +++ b/test/orm/test_association.py @@ -1,10 +1,10 @@ -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, relationship, create_session from test.orm import _base -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ class AssociationTest(_base.MappedTest): diff --git a/test/orm/test_assorted_eager.py b/test/orm/test_assorted_eager.py index ff9234d70..b11b3ddd9 100644 --- a/test/orm/test_assorted_eager.py +++ b/test/orm/test_assorted_eager.py @@ -9,11 +9,11 @@ be cleaned up and modernized. import datetime import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, relationship, backref, create_session -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from test.orm import _base diff --git a/test/orm/test_attributes.py b/test/orm/test_attributes.py index b5a6c1f5e..913c6ec52 100644 --- a/test/orm/test_attributes.py +++ b/test/orm/test_attributes.py @@ -3,10 +3,10 @@ from sqlalchemy.orm import attributes, instrumentation from sqlalchemy.orm.collections import collection from sqlalchemy.orm.interfaces import AttributeExtension from sqlalchemy import exc as sa_exc -from sqlalchemy.test import * -from sqlalchemy.test.testing import eq_, ne_, assert_raises +from test.lib import * +from test.lib.testing import eq_, ne_, assert_raises from test.orm import _base -from sqlalchemy.test.util import gc_collect, all_partial_orderings +from test.lib.util import gc_collect, all_partial_orderings from sqlalchemy.util import cmp, jython from sqlalchemy import event, topological diff --git a/test/orm/test_backref_mutations.py b/test/orm/test_backref_mutations.py index dd48df592..31cab962e 100644 --- a/test/orm/test_backref_mutations.py +++ b/test/orm/test_backref_mutations.py @@ -9,14 +9,14 @@ UPDATE in the database. """ -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message from sqlalchemy import Integer, String, ForeignKey, Sequence, exc as sa_exc -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column +from test.lib.schema import Table +from test.lib.schema import Column from sqlalchemy.orm import mapper, relationship, create_session, class_mapper, backref, sessionmaker from sqlalchemy.orm import attributes, exc as orm_exc -from sqlalchemy.test import testing -from sqlalchemy.test.testing import eq_ +from test.lib import testing +from test.lib.testing import eq_ from test.orm import _base, _fixtures class O2MCollectionTest(_fixtures.FixtureTest): diff --git a/test/orm/test_bind.py b/test/orm/test_bind.py index 9b1c20b60..bab9be428 100644 --- a/test/orm/test_bind.py +++ b/test/orm/test_bind.py @@ -1,10 +1,10 @@ -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message from sqlalchemy import MetaData, Integer -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column +from test.lib.schema import Table +from test.lib.schema import Column from sqlalchemy.orm import mapper, create_session import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from test.orm import _base diff --git a/test/orm/test_cascade.py b/test/orm/test_cascade.py index bedff2170..17b186cd8 100644 --- a/test/orm/test_cascade.py +++ b/test/orm/test_cascade.py @@ -1,13 +1,13 @@ -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message from sqlalchemy import Integer, String, ForeignKey, Sequence, \ exc as sa_exc -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, relationship, create_session, \ sessionmaker, class_mapper, backref, Session from sqlalchemy.orm import attributes, exc as orm_exc -from sqlalchemy.test import testing -from sqlalchemy.test.testing import eq_ +from test.lib import testing +from test.lib.testing import eq_ from test.orm import _base, _fixtures diff --git a/test/orm/test_collection.py b/test/orm/test_collection.py index b0ce28663..540213745 100644 --- a/test/orm/test_collection.py +++ b/test/orm/test_collection.py @@ -1,4 +1,4 @@ -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ import sys from operator import and_ @@ -6,14 +6,14 @@ import sqlalchemy.orm.collections as collections from sqlalchemy.orm.collections import collection import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import Integer, String, ForeignKey, text -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy import util, exc as sa_exc from sqlalchemy.orm import create_session, mapper, relationship, \ attributes, instrumentation from test.orm import _base -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message +from test.lib.testing import eq_, assert_raises, assert_raises_message class Canary(sa.orm.interfaces.AttributeExtension): def __init__(self): diff --git a/test/orm/test_compile.py b/test/orm/test_compile.py index 575c3ccfb..3294a853c 100644 --- a/test/orm/test_compile.py +++ b/test/orm/test_compile.py @@ -1,8 +1,8 @@ from sqlalchemy import * from sqlalchemy import exc as sa_exc from sqlalchemy.orm import * -from sqlalchemy.test import * -from sqlalchemy.test.testing import assert_raises_message +from test.lib import * +from test.lib.testing import assert_raises_message from test.orm import _base diff --git a/test/orm/test_cycles.py b/test/orm/test_cycles.py index 26765244a..e5e657933 100644 --- a/test/orm/test_cycles.py +++ b/test/orm/test_cycles.py @@ -5,13 +5,13 @@ T1<->T2, with o2m or m2o between them, and a third T3 with o2m/m2o to one/both T1/T2. """ -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, relationship, backref, \ create_session, sessionmaker -from sqlalchemy.test.testing import eq_ -from sqlalchemy.test.assertsql import RegexSQL, ExactSQL, CompiledSQL, AllOf +from test.lib.testing import eq_ +from test.lib.assertsql import RegexSQL, ExactSQL, CompiledSQL, AllOf from test.orm import _base diff --git a/test/orm/test_defaults.py b/test/orm/test_defaults.py index 389fdbc5c..e5f419ccb 100644 --- a/test/orm/test_defaults.py +++ b/test/orm/test_defaults.py @@ -1,11 +1,11 @@ import sqlalchemy as sa -from sqlalchemy.test import testing from sqlalchemy import Integer, String, ForeignKey, event -from sqlalchemy.test.schema import Table, Column +from test.lib import testing +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, relationship, create_session from test.orm import _base -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ class TriggerDefaultsTest(_base.MappedTest): diff --git a/test/orm/test_deprecations.py b/test/orm/test_deprecations.py index 76c59d029..2565105a6 100644 --- a/test/orm/test_deprecations.py +++ b/test/orm/test_deprecations.py @@ -5,10 +5,10 @@ modern (i.e. not deprecated) alternative to them. The tests snippets here can be migrated directly to the wiki, docs, etc. """ -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import Integer, String, ForeignKey, func -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column +from test.lib.schema import Table +from test.lib.schema import Column from sqlalchemy.orm import mapper, relationship, relation, create_session, sessionmaker from test.orm import _base diff --git a/test/orm/test_dynamic.py b/test/orm/test_dynamic.py index c06f6918a..c89503278 100644 --- a/test/orm/test_dynamic.py +++ b/test/orm/test_dynamic.py @@ -1,12 +1,12 @@ -from sqlalchemy.test.testing import eq_, ne_ +from test.lib.testing import eq_, ne_ import operator from sqlalchemy.orm import dynamic_loader, backref -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import Integer, String, ForeignKey, desc, select, func -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, relationship, create_session, Query, attributes from sqlalchemy.orm.dynamic import AppenderMixin -from sqlalchemy.test.testing import eq_, AssertsCompiledSQL, assert_raises_message +from test.lib.testing import eq_, AssertsCompiledSQL, assert_raises_message from sqlalchemy.util import function_named from test.orm import _base, _fixtures diff --git a/test/orm/test_eager_relations.py b/test/orm/test_eager_relations.py index 94d1b3464..6304ae45a 100644 --- a/test/orm/test_eager_relations.py +++ b/test/orm/test_eager_relations.py @@ -1,18 +1,18 @@ """tests of joined-eager loaded attributes""" -from sqlalchemy.test.testing import eq_, is_, is_not_ +from test.lib.testing import eq_, is_, is_not_ import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy.orm import joinedload, deferred, undefer, \ joinedload_all, backref from sqlalchemy import Integer, String, Date, ForeignKey, and_, select, \ func -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, relationship, create_session, \ lazyload, aliased -from sqlalchemy.test.testing import eq_, assert_raises, \ +from test.lib.testing import eq_, assert_raises, \ assert_raises_message -from sqlalchemy.test.assertsql import CompiledSQL +from test.lib.assertsql import CompiledSQL from test.orm import _base, _fixtures from sqlalchemy.util import OrderedDict as odict import datetime diff --git a/test/orm/test_evaluator.py b/test/orm/test_evaluator.py index af6a3f89e..26f9f49c3 100644 --- a/test/orm/test_evaluator.py +++ b/test/orm/test_evaluator.py @@ -1,11 +1,11 @@ """Evluating SQL expressions on ORM objects""" import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import String, Integer, select -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column +from test.lib.schema import Table +from test.lib.schema import Column from sqlalchemy.orm import mapper, create_session -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from test.orm import _base from sqlalchemy import and_, or_, not_ diff --git a/test/orm/test_expire.py b/test/orm/test_expire.py index 0d9f5e745..7189290bc 100644 --- a/test/orm/test_expire.py +++ b/test/orm/test_expire.py @@ -1,12 +1,12 @@ """Attribute/instance expiration, deferral of attributes, etc.""" -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message -from sqlalchemy.test.util import gc_collect +from test.lib.testing import eq_, assert_raises, assert_raises_message +from test.lib.util import gc_collect import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import Integer, String, ForeignKey, exc as sa_exc -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column +from test.lib.schema import Table +from test.lib.schema import Column from sqlalchemy.orm import mapper, relationship, create_session, \ attributes, deferred, exc as orm_exc, defer, undefer,\ strategies, state, lazyload, backref diff --git a/test/orm/test_extendedattr.py b/test/orm/test_extendedattr.py index 6fabe7e43..aae1ecdbb 100644 --- a/test/orm/test_extendedattr.py +++ b/test/orm/test_extendedattr.py @@ -1,4 +1,4 @@ -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message +from test.lib.testing import eq_, assert_raises, assert_raises_message import pickle from sqlalchemy import util from sqlalchemy.orm import attributes, instrumentation @@ -7,7 +7,7 @@ from sqlalchemy.orm.attributes import set_attribute, get_attribute, del_attribut from sqlalchemy.orm.instrumentation import is_instrumented from sqlalchemy.orm import clear_mappers from sqlalchemy.orm import InstrumentationManager -from sqlalchemy.test import * +from test.lib import * from test.orm import _base class MyTypesManager(InstrumentationManager): diff --git a/test/orm/test_generative.py b/test/orm/test_generative.py index 73f62b233..e3c5eee99 100644 --- a/test/orm/test_generative.py +++ b/test/orm/test_generative.py @@ -1,11 +1,11 @@ -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import Integer, String, ForeignKey, MetaData, func -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column +from test.lib.schema import Table +from test.lib.schema import Column from sqlalchemy.orm import mapper, relationship, create_session -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from test.orm import _base, _fixtures diff --git a/test/orm/test_immediate_load.py b/test/orm/test_immediate_load.py index f85208bff..66794ad4b 100644 --- a/test/orm/test_immediate_load.py +++ b/test/orm/test_immediate_load.py @@ -1,8 +1,8 @@ """basic tests of lazy loaded attributes""" -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy.orm import mapper, relationship, create_session, immediateload -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from test.orm import _fixtures diff --git a/test/orm/test_instrumentation.py b/test/orm/test_instrumentation.py index 7ffee2a2e..4bcf36351 100644 --- a/test/orm/test_instrumentation.py +++ b/test/orm/test_instrumentation.py @@ -1,12 +1,12 @@ -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message import sqlalchemy as sa from sqlalchemy import MetaData, Integer, ForeignKey, util, event -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column from sqlalchemy.orm import mapper, relationship, create_session, \ attributes, class_mapper, clear_mappers, instrumentation, events -from sqlalchemy.test.testing import eq_, ne_ +from test.lib.schema import Table +from test.lib.schema import Column +from test.lib.testing import eq_, ne_ from sqlalchemy.util import function_named from test.orm import _base diff --git a/test/orm/test_lazy_relations.py b/test/orm/test_lazy_relations.py index f6147a3eb..1bf7eecaf 100644 --- a/test/orm/test_lazy_relations.py +++ b/test/orm/test_lazy_relations.py @@ -1,17 +1,17 @@ """basic tests of lazy loaded attributes""" -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message import datetime from sqlalchemy import exc as sa_exc from sqlalchemy.orm import attributes, exc as orm_exc import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import Integer, String, ForeignKey, SmallInteger from sqlalchemy.types import TypeDecorator -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column +from test.lib.schema import Table +from test.lib.schema import Column from sqlalchemy.orm import mapper, relationship, create_session -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from test.orm import _base, _fixtures diff --git a/test/orm/test_load_on_fks.py b/test/orm/test_load_on_fks.py index 8e4f53b0d..3e7ddb8c2 100644 --- a/test/orm/test_load_on_fks.py +++ b/test/orm/test_load_on_fks.py @@ -2,11 +2,11 @@ from sqlalchemy import * from sqlalchemy.orm import * from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.test.testing import TestBase, eq_, AssertsExecutionResults, assert_raises -from sqlalchemy.test import testing +from test.lib.testing import TestBase, eq_, AssertsExecutionResults, assert_raises +from test.lib import testing from sqlalchemy.orm.attributes import instance_state from sqlalchemy.orm.exc import FlushError -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column engine = testing.db diff --git a/test/orm/test_manytomany.py b/test/orm/test_manytomany.py index d891e319e..0e7a6e40f 100644 --- a/test/orm/test_manytomany.py +++ b/test/orm/test_manytomany.py @@ -1,10 +1,10 @@ -from sqlalchemy.test.testing import assert_raises, \ +from test.lib.testing import assert_raises, \ assert_raises_message, eq_ import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column +from test.lib.schema import Table +from test.lib.schema import Column from sqlalchemy.orm import mapper, relationship, create_session, \ exc as orm_exc, sessionmaker from test.orm import _base diff --git a/test/orm/test_mapper.py b/test/orm/test_mapper.py index 78e0f2206..7ea4209a8 100644 --- a/test/orm/test_mapper.py +++ b/test/orm/test_mapper.py @@ -1,10 +1,10 @@ """General mapper operations with an emphasis on selecting/loading.""" -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message import sqlalchemy as sa -from sqlalchemy.test import testing, pickleable +from test.lib import testing, pickleable from sqlalchemy import MetaData, Integer, String, ForeignKey, func, util -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.engine import default from sqlalchemy.orm import mapper, relationship, backref, \ create_session, class_mapper, configure_mappers, reconstructor, \ @@ -13,10 +13,10 @@ from sqlalchemy.orm import defer, deferred, synonym, attributes, \ column_property, composite, relationship, dynamic_loader, \ comparable_property, AttributeExtension from sqlalchemy.orm.instrumentation import ClassManager -from sqlalchemy.test.testing import eq_, AssertsCompiledSQL +from test.lib.testing import eq_, AssertsCompiledSQL from test.orm import _base, _fixtures -from sqlalchemy.test.assertsql import AllOf, CompiledSQL from sqlalchemy import event +from test.lib.assertsql import AllOf, CompiledSQL class MapperTest(_fixtures.FixtureTest): diff --git a/test/orm/test_merge.py b/test/orm/test_merge.py index 50d001a8c..fbdbcfe21 100644 --- a/test/orm/test_merge.py +++ b/test/orm/test_merge.py @@ -1,18 +1,18 @@ -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message import sqlalchemy as sa from sqlalchemy import Integer, PickleType, String import operator -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy.util import OrderedSet from sqlalchemy.orm import mapper, relationship, create_session, \ PropComparator, synonym, comparable_property, sessionmaker, \ attributes, Session from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.orm.interfaces import MapperOption -from sqlalchemy.test.testing import eq_, ne_ +from test.lib.testing import eq_, ne_ from test.orm import _base, _fixtures -from sqlalchemy.test.schema import Table, Column from sqlalchemy import event +from test.lib.schema import Table, Column class MergeTest(_fixtures.FixtureTest): """Session.merge() functionality""" diff --git a/test/orm/test_naturalpks.py b/test/orm/test_naturalpks.py index ca88251d7..f9ce7b3ca 100644 --- a/test/orm/test_naturalpks.py +++ b/test/orm/test_naturalpks.py @@ -2,15 +2,15 @@ Primary key changing capabilities and passive/non-passive cascading updates. """ -from sqlalchemy.test.testing import eq_, ne_, \ +from test.lib.testing import eq_, ne_, \ assert_raises, assert_raises_message import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import Integer, String, ForeignKey, Unicode -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, relationship, create_session, backref, Session from sqlalchemy.orm.session import make_transient -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from test.orm import _base, _fixtures class NaturalPKTest(_base.MappedTest): @@ -862,7 +862,7 @@ class CascadeToFKPKTest(_base.MappedTest, testing.AssertsCompiledSQL): sess.add(u2) sess.add(a2) - from sqlalchemy.test.assertsql import CompiledSQL + from test.lib.assertsql import CompiledSQL # test that the primary key columns of addresses are not # being updated as well, since this is a row switch. diff --git a/test/orm/test_onetoone.py b/test/orm/test_onetoone.py index 4e3cade9b..7097a266e 100644 --- a/test/orm/test_onetoone.py +++ b/test/orm/test_onetoone.py @@ -1,7 +1,7 @@ import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, relationship, create_session from test.orm import _base diff --git a/test/orm/test_pickled.py b/test/orm/test_pickled.py index f23bc92a1..a246ddbdc 100644 --- a/test/orm/test_pickled.py +++ b/test/orm/test_pickled.py @@ -1,10 +1,10 @@ -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ import pickle import sqlalchemy as sa -from sqlalchemy.test import testing -from sqlalchemy.test.testing import assert_raises_message +from test.lib import testing +from test.lib.testing import assert_raises_message from sqlalchemy import Integer, String, ForeignKey, exc, MetaData -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, relationship, create_session, \ sessionmaker, attributes, interfaces,\ clear_mappers, exc as orm_exc,\ diff --git a/test/orm/test_query.py b/test/orm/test_query.py index 18c95167f..db7e53a0e 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -1,4 +1,4 @@ -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message +from test.lib.testing import eq_, assert_raises, assert_raises_message import operator from sqlalchemy import * from sqlalchemy import exc as sa_exc, util @@ -7,10 +7,10 @@ from sqlalchemy.engine import default from sqlalchemy.orm import * from sqlalchemy.orm import attributes -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ import sqlalchemy as sa -from sqlalchemy.test import testing, AssertsCompiledSQL, Column, engines +from test.lib import testing, AssertsCompiledSQL, Column, engines from test.orm import _fixtures from test.orm._fixtures import keywords, addresses, Base, \ diff --git a/test/orm/test_relationships.py b/test/orm/test_relationships.py index 2ad8bc8aa..423406837 100644 --- a/test/orm/test_relationships.py +++ b/test/orm/test_relationships.py @@ -1,14 +1,14 @@ -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message import datetime import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import Integer, String, ForeignKey, MetaData, and_ -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, relationship, relation, \ backref, create_session, configure_mappers, \ clear_mappers, sessionmaker, attributes,\ Session, composite, column_property -from sqlalchemy.test.testing import eq_, startswith_ +from test.lib.testing import eq_, startswith_ from test.orm import _base, _fixtures diff --git a/test/orm/test_scoping.py b/test/orm/test_scoping.py index fa1777c85..43472eae3 100644 --- a/test/orm/test_scoping.py +++ b/test/orm/test_scoping.py @@ -1,11 +1,11 @@ -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy.orm import scoped_session from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, relationship, query -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from test.orm import _base diff --git a/test/orm/test_selectable.py b/test/orm/test_selectable.py index e46d8bbc8..30278b5bc 100644 --- a/test/orm/test_selectable.py +++ b/test/orm/test_selectable.py @@ -1,11 +1,11 @@ """Generic mapping to Select statements""" -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message import sqlalchemy as sa -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy import String, Integer, select -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, create_session -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from test.orm import _base diff --git a/test/orm/test_session.py b/test/orm/test_session.py index 62047970c..0486a8124 100644 --- a/test/orm/test_session.py +++ b/test/orm/test_session.py @@ -1,18 +1,18 @@ -from sqlalchemy.test.testing import eq_, assert_raises, \ +from test.lib.testing import eq_, assert_raises, \ assert_raises_message -from sqlalchemy.test.util import gc_collect +from test.lib.util import gc_collect import inspect import pickle from sqlalchemy.orm import create_session, sessionmaker, attributes, \ make_transient, Session from sqlalchemy.orm.attributes import instance_state import sqlalchemy as sa -from sqlalchemy.test import engines, testing, config +from test.lib import engines, testing, config from sqlalchemy import Integer, String, Sequence -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, relationship, backref, joinedload, \ exc as orm_exc, object_session -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from test.engine import _base as engine_base from test.orm import _base, _fixtures from sqlalchemy import event diff --git a/test/orm/test_subquery_relations.py b/test/orm/test_subquery_relations.py index 5eba68e13..2aadf26d4 100644 --- a/test/orm/test_subquery_relations.py +++ b/test/orm/test_subquery_relations.py @@ -1,13 +1,13 @@ -from sqlalchemy.test.testing import eq_, is_, is_not_ -from sqlalchemy.test import testing -from sqlalchemy.test.schema import Table, Column +from test.lib.testing import eq_, is_, is_not_ +from test.lib import testing +from test.lib.schema import Table, Column from sqlalchemy import Integer, String, ForeignKey, bindparam from sqlalchemy.orm import backref, subqueryload, subqueryload_all, \ mapper, relationship, clear_mappers, create_session, lazyload, \ aliased, joinedload, deferred, undefer, eagerload_all -from sqlalchemy.test.testing import eq_, assert_raises, \ +from test.lib.testing import eq_, assert_raises, \ assert_raises_message -from sqlalchemy.test.assertsql import CompiledSQL +from test.lib.assertsql import CompiledSQL from test.orm import _base, _fixtures import sqlalchemy as sa diff --git a/test/orm/test_transaction.py b/test/orm/test_transaction.py index 2617f12f0..7f0ada49f 100644 --- a/test/orm/test_transaction.py +++ b/test/orm/test_transaction.py @@ -1,12 +1,12 @@ -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message +from test.lib.testing import eq_, assert_raises, assert_raises_message from sqlalchemy import * from sqlalchemy.orm import attributes from sqlalchemy import exc as sa_exc from sqlalchemy.orm import exc as orm_exc from sqlalchemy.orm import * -from sqlalchemy.test.util import gc_collect -from sqlalchemy.test import testing +from test.lib.util import gc_collect +from test.lib import testing from test.orm import _base from test.orm._fixtures import FixtureTest, User, Address, users, addresses diff --git a/test/orm/test_unitofwork.py b/test/orm/test_unitofwork.py index 511adde82..6b6251d66 100644 --- a/test/orm/test_unitofwork.py +++ b/test/orm/test_unitofwork.py @@ -1,23 +1,23 @@ # coding: utf-8 """Tests unitofwork operations.""" -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message +from test.lib.testing import eq_, assert_raises, assert_raises_message import datetime import operator from sqlalchemy.orm import mapper as orm_mapper import sqlalchemy as sa -from sqlalchemy.test import engines, testing, pickleable from sqlalchemy import Integer, String, ForeignKey, literal_column, event -from sqlalchemy.test.schema import Table -from sqlalchemy.test.schema import Column +from test.lib import engines, testing, pickleable +from test.lib.schema import Table +from test.lib.schema import Column from sqlalchemy.orm import mapper, relationship, create_session, \ column_property, attributes, Session, reconstructor, object_session -from sqlalchemy.test.testing import eq_, ne_ -from sqlalchemy.test.util import gc_collect +from test.lib.testing import eq_, ne_ +from test.lib.util import gc_collect from test.orm import _base, _fixtures from test.engine import _base as engine_base -from sqlalchemy.test.assertsql import AllOf, CompiledSQL +from test.lib.assertsql import AllOf, CompiledSQL import gc class UnitOfWorkTest(object): diff --git a/test/orm/test_unitofworkv2.py b/test/orm/test_unitofworkv2.py index d91799305..73a884e0c 100644 --- a/test/orm/test_unitofworkv2.py +++ b/test/orm/test_unitofworkv2.py @@ -1,11 +1,11 @@ -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message -from sqlalchemy.test import testing -from sqlalchemy.test.schema import Table, Column +from test.lib.testing import eq_, assert_raises, assert_raises_message +from test.lib import testing +from test.lib.schema import Table, Column from sqlalchemy import Integer, String, ForeignKey, func from test.orm import _fixtures, _base from sqlalchemy.orm import mapper, relationship, backref, \ create_session, unitofwork, attributes -from sqlalchemy.test.assertsql import AllOf, CompiledSQL +from test.lib.assertsql import AllOf, CompiledSQL from test.orm._fixtures import keywords, addresses, Base, Keyword, \ Dingaling, item_keywords, dingalings, User, items,\ diff --git a/test/orm/test_utils.py b/test/orm/test_utils.py index 43a15056c..1ee34c50f 100644 --- a/test/orm/test_utils.py +++ b/test/orm/test_utils.py @@ -1,4 +1,4 @@ -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message from sqlalchemy.orm import interfaces, util from sqlalchemy import Column from sqlalchemy import Integer @@ -8,10 +8,10 @@ from sqlalchemy.orm import aliased from sqlalchemy.orm import mapper, create_session -from sqlalchemy.test import TestBase, testing +from test.lib import TestBase, testing from test.orm import _fixtures -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ class AliasedClassTest(TestBase): diff --git a/test/orm/test_versioning.py b/test/orm/test_versioning.py index 6cb2bb9e2..75f7fbb6e 100644 --- a/test/orm/test_versioning.py +++ b/test/orm/test_versioning.py @@ -1,9 +1,9 @@ import sqlalchemy as sa -from sqlalchemy.test import engines, testing +from test.lib import engines, testing from sqlalchemy import Integer, String, ForeignKey, literal_column, orm, exc -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column from sqlalchemy.orm import mapper, relationship, create_session, column_property, sessionmaker -from sqlalchemy.test.testing import eq_, ne_, assert_raises, assert_raises_message +from test.lib.testing import eq_, ne_, assert_raises, assert_raises_message from test.orm import _base, _fixtures from test.engine import _base as engine_base diff --git a/test/perf/insertspeed.py b/test/perf/insertspeed.py index 0491e9f95..3ae1ccbde 100644 --- a/test/perf/insertspeed.py +++ b/test/perf/insertspeed.py @@ -2,7 +2,7 @@ import testenv; testenv.simple_setup() import sys, time from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test import profiling +from test.lib import profiling db = create_engine('sqlite://') metadata = MetaData(db) diff --git a/test/perf/large_flush.py b/test/perf/large_flush.py index 431a28944..b23de080c 100644 --- a/test/perf/large_flush.py +++ b/test/perf/large_flush.py @@ -3,7 +3,7 @@ from sqlalchemy import create_engine, MetaData, orm from sqlalchemy import Column, ForeignKey from sqlalchemy import Integer, String from sqlalchemy.orm import mapper -from sqlalchemy.test import profiling +from test.lib import profiling class Object(object): pass diff --git a/test/perf/masscreate2.py b/test/perf/masscreate2.py index e525fcf99..6ad2194cd 100644 --- a/test/perf/masscreate2.py +++ b/test/perf/masscreate2.py @@ -3,7 +3,7 @@ import testenv; testenv.simple_setup() import random, string from sqlalchemy.orm import attributes -from sqlalchemy.test.util import gc_collect +from test.lib.util import gc_collect # with this test, run top. make sure the Python process doenst grow in size arbitrarily. diff --git a/test/perf/masseagerload.py b/test/perf/masseagerload.py index 2ed8d2803..6e6d86d54 100644 --- a/test/perf/masseagerload.py +++ b/test/perf/masseagerload.py @@ -1,6 +1,6 @@ from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test import profiling +from test.lib import profiling NUM = 500 DIVISOR = 50 diff --git a/test/perf/massload.py b/test/perf/massload.py index f6cde3adf..06cfae786 100644 --- a/test/perf/massload.py +++ b/test/perf/massload.py @@ -2,7 +2,7 @@ import time #import sqlalchemy.orm.attributes as attributes from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test import * +from test.lib import * """ diff --git a/test/perf/masssave.py b/test/perf/masssave.py index 41acd12cc..3c1547d38 100644 --- a/test/perf/masssave.py +++ b/test/perf/masssave.py @@ -2,7 +2,7 @@ import gc import types from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test import * +from test.lib import * NUM = 2500 diff --git a/test/perf/objselectspeed.py b/test/perf/objselectspeed.py index 816643680..126c9c707 100644 --- a/test/perf/objselectspeed.py +++ b/test/perf/objselectspeed.py @@ -1,8 +1,8 @@ import time, resource from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test.util import gc_collect -from sqlalchemy.test import profiling +from test.lib.util import gc_collect +from test.lib import profiling db = create_engine('sqlite://') metadata = MetaData(db) diff --git a/test/perf/objupdatespeed.py b/test/perf/objupdatespeed.py index fad22189a..078d95fa3 100644 --- a/test/perf/objupdatespeed.py +++ b/test/perf/objupdatespeed.py @@ -1,8 +1,8 @@ import time, resource from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test import * -from sqlalchemy.test.util import gc_collect +from test.lib import * +from test.lib.util import gc_collect NUM = 100 diff --git a/test/perf/ormsession.py b/test/perf/ormsession.py index 0b01fc5a3..aff265ff1 100644 --- a/test/perf/ormsession.py +++ b/test/perf/ormsession.py @@ -3,8 +3,8 @@ from datetime import datetime from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test import * -from sqlalchemy.test.profiling import profiled +from test.lib import * +from test.lib.profiling import profiled class Item(object): def __repr__(self): diff --git a/test/perf/poolload.py b/test/perf/poolload.py index 62c66fbae..345720f0c 100644 --- a/test/perf/poolload.py +++ b/test/perf/poolload.py @@ -2,7 +2,7 @@ import thread, time from sqlalchemy import * import sqlalchemy.pool as pool -from sqlalchemy.test import testing +from test.lib import testing db = create_engine(testing.db.url, pool_timeout=30, echo_pool=True) metadata = MetaData(db) diff --git a/test/perf/sessions.py b/test/perf/sessions.py index 4210732d6..2fe4f758f 100644 --- a/test/perf/sessions.py +++ b/test/perf/sessions.py @@ -1,8 +1,8 @@ from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test.compat import gc_collect -from sqlalchemy.test import TestBase, AssertsExecutionResults, profiling, testing +from test.lib.compat import gc_collect +from test.lib import TestBase, AssertsExecutionResults, profiling, testing from test.orm import _fixtures # in this test we are specifically looking for time spent in the attributes.InstanceState.__cleanup() method. diff --git a/test/perf/wsgi.py b/test/perf/wsgi.py index 549c92ade..27aa4a8c8 100644 --- a/test/perf/wsgi.py +++ b/test/perf/wsgi.py @@ -4,7 +4,7 @@ from sqlalchemy import * from sqlalchemy.orm import * import thread -from sqlalchemy.test import * +from test.lib import * port = 8000 diff --git a/test/sql/test_case_statement.py b/test/sql/test_case_statement.py index 1a106ee5e..7bc3ab31f 100644 --- a/test/sql/test_case_statement.py +++ b/test/sql/test_case_statement.py @@ -1,7 +1,7 @@ -from sqlalchemy.test.testing import assert_raises, assert_raises_message, eq_ +from test.lib.testing import assert_raises, assert_raises_message, eq_ import sys from sqlalchemy import * -from sqlalchemy.test import * +from test.lib import * from sqlalchemy import util, exc from sqlalchemy.sql import table, column diff --git a/test/sql/test_columns.py b/test/sql/test_columns.py index 3cbb01943..95933b41b 100644 --- a/test/sql/test_columns.py +++ b/test/sql/test_columns.py @@ -1,7 +1,7 @@ -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message from sqlalchemy import * from sqlalchemy import exc, sql -from sqlalchemy.test import * +from test.lib import * from sqlalchemy import Table, Column # don't use testlib's wrappers diff --git a/test/sql/test_compiler.py b/test/sql/test_compiler.py index f6cffc116..01fe648a4 100644 --- a/test/sql/test_compiler.py +++ b/test/sql/test_compiler.py @@ -1,4 +1,4 @@ -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message +from test.lib.testing import eq_, assert_raises, assert_raises_message import datetime, re, operator from sqlalchemy import * from sqlalchemy import exc, sql, util @@ -6,7 +6,7 @@ from sqlalchemy.sql import table, column, label, compiler from sqlalchemy.sql.expression import ClauseList from sqlalchemy.engine import default from sqlalchemy.databases import * -from sqlalchemy.test import * +from test.lib import * table1 = table('mytable', column('myid', Integer), diff --git a/test/sql/test_constraints.py b/test/sql/test_constraints.py index 69f29a9bd..56c5c6205 100644 --- a/test/sql/test_constraints.py +++ b/test/sql/test_constraints.py @@ -1,11 +1,11 @@ -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message from sqlalchemy import * from sqlalchemy import exc, schema -from sqlalchemy.test import * -from sqlalchemy.test import config, engines +from test.lib import * +from test.lib import config, engines from sqlalchemy.engine import ddl -from sqlalchemy.test.testing import eq_ -from sqlalchemy.test.assertsql import AllOf, RegexSQL, ExactSQL, CompiledSQL +from test.lib.testing import eq_ +from test.lib.assertsql import AllOf, RegexSQL, ExactSQL, CompiledSQL from sqlalchemy.dialects.postgresql import base as postgresql class ConstraintTest(TestBase, AssertsExecutionResults, AssertsCompiledSQL): diff --git a/test/sql/test_defaults.py b/test/sql/test_defaults.py index f49e4d0d3..7ec43f8d2 100644 --- a/test/sql/test_defaults.py +++ b/test/sql/test_defaults.py @@ -1,13 +1,13 @@ -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message +from test.lib.testing import eq_, assert_raises, assert_raises_message import datetime from sqlalchemy import Sequence, Column, func from sqlalchemy.schema import CreateSequence, DropSequence from sqlalchemy.sql import select, text import sqlalchemy as sa -from sqlalchemy.test import testing, engines +from test.lib import testing, engines from sqlalchemy import MetaData, Integer, String, ForeignKey, Boolean, exc -from sqlalchemy.test.schema import Table -from sqlalchemy.test.testing import eq_ +from test.lib.schema import Table +from test.lib.testing import eq_ from test.sql import _base diff --git a/test/sql/test_functions.py b/test/sql/test_functions.py index c9bb8348c..396eaaf9b 100644 --- a/test/sql/test_functions.py +++ b/test/sql/test_functions.py @@ -1,17 +1,17 @@ -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ import datetime from sqlalchemy import * from sqlalchemy.sql import table, column from sqlalchemy import databases, sql, util from sqlalchemy.sql.compiler import BIND_TEMPLATES from sqlalchemy.engine import default -from sqlalchemy.test.engines import all_dialects +from test.lib.engines import all_dialects from sqlalchemy import types as sqltypes -from sqlalchemy.test import * +from test.lib import * from sqlalchemy.sql.functions import GenericFunction -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from decimal import Decimal as _python_Decimal -from sqlalchemy.test import testing +from test.lib import testing from sqlalchemy.databases import * diff --git a/test/sql/test_generative.py b/test/sql/test_generative.py index 26f9c1ad0..e129f69c4 100644 --- a/test/sql/test_generative.py +++ b/test/sql/test_generative.py @@ -1,11 +1,11 @@ from sqlalchemy import * from sqlalchemy.sql import table, column, ClauseElement from sqlalchemy.sql.expression import _clone, _from_objects -from sqlalchemy.test import * +from test.lib import * from sqlalchemy.sql.visitors import * from sqlalchemy import util from sqlalchemy.sql import util as sql_util -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ class TraversalTest(TestBase, AssertsExecutionResults): """test ClauseVisitor's traversal, particularly its ability to copy and modify diff --git a/test/sql/test_labels.py b/test/sql/test_labels.py index f67ba9855..0f84c30a0 100644 --- a/test/sql/test_labels.py +++ b/test/sql/test_labels.py @@ -1,7 +1,7 @@ -from sqlalchemy.test.testing import assert_raises, assert_raises_message +from test.lib.testing import assert_raises, assert_raises_message from sqlalchemy import * from sqlalchemy import exc as exceptions -from sqlalchemy.test import * +from test.lib import * from sqlalchemy.engine import default IDENT_LENGTH = 29 diff --git a/test/sql/test_query.py b/test/sql/test_query.py index 410ff73a6..f59b34076 100644 --- a/test/sql/test_query.py +++ b/test/sql/test_query.py @@ -1,11 +1,11 @@ -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ import datetime from sqlalchemy import * from sqlalchemy import exc, sql, util from sqlalchemy.engine import default, base -from sqlalchemy.test import * -from sqlalchemy.test.testing import eq_, assert_raises_message, assert_raises -from sqlalchemy.test.schema import Table, Column +from test.lib import * +from test.lib.testing import eq_, assert_raises_message, assert_raises +from test.lib.schema import Table, Column class QueryTest(TestBase): diff --git a/test/sql/test_quote.py b/test/sql/test_quote.py index 81a68ec74..8f27a7b3c 100644 --- a/test/sql/test_quote.py +++ b/test/sql/test_quote.py @@ -1,7 +1,7 @@ from sqlalchemy import * from sqlalchemy import sql, schema from sqlalchemy.sql import compiler -from sqlalchemy.test import * +from test.lib import * class QuoteTest(TestBase, AssertsCompiledSQL): @classmethod diff --git a/test/sql/test_returning.py b/test/sql/test_returning.py index 332f4eef5..632a739f1 100644 --- a/test/sql/test_returning.py +++ b/test/sql/test_returning.py @@ -1,7 +1,7 @@ -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ from sqlalchemy import * -from sqlalchemy.test import * -from sqlalchemy.test.schema import Table, Column +from test.lib import * +from test.lib.schema import Table, Column from sqlalchemy.types import TypeDecorator class ReturningTest(TestBase, AssertsExecutionResults): diff --git a/test/sql/test_rowcount.py b/test/sql/test_rowcount.py index ccd0d8f5e..ed40f2801 100644 --- a/test/sql/test_rowcount.py +++ b/test/sql/test_rowcount.py @@ -1,5 +1,5 @@ from sqlalchemy import * -from sqlalchemy.test import * +from test.lib import * class FoundRowsTest(TestBase, AssertsExecutionResults): diff --git a/test/sql/test_selectable.py b/test/sql/test_selectable.py index 5bebbe05f..77acf896b 100644 --- a/test/sql/test_selectable.py +++ b/test/sql/test_selectable.py @@ -1,9 +1,9 @@ """Test various algorithmic properties of selectables.""" -from sqlalchemy.test.testing import eq_, assert_raises, \ +from test.lib.testing import eq_, assert_raises, \ assert_raises_message from sqlalchemy import * -from sqlalchemy.test import * +from test.lib import * from sqlalchemy.sql import util as sql_util, visitors from sqlalchemy import exc from sqlalchemy.sql import table, column, null diff --git a/test/sql/test_types.py b/test/sql/test_types.py index 993843891..bfadca7c9 100644 --- a/test/sql/test_types.py +++ b/test/sql/test_types.py @@ -1,18 +1,18 @@ # coding: utf-8 -from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message +from test.lib.testing import eq_, assert_raises, assert_raises_message import decimal import datetime, os, re from sqlalchemy import * from sqlalchemy import exc, types, util, schema from sqlalchemy.sql import operators, column, table -from sqlalchemy.test.testing import eq_ +from test.lib.testing import eq_ import sqlalchemy.engine.url as url from sqlalchemy.databases import * -from sqlalchemy.test.schema import Table, Column -from sqlalchemy.test import * -from sqlalchemy.test.util import picklers +from test.lib.schema import Table, Column +from test.lib import * +from test.lib.util import picklers from decimal import Decimal -from sqlalchemy.test.util import round_decimal +from test.lib.util import round_decimal class AdaptTest(TestBase): diff --git a/test/sql/test_unicode.py b/test/sql/test_unicode.py index a116d34cb..2eda4ffa8 100644 --- a/test/sql/test_unicode.py +++ b/test/sql/test_unicode.py @@ -2,8 +2,8 @@ """verrrrry basic unicode column name testing""" from sqlalchemy import * -from sqlalchemy.test import * -from sqlalchemy.test.engines import utf8_engine +from test.lib import * +from test.lib.engines import utf8_engine from sqlalchemy.sql import column class UnicodeSchemaTest(TestBase): diff --git a/test/zblog/tables.py b/test/zblog/tables.py index 4907259e1..3326c28fb 100644 --- a/test/zblog/tables.py +++ b/test/zblog/tables.py @@ -1,7 +1,7 @@ """application table metadata objects are described here.""" from sqlalchemy import * -from sqlalchemy.test.schema import Table, Column +from test.lib.schema import Table, Column metadata = MetaData() diff --git a/test/zblog/test_zblog.py b/test/zblog/test_zblog.py index 8103cde8b..ec6402a6e 100644 --- a/test/zblog/test_zblog.py +++ b/test/zblog/test_zblog.py @@ -1,6 +1,6 @@ from sqlalchemy import * from sqlalchemy.orm import * -from sqlalchemy.test import * +from test.lib import * from test.zblog import mappers, tables from test.zblog.user import * from test.zblog.blog import * |