diff --git a/tests/README.md b/tests/README.md index 381a95582..4fb909294 100644 --- a/tests/README.md +++ b/tests/README.md @@ -74,7 +74,14 @@ python3 tests/run_focus.py --area services --fast --durations 25 --durations-min The `slow` marker is opt-in. Mark a test `slow` only with duration evidence (from `--durations`), not by guessing - see the fast-lane policy in -`TESTING_STANDARD.md`. +`TESTING_STANDARD.md`. `--fast` is for quick reviewer feedback and must not +replace the full suite before merge. A `slow` mark only excludes a test from the +fast lane; the test stays runnable directly, e.g.: + +```bash +python3 -m pytest tests/test_auth_config_lock_concurrency.py +python3 -m pytest -m slow +``` ## Core principles diff --git a/tests/test_auth_config_lock_concurrency.py b/tests/test_auth_config_lock_concurrency.py index 62d75a17a..f5cc8a18c 100644 --- a/tests/test_auth_config_lock_concurrency.py +++ b/tests/test_auth_config_lock_concurrency.py @@ -25,6 +25,7 @@ def _fresh_auth_manager(tmp_path): class TestConcurrentCreateUser: """Concurrent create_user calls must not lose accounts.""" + @pytest.mark.slow def test_parallel_creates_no_lost_users(self, tmp_path): mgr = _fresh_auth_manager(tmp_path) num_users = 50 @@ -63,6 +64,7 @@ class TestConcurrentCreateUser: class TestConcurrentDeleteUser: """Concurrent deletes must not corrupt state.""" + @pytest.mark.slow def test_parallel_deletes_no_corruption(self, tmp_path): mgr = _fresh_auth_manager(tmp_path) mgr.create_user("admin", "adminpw", is_admin=True) @@ -90,6 +92,7 @@ class TestConcurrentDeleteUser: class TestConcurrentRenameUser: """Concurrent renames must not lose or duplicate users.""" + @pytest.mark.slow def test_parallel_renames_no_lost_users(self, tmp_path): mgr = _fresh_auth_manager(tmp_path) mgr.create_user("admin", "adminpw", is_admin=True) @@ -115,6 +118,7 @@ class TestConcurrentRenameUser: class TestConcurrentMixedOperations: """Mixed create/delete/rename at the same time.""" + @pytest.mark.slow def test_mixed_operations_no_corruption(self, tmp_path): mgr = _fresh_auth_manager(tmp_path) mgr.create_user("admin", "adminpw", is_admin=True) @@ -161,6 +165,7 @@ class TestConcurrentMixedOperations: class TestDiskConsistency: """Verify auth.json is never in a corrupt state during concurrent writes.""" + @pytest.mark.slow def test_file_always_valid_json_during_concurrent_ops(self, tmp_path): mgr = _fresh_auth_manager(tmp_path) mgr.create_user("admin", "adminpw", is_admin=True) diff --git a/tests/test_run_focus.py b/tests/test_run_focus.py index a19a9cf5b..696999605 100644 --- a/tests/test_run_focus.py +++ b/tests/test_run_focus.py @@ -7,7 +7,9 @@ injected fake executor so no pytest subprocess is ever spawned. from __future__ import annotations import argparse +import subprocess import sys +from pathlib import Path import pytest @@ -351,3 +353,47 @@ def test_durations_min_with_durations_is_allowed(): "--durations=25", "--durations-min=0.05", ]] + + +# --- fast lane deselects evidence-backed slow tests (real collection) ------- + +# Node names in tests/test_auth_config_lock_concurrency.py: the single unmarked +# fast test, and the five @pytest.mark.slow tests the fast lane must exclude. +_FAST_AUTH_CONCURRENCY_TEST = "test_parallel_creates_same_username_only_one_wins" +_SLOW_AUTH_CONCURRENCY_TESTS = ( + "test_parallel_creates_no_lost_users", + "test_parallel_deletes_no_corruption", + "test_parallel_renames_no_lost_users", + "test_mixed_operations_no_corruption", + "test_file_always_valid_json_during_concurrent_ops", +) + + +def test_fast_lane_collects_only_unmarked_auth_concurrency_test(): + """`--fast` collection drops the marked slow tests but keeps the fast one. + + Unlike the other tests here, this runs a real `--collect-only` so it proves + the `slow` markers actually deselect during collection, not just that the + command is built with `not slow`. + """ + repo_root = Path(__file__).resolve().parents[1] + result = subprocess.run( + [ + sys.executable, + "tests/run_focus.py", + "--fast", + "--", + "--collect-only", + "-q", + "tests/test_auth_config_lock_concurrency.py", + ], + cwd=repo_root, + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr or result.stdout + collected = result.stdout + + assert _FAST_AUTH_CONCURRENCY_TEST in collected + for slow_test in _SLOW_AUTH_CONCURRENCY_TESTS: + assert slow_test not in collected, f"slow test was not deselected: {slow_test}"