b747f1aab1
CI / benchmark-publish (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 31s
CI / helm (pull_request) Successful in 42s
CI / build (pull_request) Successful in 59s
CI / lint (pull_request) Successful in 1m7s
CI / typecheck (pull_request) Successful in 1m30s
CI / quality (pull_request) Successful in 1m31s
CI / security (pull_request) Successful in 1m39s
CI / integration_tests (pull_request) Successful in 4m6s
CI / unit_tests (pull_request) Successful in 4m32s
CI / e2e_tests (pull_request) Successful in 4m54s
CI / docker (pull_request) Successful in 1m28s
CI / coverage (pull_request) Successful in 11m22s
CI / status-check (push) Blocked by required conditions
CI / status-check (pull_request) Successful in 4s
CI / benchmark-publish (push) Failing after 53s
CI / lint (push) Successful in 59s
CI / quality (push) Successful in 1m18s
CI / build (push) Successful in 36s
CI / typecheck (push) Successful in 1m29s
CI / security (push) Successful in 1m37s
CI / helm (push) Successful in 35s
CI / push-validation (push) Successful in 20s
CI / integration_tests (push) Successful in 3m28s
CI / e2e_tests (push) Successful in 3m32s
CI / unit_tests (push) Successful in 4m39s
CI / coverage (push) Has started running
CI / docker (push) Successful in 1m56s
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.
221 lines
8.2 KiB
Python
221 lines
8.2 KiB
Python
"""Step definitions for CLI main.py coverage round 3 (lines 104-110, 582-590, 593+)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from behave import given, then, when
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lines 104-110: _register_subcommands exception handler
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("cmcov3 the _subcommands_registered flag is reset to False")
|
|
def step_reset_subcommands_flag(context: Any) -> None:
|
|
"""Reset the module-level flag so _register_subcommands runs fresh."""
|
|
import importlib
|
|
|
|
mod = importlib.import_module("cleveragents.cli.main")
|
|
context.cmcov3_mod = mod
|
|
context.cmcov3_orig_registered = mod._subcommands_registered
|
|
mod._subcommands_registered = False
|
|
|
|
# Restore the flag after the scenario to avoid polluting other tests.
|
|
def _restore() -> None:
|
|
mod._subcommands_registered = context.cmcov3_orig_registered
|
|
|
|
context.add_cleanup(_restore)
|
|
|
|
|
|
@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.
|
|
|
|
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 == 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.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 BaseException as exc:
|
|
context.cmcov3_error = exc
|
|
|
|
# Restore original sys.modules entries for other scenarios.
|
|
context.add_cleanup(lambda: sys.modules.update(original_modules))
|
|
|
|
context.cmcov3_err_console = mock_err_console
|
|
|
|
|
|
@then("cmcov3 the error console should have printed the failure message")
|
|
def step_check_failure_message(context: Any) -> None:
|
|
"""Verify the error console received the failure message."""
|
|
mock_console = context.cmcov3_err_console
|
|
assert mock_console.print.called, "Expected err console print to be called"
|
|
all_calls = " ".join(str(c) for c in mock_console.print.call_args_list)
|
|
assert "Failed to register subcommands" in all_calls, (
|
|
f"Expected 'Failed to register subcommands' in output, got: {all_calls}"
|
|
)
|
|
|
|
|
|
@then("cmcov3 the _subcommands_registered flag should still be False")
|
|
def step_check_flag_still_false(context: Any) -> None:
|
|
"""The flag must remain False since registration failed."""
|
|
mod = context.cmcov3_mod
|
|
assert mod._subcommands_registered is False, (
|
|
f"Expected _subcommands_registered to be False, got {mod._subcommands_registered}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lines 582-590: completion command with subprocess.run
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('cmcov3 subprocess.run is mocked to return stdout "{stdout_text}"')
|
|
def step_mock_subprocess_with_stdout(context: Any, stdout_text: str) -> None:
|
|
"""Set up a mock for subprocess.run that returns the given stdout."""
|
|
mock_result = MagicMock()
|
|
mock_result.stdout = stdout_text
|
|
mock_result.stderr = ""
|
|
mock_result.returncode = 0
|
|
|
|
patcher = patch("subprocess.run", return_value=mock_result)
|
|
context.cmcov3_subprocess_mock = patcher.start()
|
|
context.add_cleanup(patcher.stop)
|
|
|
|
|
|
@given("cmcov3 subprocess.run is mocked to return empty stdout")
|
|
def step_mock_subprocess_empty_stdout(context: Any) -> None:
|
|
"""Set up a mock for subprocess.run that returns empty stdout."""
|
|
mock_result = MagicMock()
|
|
mock_result.stdout = ""
|
|
mock_result.stderr = ""
|
|
mock_result.returncode = 0
|
|
|
|
patcher = patch("subprocess.run", return_value=mock_result)
|
|
context.cmcov3_subprocess_mock = patcher.start()
|
|
context.add_cleanup(patcher.stop)
|
|
|
|
|
|
@when('cmcov3 the completion command is invoked with shell "{shell}"')
|
|
def step_invoke_completion(context: Any, shell: str) -> None:
|
|
"""Invoke the completion CLI command with the given shell argument."""
|
|
from typer.testing import CliRunner
|
|
|
|
from cleveragents.cli.main import app
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(app, ["completion", shell])
|
|
context.cmcov3_cli_result = result
|
|
|
|
|
|
@then('cmcov3 the output should contain "{expected}"')
|
|
def step_check_output_contains(context: Any, expected: str) -> None:
|
|
"""Verify the CLI output contains the expected text."""
|
|
output = context.cmcov3_cli_result.output
|
|
assert expected in output, (
|
|
f"Expected output to contain {expected!r}, got: {output!r}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lines 593+: convert_exit_code function
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("cmcov3 convert_exit_code is called with None")
|
|
def step_convert_exit_code_none(context: Any) -> None:
|
|
"""Call convert_exit_code with None."""
|
|
from cleveragents.cli.main import convert_exit_code
|
|
|
|
try:
|
|
context.cmcov3_exit_code_result = convert_exit_code(None)
|
|
except Exception as exc:
|
|
context.cmcov3_error = exc
|
|
|
|
|
|
@when("cmcov3 convert_exit_code is called with an object having exit_code {code:d}")
|
|
def step_convert_exit_code_object(context: Any, code: int) -> None:
|
|
"""Call convert_exit_code with an object that has an exit_code attribute."""
|
|
from cleveragents.cli.main import convert_exit_code
|
|
|
|
obj = MagicMock()
|
|
obj.exit_code = code
|
|
try:
|
|
context.cmcov3_exit_code_result = convert_exit_code(obj)
|
|
except Exception as exc:
|
|
context.cmcov3_error = exc
|
|
|
|
|
|
@when("cmcov3 convert_exit_code is called with integer {code:d}")
|
|
def step_convert_exit_code_int(context: Any, code: int) -> None:
|
|
"""Call convert_exit_code with an integer."""
|
|
from cleveragents.cli.main import convert_exit_code
|
|
|
|
try:
|
|
context.cmcov3_exit_code_result = convert_exit_code(code)
|
|
except Exception as exc:
|
|
context.cmcov3_error = exc
|
|
|
|
|
|
@when("cmcov3 convert_exit_code is called with a non-convertible value")
|
|
def step_convert_exit_code_nonconvertible(context: Any) -> None:
|
|
"""Call convert_exit_code with a value that cannot be converted to int."""
|
|
from cleveragents.cli.main import convert_exit_code
|
|
|
|
try:
|
|
context.cmcov3_exit_code_result = convert_exit_code("not_a_number")
|
|
except Exception as exc:
|
|
context.cmcov3_error = exc
|
|
|
|
|
|
@then("cmcov3 the exit code result should be {expected:d}")
|
|
def step_check_exit_code_result(context: Any, expected: int) -> None:
|
|
"""Verify convert_exit_code returned the expected value."""
|
|
if hasattr(context, "cmcov3_error"):
|
|
raise AssertionError(
|
|
f"convert_exit_code raised an exception: {context.cmcov3_error}"
|
|
)
|
|
actual = context.cmcov3_exit_code_result
|
|
assert actual == expected, (
|
|
f"Expected convert_exit_code to return {expected}, got {actual}"
|
|
)
|