Keep the browser tests' database in a file, a connection per request (#1024) #1074

Merged
tiagoagueda merged 1 commit from 1024-live-server-db into main 2026-10-29 10:18:31 +00:00
Owner

On an in-memory SQLite database pytest-django's live server hands the test
thread's one connection to every request thread, and every request is a
transaction (ATOMIC_REQUESTS). Requests in flight at once tangled their
transactions in that one wrapper's bookkeeping: a commit took away another
request's savepoint, and the browser job failed at random with "no such
savepoint" and then a TransactionManagementError on every request and
teardown after it.

A run with an e2e test in it now gives the test database a file of its own
(django_db_modify_db_settings in tests/conftest.py), one per xdist worker in
a temporary directory removed at the end, opened with the instance's own
options (IMMEDIATE transactions, WAL, a 20-second busy timeout; #206), so a
second writer waits its turn. pytest-django shares only in-memory
connections, so each request thread connects for itself. The live server
now waits for the test database (tests/e2e/conftest.py): the font check
started it first, while the connection still named :memory:, and it shared
the connection all the same. The unit suite and a PostgreSQL run are left
as they were.

tests/e2e/test_live_server_connections.py plays the failing order out with
three requests and a pause held open between SAVEPOINT and Django noting it
down; it failed with the CI's errors before the change and passes after.

Closes #1024

Notes for review

  • Root cause: pytest-django builds connections_override when the live server starts. The autouse font check started the server before django_db_setup, while the database was still :memory:, so every request thread used the test thread's own connection. With ATOMIC_REQUESTS, the savepoints of overlapping requests got released by the wrong thread: "no such savepoint", then a TransactionManagementError cascade (1,829 errors in one CI run).

  • Fix:

    • When a run contains browser tests, the test database is a file, one per xdist worker, with the production SQLite options (IMMEDIATE, WAL, 20 s timeout). It is removed at the end.
    • live_server now depends on django_db_setup, so the override is empty.
    • Unit runs and PostgreSQL runs are untouched.
  • Regression test: tests/e2e/test_live_server_connections.py reproduces the failure deterministically (failed before the fix, passes after) and asserts the override is empty.

  • Timings, each file alone, run twice, in seconds:

    File Before After
    test_reflow 180 / 149 121 / 118
    test_every_page 443 / 342 317 / 343
    test_accessibility 91 / 111 88 / 83
  • Not yet run: with -n, where the per-worker file is reasoned from pytest-django's source. That arrives with #1033's second worker.

🤖 Generated with Claude Code

On an in-memory SQLite database pytest-django's live server hands the test thread's one connection to every request thread, and every request is a transaction (ATOMIC_REQUESTS). Requests in flight at once tangled their transactions in that one wrapper's bookkeeping: a commit took away another request's savepoint, and the browser job failed at random with "no such savepoint" and then a TransactionManagementError on every request and teardown after it. A run with an e2e test in it now gives the test database a file of its own (django_db_modify_db_settings in tests/conftest.py), one per xdist worker in a temporary directory removed at the end, opened with the instance's own options (IMMEDIATE transactions, WAL, a 20-second busy timeout; #206), so a second writer waits its turn. pytest-django shares only in-memory connections, so each request thread connects for itself. The live server now waits for the test database (tests/e2e/conftest.py): the font check started it first, while the connection still named :memory:, and it shared the connection all the same. The unit suite and a PostgreSQL run are left as they were. tests/e2e/test_live_server_connections.py plays the failing order out with three requests and a pause held open between SAVEPOINT and Django noting it down; it failed with the CI's errors before the change and passes after. Closes #1024 ## Notes for review - **Root cause:** pytest-django builds `connections_override` when the live server starts. The autouse font check started the server before `django_db_setup`, while the database was still `:memory:`, so every request thread used the test thread's own connection. With `ATOMIC_REQUESTS`, the savepoints of overlapping requests got released by the wrong thread: "no such savepoint", then a `TransactionManagementError` cascade (1,829 errors in one CI run). - **Fix:** - When a run contains browser tests, the test database is a file, one per xdist worker, with the production SQLite options (IMMEDIATE, WAL, 20 s timeout). It is removed at the end. - `live_server` now depends on `django_db_setup`, so the override is empty. - Unit runs and PostgreSQL runs are untouched. - **Regression test:** `tests/e2e/test_live_server_connections.py` reproduces the failure deterministically (failed before the fix, passes after) and asserts the override is empty. - **Timings, each file alone, run twice, in seconds:** | File | Before | After | |---|---|---| | `test_reflow` | 180 / 149 | 121 / 118 | | `test_every_page` | 443 / 342 | 317 / 343 | | `test_accessibility` | 91 / 111 | 88 / 83 | - **Not yet run:** with `-n`, where the per-worker file is reasoned from pytest-django's source. That arrives with #1033's second worker. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Keep the browser tests' database in a file, a connection per request (#1024)
Some checks failed
CI / Unit tests and coverage (Python 3.14) (pull_request) Successful in 13m33s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 3m20s
CI / Database tests on PostgreSQL (pull_request) Successful in 4m17s
CI / Security audit (pull_request) Successful in 1m10s
CI / Browser tests (Chromium) (pull_request) Failing after 36m22s
CI / Unit tests and coverage (Python 3.14) (push) Successful in 13m16s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 2m59s
CI / Database tests on PostgreSQL (push) Successful in 4m14s
CI / Security audit (push) Successful in 1m9s
CI / Browser tests (Chromium) (push) Failing after 36m1s
Every Python / Unit tests (Python 3.12) (push) Successful in 13m25s
Every Python / Unit tests (Python 3.13) (push) Successful in 13m19s
241bbf93b7
On an in-memory SQLite database pytest-django's live server hands the test
thread's one connection to every request thread, and every request is a
transaction (ATOMIC_REQUESTS). Requests in flight at once tangled their
transactions in that one wrapper's bookkeeping: a commit took away another
request's savepoint, and the browser job failed at random with "no such
savepoint" and then a TransactionManagementError on every request and
teardown after it.

A run with an e2e test in it now gives the test database a file of its own
(django_db_modify_db_settings in tests/conftest.py), one per xdist worker in
a temporary directory removed at the end, opened with the instance's own
options (IMMEDIATE transactions, WAL, a 20-second busy timeout; #206), so a
second writer waits its turn. pytest-django shares only in-memory
connections, so each request thread connects for itself. The live server
now waits for the test database (tests/e2e/conftest.py): the font check
started it first, while the connection still named :memory:, and it shared
the connection all the same. The unit suite and a PostgreSQL run are left
as they were.

tests/e2e/test_live_server_connections.py plays the failing order out with
three requests and a pause held open between SAVEPOINT and Django noting it
down; it failed with the CI's errors before the change and passes after.

Closes #1024

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tiagoagueda force-pushed 1024-live-server-db from 241bbf93b7
Some checks failed
CI / Unit tests and coverage (Python 3.14) (pull_request) Successful in 13m33s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 3m20s
CI / Database tests on PostgreSQL (pull_request) Successful in 4m17s
CI / Security audit (pull_request) Successful in 1m10s
CI / Browser tests (Chromium) (pull_request) Failing after 36m22s
CI / Unit tests and coverage (Python 3.14) (push) Successful in 13m16s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 2m59s
CI / Database tests on PostgreSQL (push) Successful in 4m14s
CI / Security audit (push) Successful in 1m9s
CI / Browser tests (Chromium) (push) Failing after 36m1s
Every Python / Unit tests (Python 3.12) (push) Successful in 13m25s
Every Python / Unit tests (Python 3.13) (push) Successful in 13m19s
to ece1170f6a
All checks were successful
CI / Unit tests and coverage (Python 3.14) (pull_request) Successful in 14m9s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 3m2s
CI / Database tests on PostgreSQL (pull_request) Successful in 4m19s
CI / Security audit (pull_request) Successful in 1m8s
CI / Browser tests (Chromium) (pull_request) Successful in 36m25s
2026-10-28 19:48:39 +00:00
Compare
tiagoagueda force-pushed 1024-live-server-db from ece1170f6a
All checks were successful
CI / Unit tests and coverage (Python 3.14) (pull_request) Successful in 14m9s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 3m2s
CI / Database tests on PostgreSQL (pull_request) Successful in 4m19s
CI / Security audit (pull_request) Successful in 1m8s
CI / Browser tests (Chromium) (pull_request) Successful in 36m25s
to 9691831204
All checks were successful
CI / Unit tests and coverage (Python 3.14) (pull_request) Successful in 12m48s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 2m36s
CI / Database tests on PostgreSQL (pull_request) Successful in 3m45s
CI / Security audit (pull_request) Successful in 52s
CI / Browser tests (Chromium) (pull_request) Successful in 33m42s
2026-10-29 10:13:32 +00:00
Compare
tiagoagueda deleted branch 1024-live-server-db 2026-10-29 10:18:32 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo!1074
No description provided.