Opened 53 minutes ago

Last modified 16 minutes ago

#37371 new Bug

Inconsistent behaviour when running tests with `--keepdb` flag and sqlite backend

Reported by: Rebecca Smith Owned by:
Component: Testing framework Version: 6.1
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Rebecca Smith)

--keepdb is expected to preserve a database between test runs. For sqlite backends, there are 2 issues:

1. Misleading logs and unexpected behaviour using --keepdb with default TEST settings

By default, --keepdb only has an effect if there is a TEST database name defined. If the TEST name is the default (None) , the sqlite backend ALWAYS uses an in-memory database, irrespective of the --keepdb flag.

This is documented here:
​https://docs.djangoproject.com/en/6.1/ref/settings/#std-setting-TEST_NAME

However: it's not easy to find (it's not mentioned in the testing docs or the sqlite notes), and it's not documented anywhere that when the TEST name is None, using --keepdb will not make it change behaviour - it will ​always use an in-memory db

Docs on ​the test database and ​preserving the db don't mention that for sqlite, --keepdb does not preserve the test db unless you have an explicit TEST setting.

In addition, when the tests run, they print misleading logs.
For example, take these settings:

DATABASES = {
   'default': {
       'ENGINE': 'django.db.backends.sqlite3',
       'NAME': BASE_DIR / 'db.sqlite3',
   }
}

Running python manage.py test --keepdb prints:

Found 2 test(s).
Using existing test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.669s

OK
Preserving test database for alias 'default'...

But no test database was preserved. (Running with -v 2 will show the migrations being appplied on repeated test runs, despite the logs that say it's reusing an existing db).

In contrast, with these settings:

DATABASES = {
   'default': {
       'ENGINE': 'django.db.backends.sqlite3',
       'NAME': BASE_DIR / 'db.sqlite3',
       "TEST": {"NAME": BASE_DIR / "test_db.sqlite3"},
   }
}

Running python manage.py test --keepdb prints the exact same message as before, but this time a file called test_db.sqlite3 is preserved.

I think there are a few options to improve this:
1) Raise an error when --keepdb is used with no TEST db configured, with an informative error message
2) Raise a warning when --keepdb is used with no TEST db configured, and/or fix the misleading logs
3) Make --keepdb work when there is no TEST db configured

(3) makes sqlite behave similarly to other DBs, rather than have a --keepdb flag that's silently ignored in some scenarios.
(1) or (2) keep the current behaviour but make it more obvious to the user what's going on. I'd lean towards (3) as the best option.

2. Inconsistent behaviour when using --keepdb with default TEST settings and running tests in parallel
When running tests in parallel with default TEST settings (i.e. TEST db name None), the testrunner's database setup creates a clone of the (in-memory) test database, one per worker. This is fine when the start method is fork, but the forksever/spawn start methods require migrating the in-memory db to disk. This means that when the TEST db name is None, and tests are run in parallel, they actually DON'T use an in-memory db. This isn't noticeable unless also run with the --keepdb flag, which prevents cleanup of the cloned test dbs.

i.e. with these settings (no TEST db configured):

DATABASES = {
   'default': {
       'ENGINE': 'django.db.backends.sqlite3',
       'NAME': BASE_DIR / 'db.sqlite3',
   }
}

Running python manage.py test --keepdb --parallel=auto (with at least 2 tests so there's something to parallise) leaves behind databases called default_1.sqlite3, default_2.sqlite3 etc if the multiprocessing start method is spawn or forkserver, but not if it's fork. As of python 3.14, ​fork isn't the default behaviour on any system, so users are likely to see the preserved dbs in parallel mode. The parallel case should behave consistently with serial test runs.

The fix for this will depend on how --keepdb is handled in the serial case. If we update the docs and logs to be more explicit about the fact that --keepdb will have no effect when there's no TEST db configured, then the parallel case should behave consistently and clean up its cloned dbs when there's no TEST db configured. If we modify the behaviour so that --keepdb uses an on-disk file that can be preserved, then I think the existing code will work as expected, although it would be worth documenting that running in parallel will use on-disk files, even if the TEST settings specify an in-memory file.

Attachments (1)

exampleprj.zip​ (4.5 KB ) - added by Rebecca Smith 53 minutes ago.

Download all attachments as: .zip

Change History (3)

by Rebecca Smith, 53 minutes ago

Attachment: exampleprj.zip​ added

comment:1 by Rebecca Smith, 41 minutes ago

I've attached an example project with minimal tests require to demonstrate the issues. I have a patch that makes --keepdb work as expected with sqlite (i.e. use on-disk files) if that's confirmed as the best fix for this.

comment:2 by Rebecca Smith, 16 minutes ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top