Opened 3 years ago

Closed 3 years ago

Last modified 3 years ago

#33275 closed Bug (duplicate)

DiscoverRunner.setup_databases() creates db based on ordering of aliases from unordered set.

Reported by: augb Owned by: nobody
Component: Testing framework Version: 3.2
Severity: Normal Keywords: DiscoverRunner setup_database test database
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

We use settings.DATABASES to change which database user is used to connect to our database. For example, 'default' might use something along the lines of admin_user for use in migrations, tests, etc. restricted_user might be used for the actual Django web app.

Given a settings.DATABASES config along these lines:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'my_database_name',
        'USER': 'admin_user',
        # rest of actual config here
    },
    'restricted': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'my_database_name',
        'USER': 'restricted_user',
        # rest of actual config here
    },
    # other databases, as needed ...
}

The problem I appear to be running into is that the stock test runner sometimes creates a test database using the "default" alias. At other times, it appears to create the test database using the "restricted" alias. (Actual alias names may be different, but illustrative.) For the actual db tests, we have middleware in place to decide which connection should be used for that test. (Sometimes we may want to use admin_user in the test, sometimes restricted_user, but this is not the current concern.)

Given our setup, the actual database to be created will be correctly identified but with the wrong database user, in some cases. (admin_user has the ability to create a database, but restricted_user does not.) If the 'restricted' alias is used the tests will fail since restricted_user does not have permission to create a database.

Sometimes it picks 'default' ...

(Pdb) kwargs
{'aliases': {'default', 'restricted'}}
(Pdb) n
Using existing test database for alias 'default'...

Sometimes it picks 'restricted' ...

(Pdb) kwargs
{'aliases': {'restricted', 'default'}}
(Pdb) n
Using existing test database for alias 'restricted'...

In practice this seems to be hit or miss due to the apparent use of a set (which does not guarantee ordering; see: https://docs.python.org/3.8/library/stdtypes.html#set) in an ordered scenario:

Relevant code in Django:

for alias in aliases:
    connection = connections[alias]
    old_names.append((connection, db_name, first_alias is None))

    # Actually create the database for the first connection
    if first_alias is None:
        first_alias = alias

from django/django/test/utils.py / setup_databases (see: https://github.com/django/django/blob/1b3c0d3b54d4ff5f75af57d3130180b1d22468e9/django/test/utils.py#L171)

django/django/test/runner.py / DiscoverRunner appears to import setup_databases from django.test.utils (see: https://github.com/django/django/blob/1b3c0d3b54d4ff5f75af57d3130180b1d22468e9/django/test/runner.py#L642)

Simply overriding the passed in aliases kwarg with an ordered list, does not appear to have any effect. This appears to be due to pulling the alias from the connections instead of the aliases parameter in get_unique_databases_and_mirrors:

def get_unique_databases_and_mirrors(aliases=None):
    """
    Figure out which databases actually need to be created.
    Deduplicate entries in DATABASES that correspond the same database or are
    configured as test mirrors.
    Return two values:
    - test_databases: ordered mapping of signatures to (name, list of aliases)
                      where all aliases share the same underlying database.
    - mirrored_aliases: mapping of mirror aliases to original aliases.
    """
    if aliases is None:
        aliases = connections
    mirrored_aliases = {}
    test_databases = {}
    dependencies = {}
    default_sig = connections[DEFAULT_DB_ALIAS].creation.test_db_signature()

    for alias in connections:
        # elided ...

(see: https://github.com/django/django/blob/1b3c0d3b54d4ff5f75af57d3130180b1d22468e9/django/test/utils.py#L270)

Indeed line 270 referenced above appears to be the culprit.

For our use case, either always using the 'default' alias to create the test database, truly honoring the order of aliases, or fixing the aliases kwarg would solve our problem.

We are using 3.1; however, the code references above are essentially identical and are from 3.2.

Change History (2)

comment:1 by Mariusz Felisiak, 3 years ago

Resolution: duplicate
Status: newclosed

Duplicate of #29052 (Django 3.2+).

comment:2 by Mariusz Felisiak, 3 years ago

Summary: django.test.runner.DiscoverRunner.setup_databases() appears to create db based on ordering of aliases from unordered setDiscoverRunner.setup_databases() creates db based on ordering of aliases from unordered set.
Note: See TracTickets for help on using tickets.
Back to Top