Handle plugin SystemExit failures

This commit is contained in:
Janik Besendorf
2026-08-05 23:28:54 +02:00
parent a66eeaa089
commit f7e48f0c65
2 changed files with 40 additions and 3 deletions
+3 -3
View File
@@ -30,7 +30,7 @@ class CustomCommandLoadError(Exception):
class BrokenPluginCommand(click.Command):
"""A placeholder for an installed or configured command that failed to load."""
def __init__(self, name: str, source: str, exception: Exception):
def __init__(self, name: str, source: str, exception: BaseException):
super().__init__(
name,
help=(
@@ -87,7 +87,7 @@ def _load_python_file(path: Path) -> ModuleType:
sys.modules[module_name] = module
try:
spec.loader.exec_module(module)
except Exception as exc:
except (Exception, SystemExit) as exc:
raise CustomCommandLoadError(
f"Unable to import custom command {path}: {exc}"
) from exc
@@ -234,7 +234,7 @@ def register_installed_cli_commands(
f"entry point must resolve to a Click command or group, "
f"not {type(command).__name__}"
)
except Exception as exc:
except (Exception, SystemExit) as exc:
command = BrokenPluginCommand(entry_point.name, source, exc)
if _register_command(
+37
View File
@@ -158,6 +158,21 @@ def test_environment_command_failure_gets_broken_placeholder(tmp_path):
assert str(command_path) in result.output
def test_environment_command_system_exit_gets_broken_placeholder(tmp_path):
command_path = tmp_path / "exiting_command.py"
command_path.write_text("raise SystemExit(7)", encoding="utf-8")
group = click.Group()
registered = register_cli_commands_from_path(group, command_path)
assert registered == ["exiting-command"]
assert isinstance(group.commands["exiting-command"], BrokenPluginCommand)
result = CliRunner().invoke(group, ["exiting-command"])
assert result.exit_code == 1
assert "Unable to import custom command" in result.output
assert result.output.rstrip().endswith(": 7")
def test_installed_entry_point_name_is_the_command_name(monkeypatch):
@click.command("internal-name")
def command():
@@ -208,6 +223,28 @@ def test_broken_installed_plugin_does_not_break_cli(monkeypatch):
assert "RuntimeError: missing dependency" in result.output
def test_installed_plugin_system_exit_does_not_break_cli(monkeypatch):
exiting = _entry_point(
"exiting",
"exiting_plugin:cli",
exception=SystemExit(7),
distribution="exiting-plugin",
)
monkeypatch.setattr(
"mvt.common.cli_plugins.importlib.metadata.entry_points",
lambda **kwargs: [exiting],
)
group = click.Group()
register_installed_cli_commands(group, IOS_CLI_PLUGIN_GROUP)
help_result = CliRunner().invoke(group, ["--help"])
assert help_result.exit_code == 0
result = CliRunner().invoke(group, ["exiting"])
assert result.exit_code == 1
assert "SystemExit: 7" in result.output
def test_non_click_entry_point_gets_broken_placeholder(monkeypatch):
invalid = _entry_point("invalid", "plugin:value", command=object())
monkeypatch.setattr(