Files
cleveragents-core/features/steps/lsp_runtime_coverage_steps.py
T
freemo 31472b5413
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 21s
CI / helm (pull_request) Successful in 22s
CI / lint (pull_request) Successful in 3m20s
CI / quality (pull_request) Successful in 3m43s
CI / typecheck (pull_request) Successful in 3m58s
CI / security (pull_request) Successful in 4m8s
CI / integration_tests (pull_request) Successful in 9m33s
CI / unit_tests (pull_request) Successful in 10m12s
CI / docker (pull_request) Successful in 1m28s
CI / coverage (pull_request) Successful in 12m18s
CI / e2e_tests (pull_request) Successful in 19m51s
CI / status-check (pull_request) Successful in 1s
CI / build (push) Successful in 15s
CI / helm (push) Successful in 22s
CI / lint (push) Successful in 3m18s
CI / quality (push) Successful in 3m41s
CI / typecheck (push) Successful in 3m57s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 4m10s
CI / unit_tests (push) Failing after 6m58s
CI / docker (push) Has been skipped
CI / integration_tests (push) Successful in 9m15s
CI / coverage (push) Successful in 12m26s
CI / e2e_tests (push) Successful in 23m11s
CI / status-check (push) Failing after 1s
CI / benchmark-publish (push) Successful in 28m31s
CI / benchmark-regression (pull_request) Successful in 54m53s
test(coverage): add Behave scenarios for 39 under-covered modules
Add Behave feature/step pairs that exercise uncovered branches across handlers, LSP, CLI, and service layers to reach the coverage gate.

ISSUES CLOSED: #1232
2026-03-31 21:47:12 +00:00

479 lines
17 KiB
Python

"""Step definitions for lsp_runtime_coverage.feature.
Exercises uncovered lines in src/cleveragents/lsp/runtime.py using
the ``lrcov`` step prefix to avoid Behave AmbiguousStep errors.
"""
from __future__ import annotations
import os
import tempfile
from unittest.mock import MagicMock, patch
from behave import given, then, when
from behave.runner import Context
from cleveragents.lsp.errors import LspError, LspServerNotFoundError
from cleveragents.lsp.lifecycle import LspLifecycleManager
from cleveragents.lsp.models import LspServerConfig
from cleveragents.lsp.registry import LspRegistry
from cleveragents.lsp.runtime import LspRuntime
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_config(name: str = "local/pyright") -> LspServerConfig:
"""Build a minimal server config for testing."""
return LspServerConfig(
name=name,
command="echo",
languages=["python"],
)
def _make_mock_client() -> MagicMock:
"""Create a mock LSP client with the methods runtime calls."""
client = MagicMock(name="mock_lsp_client")
client.did_open = MagicMock()
client.did_close = MagicMock()
client.get_diagnostics = MagicMock(return_value=[])
client.get_completions = MagicMock(return_value=[])
return client
# ---------------------------------------------------------------------------
# Given steps
# ---------------------------------------------------------------------------
@given("lrcov I create an LspRuntime with defaults")
def step_lrcov_create_runtime_defaults(context: Context) -> None:
context.lrcov_runtime = LspRuntime()
context.lrcov_error = None
@given('lrcov I create an LspRuntime with a registered server "{name}"')
def step_lrcov_create_runtime_registered(context: Context, name: str) -> None:
registry = LspRegistry()
config = _make_config(name)
registry.register(config)
mock_lifecycle = MagicMock(spec=LspLifecycleManager)
mock_lifecycle.start_server = MagicMock()
mock_lifecycle.stop_server = MagicMock()
mock_lifecycle.stop_all = MagicMock()
mock_lifecycle.health_check = MagicMock(return_value=True)
mock_lifecycle.get_client = MagicMock(return_value=_make_mock_client())
context.lrcov_runtime = LspRuntime(
registry=registry,
lifecycle_manager=mock_lifecycle,
)
context.lrcov_mock_lifecycle = mock_lifecycle
context.lrcov_config = config
context.lrcov_error = None
@given("lrcov I create an LspRuntime with a mock lifecycle")
def step_lrcov_create_runtime_mock_lifecycle(context: Context) -> None:
mock_lifecycle = MagicMock(spec=LspLifecycleManager)
mock_lifecycle.start_server = MagicMock()
mock_lifecycle.stop_server = MagicMock()
mock_lifecycle.stop_all = MagicMock()
mock_lifecycle.health_check = MagicMock(return_value=True)
mock_lifecycle.get_client = MagicMock(return_value=_make_mock_client())
context.lrcov_runtime = LspRuntime(lifecycle_manager=mock_lifecycle)
context.lrcov_mock_lifecycle = mock_lifecycle
context.lrcov_error = None
@given("lrcov I create an LspRuntime with a mock lifecycle that raises on stop")
def step_lrcov_create_runtime_lifecycle_raises_stop(context: Context) -> None:
mock_lifecycle = MagicMock(spec=LspLifecycleManager)
mock_lifecycle.stop_server = MagicMock(
side_effect=LspServerNotFoundError("local/pyright"),
)
mock_lifecycle.stop_all = MagicMock()
context.lrcov_runtime = LspRuntime(lifecycle_manager=mock_lifecycle)
context.lrcov_mock_lifecycle = mock_lifecycle
context.lrcov_error = None
@given('lrcov I create an LspRuntime with a healthy mock server "{name}"')
def step_lrcov_create_runtime_healthy_server(context: Context, name: str) -> None:
"""Build a runtime where _get_healthy_client returns a mock client."""
mock_client = _make_mock_client()
mock_lifecycle = MagicMock(spec=LspLifecycleManager)
mock_lifecycle.health_check = MagicMock(return_value=True)
mock_lifecycle.get_client = MagicMock(return_value=mock_client)
context.lrcov_runtime = LspRuntime(lifecycle_manager=mock_lifecycle)
context.lrcov_mock_lifecycle = mock_lifecycle
context.lrcov_mock_client = mock_client
context.lrcov_error = None
@given('lrcov I have a temp Python file with content "{content}"')
def step_lrcov_create_temp_python_file(context: Context, content: str) -> None:
fd, path = tempfile.mkstemp(suffix=".py")
with os.fdopen(fd, "w") as f:
f.write(content)
context.lrcov_temp_file = path
def cleanup() -> None:
if os.path.exists(path):
os.unlink(path)
context.add_cleanup(cleanup)
@given('lrcov I have a temp file with content "{content}"')
def step_lrcov_create_temp_file(context: Context, content: str) -> None:
fd, path = tempfile.mkstemp(suffix=".txt")
with os.fdopen(fd, "w") as f:
f.write(content)
context.lrcov_temp_file = path
def cleanup() -> None:
if os.path.exists(path):
os.unlink(path)
context.add_cleanup(cleanup)
@given("lrcov I patch read_file to raise OSError")
def step_lrcov_patch_read_file_oserror(context: Context) -> None:
"""Patch LspRuntime._read_file to raise an OSError.
This exercises the ``except OSError`` branches in get_diagnostics
(lines 149-155) and get_completions (lines 210-216).
"""
patcher = patch.object(
LspRuntime,
"_read_file",
side_effect=OSError("Permission denied"),
)
patcher.start()
context.add_cleanup(patcher.stop)
# ---------------------------------------------------------------------------
# When steps
# ---------------------------------------------------------------------------
@when('lrcov I start server "{name}" with workspace "{workspace}"')
def step_lrcov_start_server(context: Context, name: str, workspace: str) -> None:
try:
context.lrcov_runtime.start_server(name, workspace)
context.lrcov_error = None
except Exception as exc:
context.lrcov_error = exc
@when("lrcov I try to stop server with empty name")
def step_lrcov_stop_empty_name(context: Context) -> None:
try:
context.lrcov_runtime.stop_server("")
context.lrcov_error = None
except ValueError as exc:
context.lrcov_error = exc
@when('lrcov I stop server "{name}"')
def step_lrcov_stop_server(context: Context, name: str) -> None:
try:
context.lrcov_runtime.stop_server(name)
context.lrcov_error = None
except Exception as exc:
context.lrcov_error = exc
@when('lrcov I try to get diagnostics with name "{name}" and empty file_path')
def step_lrcov_diagnostics_empty_filepath(context: Context, name: str) -> None:
try:
context.lrcov_runtime.get_diagnostics(name, "")
context.lrcov_error = None
except ValueError as exc:
context.lrcov_error = exc
@when('lrcov I get diagnostics for "{name}" on the temp file')
def step_lrcov_get_diagnostics_temp(context: Context, name: str) -> None:
try:
context.lrcov_result = context.lrcov_runtime.get_diagnostics(
name, context.lrcov_temp_file
)
context.lrcov_error = None
except Exception as exc:
context.lrcov_error = exc
@when('lrcov I try to get diagnostics for "{name}" on file "{file_path}"')
def step_lrcov_get_diagnostics_on_file(
context: Context, name: str, file_path: str
) -> None:
try:
context.lrcov_runtime.get_diagnostics(name, file_path)
context.lrcov_error = None
except Exception as exc:
context.lrcov_error = exc
@when("lrcov I try to get completions with empty name")
def step_lrcov_completions_empty_name(context: Context) -> None:
try:
context.lrcov_runtime.get_completions("", "/tmp/test.py", 1, 1)
context.lrcov_error = None
except ValueError as exc:
context.lrcov_error = exc
@when('lrcov I try to get completions with name "{name}" and empty file_path')
def step_lrcov_completions_empty_filepath(context: Context, name: str) -> None:
try:
context.lrcov_runtime.get_completions(name, "", 1, 1)
context.lrcov_error = None
except ValueError as exc:
context.lrcov_error = exc
@when(
'lrcov I get completions for "{name}" on the temp file'
" at line {line:d} column {col:d}"
)
def step_lrcov_get_completions_temp(
context: Context, name: str, line: int, col: int
) -> None:
try:
context.lrcov_result = context.lrcov_runtime.get_completions(
name, context.lrcov_temp_file, line, col
)
context.lrcov_error = None
except Exception as exc:
context.lrcov_error = exc
@when(
'lrcov I try to get completions for "{name}" on file "{file_path}"'
" at line {line:d} column {col:d}"
)
def step_lrcov_get_completions_on_file(
context: Context, name: str, file_path: str, line: int, col: int
) -> None:
try:
context.lrcov_runtime.get_completions(name, file_path, line, col)
context.lrcov_error = None
except Exception as exc:
context.lrcov_error = exc
@when('lrcov I call get_healthy_client for "{name}"')
def step_lrcov_get_healthy_client(context: Context, name: str) -> None:
try:
context.lrcov_result = context.lrcov_runtime._get_healthy_client(name)
context.lrcov_error = None
except Exception as exc:
context.lrcov_error = exc
@when('lrcov I convert "{path}" using path_to_uri')
def step_lrcov_path_to_uri(context: Context, path: str) -> None:
context.lrcov_uri = LspRuntime._path_to_uri(path)
@when("lrcov I call read_file on the temp file")
def step_lrcov_read_file(context: Context) -> None:
try:
context.lrcov_file_content = LspRuntime._read_file(context.lrcov_temp_file)
context.lrcov_error = None
except Exception as exc:
context.lrcov_error = exc
@when("lrcov I call read_file on a directory path")
def step_lrcov_read_file_dir(context: Context) -> None:
try:
context.lrcov_file_content = LspRuntime._read_file("/tmp")
context.lrcov_error = None
except Exception as exc:
context.lrcov_error = exc
@when('lrcov I call detect_language on "{filename}"')
def step_lrcov_detect_language(context: Context, filename: str) -> None:
context.lrcov_detected_language = LspRuntime._detect_language(filename)
@when("lrcov I activate bindings with a binding that has empty server_name")
def step_lrcov_activate_empty_binding(context: Context) -> None:
binding = MagicMock()
binding.lsp_server_name = ""
context.lrcov_activated = context.lrcov_runtime.activate_bindings([binding], "/tmp")
@when('lrcov I activate bindings with a binding for "{name}" and workspace "{ws}"')
def step_lrcov_activate_binding_success(context: Context, name: str, ws: str) -> None:
binding = MagicMock()
binding.lsp_server_name = name
binding.node_name = "test_node"
context.lrcov_activated = context.lrcov_runtime.activate_bindings([binding], ws)
@when('lrcov I deactivate bindings with a binding for "{name}"')
def step_lrcov_deactivate_binding(context: Context, name: str) -> None:
binding = MagicMock()
binding.lsp_server_name = name
try:
context.lrcov_runtime.deactivate_bindings([binding])
context.lrcov_error = None
except Exception as exc:
context.lrcov_error = exc
@when("lrcov I deactivate bindings with a binding that has empty server_name")
def step_lrcov_deactivate_empty_binding(context: Context) -> None:
binding = MagicMock()
binding.lsp_server_name = ""
context.lrcov_runtime.deactivate_bindings([binding])
@when("lrcov I call stop_all")
def step_lrcov_stop_all(context: Context) -> None:
context.lrcov_runtime.stop_all()
# ---------------------------------------------------------------------------
# Then steps
# ---------------------------------------------------------------------------
@then("lrcov the registry property returns the registry")
def step_lrcov_check_registry(context: Context) -> None:
reg = context.lrcov_runtime.registry
assert isinstance(reg, LspRegistry), f"Expected LspRegistry, got {type(reg)}"
@then("lrcov the lifecycle property returns the lifecycle manager")
def step_lrcov_check_lifecycle(context: Context) -> None:
lc = context.lrcov_runtime.lifecycle
# Could be a real LspLifecycleManager or a Mock spec'd to one
assert lc is not None
@then("lrcov the lifecycle start_server should have been called")
def step_lrcov_lifecycle_start_called(context: Context) -> None:
context.lrcov_mock_lifecycle.start_server.assert_called_once()
@then("lrcov a ValueError should be raised")
def step_lrcov_valueerror_raised(context: Context) -> None:
assert context.lrcov_error is not None, (
"Expected a ValueError but no error occurred"
)
assert isinstance(context.lrcov_error, ValueError), (
f"Expected ValueError, got {type(context.lrcov_error).__name__}: "
f"{context.lrcov_error}"
)
@then('lrcov the lifecycle stop_server should have been called with "{name}"')
def step_lrcov_lifecycle_stop_called(context: Context, name: str) -> None:
context.lrcov_mock_lifecycle.stop_server.assert_called_once_with(name)
@then("lrcov diagnostics should be returned as a list")
def step_lrcov_diagnostics_is_list(context: Context) -> None:
assert context.lrcov_error is None, f"Expected no error, got {context.lrcov_error}"
assert isinstance(context.lrcov_result, list)
@then("lrcov the mock client did_open should have been called")
def step_lrcov_client_did_open(context: Context) -> None:
context.lrcov_mock_client.did_open.assert_called()
@then("lrcov the mock client did_close should have been called")
def step_lrcov_client_did_close(context: Context) -> None:
context.lrcov_mock_client.did_close.assert_called()
@then('lrcov an LspError should be raised with message containing "{msg}"')
def step_lrcov_lsp_error_msg(context: Context, msg: str) -> None:
assert context.lrcov_error is not None, "Expected an LspError but no error occurred"
assert isinstance(context.lrcov_error, LspError), (
f"Expected LspError, got {type(context.lrcov_error).__name__}: "
f"{context.lrcov_error}"
)
assert msg in str(context.lrcov_error), (
f"Expected '{msg}' in error message, got: {context.lrcov_error}"
)
@then("lrcov completions should be returned as a list")
def step_lrcov_completions_is_list(context: Context) -> None:
assert context.lrcov_error is None, f"Expected no error, got {context.lrcov_error}"
assert isinstance(context.lrcov_result, list)
@then("lrcov the returned client should be the mock client")
def step_lrcov_returned_client(context: Context) -> None:
assert context.lrcov_error is None, f"Expected no error, got {context.lrcov_error}"
assert context.lrcov_result is context.lrcov_mock_client
@then('lrcov the URI should be "{expected}"')
def step_lrcov_uri_check(context: Context, expected: str) -> None:
assert context.lrcov_uri == expected, (
f"Expected '{expected}', got '{context.lrcov_uri}'"
)
@then('lrcov the file content should be "{expected}"')
def step_lrcov_file_content(context: Context, expected: str) -> None:
assert context.lrcov_error is None, f"Expected no error, got {context.lrcov_error}"
assert context.lrcov_file_content == expected, (
f"Expected '{expected}', got '{context.lrcov_file_content}'"
)
@then('lrcov the detected language should be "{expected}"')
def step_lrcov_detected_language(context: Context, expected: str) -> None:
assert context.lrcov_detected_language == expected, (
f"Expected '{expected}', got '{context.lrcov_detected_language}'"
)
@then("lrcov the activated list should be empty")
def step_lrcov_activated_empty(context: Context) -> None:
assert context.lrcov_activated == [], (
f"Expected empty list, got {context.lrcov_activated}"
)
@then('lrcov the activated list should contain "{name}"')
def step_lrcov_activated_contains(context: Context, name: str) -> None:
assert name in context.lrcov_activated, (
f"Expected '{name}' in {context.lrcov_activated}"
)
@then("lrcov the lifecycle stop_server should not have been called")
def step_lrcov_lifecycle_stop_not_called(context: Context) -> None:
context.lrcov_mock_lifecycle.stop_server.assert_not_called()
@then("lrcov no error should be raised")
def step_lrcov_no_error(context: Context) -> None:
assert context.lrcov_error is None, f"Expected no error, got {context.lrcov_error}"
@then("lrcov the lifecycle stop_all should have been called")
def step_lrcov_lifecycle_stop_all_called(context: Context) -> None:
context.lrcov_mock_lifecycle.stop_all.assert_called_once()