Opened 6 weeks ago
Last modified 3 days ago
#37371 assigned Bug
Misleading test database messages when using --keepdb with in-memory SQLite databases
| Reported by: | Becky Smith | Owned by: | Becky Smith |
|---|---|---|---|
| Component: | Testing framework | Version: | 6.1 |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Accepted | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description (last modified by )
--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.
Following the discussion below, this ticket is limited to correcting the misleading log messages and documenting that --keepdb does not preserve in-memory SQLite test databases. This applies both to the default configuration and to an explicitly configured in-memory TEST["NAME"]. Database selection and preservation behaviour should remain unchanged.
Attachments (1)
Change History (10)
by , 6 weeks ago
| Attachment: | exampleprj.zip added |
|---|
comment:1 by , 6 weeks ago
comment:2 by , 6 weeks ago
| Description: | modified (diff) |
|---|
comment:3 by , 6 weeks ago
| Owner: | set to |
|---|---|
| Status: | new → assigned |
comment:4 by , 3 weeks ago
Thanks for the report! I'd split this into 2 tickets; one for the incorrect logs and one for the leaked clone files when using --parallel.
Both issues look valid. For the first, I think I'd rather not make --keepdb switch SQLite to an on-disk database implicitly: Django would have to choose a file location, and multi-backend setups would see unexpected sqlite database files. Users who want a persistent SQLite test database can opt in with "NAME".
Correcting the log messages for in-memory databases and documenting the limitation for --keepdb here seems like the right scope.
For the second issue (which you should split into its own ticket), I think th per-worker files created for forkserver are an implementation detail and should be removed regardless of --keepdb when the source is in-memory.
#26822 seems related.
comment:5 by , 2 weeks ago
Thanks, I think this makes sense. So I will update this ticket to cover:
1) --keep-db with an in-memory source continues to use in-memory, but the misleading logs are corrected (no "Preserving test database..." any more, and a more informative message)
2) --keep-db with a TEST NAME works as it does now (uses on-disk files, and doesn't clean up), unless the NAME specifies an in-memory source, in which case, it does the same as above (fixes the log messages)
3) Update the docs one the test database, and preserving the test db
And I'll make a new ticket to make the files created with --parallel clean up if there is no TEST db NAME specified (i.e. it's expected to use the default in-memory db), or if the NAME is an in-memory source, even if --keep-db is used. --keep-db will show the new fixed log messages once this ticket is done.
comment:6 by , 11 days ago
Reproduced on main (2d9d619676), Python 3.13.18, macOS, with a fresh startproject, two trivial TestCase tests, and no TEST NAME set.
The logs say a test database is reused and preserved, but it's in-memory, so nothing is kept:
$ python manage.py test --keepdb -v 2
Using existing test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')...
...
Applying contenttypes.0001_initial... OK
... (18 migrations applied)
...
Preserving test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')...
Running it again applies all 18 migrations again, and no test database file is created.
For the follow-up ticket: with the default "spawn" start method on macOS, the parallel run leaves the clone files behind. On the very first parallel run it also reports "Using existing clone" even though no clone existed yet:
$ python manage.py test --keepdb --parallel=2 Using existing test database for alias 'default'... Using existing clone for alias 'default'... Using existing clone for alias 'default'... ... Preserving test database for alias 'default'... $ ls default_1.sqlite3 default_2.sqlite3 manage.py mysite polls
Hope this confirmation helps move the ticket forward.
comment:7 by , 11 days ago
PR #22068 would add the temporary .sqlite files to .gitignore, which doesn't fix the other issues here but would at least avoid accidentally including them in a PR.
comment:9 by , 3 days ago
| Description: | modified (diff) |
|---|---|
| Summary: | Inconsistent behaviour when running tests with `--keepdb` flag and sqlite backend → Misleading test database messages when using --keepdb with in-memory SQLite databases |
| Triage Stage: | Unreviewed → Accepted |
Thank you!
I've accepted the ticket with the suggested scope (and updated the title and description to reflect this)
Feel free to open a separate ticket for cleaning up the parallel worker database files when the source database is in-memory and we'll triage it 👍
I've attached an example project with minimal tests require to demonstrate the issues. I have a patch that makes
--keepdbwork as expected with sqlite (i.e. use on-disk files) if that's confirmed as the best fix for this.