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 <noreply@anthropic.com>
This commit is contained in:
parent
06ba106795
commit
a49446cfef
4 changed files with 83 additions and 5 deletions
5
Makefile
5
Makefile
|
|
@ -113,11 +113,10 @@ activate:
|
||||||
@echo "To activate the virtual environment, run:"
|
@echo "To activate the virtual environment, run:"
|
||||||
@echo " source $(VENV_DIR)/bin/activate"
|
@echo " source $(VENV_DIR)/bin/activate"
|
||||||
|
|
||||||
# Run the test suite (installs test dependencies if needed)
|
|
||||||
# Run the test suite (installs test dependencies if needed)
|
# Run the test suite (installs test dependencies if needed)
|
||||||
test: install-source-dev-and-test
|
test: install-source-dev-and-test
|
||||||
@echo "Running tests..."
|
@echo "Running tests in parallel..."
|
||||||
$(VENV_DIR)/bin/py.test
|
$(VENV_DIR)/bin/py.test -n auto
|
||||||
|
|
||||||
# Start a simple HTTP server (for serving static files like index.html)
|
# Start a simple HTTP server (for serving static files like index.html)
|
||||||
http: venv
|
http: venv
|
||||||
|
|
|
||||||
77
remarkbox/tests/conftest.py
Normal file
77
remarkbox/tests/conftest.py
Normal file
|
|
@ -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()
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
# testing.
|
# testing.
|
||||||
pytest
|
pytest
|
||||||
|
pytest-cov
|
||||||
|
pytest-xdist # Parallel test execution
|
||||||
nose
|
nose
|
||||||
mock
|
mock
|
||||||
webtest
|
webtest
|
||||||
|
|
|
||||||
4
test.ini
4
test.ini
|
|
@ -2,12 +2,12 @@
|
||||||
|
|
||||||
[alembic]
|
[alembic]
|
||||||
script_location = remarkbox:scripts/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]
|
[app:main]
|
||||||
use = egg:remarkbox
|
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
|
#sqlalchemy.url = postgres://localhost/remarkbox_test
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue