diff --git a/features/steps/cli_main_cov3_steps.py b/features/steps/cli_main_cov3_steps.py index 5a6bbc3c5..bace7a26b 100644 --- a/features/steps/cli_main_cov3_steps.py +++ b/features/steps/cli_main_cov3_steps.py @@ -31,15 +31,32 @@ 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) @@ -51,13 +68,18 @@ def step_register_subcommands_import_error(context: Any) -> None: 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