1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-10 12:16:49 +00:00

fix(tests): a killed test run no longer breaks the next one

Django replaces a SQLite test database and its parallel clones by the main
file only. A run that is killed, or whose worker pool crashes on a failure
it cannot send back, leaves the clones' WAL files behind; the next run's
fresh copies then replay that run's writes, and hundreds of tests fail with
"Cannot operate on a closed database" or foreign key errors until a clean
run checkpoints the stale WAL away. The test runner now removes the test
database's and its clones' WAL files before it creates and clones them
(not with --keepdb, whose databases they belong to).
This commit is contained in:
catborise 2026-10-26 16:37:22 +03:00
parent b89f280076
commit c69c65728b
2 changed files with 39 additions and 0 deletions

View file

@ -1,8 +1,21 @@
"""manage.py test --parallel on the SQLite database in WAL mode."""
import contextlib
import os
from django.db import connections
from django.test.runner import DiscoverRunner
def remove_wal_files(name):
"""Django replaces a SQLite test database by its main file only. A run
that was killed or whose pool crashed leaves the database's WAL behind,
and SQLite would replay that run's writes into the new database."""
for suffix in ("-wal", "-shm"):
with contextlib.suppress(FileNotFoundError):
os.remove(f"{name}{suffix}")
class TestRunner(DiscoverRunner):
def build_suite(self, *args, **kwargs):
suite = super().build_suite(*args, **kwargs)
@ -19,6 +32,9 @@ class TestRunner(DiscoverRunner):
# migrate's writes are still in the WAL file (operations/apps.py):
# create the database alone, write the WAL back, then clone.
parallel, self.parallel = self.parallel, 0
test_files = [c.settings_dict["TEST"]["NAME"] for c in connections.all() if c.vendor == "sqlite"]
for name in filter(None, test_files) if not self.keepdb else ():
remove_wal_files(name)
old_config = super().setup_databases(**kwargs)
self.parallel = parallel
for connection, _, created in old_config:
@ -30,6 +46,8 @@ class TestRunner(DiscoverRunner):
if cursor.fetchone()[0]:
raise RuntimeError("The test database's WAL could not be written back before cloning")
for index in range(parallel):
if connection.vendor == "sqlite" and not self.keepdb:
remove_wal_files(connection.creation.get_test_db_clone_settings(str(index + 1))["NAME"])
connection.creation.clone_test_db(
suffix=str(index + 1), verbosity=self.verbosity, keepdb=self.keepdb
)