Skip to content

Commit d44f4d3

Browse files
committed
Extend and complete the test suite
1 parent 4283fc8 commit d44f4d3

6 files changed

Lines changed: 739 additions & 58 deletions

File tree

tests/test_persistent_db.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,40 @@ def test_no_threadsafety(dbapi, threadsafety):
3333
PersistentDB(dbapi)
3434

3535

36+
def test_creator_function(dbapi):
37+
"""Check that a creator function can be used instead of a module."""
38+
persist = PersistentDB(dbapi.connect, database='ok')
39+
# the threadsafety cannot be determined from a plain creator function,
40+
# so the connections are optimistically assumed to be thread-safe
41+
db = persist.connection()
42+
assert db.threadsafety() == dbapi.threadsafety
43+
cursor = db.cursor()
44+
cursor.execute('select test')
45+
assert cursor.fetchone() == 'test'
46+
47+
48+
def test_creator_without_threadsafety(dbapi):
49+
"""Check that a creator that hides its threadsafety is rejected."""
50+
51+
class Creator:
52+
"""A database module that does not report its threadsafety."""
53+
54+
connect = staticmethod(dbapi.connect)
55+
56+
with pytest.raises(NotSupportedError):
57+
PersistentDB(Creator)
58+
59+
60+
def test_creator_with_unsafe_connections(dbapi, monkeypatch):
61+
"""Check that connections that are not thread-safe are rejected."""
62+
monkeypatch.delattr(dbapi, 'threadsafety')
63+
# a creator function is optimistically assumed to provide thread-safe
64+
# connections, but the connections themselves know better
65+
persist = PersistentDB(dbapi.connect)
66+
with pytest.raises(NotSupportedError):
67+
persist.connection()
68+
69+
3670
@pytest.mark.parametrize("closeable", [False, True])
3771
def test_close(dbapi, closeable):
3872
"""Check that closing is only allowed when the connection is closeable."""

tests/test_pooled_db.py

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,42 @@ def test_no_threadsafety(dbapi, threadsafety):
4040
PooledDB(dbapi)
4141

4242

43+
def test_creator_function(dbapi):
44+
"""Check that a creator function can be used instead of a module."""
45+
pool = PooledDB(dbapi.connect, 1, database='ok')
46+
# the threadsafety cannot be determined from a plain creator function,
47+
# so the connections are optimistically assumed to be thread-safe,
48+
# but not safe enough to be shared between several threads
49+
assert pool._maxshared == 0
50+
db = pool.connection()
51+
assert db.threadsafety() == dbapi.threadsafety
52+
cursor = db.cursor()
53+
cursor.execute('select test')
54+
assert cursor.fetchone() == 'test'
55+
56+
57+
def test_creator_without_threadsafety(dbapi):
58+
"""Check that a creator that hides its threadsafety is rejected."""
59+
60+
class Creator:
61+
"""A database module that does not report its threadsafety."""
62+
63+
connect = staticmethod(dbapi.connect)
64+
65+
with pytest.raises(NotSupportedError):
66+
PooledDB(Creator)
67+
68+
69+
def test_creator_with_unsafe_connections(dbapi, monkeypatch):
70+
"""Check that connections that are not thread-safe are rejected."""
71+
monkeypatch.delattr(dbapi, 'threadsafety')
72+
# the pool optimistically assumes that a creator function provides
73+
# thread-safe connections, but the connections themselves know better
74+
pool = PooledDB(dbapi.connect, 0)
75+
with pytest.raises(NotSupportedError):
76+
pool.connection()
77+
78+
4379
@pytest.mark.parametrize("threadsafety", [1, 2, 3])
4480
def test_threadsafety(dbapi, threadsafety):
4581
"""Check that connections are only shared when they may be."""
@@ -470,6 +506,8 @@ def test_unshare_connection(dbapi, threadsafety):
470506
# every round takes the given number of connections out of the pool
471507
# and expects the given number of idle connections after releasing them
472508
(3, 0, 3, [(3, 3), (6, 6)]), # unlimited cache grows with the demand
509+
(3, None, 3, [(3, 3), (6, 6)]), # None is the same as zero here
510+
(None, None, 0, [(3, 3), (6, 6)]), # and also when nothing is cached
473511
(0, 3, 0, [(3, 3), (6, 3)]), # cache fills up to maxcached
474512
(3, 3, 3, [(3, 3), (6, 3)]), # cache stays at the common bound
475513
(3, 2, 3, [(4, 3)]), # mincached wins when it exceeds maxcached
@@ -959,11 +997,12 @@ def test_maxconnections_equal_to_maxshared(dbapi, threadsafety):
959997

960998

961999
@pytest.mark.parametrize("threadsafety", [1, 2])
962-
def test_maxconnections_unlimited(dbapi, threadsafety):
1000+
@pytest.mark.parametrize("maxconnections", [0, None])
1001+
def test_maxconnections_unlimited(dbapi, threadsafety, maxconnections):
9631002
"""Check that the number of connections is unlimited by default."""
9641003
dbapi.threadsafety = threadsafety
9651004
shareable = threadsafety > 1
966-
pool = PooledDB(dbapi, 0, 0, 3)
1005+
pool = PooledDB(dbapi, 0, 0, 3, maxconnections)
9671006
assert pool._maxconnections == 0
9681007
assert pool._connections == 0
9691008
cache = []
@@ -1312,6 +1351,34 @@ def test_shared_in_transaction(dbapi):
13121351
pool.connection()
13131352

13141353

1354+
def test_shared_in_transaction_blocking(dbapi):
1355+
"""Check that a thread waits for a shared connection in a transaction."""
1356+
pool = PooledDB(dbapi, 0, 0, 1, 0, True)
1357+
db = pool.connection()
1358+
con = db._con
1359+
db.begin()
1360+
# the only connection that may be shared is in a transaction now
1361+
shared = []
1362+
1363+
def connection():
1364+
shared.append(pool.connection())
1365+
1366+
thread = Thread(target=connection)
1367+
thread.start()
1368+
thread.join(0.1)
1369+
# the thread cannot share that connection and blocks instead of failing
1370+
assert thread.is_alive()
1371+
assert not shared
1372+
db.commit()
1373+
# the thread is woken up when a connection is put back into the pool,
1374+
# and then finds the connection shareable again
1375+
pool.dedicated_connection().close()
1376+
thread.join(0.1)
1377+
assert not thread.is_alive()
1378+
assert len(shared) == 1
1379+
assert shared[0]._con is con
1380+
1381+
13151382
def test_shared_in_transaction_with_two_connections(dbapi):
13161383
"""Check that sharing prefers connections without a transaction."""
13171384
pool = PooledDB(dbapi, 0, 2, 2)
@@ -1445,6 +1512,19 @@ def test_shared_db_connection_compare(dbapi):
14451512
assert con1 > con2
14461513

14471514

1515+
def test_shared_db_connection_hash(dbapi):
1516+
"""Check that shared connections stay hashable."""
1517+
# defining __eq__ would otherwise make the class unhashable
1518+
con = SharedDBConnection(dbapi.connect())
1519+
hashed = hash(con)
1520+
assert hash(con) == hashed
1521+
# the hash is derived from the underlying connection and the shares
1522+
con.share()
1523+
assert hash(con) != hashed
1524+
con.unshare()
1525+
assert hash(con) == hashed
1526+
1527+
14481528
def timeout_is_not_fatal(error):
14491529
"""Treat a deliberate server side timeout as not fatal."""
14501530
return not error.args or error.args[0] != 3024

tests/test_pooled_pg.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ def test_close_connection():
126126
# every round takes the given number of connections out of the pool
127127
# and expects the given number of cached connections after closing them
128128
(3, 0, 3, [(3, 3), (6, 6)]), # unlimited cache grows with the demand
129+
(3, None, 3, [(3, 3), (6, 6)]), # None is the same as zero here
130+
(None, None, 0, [(3, 3), (6, 6)]), # and also when nothing is cached
129131
(3, 4, 3, [(3, 3), (6, 4)]), # cache fills up to maxcached
130132
(3, 2, 3, [(4, 3)]), # mincached wins when it exceeds maxcached
131133
(2, 5, 2, [(10, 5)]), # cache grows from mincached to maxcached
@@ -156,6 +158,15 @@ def test_max_connections():
156158
assert cache
157159

158160

161+
def test_max_connections_unlimited():
162+
"""Check that the number of connections is unlimited by default."""
163+
pool = PooledPg(0, 0, None)
164+
assert pool._connections is None
165+
cache = [pool.connection() for _i in range(10)]
166+
assert pool._cache.qsize() == 0
167+
assert cache
168+
169+
159170
def test_max_connections_one():
160171
"""Check that a pool can be limited to a single connection."""
161172
pool = PooledPg(0, 1, 1, False)
@@ -327,6 +338,38 @@ def test_reset_transaction_completely():
327338
assert con.num_queries == 0
328339

329340

341+
def test_reopen_connection():
342+
"""Check that a pooled connection in use can be reopened."""
343+
pool = PooledPg(1)
344+
db = pool.connection()
345+
con = db._con
346+
db.query('select test')
347+
assert con.num_queries == 1
348+
# the underlying connection is reopened, which resets the counter
349+
db.reopen()
350+
assert db._con is con
351+
assert con.num_queries == 0
352+
assert db.query('select test') == 'test'
353+
assert con.num_queries == 1
354+
355+
356+
def test_reopen_connection_after_closing():
357+
"""Check that a connection that is back in the pool can be reopened."""
358+
pool = PooledPg(1)
359+
db = pool.connection()
360+
con = db._con
361+
db.close()
362+
assert db._con is None
363+
# the connection cannot be used any more when it has been closed
364+
with pytest.raises(InvalidConnectionError):
365+
db.query # noqa: B018
366+
# but reopening takes another connection out of the pool
367+
db.reopen()
368+
assert db._con is not None
369+
assert db.query('select test') == 'test'
370+
assert con.num_queries == 1
371+
372+
330373
def test_context_manager():
331374
"""Check that the connection can be used as a context manager."""
332375
pool = PooledPg(1, 1, 1)

tests/test_simple_pooled_db.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ def test_no_threadsafety(threadsafety):
4747
my_db_pool(threadsafety, 1)
4848

4949

50+
def test_no_threadsafety_attribute(monkeypatch):
51+
"""Check that a module hiding its threadsafety is rejected."""
52+
monkeypatch.delattr(dbapi, 'threadsafety')
53+
with pytest.raises(simple_pooled_db.NotSupportedError):
54+
simple_pooled_db.PooledDB(
55+
dbapi, 1, 'SimplePooledDBTestDB', 'SimplePooledDBTestUser')
56+
57+
5058
@pytest.mark.parametrize("threadsafety", [1, 2, 3])
5159
def test_create_connection(threadsafety):
5260
"""Check that the pool creates a usable connection."""

0 commit comments

Comments
 (0)