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
The `else: proc.stderr = stderr` clause was dropped when the helper
was extracted into `_ltcov_helpers.py`. `stdin` and `stdout` both
have the corresponding else branches; this restores parity so that
callers passing a non-"auto" stderr value have it honoured.
- features/steps/lsp_transport_coverage_steps.py: change relative import
`from ._ltcov_helpers import build_lsp_frame, ...` to absolute
`from _ltcov_helpers import make_mock_process ...`, removing unused
`build_lsp_frame`
- features/steps/lsp_transport_post_spawn_cleanup_steps.py: remove unused
`subprocess` and `MagicMock` imports; fix relative import to absolute;
collapse short assert messages to single lines; remove trailing blank line
Behave's exec_file loader does not set __name__ in globals, causing relative
imports to raise KeyError at step-module load time. Absolute imports work
because Behave adds features/steps/ to sys.path.
ISSUES CLOSED: #11237
Closes#7044
The StdioTransport.start() method had an unprotected logger.info() call
after successful Popen(). If that call raised, the subprocess would
leak as an orphaned process. Wrap all post-spawn initialization in a
try/except guard: on any exception after spawn, terminate and wait for
the process (with kill fallback), reset state to None, then re-raise
so callers still get proper error semantics.
The existing stop() method cleanup pattern (terminate → wait → kill) is
mirrored here for consistency across the transport lifecycle.
Tests added:
- TDD scenario with @tdd_issue_7044 verifying subprocess cleanup on
post-Popen exception and state reset to None
- Explicit is_alive() scenarios covering both alive and not-alive states
Refactoring:
- Extracted _make_mock_process and _build_lsp_frame helpers into a
shared _ltcov_helpers module to keep step files under the 500-line
CONTRIBUTING.md limit.
ISSUES CLOSED: #7044