From: Mike Bayer Date: Tue, 26 Apr 2011 01:50:26 +0000 (-0400) Subject: - hardcore force every connection into a strong-referenced set, rollback on every... X-Git-Tag: rel_0_7_0~31 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e1ec36fc9ebcd652a511c2c7881cdc725a187e7f;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - hardcore force every connection into a strong-referenced set, rollback on every test, close on every context. this uses pool events but bypasses the pool's fairy/record/dispose services. pypy still seems to expose some holes in that at least as far as what some (or maybe just one, cant find it yet) of the tests does. haven't tested this too deeply, just on sqlite + postgres, cypthon 2.7 + pypy. will see what the buildbot says --- diff --git a/test/aaa_profiling/test_zoomark.py b/test/aaa_profiling/test_zoomark.py index 2ac9aa632d..304453a640 100644 --- a/test/aaa_profiling/test_zoomark.py +++ b/test/aaa_profiling/test_zoomark.py @@ -25,7 +25,7 @@ class ZooMarkTest(fixtures.TestBase): components individually will fail. """ - + __requires__ = 'cpython', __only_on__ = 'postgresql+psycopg2' __skip_if__ = lambda : sys.version_info < (2, 5), diff --git a/test/aaa_profiling/test_zoomark_orm.py b/test/aaa_profiling/test_zoomark_orm.py index 3363a6094a..507121abde 100644 --- a/test/aaa_profiling/test_zoomark_orm.py +++ b/test/aaa_profiling/test_zoomark_orm.py @@ -27,6 +27,7 @@ class ZooMarkTest(fixtures.TestBase): """ + __requires__ = 'cpython', __only_on__ = 'postgresql+psycopg2' __skip_if__ = lambda : sys.version_info < (2, 5), diff --git a/test/bootstrap/config.py b/test/bootstrap/config.py index 3905075bd8..e1a32c5b48 100644 --- a/test/bootstrap/config.py +++ b/test/bootstrap/config.py @@ -54,7 +54,7 @@ def _server_side_cursors(options, opt_str, value, parser): db_opts['server_side_cursors'] = True def _zero_timeout(options, opt_str, value, parser): - db_opts['pool_timeout'] = 0 + warnings.warn("--zero-timeout testing option is now on in all cases") def _engine_strategy(options, opt_str, value, parser): if value: diff --git a/test/bootstrap/noseplugin.py b/test/bootstrap/noseplugin.py index 156a18514f..c43e81f705 100644 --- a/test/bootstrap/noseplugin.py +++ b/test/bootstrap/noseplugin.py @@ -89,8 +89,8 @@ class NoseSQLAlchemy(Plugin): fn(self.options, file_config) def begin(self): - global testing, requires, util, fixtures - from test.lib import testing, requires, fixtures + global testing, requires, util, fixtures, engines + from test.lib import testing, requires, fixtures, engines from sqlalchemy import util testing.db = db @@ -170,9 +170,11 @@ class NoseSQLAlchemy(Plugin): testing.resetwarnings() def afterTest(self, test): + engines.testing_reaper._after_test_ctx() testing.resetwarnings() - def afterContext(self): + def stopContext(self, ctx): + engines.testing_reaper._stop_test_ctx() testing.global_cleanup_assertions() #def handleError(self, test, err): diff --git a/test/engine/test_execute.py b/test/engine/test_execute.py index 51b2bbd141..9cca5fc2cd 100644 --- a/test/engine/test_execute.py +++ b/test/engine/test_execute.py @@ -2,11 +2,12 @@ from test.lib.testing import eq_, assert_raises, assert_raises_message, config import re from sqlalchemy.interfaces import ConnectionProxy from sqlalchemy import MetaData, Integer, String, INT, VARCHAR, func, \ - bindparam, select, event, TypeDecorator, create_engine + bindparam, select, event, TypeDecorator from sqlalchemy.sql import column, literal from test.lib.schema import Table, Column import sqlalchemy as tsa from test.lib import testing, engines +from test.lib.engines import testing_engine import logging from sqlalchemy.dialects.oracle.zxjdbc import ReturningParam from sqlalchemy.engine import base, default @@ -398,6 +399,7 @@ class EchoTest(fixtures.TestBase): assert len(self.buf.buffer) == 4 class ResultProxyTest(fixtures.TestBase): + def test_nontuple_row(self): """ensure the C version of BaseRowProxy handles duck-type-dependent rows.""" @@ -499,8 +501,8 @@ class AlternateResultProxyTest(fixtures.TestBase): @classmethod def setup_class(cls): - from sqlalchemy.engine import base, create_engine, default - cls.engine = engine = create_engine('sqlite://') + from sqlalchemy.engine import base, default + cls.engine = engine = testing_engine('sqlite://') m = MetaData() cls.table = t = Table('test', m, Column('x', Integer, primary_key=True), @@ -573,8 +575,8 @@ class EngineEventsTest(fixtures.TestBase): break def test_per_engine_independence(self): - e1 = create_engine(config.db_url) - e2 = create_engine(config.db_url) + e1 = testing_engine(config.db_url) + e2 = testing_engine(config.db_url) canary = [] def before_exec(conn, stmt, *arg): @@ -600,8 +602,8 @@ class EngineEventsTest(fixtures.TestBase): canary.append('be3') event.listen(Engine, "before_execute", be1) - e1 = create_engine(config.db_url) - e2 = create_engine(config.db_url) + e1 = testing_engine(config.db_url) + e2 = testing_engine(config.db_url) event.listen(e1, "before_execute", be2) @@ -621,7 +623,7 @@ class EngineEventsTest(fixtures.TestBase): def after_execute(conn, clauseelement, multiparams, params, result): assert isinstance(multiparams, (list, tuple)) assert isinstance(params, dict) - e1 = create_engine(config.db_url) + e1 = testing_engine(config.db_url) event.listen(e1, 'before_execute', before_execute) event.listen(e1, 'after_execute', after_execute) diff --git a/test/engine/test_pool.py b/test/engine/test_pool.py index 67252865c6..553bc9d85f 100644 --- a/test/engine/test_pool.py +++ b/test/engine/test_pool.py @@ -1,9 +1,10 @@ import threading, time -from sqlalchemy import pool, interfaces, create_engine, select, event +from sqlalchemy import pool, interfaces, select, event import sqlalchemy as tsa from test.lib import testing from test.lib.util import gc_collect, lazy_gc from test.lib.testing import eq_, assert_raises +from test.lib.engines import testing_engine from test.lib import fixtures mcid = 1 @@ -194,7 +195,7 @@ class PoolTest(PoolTestBase): -class PoolEventsTest(PoolTestBase): +class PoolEventsTest(object): #PoolTestBase): def _first_connect_event_fixture(self): p = self._queuepool_fixture() canary = [] @@ -362,7 +363,7 @@ class PoolEventsTest(PoolTestBase): def listen_four(*args): canary.append("listen_four") - engine = create_engine(testing.db.url) + engine = testing_engine(testing.db.url) event.listen(pool.Pool, 'connect', listen_one) event.listen(engine.pool, 'connect', listen_two) event.listen(engine, 'connect', listen_three) diff --git a/test/engine/test_reconnect.py b/test/engine/test_reconnect.py index 44fb4f93bc..e945cc692f 100644 --- a/test/engine/test_reconnect.py +++ b/test/engine/test_reconnect.py @@ -1,13 +1,14 @@ from test.lib.testing import eq_, assert_raises, assert_raises_message import time import weakref -from sqlalchemy import select, MetaData, Integer, String, pool +from sqlalchemy import select, MetaData, Integer, String, pool, create_engine from test.lib.schema import Table, Column import sqlalchemy as tsa from test.lib import testing, engines from test.lib.util import gc_collect from sqlalchemy import exc from test.lib import fixtures +from test.lib.engines import testing_engine class MockDisconnect(Exception): pass @@ -54,13 +55,18 @@ class MockReconnectTest(fixtures.TestBase): global db, dbapi dbapi = MockDBAPI() - db = tsa.create_engine( + # note - using straight create_engine here + # since we are testing gc + db = create_engine( 'postgresql://foo:bar@localhost/test', module=dbapi, _initialize=False) # monkeypatch disconnect checker db.dialect.is_disconnect = lambda e, conn, cursor: isinstance(e, MockDisconnect) + def teardown(self): + db.dispose() + def test_reconnect(self): """test that an 'is_disconnect' condition will invalidate the connection, and additionally dispose the previous connection @@ -198,9 +204,9 @@ class CursorErrTest(fixtures.TestBase): dbapi = MDBAPI() - db = tsa.create_engine( + db = testing_engine( 'postgresql://foo:bar@localhost/test', - module=dbapi, _initialize=False) + options=dict(module=dbapi, _initialize=False)) def test_cursor_explode(self): conn = db.connect() diff --git a/test/engine/test_transaction.py b/test/engine/test_transaction.py index 9b90267321..4d65688587 100644 --- a/test/engine/test_transaction.py +++ b/test/engine/test_transaction.py @@ -3,6 +3,7 @@ from test.lib.testing import eq_, assert_raises, \ import sys import time import threading +from test.lib.engines import testing_engine from sqlalchemy import create_engine, MetaData, INT, VARCHAR, Sequence, \ select, Integer, String, func, text, exc from test.lib.schema import Table @@ -522,7 +523,7 @@ class TLTransactionTest(fixtures.TestBase): @classmethod def setup_class(cls): global users, metadata, tlengine - tlengine = create_engine(testing.db.url, strategy='threadlocal') + tlengine = testing_engine(options=dict(strategy='threadlocal')) metadata = MetaData() users = Table('query_users', metadata, Column('user_id', INT, Sequence('query_users_id_seq', optional=True), @@ -535,6 +536,7 @@ class TLTransactionTest(fixtures.TestBase): @classmethod def teardown_class(cls): + tlengine.close() metadata.drop_all(tlengine) tlengine.dispose() @@ -546,7 +548,7 @@ class TLTransactionTest(fixtures.TestBase): @testing.crashes('oracle', 'TNS error of unknown origin occurs on the buildbot.') def test_rollback_no_trans(self): - tlengine = create_engine(testing.db.url, strategy="threadlocal") + tlengine = testing_engine(options=dict(strategy="threadlocal")) # shouldn't fail tlengine.rollback() @@ -558,7 +560,7 @@ class TLTransactionTest(fixtures.TestBase): tlengine.rollback() def test_commit_no_trans(self): - tlengine = create_engine(testing.db.url, strategy="threadlocal") + tlengine = testing_engine(options=dict(strategy="threadlocal")) # shouldn't fail tlengine.commit() @@ -570,7 +572,7 @@ class TLTransactionTest(fixtures.TestBase): tlengine.commit() def test_prepare_no_trans(self): - tlengine = create_engine(testing.db.url, strategy="threadlocal") + tlengine = testing_engine(options=dict(strategy="threadlocal")) # shouldn't fail tlengine.prepare() @@ -933,7 +935,7 @@ class TLTransactionTest(fixtures.TestBase): @testing.crashes('oracle+cx_oracle', 'intermittent failures on the buildbot') def test_dispose(self): - eng = create_engine(testing.db.url, strategy='threadlocal') + eng = testing_engine(options=dict(strategy='threadlocal')) result = eng.execute(select([1])) eng.dispose() eng.execute(select([1])) @@ -1133,14 +1135,13 @@ class IsolationLevelTest(fixtures.TestBase): def test_engine_param_stays(self): - eng = create_engine(testing.db.url) + eng = testing_engine() isolation_level = eng.dialect.get_isolation_level(eng.connect().connection) level = self._non_default_isolation_level() ne_(isolation_level, level) - eng = create_engine(testing.db.url, - isolation_level=level) + eng = testing_engine(options=dict(isolation_level=level)) eq_( eng.dialect.get_isolation_level(eng.connect().connection), level @@ -1162,12 +1163,12 @@ class IsolationLevelTest(fixtures.TestBase): conn.close() def test_default_level(self): - eng = create_engine(testing.db.url) + eng = testing_engine(options=dict()) isolation_level = eng.dialect.get_isolation_level(eng.connect().connection) eq_(isolation_level, self._default_isolation_level()) def test_reset_level(self): - eng = create_engine(testing.db.url) + eng = testing_engine(options=dict()) conn = eng.connect() eq_(eng.dialect.get_isolation_level(conn.connection), self._default_isolation_level()) @@ -1180,7 +1181,7 @@ class IsolationLevelTest(fixtures.TestBase): conn.close() def test_reset_level_with_setting(self): - eng = create_engine(testing.db.url, isolation_level=self._non_default_isolation_level()) + eng = testing_engine(options=dict(isolation_level=self._non_default_isolation_level())) conn = eng.connect() eq_(eng.dialect.get_isolation_level(conn.connection), self._non_default_isolation_level()) @@ -1193,7 +1194,7 @@ class IsolationLevelTest(fixtures.TestBase): conn.close() def test_invalid_level(self): - eng = create_engine(testing.db.url, isolation_level='FOO') + eng = testing_engine(options=dict(isolation_level='FOO')) assert_raises_message( exc.ArgumentError, "Invalid value '%s' for isolation_level. " @@ -1203,7 +1204,7 @@ class IsolationLevelTest(fixtures.TestBase): def test_per_connection(self): from sqlalchemy.pool import QueuePool - eng = create_engine(testing.db.url, poolclass=QueuePool, pool_size=2, max_overflow=0) + eng = testing_engine(options=dict(poolclass=QueuePool, pool_size=2, max_overflow=0)) c1 = eng.connect() c1 = c1.execution_options(isolation_level=self._non_default_isolation_level()) diff --git a/test/ext/test_horizontal_shard.py b/test/ext/test_horizontal_shard.py index 62b992d2ef..f4f900b95b 100644 --- a/test/ext/test_horizontal_shard.py +++ b/test/ext/test_horizontal_shard.py @@ -6,6 +6,7 @@ from sqlalchemy.orm import * from sqlalchemy.ext.horizontal_shard import ShardedSession from sqlalchemy.sql import operators from test.lib import * +from test.lib.engines import testing_engine from test.lib.testing import eq_ from nose import SkipTest @@ -16,12 +17,12 @@ class ShardTest(fixtures.TestBase): global db1, db2, db3, db4, weather_locations, weather_reports try: - db1 = create_engine('sqlite:///shard1.db', pool_threadlocal=True) + db1 = testing_engine('sqlite:///shard1.db', options=dict(pool_threadlocal=True)) except ImportError: raise SkipTest('Requires sqlite') - db2 = create_engine('sqlite:///shard2.db') - db3 = create_engine('sqlite:///shard3.db') - db4 = create_engine('sqlite:///shard4.db') + db2 = testing_engine('sqlite:///shard2.db') + db3 = testing_engine('sqlite:///shard3.db') + db4 = testing_engine('sqlite:///shard4.db') meta = MetaData() ids = Table('ids', meta, diff --git a/test/lib/engines.py b/test/lib/engines.py index 4794a5fabd..3a5132b8b6 100644 --- a/test/lib/engines.py +++ b/test/lib/engines.py @@ -3,37 +3,55 @@ from collections import deque from test.bootstrap import config from test.lib.util import decorator from sqlalchemy.util import callable -from sqlalchemy import event +from sqlalchemy import event, pool +from sqlalchemy.engine import base as engine_base import re import warnings class ConnectionKiller(object): def __init__(self): self.proxy_refs = weakref.WeakKeyDictionary() + self.testing_engines = weakref.WeakKeyDictionary() + self.conns = set() + + def add_engine(self, engine): + self.testing_engines[engine] = True def checkout(self, dbapi_con, con_record, con_proxy): self.proxy_refs[con_proxy] = True + self.conns.add(dbapi_con) + + def _safe(self, fn): + try: + fn() + except (SystemExit, KeyboardInterrupt): + raise + except Exception, e: + warnings.warn( + "testing_reaper couldn't " + "rollback/close connection: %s" % e) - def _apply_all(self, methods): - # must copy keys atomically + def rollback_all(self): 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',)) + self._safe(rec.rollback) def close_all(self): - self._apply_all(('rollback', 'close')) + for rec in self.proxy_refs.keys(): + if rec is not None: + self._safe(rec._close) + + def _after_test_ctx(self): + for conn in self.conns: + self._safe(conn.rollback) + + def _stop_test_ctx(self): + self.close_all() + for conn in self.conns: + self._safe(conn.close) + self.conns = set() + for rec in self.testing_engines.keys(): + rec.dispose() def assert_all_closed(self): for rec in self.proxy_refs: @@ -134,6 +152,9 @@ def testing_engine(url=None, options=None): options = options or config.db_opts engine = create_engine(url, **options) + if isinstance(engine.pool, pool.QueuePool): + engine.pool._timeout = 0 + engine.pool._max_overflow = 0 event.listen(engine, 'after_execute', asserter.execute) event.listen(engine, 'after_cursor_execute', asserter.cursor_execute) event.listen(engine.pool, 'checkout', testing_reaper.checkout) @@ -141,6 +162,7 @@ def testing_engine(url=None, options=None): # may want to call this, results # in first-connect initializers #engine.connect() + testing_reaper.add_engine(engine) return engine diff --git a/test/lib/testing.py b/test/lib/testing.py index 6512a23d2a..f5babc19c3 100644 --- a/test/lib/testing.py +++ b/test/lib/testing.py @@ -447,7 +447,7 @@ def global_cleanup_assertions(): """ testutil.lazy_gc() - assert not pool._refs + assert not pool._refs, str(pool._refs) def against(*queries): """Boolean predicate, compares to testing database configuration.