forked from hans/Nominatim
organise python tests in subdirectories
The directories follow the same structure as the modules in nominatim/.
This commit is contained in:
107
test/python/db/test_async_connection.py
Normal file
107
test/python/db/test_async_connection.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
Tests for function providing a non-blocking query interface towards PostgreSQL.
|
||||
"""
|
||||
from contextlib import closing
|
||||
import concurrent.futures
|
||||
|
||||
import pytest
|
||||
import psycopg2
|
||||
|
||||
from nominatim.db.async_connection import DBConnection, DeadlockHandler
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def conn(temp_db):
|
||||
with closing(DBConnection('dbname=' + temp_db)) as connection:
|
||||
yield connection
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def simple_conns(temp_db):
|
||||
conn1 = psycopg2.connect('dbname=' + temp_db)
|
||||
conn2 = psycopg2.connect('dbname=' + temp_db)
|
||||
|
||||
yield conn1.cursor(), conn2.cursor()
|
||||
|
||||
conn1.close()
|
||||
conn2.close()
|
||||
|
||||
|
||||
def test_simple_query(conn, temp_db_conn):
|
||||
conn.connect()
|
||||
|
||||
conn.perform('CREATE TABLE foo (id INT)')
|
||||
conn.wait()
|
||||
|
||||
temp_db_conn.table_exists('foo')
|
||||
|
||||
|
||||
def test_wait_for_query(conn):
|
||||
conn.connect()
|
||||
|
||||
conn.perform('SELECT pg_sleep(1)')
|
||||
|
||||
assert not conn.is_done()
|
||||
|
||||
conn.wait()
|
||||
|
||||
|
||||
def test_bad_query(conn):
|
||||
conn.connect()
|
||||
|
||||
conn.perform('SELECT efasfjsea')
|
||||
|
||||
with pytest.raises(psycopg2.ProgrammingError):
|
||||
conn.wait()
|
||||
|
||||
|
||||
def test_bad_query_ignore(temp_db):
|
||||
with closing(DBConnection('dbname=' + temp_db, ignore_sql_errors=True)) as conn:
|
||||
conn.connect()
|
||||
|
||||
conn.perform('SELECT efasfjsea')
|
||||
|
||||
conn.wait()
|
||||
|
||||
|
||||
def exec_with_deadlock(cur, sql, detector):
|
||||
with DeadlockHandler(lambda *args: detector.append(1)):
|
||||
cur.execute(sql)
|
||||
|
||||
|
||||
def test_deadlock(simple_conns):
|
||||
cur1, cur2 = simple_conns
|
||||
|
||||
cur1.execute("""CREATE TABLE t1 (id INT PRIMARY KEY, t TEXT);
|
||||
INSERT into t1 VALUES (1, 'a'), (2, 'b')""")
|
||||
cur1.connection.commit()
|
||||
|
||||
cur1.execute("UPDATE t1 SET t = 'x' WHERE id = 1")
|
||||
cur2.execute("UPDATE t1 SET t = 'x' WHERE id = 2")
|
||||
|
||||
# This is the tricky part of the test. The first SQL command runs into
|
||||
# a lock and blocks, so we have to run it in a separate thread. When the
|
||||
# second deadlocking SQL statement is issued, Postgresql will abort one of
|
||||
# the two transactions that cause the deadlock. There is no way to tell
|
||||
# which one of the two. Therefore wrap both in a DeadlockHandler and
|
||||
# expect that exactly one of the two triggers.
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
||||
deadlock_check = []
|
||||
try:
|
||||
future = executor.submit(exec_with_deadlock, cur2,
|
||||
"UPDATE t1 SET t = 'y' WHERE id = 1",
|
||||
deadlock_check)
|
||||
|
||||
while not future.running():
|
||||
pass
|
||||
|
||||
|
||||
exec_with_deadlock(cur1, "UPDATE t1 SET t = 'y' WHERE id = 2",
|
||||
deadlock_check)
|
||||
finally:
|
||||
# Whatever happens, make sure the deadlock gets resolved.
|
||||
cur1.connection.rollback()
|
||||
|
||||
future.result()
|
||||
|
||||
assert len(deadlock_check) == 1
|
||||
Reference in New Issue
Block a user