From 4d27b327852e2fac02232259dc9b147d434207fc Mon Sep 17 00:00:00 2001 From: stumpylog <797416+stumpylog@users.noreply.github.com> Date: Thu, 17 Sep 2026 17:55:35 -0700 Subject: [PATCH] Chore: Randomize test order and seed Faker per run Enables pytest-randomly, which has sat commented out in pyproject.toml since the Pytest 9 upgrade. Tests now run in a different order every session, so a test cannot quietly depend on another having run first. --- docs/development.md | 1 + pyproject.toml | 2 +- src/conftest.py | 25 +++++++++++++++++++------ uv.lock | 16 ++++++++++++++++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/docs/development.md b/docs/development.md index d4c44ce89..bfab008f6 100644 --- a/docs/development.md +++ b/docs/development.md @@ -150,6 +150,7 @@ pnpm ng build --configuration production is loaded as well. However, the tests rely on the default configuration. This is not ideal. But for now, make sure no settings except for DEBUG are overridden when testing. +- Tests run in a random order each session, so that one test cannot quietly depend on another having run first. The seed is printed at the top of the run; pass `--randomly-seed=` to replay that exact order, or `--randomly-seed=last` to repeat the previous run. !!! note diff --git a/pyproject.toml b/pyproject.toml index d67a60d39..a627d6f6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,7 +120,7 @@ testing = [ "pytest-env~=1.7.0", "pytest-httpx", "pytest-mock~=3.15.1", - # "pytest-randomly~=4.0.1", + "pytest-randomly~=5.0.0", "pytest-rerunfailures~=16.4", "pytest-sugar", "pytest-xdist~=3.8.0", diff --git a/src/conftest.py b/src/conftest.py index ef4e61ca2..10293880a 100644 --- a/src/conftest.py +++ b/src/conftest.py @@ -22,15 +22,13 @@ if TYPE_CHECKING: @pytest.fixture(scope="session", autouse=True) def faker_session_locale() -> str: - """Set Faker locale for reproducibility.""" + """Pin Faker's locale so generated data does not follow the host locale. + + The seed itself is left to pytest-randomly, which derives one per run. + """ return "en_US" -@pytest.fixture(scope="session", autouse=True) -def faker_seed() -> int: - return 12345 - - @pytest.fixture(autouse=True) def _clear_content_type_caches() -> None: """Clear Django's ContentType cache and guardian's lru_cache before each test. @@ -47,6 +45,21 @@ def _clear_content_type_caches() -> None: clear_ct_cache() +@pytest.fixture(autouse=True) +def _clear_django_caches() -> None: + """Clear every configured cache before each test. + + Cached values outlive the test that wrote them: the classifier keys its + vectorized content on a hash of the content itself, so a second test + generating the same fixture data takes the cache-hit path and never calls + the code it is asserting against. + """ + from django.core.cache import caches + + for cache in caches.all(initialized_only=False): + cache.clear() + + @pytest.fixture def paperless_dirs( tmp_path: Path, diff --git a/uv.lock b/uv.lock index 5ad211d97..94c3533c7 100644 --- a/uv.lock +++ b/uv.lock @@ -2969,6 +2969,7 @@ dev = [ { name = "pytest-env" }, { name = "pytest-httpx" }, { name = "pytest-mock" }, + { name = "pytest-randomly" }, { name = "pytest-rerunfailures" }, { name = "pytest-sugar" }, { name = "pytest-xdist" }, @@ -2994,6 +2995,7 @@ testing = [ { name = "pytest-env" }, { name = "pytest-httpx" }, { name = "pytest-mock" }, + { name = "pytest-randomly" }, { name = "pytest-rerunfailures" }, { name = "pytest-sugar" }, { name = "pytest-xdist" }, @@ -3111,6 +3113,7 @@ dev = [ { name = "pytest-env", specifier = "~=1.7.0" }, { name = "pytest-httpx" }, { name = "pytest-mock", specifier = "~=3.15.1" }, + { name = "pytest-randomly", specifier = "~=5.0.0" }, { name = "pytest-rerunfailures", specifier = "~=16.4" }, { name = "pytest-sugar" }, { name = "pytest-xdist", specifier = "~=3.8.0" }, @@ -3134,6 +3137,7 @@ testing = [ { name = "pytest-env", specifier = "~=1.7.0" }, { name = "pytest-httpx" }, { name = "pytest-mock", specifier = "~=3.15.1" }, + { name = "pytest-randomly", specifier = "~=5.0.0" }, { name = "pytest-rerunfailures", specifier = "~=16.4" }, { name = "pytest-sugar" }, { name = "pytest-xdist", specifier = "~=3.8.0" }, @@ -3911,6 +3915,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] +[[package]] +name = "pytest-randomly" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/3b/6a40e1b9d925651e601e056a97f60d8a1daeddeac03d5609be60cb4362ce/pytest_randomly-5.0.0.tar.gz", hash = "sha256:e9c575a5873ef168ddbe340ed9e97ce9edb4492ccc821e4b2ac6bb1f0ed515d2", size = 8542, upload-time = "2026-09-01T22:34:20.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/b4/47e939285caad9a623d021512912ac08dc92a467ad075d179f43729d2934/pytest_randomly-5.0.0-py3-none-any.whl", hash = "sha256:8a0d4703115c0c25b38b6e129fc16b1947b9643ff26a41bc1d185d7e5a7689c1", size = 8920, upload-time = "2026-09-01T22:34:19.227Z" }, +] + [[package]] name = "pytest-rerunfailures" version = "16.6.1"