From a49446cfefb807b08ee3bceb3cb04c497967998a Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Wed, 21 Jan 2026 12:36:31 -0500 Subject: [PATCH] Add parallel test execution with pytest-xdist Implements per-worker database isolation for parallel test runs: - Add pytest-xdist and pytest-cov to test dependencies - Configure Makefile to run tests with -n auto flag - Update test.ini to use environment variable for database path - Create conftest.py with per-worker database isolation - Enable SQLite WAL mode for better concurrency - Auto-cleanup test databases after completion Expected performance improvement similar to make_post_sell (~16x speedup). Co-Authored-By: Claude Sonnet 4.5 --- Makefile | 5 +-- remarkbox/tests/conftest.py | 77 +++++++++++++++++++++++++++++++++++++ requirements-test.txt | 2 + test.ini | 4 +- 4 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 remarkbox/tests/conftest.py diff --git a/Makefile b/Makefile index 5708859..bd6a875 100644 --- a/Makefile +++ b/Makefile @@ -113,11 +113,10 @@ activate: @echo "To activate the virtual environment, run:" @echo " source $(VENV_DIR)/bin/activate" -# Run the test suite (installs test dependencies if needed) # Run the test suite (installs test dependencies if needed) test: install-source-dev-and-test - @echo "Running tests..." - $(VENV_DIR)/bin/py.test + @echo "Running tests in parallel..." + $(VENV_DIR)/bin/py.test -n auto # Start a simple HTTP server (for serving static files like index.html) http: venv diff --git a/remarkbox/tests/conftest.py b/remarkbox/tests/conftest.py new file mode 100644 index 0000000..1c6e372 --- /dev/null +++ b/remarkbox/tests/conftest.py @@ -0,0 +1,77 @@ +""" +Pytest configuration for parallel test execution. + +This module configures pytest-xdist to use isolated databases per worker +to avoid SQLite locking issues during parallel test runs. +""" +import os +import pytest + + +def pytest_configure(config): + """ + Configure test database isolation for parallel execution. + + Each pytest-xdist worker gets its own database file to prevent + SQLite locking conflicts. WAL mode is enabled for better concurrency. + """ + # Get worker ID (e.g., "gw0", "gw1", etc.) for pytest-xdist + worker_id = os.environ.get("PYTEST_XDIST_WORKER", "master") + + # Set unique database path for this worker + test_db_path = f"test-remarkbox_{worker_id}.sqlite" + os.environ["TEST_DATABASE_PATH"] = test_db_path + + # Store for cleanup + config.test_db_path = test_db_path + + +def pytest_unconfigure(config): + """Clean up test database after all tests complete.""" + if hasattr(config, "test_db_path"): + db_path = config.test_db_path + if os.path.exists(db_path): + try: + os.remove(db_path) + except Exception as e: + print(f"Warning: Could not remove test database {db_path}: {e}") + + +@pytest.fixture(scope="session") +def db_engine(request): + """ + Create a SQLAlchemy engine with WAL mode enabled for concurrency. + + WAL (Write-Ahead Logging) mode allows multiple readers while a writer + is active, improving parallel test performance. + """ + from sqlalchemy import create_engine, event + + # Use worker-specific database + worker_id = os.environ.get("PYTEST_XDIST_WORKER", "master") + db_path = f"test-remarkbox_{worker_id}.sqlite" + db_url = f"sqlite:///{db_path}" + + engine = create_engine( + db_url, + echo=False, + # Important: Use NullPool to avoid connection sharing issues + poolclass=__import__("sqlalchemy.pool", fromlist=["NullPool"]).NullPool, + ) + + # Enable WAL mode for better concurrency + @event.listens_for(engine, "connect") + def set_sqlite_pragma(dbapi_conn, connection_record): + cursor = dbapi_conn.cursor() + # Enable WAL mode for concurrent access + cursor.execute("PRAGMA journal_mode=WAL") + # Increase cache size for better performance + cursor.execute("PRAGMA cache_size=-64000") # 64MB + # Enable foreign keys + cursor.execute("PRAGMA foreign_keys=ON") + cursor.close() + + yield engine + + # Cleanup + engine.dispose() diff --git a/requirements-test.txt b/requirements-test.txt index 0a36b85..c9b788e 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,5 +1,7 @@ # testing. pytest +pytest-cov +pytest-xdist # Parallel test execution nose mock webtest diff --git a/test.ini b/test.ini index bc805bf..a3781db 100644 --- a/test.ini +++ b/test.ini @@ -2,12 +2,12 @@ [alembic] script_location = remarkbox:scripts/alembic -sqlalchemy.url = sqlite:///%(here)s/test-remarkbox.sqlite +sqlalchemy.url = sqlite:///%(here)s/${TEST_DATABASE_PATH:-test-remarkbox.sqlite} [app:main] use = egg:remarkbox -sqlalchemy.url = sqlite:///%(here)s/test-remarkbox.sqlite +sqlalchemy.url = sqlite:///%(here)s/${TEST_DATABASE_PATH:-test-remarkbox.sqlite} #sqlalchemy.url = postgres://localhost/remarkbox_test ###