]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- hardcore force every connection into a strong-referenced set, rollback on every...
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 26 Apr 2011 01:50:26 +0000 (21:50 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 26 Apr 2011 01:50:26 +0000 (21:50 -0400)
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

test/aaa_profiling/test_zoomark.py
test/aaa_profiling/test_zoomark_orm.py
test/bootstrap/config.py
test/bootstrap/noseplugin.py
test/engine/test_execute.py
test/engine/test_pool.py
test/engine/test_reconnect.py
test/engine/test_transaction.py
test/ext/test_horizontal_shard.py
test/lib/engines.py
test/lib/testing.py

index 2ac9aa632d1410e1f495e961e4983c7ae7282e5f..304453a640db186b2f82a53c45754c98d9e88e85 100644 (file)
@@ -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),
 
index 3363a6094a4eaa44ed1ae5b59876bbb4d4dde1f0..507121abde44c0fbd45d551f6c946982b1e0ec86 100644 (file)
@@ -27,6 +27,7 @@ class ZooMarkTest(fixtures.TestBase):
 
     """
 
+    __requires__ = 'cpython',
     __only_on__ = 'postgresql+psycopg2'
     __skip_if__ = lambda : sys.version_info < (2, 5),
 
index 3905075bd84bc536b6712e1b686ae3827070c745..e1a32c5b48db28a8b828aa98e74bff543a14d240 100644 (file)
@@ -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:
index 156a18514fcb733aec547999e51f375a367227d6..c43e81f705d781e0803152e763c20e7de5e5d582 100644 (file)
@@ -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):
index 51b2bbd1416c256e54ea06086b6aafe7e88e54ab..9cca5fc2cdc6e5afe52976ef42c64adedf41aac2 100644 (file)
@@ -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)
 
index 67252865c69f96ca4297d35a4e1dae2e0ed629dd..553bc9d85f56cd59b8a5df62f25111e8beb82a4d 100644 (file)
@@ -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)
index 44fb4f93bc690d0363f1f66dc7d9ce435f897514..e945cc692f3506cb7bae048ca379841a38f2c065 100644 (file)
@@ -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()
index 9b9026732158922c450db8ee2932fdd70e249da9..4d656885872a93659267ad309cc9e8250d5ab539 100644 (file)
@@ -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())
index 62b992d2ef00e6b9c34a2400a604526df308ca69..f4f900b95ba50d9be93c4357c83cdfa65ee944f6 100644 (file)
@@ -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,
index 4794a5fabdaa2e9a4bce9b4401c03a0e429a1599..3a5132b8b6f9c26c9772166778f1756140ec27e8 100644 (file)
@@ -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
 
index 6512a23d2a921e2de84d0a1a4060bf0f3794359c..f5babc19c313300c3a83ed54c414c3d03ead70c4 100644 (file)
@@ -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.