Skip to content

Commit 090352f

Browse files
committed
Add postgresql support to test suite
1 parent 2859ee1 commit 090352f

File tree

2 files changed

+84
-21
lines changed

2 files changed

+84
-21
lines changed

sogs/db.py

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -587,24 +587,17 @@ def create_admin_user(dbconn):
587587
)
588588

589589

590-
if config.DB_URL.startswith('postgresql'):
591-
# rooms.token is a 'citext' (case-insensitive text), which sqlalchemy doesn't recognize out of
592-
# the box. Map it to a plain TEXT which is good enough for what we need (if we actually needed
593-
# to generate this wouldn't suffice: we'd have to use something like the sqlalchemy-citext
594-
# module).
595-
from sqlalchemy.dialects.postgresql.base import ischema_names
596-
597-
if 'citext' not in ischema_names:
598-
ischema_names['citext'] = ischema_names['text']
599-
600-
601590
engine, engine_initial_pid, metadata = None, None, None
602591

603592

604593
def _init_engine(*args, **kwargs):
605594
"""
606595
Initializes or reinitializes db.engine. (Only the test suite should be calling this externally
607596
to reinitialize).
597+
598+
Arguments:
599+
sogs_preinit - a callable to invoke after setting up `engine` but before calling
600+
`database_init()`.
608601
"""
609602
global engine, engine_initial_pid, metadata, have_returning
610603

@@ -616,9 +609,17 @@ def _init_engine(*args, **kwargs):
616609
return
617610
args = (config.DB_URL,)
618611

619-
# Disable *sqlalchemy*-level autocommit, which works so badly that it got completely removed in
620-
# 2.0. (We put the actual sqlite into driver-level autocommit mode below).
621-
engine = sqlalchemy.create_engine(*args, **kwargs).execution_options(autocommit=False)
612+
preinit = kwargs.pop('sogs_preinit', None)
613+
614+
exec_opts_args = {}
615+
if args[0].startswith('postgresql'):
616+
exec_opts_args['isolation_level'] = 'READ COMMITTED'
617+
else:
618+
# SQLite's Python code is seriously broken, so we have to force off autocommit mode and turn
619+
# on driver-level autocommit (which we do below).
620+
exec_opts_args['autocommit'] = False
621+
622+
engine = sqlalchemy.create_engine(*args, **kwargs).execution_options(**exec_opts_args)
622623
engine_initial_pid = os.getpid()
623624
metadata = sqlalchemy.MetaData()
624625

@@ -651,6 +652,18 @@ def do_begin(conn):
651652
else:
652653
have_returning = True
653654

655+
# rooms.token is a 'citext' (case-insensitive text), which sqlalchemy doesn't recognize out
656+
# of the box. Map it to a plain TEXT which is good enough for what we need (if we actually
657+
# needed to generate this wouldn't suffice: we'd have to use something like the
658+
# sqlalchemy-citext module).
659+
from sqlalchemy.dialects.postgresql.base import ischema_names
660+
661+
if 'citext' not in ischema_names:
662+
ischema_names['citext'] = ischema_names['text']
663+
664+
if preinit:
665+
preinit()
666+
654667
database_init()
655668

656669

tests/conftest.py

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,25 @@
1414
sogs.omq.test_suite = True
1515

1616

17+
def pgsql_url(arg):
18+
if not arg.startswith('postgresql:'):
19+
raise ValueError("Invalid postgresql url; see SQLAlchemy postgresql docs")
20+
return arg
21+
22+
1723
def pytest_addoption(parser):
1824
parser.addoption(
1925
"--sql-tracing", action="store_true", default=False, help="Log all SQL queries"
2026
)
27+
parser.addoption(
28+
"--pgsql", type=pgsql_url, help="Use the given postgresql database url for testing"
29+
)
30+
parser.addoption(
31+
"--pgsql-no-drop-schema",
32+
action="store_true",
33+
default=False,
34+
help="Don't clean up the final test schema; typically used with --maxfail=1",
35+
)
2136

2237

2338
@pytest.fixture
@@ -40,28 +55,63 @@ def db(request):
4055
"""
4156

4257
trace = request.config.getoption("--sql-tracing")
58+
pgsql = request.config.getoption("--pgsql")
4359

4460
from sogs import db as db_
4561

4662
global db_counter_
4763
db_counter_ += 1
48-
sqlite_uri = f'file:sogs_testdb{db_counter_}?mode=memory&cache=shared'
4964

50-
web.app.logger.warning(f"using sqlite {sqlite_uri}")
65+
if pgsql:
66+
web.app.logger.warning(f"using postgresql {pgsql}")
67+
68+
first = True
69+
70+
def pg_setup_schema():
71+
# Run everything in a separate schema that we can easily drop when done
72+
from sqlalchemy import event
73+
74+
@event.listens_for(db_.engine, "connect", insert=True)
75+
def setup_schema(dbapi_connection, connection_record):
76+
existing_autocommit = dbapi_connection.autocommit
77+
dbapi_connection.autocommit = True
78+
79+
cursor = dbapi_connection.cursor()
80+
nonlocal first
81+
if first:
82+
cursor.execute("DROP SCHEMA IF EXISTS sogs_tests CASCADE")
83+
first = False
84+
cursor.execute("CREATE SCHEMA IF NOT EXISTS sogs_tests")
85+
cursor.execute("SET search_path TO sogs_tests")
86+
cursor.close()
87+
88+
dbapi_connection.autocommit = existing_autocommit
89+
90+
db_._init_engine(pgsql, echo=trace, sogs_preinit=pg_setup_schema)
91+
92+
else:
93+
sqlite_uri = f'file:sogs_testdb{db_counter_}?mode=memory&cache=shared'
94+
95+
web.app.logger.warning(f"using sqlite {sqlite_uri}")
96+
97+
def sqlite_connect():
98+
import sqlite3
5199

52-
def sqlite_connect():
53-
import sqlite3
100+
web.app.logger.warning(f"connecting to {sqlite_uri}")
101+
return sqlite3.connect(sqlite_uri, uri=True)
54102

55-
web.app.logger.warning(f"connecting to {sqlite_uri}")
56-
return sqlite3.connect(sqlite_uri, uri=True)
103+
db_._init_engine("sqlite://", creator=sqlite_connect, echo=trace)
57104

58-
db_._init_engine("sqlite://", creator=sqlite_connect, echo=trace)
105+
db_.database_init()
59106

60107
web.appdb = db_.get_conn()
61108

62109
yield db_
63110

64111
web.app.logger.warning("closing db")
112+
if pgsql and not request.config.getoption("--pgsql-no-drop-schema"):
113+
web.app.logger.critical("DROPPING SCHEMA")
114+
db_.query("DROP SCHEMA sogs_tests CASCADE")
65115
web.appdb.close()
66116

67117

0 commit comments

Comments
 (0)