From 6b40797857703167345571e0080e87d605047513 Mon Sep 17 00:00:00 2001 From: Thomas Juul Dyhr Date: Tue, 7 Apr 2026 21:23:10 +0200 Subject: [PATCH] test: fix two more flaky e2e tests with proper handler-level mocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_concurrent_operations_workflow: patch _get_apps_data once outside threads — mock.patch is not thread-safe; patching from 3 threads simultaneously corrupted the save/restore chain leaving the mock live after the test. test_outdated_applications_workflow: mock _get_installed_applications and _get_homebrew_casks at the handler's local scope to prevent real system_profiler / brew subprocess calls regardless of test execution order. Co-Authored-By: Claude Sonnet 4.6 --- tests/test_end_to_end_integration.py | 48 ++++++++++++++++------------ 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/tests/test_end_to_end_integration.py b/tests/test_end_to_end_integration.py index db51d7a..5aafad6 100644 --- a/tests/test_end_to_end_integration.py +++ b/tests/test_end_to_end_integration.py @@ -197,12 +197,20 @@ def test_auto_updates_management_workflow(self, mock_homebrew_casks, mock_homebr def test_outdated_applications_workflow(self, mock_applications, mock_homebrew_available): """Test outdated applications detection workflow.""" - # Mock the check_outdated_apps function called inside the handler path - # to avoid real subprocess/Homebrew calls that would timeout. - mock_outdated = [ - ("Firefox", "109.0", "110.0"), - ] + mock_outdated = [("Firefox", "109.0", "110.0")] + mock_app_tuples = [("Firefox", "110.0"), ("Visual Studio Code", "1.74.0")] + # The handler calls _get_apps_data() and _get_homebrew_casks() before reaching + # check_outdated_apps — mock them at the handler's local import level to prevent + # real system_profiler / brew subprocess calls regardless of test order. with ( + mock.patch( + "versiontracker.handlers.outdated_handlers._get_installed_applications", + return_value=mock_app_tuples, + ), + mock.patch( + "versiontracker.handlers.outdated_handlers._get_homebrew_casks", + return_value=["firefox", "visual-studio-code"], + ), mock.patch( "versiontracker.handlers.outdated_handlers.check_outdated_apps", return_value=mock_outdated, @@ -298,22 +306,22 @@ def test_concurrent_operations_workflow(self, mock_applications, mock_homebrew_c results = [] def run_versiontracker(): - # Patch the handler's local import (imported by value, so source-module mock doesn't intercept) - with mock.patch("versiontracker.handlers.app_handlers._get_apps_data", return_value=mock_app_tuples): - with mock.patch("sys.argv", ["versiontracker", "--apps"]): - result = versiontracker_main() - results.append(result) - - # Run multiple instances concurrently - threads = [] - for _ in range(3): - thread = threading.Thread(target=run_versiontracker) - threads.append(thread) - thread.start() + with mock.patch("sys.argv", ["versiontracker", "--apps"]): + result = versiontracker_main() + results.append(result) - # Wait for all to complete - for thread in threads: - thread.join(timeout=30) + # 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): + threads = [] + for _ in range(3): + thread = threading.Thread(target=run_versiontracker) + threads.append(thread) + thread.start() + + for thread in threads: + thread.join(timeout=30) # All should succeed assert len(results) == 3