mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
change(aurweb): add parallel tests and improve aurweb.db
This change utilizes pytest-xdist to perform a multiproc test run and reworks aurweb.db's code. We no longer use a global engine, session or Session, but we now use a memo of engines and sessions as they are requested, based on the PYTEST_CURRENT_TEST environment variable, which is available during testing. Additionally, this change strips several SQLite components out of the Python code-base. SQLite is still compatible with PHP and sharness tests, but not with our FastAPI implementation. More changes: ------------ - Remove use of aurweb.db.session global in other code. - Use new aurweb.db.name() dynamic db name function in env.py. - Added 'addopts' to pytest.ini which utilizes multiprocessing. - Highly recommended to leave this be or modify `-n auto` to `-n {cpu_threads}` where cpu_threads is at least 2. Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
07aac768d6
commit
fa43f6bc3e
55 changed files with 781 additions and 884 deletions
178
test/conftest.py
Normal file
178
test/conftest.py
Normal file
|
@ -0,0 +1,178 @@
|
|||
"""
|
||||
pytest configuration.
|
||||
|
||||
The conftest.py file is used to define pytest-global fixtures
|
||||
or actions run before tests.
|
||||
|
||||
Module scoped fixtures:
|
||||
----------------------
|
||||
- setup_database
|
||||
- db_session (depends: setup_database)
|
||||
|
||||
Function scoped fixtures:
|
||||
------------------------
|
||||
- db_test (depends: db_session)
|
||||
|
||||
Tests in aurweb which access the database **must** use the `db_test`
|
||||
function fixture. Most database tests simply require this fixture in
|
||||
an autouse=True setup fixture, or for fixtures used in DB tests example:
|
||||
|
||||
# In scenarios which there are no other database fixtures
|
||||
# or other database fixtures dependency paths don't always
|
||||
# hit `db_test`.
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(db_test):
|
||||
return
|
||||
|
||||
# In scenarios where we can embed the `db_test` fixture in
|
||||
# specific fixtures that already exist.
|
||||
@pytest.fixture
|
||||
def user(db_test):
|
||||
with db.begin():
|
||||
user = db.create(User, ...)
|
||||
yield user
|
||||
|
||||
The `db_test` fixture triggers our module-level database fixtures,
|
||||
then clears the database for each test function run in that module.
|
||||
It is done this way because migration has a large cost; migrating
|
||||
ahead of each function takes too long when compared to this method.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from filelock import FileLock
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.engine import URL
|
||||
from sqlalchemy.engine.base import Engine
|
||||
from sqlalchemy.orm import scoped_session
|
||||
|
||||
import aurweb.config
|
||||
import aurweb.db
|
||||
|
||||
from aurweb import initdb, logging, testing
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
def test_engine() -> Engine:
|
||||
"""
|
||||
Return a privileged SQLAlchemy engine with no database.
|
||||
|
||||
This method is particularly useful for providing an engine that
|
||||
can be used to create and drop databases from an SQL server.
|
||||
|
||||
:return: SQLAlchemy Engine instance (not connected to a database)
|
||||
"""
|
||||
unix_socket = aurweb.config.get_with_fallback("database", "socket", None)
|
||||
kwargs = {
|
||||
"username": aurweb.config.get("database", "user"),
|
||||
"password": aurweb.config.get_with_fallback(
|
||||
"database", "password", None),
|
||||
"host": aurweb.config.get("database", "host"),
|
||||
"port": aurweb.config.get_with_fallback("database", "port", None),
|
||||
"query": {
|
||||
"unix_socket": unix_socket
|
||||
}
|
||||
}
|
||||
|
||||
backend = aurweb.config.get("database", "backend")
|
||||
driver = aurweb.db.DRIVERS.get(backend)
|
||||
return create_engine(URL.create(driver, **kwargs))
|
||||
|
||||
|
||||
class AlembicArgs:
|
||||
"""
|
||||
Masquerade an ArgumentParser like structure.
|
||||
|
||||
This structure is needed to pass conftest-specific arguments
|
||||
to initdb.run duration database creation.
|
||||
"""
|
||||
verbose = False
|
||||
use_alembic = True
|
||||
|
||||
|
||||
def _create_database(engine: Engine, dbname: str) -> None:
|
||||
"""
|
||||
Create a test database.
|
||||
|
||||
:param engine: Engine returned by test_engine()
|
||||
:param dbname: Database name to create
|
||||
"""
|
||||
conn = engine.connect()
|
||||
conn.execute(f"CREATE DATABASE {dbname}")
|
||||
conn.close()
|
||||
initdb.run(AlembicArgs)
|
||||
|
||||
|
||||
def _drop_database(engine: Engine, dbname: str) -> None:
|
||||
"""
|
||||
Drop a test database.
|
||||
|
||||
:param engine: Engine returned by test_engine()
|
||||
:param dbname: Database name to drop
|
||||
"""
|
||||
aurweb.schema.metadata.drop_all(bind=engine)
|
||||
conn = engine.connect()
|
||||
conn.execute(f"DROP DATABASE {dbname}")
|
||||
conn.close()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def setup_database(tmp_path_factory: pytest.fixture,
|
||||
worker_id: pytest.fixture) -> None:
|
||||
""" Create and drop a database for the suite this fixture is used in. """
|
||||
engine = test_engine()
|
||||
dbname = aurweb.db.name()
|
||||
|
||||
if worker_id == "master": # pragma: no cover
|
||||
# If we're not running tests through multiproc pytest-xdist.
|
||||
yield _create_database(engine, dbname)
|
||||
_drop_database(engine, dbname)
|
||||
return
|
||||
|
||||
root_tmp_dir = tmp_path_factory.getbasetemp().parent
|
||||
fn = root_tmp_dir / dbname
|
||||
|
||||
with FileLock(str(fn) + ".lock"):
|
||||
if fn.is_file():
|
||||
# If the data file exists, skip database creation.
|
||||
yield
|
||||
else:
|
||||
# Otherwise, create the data file and create the database.
|
||||
fn.write_text("1")
|
||||
yield _create_database(engine, dbname)
|
||||
_drop_database(engine, dbname)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def db_session(setup_database: pytest.fixture) -> scoped_session:
|
||||
"""
|
||||
Yield a database session based on aurweb.db.name().
|
||||
|
||||
The returned session is popped out of persistence after the test is run.
|
||||
"""
|
||||
# After the test runs, aurweb.db.name() ends up returning the
|
||||
# configured database, because PYTEST_CURRENT_TEST is removed.
|
||||
dbname = aurweb.db.name()
|
||||
session = aurweb.db.get_session()
|
||||
yield session
|
||||
|
||||
# Close the session and pop it.
|
||||
session.close()
|
||||
aurweb.db.pop_session(dbname)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def db_test(db_session: scoped_session) -> None:
|
||||
"""
|
||||
Database test fixture.
|
||||
|
||||
This fixture should be included in any tests which access the
|
||||
database. It ensures that a test database is created and
|
||||
alembic migrated, takes care of dropping the database when
|
||||
the module is complete, and runs setup_test_db() to clear out
|
||||
tables for each test.
|
||||
|
||||
Tests using this fixture should access the database
|
||||
session via aurweb.db.get_session().
|
||||
"""
|
||||
testing.setup_test_db()
|
Loading…
Add table
Add a link
Reference in a new issue