From dd5a49850c413341f33ae3cc2dcacb7ab76ffd63 Mon Sep 17 00:00:00 2001 From: Thomas Juul Dyhr Date: Fri, 1 May 2026 11:26:01 +0200 Subject: [PATCH] =?UTF-8?q?test:=20fix=20flaky=20concurrent=20e2e=20test?= =?UTF-8?q?=20=E2=80=94=20move=20sys.argv=20patch=20outside=20threads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mock.patch("sys.argv") inside each thread body is not thread-safe: threads racing to enter/exit the context manager corrupt the save/restore chain and expose the real pytest argv to versiontracker_main(), which made argparse reject the test runner arguments as unrecognized CLI flags. Fix: hoist both sys.argv and _get_apps_data patches outside the thread spawn loop, matching the existing pattern established in #134-136. Also capture thread exceptions into an errors list and assert threads completed within the timeout, so future failures surface a clear message instead of a count mismatch. Also bumps .python-version from 3.13.3 → 3.13.13 (pyenv minor update). Co-Authored-By: Claude Sonnet 4.6 --- .python-version | 2 +- tests/test_end_to_end_integration.py | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.python-version b/.python-version index 2c20ac9..655354d 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.13.3 +3.13.13 diff --git a/tests/test_end_to_end_integration.py b/tests/test_end_to_end_integration.py index 5aafad6..f3bfe67 100644 --- a/tests/test_end_to_end_integration.py +++ b/tests/test_end_to_end_integration.py @@ -304,16 +304,23 @@ def test_concurrent_operations_workflow(self, mock_applications, mock_homebrew_c mock_app_tuples = [("Firefox", "110.0"), ("Google Chrome", "110.0.5481.177"), ("Visual Studio Code", "1.74.0")] results = [] + errors = [] def run_versiontracker(): - with mock.patch("sys.argv", ["versiontracker", "--apps"]): + try: result = versiontracker_main() results.append(result) + except Exception as exc: # noqa: BLE001 + errors.append(exc) - # Patch _get_apps_data once outside threads — mock.patch is not thread-safe; - # patching the same attribute from multiple threads simultaneously corrupts the - # save/restore chain and leaves the mock live after the test exits. - with mock.patch("versiontracker.handlers.app_handlers._get_apps_data", return_value=mock_app_tuples): + # Patch sys.argv and _get_apps_data once outside threads — mock.patch is not + # thread-safe; patching from multiple threads simultaneously corrupts the + # save/restore chain and can expose the real pytest argv to versiontracker_main(), + # causing argparse to reject the test arguments as unrecognized. + with ( + mock.patch("sys.argv", ["versiontracker", "--apps"]), + mock.patch("versiontracker.handlers.app_handlers._get_apps_data", return_value=mock_app_tuples), + ): threads = [] for _ in range(3): thread = threading.Thread(target=run_versiontracker) @@ -322,8 +329,9 @@ def run_versiontracker(): for thread in threads: thread.join(timeout=30) + assert not thread.is_alive(), "Thread timed out after 30 seconds" - # All should succeed + assert not errors, f"Thread(s) raised exceptions: {errors}" assert len(results) == 3 assert all(result == 0 for result in results)