Skip to content

API Tests: Apps registry pollution from test_unit_app_routers.py breaks migration tests #7368

@khvn26

Description

@khvn26

The API unit test suite has been failing consistently on the Python 3.11 matrix in Depot CI, and a failure has now reproduced in GitHub Actions: https://github.com/Flagsmith/flagsmith/actions/runs/25070433056/job/73448935538 (run on commit 5c5ebc4).

Symptoms

Two failure clusters in the same job:

  1. tests/unit/core/management/test_unit_core_management_makemigrations.py::test_makemigrations__with_check_changes__runs_without_error exits with SystemExit: 1. Captured stdout:

    Migrations for 'app_analytics':
      app_analytics/migrations/0009_analyticsmodel_mymodel_mymodel1_mymodel2.py
        + Create model AnalyticsModel
        + Create model MyModel
        + Create model MyModel1
        + Create model MyModel2
    
  2. Roughly a dozen migration tests using django_test_migrations.Migrator fail at teardown across multiple xdist workers with:

    psycopg2.errors.DuplicateTable: relation "app_analytics_analyticsmodel" already exists
    

    Affected files include tests/unit/segments/test_unit_segments_migrations.py, tests/unit/features/test_migrations.py, tests/unit/organisations/permissions/test_unit_organisations_migrations.py, tests/unit/environments/permissions/test_unit_environments_permissions_migrations.py, tests/unit/features/versioning/test_unit_versioning_migrations.py, tests/unit/projects/test_migrations.py.

Root cause

api/tests/unit/app/test_unit_app_routers.py defines four django.db.models.Model subclasses inline inside parametrised test functions:

  • AnalyticsModel (line 19) with app_label = "app_analytics" (parametrised) or "another_app"
  • MyModel (line 44) with the same parametrisation
  • MyModel1 (line 70) and MyModel2 (line 74) with the same parametrisation

Defining a Django model class registers it in the apps registry for the lifetime of the worker process. There is no apps registry isolation around these tests. Once these tests run with app_label="app_analytics", the four model classes are permanently registered against the real app_analytics app for that worker.

That registration is exactly what we see in cluster 1: makemigrations --check finds four models with no migrations and exits non-zero, listing the four polluting model names verbatim.

It also explains cluster 2: when Migrator.apply_initial_migration rebuilds migration state, the polluted apps registry causes the migration executor to attempt CREATE TABLE \"app_analytics_analyticsmodel\", which collides with prior state in the worker's test database.

Why it's flaky

pytest-xdist distributes tests across workers, and the failure only manifests on a worker that runs test_unit_app_routers.py before any victim test in the same process. Whether that ordering occurs depends on collection and load distribution, hence the intermittence. Recently it has been firing reliably on the 3.11 matrix in Depot, and now in GHA, suggesting collection or scheduling has shifted such that the bad ordering is the common case.

Fix options

  • Drop the inline models.Model subclasses and exercise the routers with simple objects exposing _meta.app_label (the routers only inspect app_label).
  • Or wrap the tests in django.test.utils.isolate_apps(\"tests\") so model registration is reverted at the end of each test.
  • Or set app_label to a throwaway label (e.g. \"tests\") — this would stop polluting app_analytics, but the tests are parametrised on app_label itself, so the first option is cleaner.

Job artefacts

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions