From 7db9fd1ab2940fa4d401b81f29ef43fcb8964a30 Mon Sep 17 00:00:00 2001 From: HAL9000 Date: Wed, 29 Apr 2026 08:01:21 +0000 Subject: [PATCH 1/2] fix(test): evict sys.modules cache in _register_subcommands import error test --- features/steps/cli_main_cov3_steps.py | 36 +++++++++++++++++++++------ 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/features/steps/cli_main_cov3_steps.py b/features/steps/cli_main_cov3_steps.py index 5a6bbc3c5..147a69318 100644 --- a/features/steps/cli_main_cov3_steps.py +++ b/features/steps/cli_main_cov3_steps.py @@ -31,33 +31,53 @@ def step_reset_subcommands_flag(context: Any) -> None: @when("cmcov3 _register_subcommands is called and an import raises an error") def step_register_subcommands_import_error(context: Any) -> None: - """Patch a subcommand import so _register_subcommands hits its except block.""" + """Patch a subcommand import so _register_subcommands hits its except block. + + Also evicts cleveragents.cli.commands and all its sub-packages from + sys.modules so the __import__ patch actually takes effect — the module is + normally cached from the eager _register_subcommands() call at import time. + """ + import sys import builtins mod = context.cmcov3_mod mock_err_console = MagicMock() original_import = builtins.__import__ + # Evict cleveragents.cli.commands and all its sub-packages/modules from + # sys.modules so the __import__ patch takes effect. + module_name = "cleveragents.cli.commands" + original_modules = { + key: sys.modules[key] + for key in list(sys.modules) + if key == module_name or key.startswith(module_name + ".") + } + for key in original_modules: + del sys.modules[key] + def failing_import(name: str, *args: Any, **kwargs: Any) -> Any: - if name == "cleveragents.cli.commands": + if name == module_name: raise ImportError("cmcov3 forced import failure") return original_import(name, *args, **kwargs) - patcher_console = patch.object( - mod, "get_err_console", return_value=mock_err_console - ) + patcher_console = patch.object(mod, "get_err_console", return_value=mock_err_console) patcher_console.start() context.add_cleanup(patcher_console.stop) patcher_builtins = patch.object(builtins, "__import__", side_effect=failing_import) patcher_builtins.start() + context.add_cleanup(patcher_builtins.stop) + # Use BaseException to also catch SystemExit which is raised by + # _register_subcommands() on import failure (SystemExit extends + # BaseException, not Exception, so except Exception would not catch it). try: mod._register_subcommands() - except Exception as exc: + except BaseException as exc: context.cmcov3_error = exc - finally: - patcher_builtins.stop() + + # Restore original sys.modules entries for other scenarios. + context.add_cleanup(lambda: sys.modules.update(original_modules)) context.cmcov3_err_console = mock_err_console -- 2.52.0 From b747f1aab1c5903e906e5529bfe4f44e1f7c0627 Mon Sep 17 00:00:00 2001 From: CleverThis Date: Wed, 29 Apr 2026 19:41:47 +0000 Subject: [PATCH 2/2] style: reformat cli_main_cov3_steps.py for ruff The get_err_console patch.object call was being split across lines in a way ruff did not approve; format it the way ruff expects. --- features/steps/cli_main_cov3_steps.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/features/steps/cli_main_cov3_steps.py b/features/steps/cli_main_cov3_steps.py index 147a69318..bace7a26b 100644 --- a/features/steps/cli_main_cov3_steps.py +++ b/features/steps/cli_main_cov3_steps.py @@ -60,7 +60,9 @@ def step_register_subcommands_import_error(context: Any) -> None: raise ImportError("cmcov3 forced import failure") return original_import(name, *args, **kwargs) - patcher_console = patch.object(mod, "get_err_console", return_value=mock_err_console) + patcher_console = patch.object( + mod, "get_err_console", return_value=mock_err_console + ) patcher_console.start() context.add_cleanup(patcher_console.stop) -- 2.52.0