Skip to content

Commit 3595b0d

Browse files
committed
ControlMode(test[regressions]): Add xfail coverage for control-mode gaps
why: Document current control-mode discrepancies and protect against regressions. what: - Add xfail regression tests for trailing stdout blanks, kill-server EOF, is_alive bootstrap, switch_client client counting - Capture fixtures using NamedTuple parametrization with test_id identifiers - Keep failing cases isolated while keeping lint/format/type checks passing
1 parent 3eef36e commit 3595b0d

File tree

1 file changed

+141
-0
lines changed

1 file changed

+141
-0
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
"""Regression repros for current control-mode gaps (marked xfail)."""
2+
3+
from __future__ import annotations
4+
5+
import contextlib
6+
import typing as t
7+
import uuid
8+
9+
import pytest
10+
11+
from libtmux import exc
12+
from libtmux._internal.engines.base import ExitStatus
13+
from libtmux._internal.engines.control_mode import ControlModeEngine
14+
from libtmux._internal.engines.control_protocol import (
15+
CommandContext,
16+
ControlProtocol,
17+
)
18+
from libtmux.server import Server
19+
20+
21+
class TrailingOutputFixture(t.NamedTuple):
22+
"""Fixture for trailing-blank stdout normalization."""
23+
24+
test_id: str
25+
raw_lines: list[str]
26+
expected_stdout: list[str]
27+
28+
29+
TRAILING_OUTPUT_CASES = [
30+
pytest.param(
31+
TrailingOutputFixture(
32+
test_id="no_blanks",
33+
raw_lines=["line1"],
34+
expected_stdout=["line1"],
35+
),
36+
id="no_blanks",
37+
),
38+
pytest.param(
39+
TrailingOutputFixture(
40+
test_id="one_blank",
41+
raw_lines=["line1", ""],
42+
expected_stdout=["line1"],
43+
),
44+
id="one_blank",
45+
marks=pytest.mark.xfail(
46+
reason="control-mode preserves trailing blank stdout lines",
47+
strict=True,
48+
),
49+
),
50+
pytest.param(
51+
TrailingOutputFixture(
52+
test_id="many_blanks",
53+
raw_lines=["line1", "", "", ""],
54+
expected_stdout=["line1"],
55+
),
56+
id="many_blanks",
57+
marks=pytest.mark.xfail(
58+
reason="control-mode preserves trailing blank stdout lines",
59+
strict=True,
60+
),
61+
),
62+
]
63+
64+
65+
@pytest.mark.parametrize(
66+
"case",
67+
TRAILING_OUTPUT_CASES,
68+
)
69+
def test_control_protocol_trims_trailing_blank_lines(
70+
case: TrailingOutputFixture,
71+
) -> None:
72+
"""ControlProtocol should trim trailing blank lines like subprocess engine."""
73+
proto = ControlProtocol()
74+
ctx = CommandContext(argv=["tmux", "list-sessions"])
75+
proto.register_command(ctx)
76+
proto.feed_line("%begin 0 1 0")
77+
for line in case.raw_lines:
78+
proto.feed_line(line)
79+
proto.feed_line("%end 0 1 0")
80+
81+
assert ctx.done.wait(timeout=0.05)
82+
result = proto.build_result(ctx)
83+
assert result.stdout == case.expected_stdout
84+
85+
86+
@pytest.mark.xfail(
87+
reason="kill-server EOF currently surfaces as ControlModeConnectionError",
88+
strict=True,
89+
)
90+
def test_kill_server_eof_marks_success() -> None:
91+
"""EOF during kill-server should be treated as a successful completion."""
92+
proto = ControlProtocol()
93+
ctx = CommandContext(argv=["tmux", "kill-server"])
94+
proto.register_command(ctx)
95+
proto.feed_line("%begin 0 1 0")
96+
97+
proto.mark_dead("EOF from tmux")
98+
99+
assert ctx.done.is_set()
100+
assert ctx.error is None
101+
result = proto.build_result(ctx)
102+
assert result.exit_status is ExitStatus.OK
103+
104+
105+
@pytest.mark.xfail(
106+
reason="control-mode bootstrap currently makes is_alive start a server",
107+
strict=True,
108+
)
109+
def test_is_alive_does_not_bootstrap_control_mode() -> None:
110+
"""is_alive should not spin up control-mode process for an unknown socket."""
111+
socket_name = f"libtmux_test_{uuid.uuid4().hex[:8]}"
112+
engine = ControlModeEngine()
113+
server = Server(socket_name=socket_name, engine=engine)
114+
try:
115+
assert server.is_alive() is False
116+
assert engine.process is None
117+
finally:
118+
# Best-effort cleanup; current behavior may have started tmux.
119+
with contextlib.suppress(Exception):
120+
server.kill()
121+
122+
123+
@pytest.mark.xfail(
124+
reason="control-mode client counts as attached, so switch_client succeeds",
125+
strict=True,
126+
)
127+
def test_switch_client_raises_without_user_clients() -> None:
128+
"""switch_client should raise when no user clients are attached."""
129+
socket_name = f"libtmux_test_{uuid.uuid4().hex[:8]}"
130+
engine = ControlModeEngine()
131+
server = Server(socket_name=socket_name, engine=engine)
132+
133+
try:
134+
session = server.new_session(session_name="switch_client_repro", attach=False)
135+
assert session is not None
136+
137+
with pytest.raises(exc.LibTmuxException):
138+
server.switch_client("switch_client_repro")
139+
finally:
140+
with contextlib.suppress(Exception):
141+
server.kill()

0 commit comments

Comments
 (0)