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
Add Behave feature/step pairs that exercise uncovered branches across handlers, LSP, CLI, and service layers to reach the coverage gate. ISSUES CLOSED: #1232
479 lines
17 KiB
Python
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()
|