Files
cleveragents-core/features/lsp_transport_coverage.feature
T
HAL9000 22831e4f50
CI / lint (pull_request) Successful in 53s
CI / quality (pull_request) Successful in 54s
CI / typecheck (pull_request) Successful in 1m30s
CI / security (pull_request) Successful in 1m32s
CI / helm (pull_request) Successful in 29s
CI / build (pull_request) Successful in 33s
CI / push-validation (pull_request) Successful in 30s
CI / integration_tests (pull_request) Successful in 2m57s
CI / unit_tests (pull_request) Successful in 4m38s
CI / docker (pull_request) Successful in 1m25s
CI / coverage (pull_request) Successful in 13m7s
CI / status-check (pull_request) Successful in 6s
fix(lsp): make post-spawn logger.info mock selective and remove tdd_expected_fail
The _info_raises side_effect previously raised RuntimeError on every
logger.info call, which caused the pre-Popen "lsp.transport.starting"
call (transport.py:109) to raise before subprocess.Popen was ever
reached. This meant the cleanup guard at lines 160-186 was never
exercised, making the scenario a permanent no-op TDD stub rather than
a genuine regression guard.

Fix: make _info_raises conditional on the first positional argument
being "lsp.transport.started" (the post-Popen message). The pre-Popen
"lsp.transport.starting" call now passes through normally, Popen
succeeds (mocked), and the RuntimeError is raised inside the guarded
try/except block, triggering terminate() + wait() cleanup.

Remove @tdd_expected_fail from the scenario since the cleanup code at
transport.py:160-186 is in place and the scenario now passes.

Closes #7044
2026-05-28 11:36:14 -04:00

175 lines
8.7 KiB
Gherkin

Feature: LSP StdioTransport coverage
As a developer maintaining the LSP transport layer
I need thorough tests for StdioTransport
So that all error paths and normal flows are exercised
# start() error paths
@tdd_issue @tdd_issue_7044
Scenario: ltcov start cleans up subprocess when post-Popen init fails
Given ltcov I create a StdioTransport for command "cat"
And ltcov Popen is mocked to succeed with a running process
And ltcov logger.info after spawn raises RuntimeError
When ltcov I try to start the transport
Then ltcov an error occurred during start()
And ltcov the internal process should be None
And ltcov the mock process should have been terminated
And ltcov the mock process should have been waited on
Scenario: ltcov start raises RuntimeError when already alive
Given ltcov I create a StdioTransport for command "cat"
And ltcov the transport has a running mock process
When ltcov I try to start the transport
Then ltcov the error should be a RuntimeError with message "already started"
@tdd_issue @tdd_issue_7044
Scenario: ltcov start is alive after successful spawn
Given ltcov I create a StdioTransport for command "cat"
When ltcov I try to start the transport
Then ltcov the transport should be alive
And ltcov the internal process should not be None
Scenario: ltcov is_alive returns False when no process
Given ltcov I create a StdioTransport for command "cat"
When ltcov I check if the transport is alive
Then ltcov the is_alive result should be false
Scenario: ltcov start raises LspError on FileNotFoundError
Given ltcov I create a StdioTransport for command "nonexistent_binary_xyz"
And ltcov Popen is mocked to raise FileNotFoundError
When ltcov I try to start the transport
Then ltcov the error should be an LspError with message "not found"
Scenario: ltcov start raises LspError on OSError
Given ltcov I create a StdioTransport for command "bad_command"
And ltcov Popen is mocked to raise OSError with "Permission denied"
When ltcov I try to start the transport
Then ltcov the error should be an LspError with message "Failed to start"
# ── stop() paths ────────────────────────────────────────────────
Scenario: ltcov stop returns None when not started
Given ltcov I create a StdioTransport for command "echo"
When ltcov I stop the transport
Then ltcov the stop result should be None
Scenario: ltcov stop returns exit code when process already exited
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process that already exited with code 0
When ltcov I stop the transport
Then ltcov the stop result should be 0
And ltcov the internal process should be None
Scenario: ltcov stop returns exit code when process already exited with error
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process that already exited with code 1
When ltcov I stop the transport
Then ltcov the stop result should be 1
Scenario: ltcov stop force kills on timeout
Given ltcov I create a StdioTransport for command "sleep"
And ltcov the transport has a mock process that ignores terminate
When ltcov I stop the transport with timeout 0.1
Then ltcov the mock process should have been killed
And ltcov the stop result should be an integer
# ── send_message() paths ────────────────────────────────────────
Scenario: ltcov send_message raises RuntimeError when not started
Given ltcov I create a StdioTransport for command "echo"
When ltcov I try to send a message
Then ltcov the error should be a RuntimeError with message "not started"
Scenario: ltcov send_message raises RuntimeError when stdin is None
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process with no stdin
When ltcov I try to send a message
Then ltcov the error should be a RuntimeError with message "not started"
Scenario: ltcov send_message writes Content-Length framed JSON-RPC
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process with writable stdin
When ltcov I send the message {"jsonrpc":"2.0","method":"initialize","id":1}
Then ltcov the written bytes should contain "Content-Length:"
And ltcov the written bytes should contain "initialize"
Scenario: ltcov send_message raises BrokenPipeError on dead process
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process with broken stdin
When ltcov I try to send a message
Then ltcov the error should be a BrokenPipeError
# ── read_message() paths ────────────────────────────────────────
Scenario: ltcov read_message raises RuntimeError when not started
Given ltcov I create a StdioTransport for command "echo"
When ltcov I try to read a message
Then ltcov the error should be a RuntimeError with message "not started"
Scenario: ltcov read_message raises RuntimeError when stdout is None
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process with no stdout
When ltcov I try to read a message
Then ltcov the error should be a RuntimeError with message "not started"
Scenario: ltcov read_message returns None on select timeout
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process with empty stdout
And ltcov select is mocked to return not ready
When ltcov I read a message with timeout 1.0
Then ltcov the read result should be None
Scenario: ltcov read_message returns None on EOF
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process with stdout that returns EOF
When ltcov I read a message with timeout 1.0
Then ltcov the read result should be None
Scenario: ltcov read_message uses default timeout when None
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process with stdout that returns EOF
When ltcov I read a message with default timeout
Then ltcov the read result should be None
Scenario: ltcov read_message parses valid Content-Length message
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process with a valid JSON-RPC response
When ltcov I read a message with timeout 5.0
Then ltcov the read result should have key "jsonrpc" with value "2.0"
Scenario: ltcov read_message returns None on invalid content-length value
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process with invalid content-length header
When ltcov I read a message with timeout 5.0
Then ltcov the read result should be None
Scenario: ltcov read_message returns None when no content-length header
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process with no content-length header
When ltcov I read a message with timeout 5.0
Then ltcov the read result should be None
Scenario: ltcov read_message raises ValueError on oversized message
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process with oversized content-length
When ltcov I try to read a message
Then ltcov the error should be a ValueError with message "too large"
Scenario: ltcov read_message returns None on body select timeout
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process with headers but body times out
When ltcov I read a message with timeout 5.0
Then ltcov the read result should be None
Scenario: ltcov read_message returns None on truncated body
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process with truncated body
When ltcov I read a message with timeout 5.0
Then ltcov the read result should be None
Scenario: ltcov read_message returns None on invalid JSON body
Given ltcov I create a StdioTransport for command "echo"
And ltcov the transport has a mock process with invalid JSON body
When ltcov I read a message with timeout 5.0
Then ltcov the read result should be None