Satisfy project quality gates
This commit is contained in:
+168
-88
@@ -15,10 +15,14 @@ def runner():
|
||||
@pytest.mark.asyncio
|
||||
async def test_new_window(runner):
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n", ""), # list-windows -F '#{window_index}' (next-index query)
|
||||
(0, "0\n", ""), # list-windows -F '#{window_index}' (next-index query)
|
||||
(0, "@1\n", ""), # new-window -P -F
|
||||
(0, "", ""), # set-option (combined: remain-on-exit + allow-rename + automatic-rename)
|
||||
(0, "", ""), # respawn-pane
|
||||
(
|
||||
0,
|
||||
"",
|
||||
"",
|
||||
), # set-option (combined: remain-on-exit + allow-rename + automatic-rename)
|
||||
(0, "", ""), # respawn-pane
|
||||
]
|
||||
result = await runner.new_window("hqt-1", "/tmp", "kiro-cli chat")
|
||||
assert result == "@1"
|
||||
@@ -26,9 +30,23 @@ async def test_new_window(runner):
|
||||
# options, then the per-window Frappé theme options (see new_window).
|
||||
set_call = runner._exec.call_args_list[2].args
|
||||
assert set_call[:17] == (
|
||||
"set-option", "-t", "hqt-main:=hqt-1", "remain-on-exit", "on",
|
||||
";", "set-option", "-t", "hqt-main:=hqt-1", "allow-rename", "off",
|
||||
";", "set-option", "-t", "hqt-main:=hqt-1", "automatic-rename", "off",
|
||||
"set-option",
|
||||
"-t",
|
||||
"hqt-main:=hqt-1",
|
||||
"remain-on-exit",
|
||||
"on",
|
||||
";",
|
||||
"set-option",
|
||||
"-t",
|
||||
"hqt-main:=hqt-1",
|
||||
"allow-rename",
|
||||
"off",
|
||||
";",
|
||||
"set-option",
|
||||
"-t",
|
||||
"hqt-main:=hqt-1",
|
||||
"automatic-rename",
|
||||
"off",
|
||||
)
|
||||
assert "window-status-current-style" in set_call
|
||||
# respawn-pane runs the actual command
|
||||
@@ -51,9 +69,9 @@ async def test_new_window_targets_explicit_free_index(runner):
|
||||
and targeting `session:idx` sidesteps the resolution entirely."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n1\n2\n", ""), # list-windows -F '#{window_index}' (existing: 0,1,2)
|
||||
(0, "@9\n", ""), # new-window -P -F -> window_id
|
||||
(0, "", ""), # set-option
|
||||
(0, "", ""), # respawn-pane
|
||||
(0, "@9\n", ""), # new-window -P -F -> window_id
|
||||
(0, "", ""), # set-option
|
||||
(0, "", ""), # respawn-pane
|
||||
]
|
||||
window_id = await runner.new_window("hqt-9", "/tmp", "claude")
|
||||
assert window_id == "@9"
|
||||
@@ -61,7 +79,9 @@ async def test_new_window_targets_explicit_free_index(runner):
|
||||
new_win_args = runner._exec.call_args_list[1].args
|
||||
assert "new-window" in new_win_args
|
||||
# Explicit free index = max(0,1,2)+1 = 3, targeted as "<session>:3".
|
||||
assert "hqt-main:3" in new_win_args, f"expected explicit free index target, got {new_win_args}"
|
||||
assert "hqt-main:3" in new_win_args, (
|
||||
f"expected explicit free index target, got {new_win_args}"
|
||||
)
|
||||
# Must NOT use the bare session name as the new-window target.
|
||||
ti = new_win_args.index("-t")
|
||||
assert new_win_args[ti + 1] == "hqt-main:3"
|
||||
@@ -102,7 +122,13 @@ async def test_select_window(runner):
|
||||
async def test_respawn_pane(runner):
|
||||
await runner.respawn_pane("hqt-1", "kiro-cli chat", "/tmp")
|
||||
runner._exec.assert_called_with(
|
||||
"respawn-pane", "-t", "hqt-main:=hqt-1", "-k", "-c", "/tmp", "kiro-cli chat",
|
||||
"respawn-pane",
|
||||
"-t",
|
||||
"hqt-main:=hqt-1",
|
||||
"-k",
|
||||
"-c",
|
||||
"/tmp",
|
||||
"kiro-cli chat",
|
||||
)
|
||||
|
||||
|
||||
@@ -116,10 +142,10 @@ async def test_list_windows(runner):
|
||||
@pytest.mark.asyncio
|
||||
async def test_manager_spawn(runner):
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@3\n", ""), # new-window -P -F
|
||||
(0, "", ""), # set-option
|
||||
(0, "", ""), # respawn-pane
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@3\n", ""), # new-window -P -F
|
||||
(0, "", ""), # set-option
|
||||
(0, "", ""), # respawn-pane
|
||||
]
|
||||
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
|
||||
mgr = TmuxManager(runner)
|
||||
@@ -150,7 +176,7 @@ async def test_manager_is_alive_true(runner):
|
||||
# is_pane_dead needs list-panes to return "0"
|
||||
runner._exec.side_effect = [
|
||||
(0, "hqt-1\n", ""), # has_window
|
||||
(0, "0\n", ""), # is_pane_dead
|
||||
(0, "0\n", ""), # is_pane_dead
|
||||
]
|
||||
mgr = TmuxManager(runner)
|
||||
assert await mgr.is_alive("hqt-1") is True
|
||||
@@ -161,7 +187,7 @@ async def test_manager_is_alive_false_dead_pane(runner):
|
||||
"""Window exists but pane dead."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "hqt-1\n", ""), # has_window
|
||||
(0, "1\n", ""), # is_pane_dead
|
||||
(0, "1\n", ""), # is_pane_dead
|
||||
]
|
||||
mgr = TmuxManager(runner)
|
||||
assert await mgr.is_alive("hqt-1") is False
|
||||
@@ -199,10 +225,10 @@ async def test_has_window_false_single_exec(runner):
|
||||
async def test_new_window_race_free_sequence(runner):
|
||||
"""new_window issues: new-window (no command), set-option, respawn-pane — in order."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@5\n", ""), # new-window -P -F -> window_id
|
||||
(0, "", ""), # set-option
|
||||
(0, "", ""), # respawn-pane
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@5\n", ""), # new-window -P -F -> window_id
|
||||
(0, "", ""), # set-option
|
||||
(0, "", ""), # respawn-pane
|
||||
]
|
||||
window_id = await runner.new_window("hqt-1", "/tmp", "kiro-cli chat")
|
||||
assert window_id == "@5"
|
||||
@@ -226,9 +252,23 @@ async def test_new_window_race_free_sequence(runner):
|
||||
|
||||
set_args = calls[2].args
|
||||
assert set_args[:17] == (
|
||||
"set-option", "-t", "hqt-main:=hqt-1", "remain-on-exit", "on",
|
||||
";", "set-option", "-t", "hqt-main:=hqt-1", "allow-rename", "off",
|
||||
";", "set-option", "-t", "hqt-main:=hqt-1", "automatic-rename", "off",
|
||||
"set-option",
|
||||
"-t",
|
||||
"hqt-main:=hqt-1",
|
||||
"remain-on-exit",
|
||||
"on",
|
||||
";",
|
||||
"set-option",
|
||||
"-t",
|
||||
"hqt-main:=hqt-1",
|
||||
"allow-rename",
|
||||
"off",
|
||||
";",
|
||||
"set-option",
|
||||
"-t",
|
||||
"hqt-main:=hqt-1",
|
||||
"automatic-rename",
|
||||
"off",
|
||||
)
|
||||
# Window-scoped theme options painted on this window, current-style = peach.
|
||||
assert "window-status-current-style" in set_args
|
||||
@@ -249,12 +289,14 @@ async def test_new_window_race_free_sequence(runner):
|
||||
async def test_new_window_with_env(runner):
|
||||
"""new_window passes -e KEY=VALUE flags on respawn-pane (step 3), NOT on new-window (step 1)."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@7\n", ""),
|
||||
(0, "", ""),
|
||||
(0, "", ""),
|
||||
]
|
||||
window_id = await runner.new_window("hqt-2", "/tmp", "claude", env={"FOO": "bar", "X": "1"})
|
||||
window_id = await runner.new_window(
|
||||
"hqt-2", "/tmp", "claude", env={"FOO": "bar", "X": "1"}
|
||||
)
|
||||
assert window_id == "@7"
|
||||
|
||||
calls_list = runner._exec.call_args_list
|
||||
@@ -278,10 +320,10 @@ async def test_new_window_with_env(runner):
|
||||
async def test_new_window_set_option_failure_aborts(runner):
|
||||
"""If set-option fails, new_window kills the orphaned window and returns None."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@3\n", ""), # new-window succeeds
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@3\n", ""), # new-window succeeds
|
||||
(1, "", "some tmux error"), # set-option fails
|
||||
(0, "", ""), # kill-window cleanup
|
||||
(0, "", ""), # kill-window cleanup
|
||||
]
|
||||
result = await runner.new_window("hqt-3", "/tmp", "claude")
|
||||
assert result is None
|
||||
@@ -296,11 +338,11 @@ async def test_new_window_set_option_failure_aborts(runner):
|
||||
async def test_new_window_respawn_failure_kills_window(runner):
|
||||
"""If respawn-pane fails, new_window kills the orphaned window and returns None."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@4\n", ""), # new-window succeeds
|
||||
(0, "", ""), # set-option succeeds
|
||||
(1, "", "respawn error"), # respawn-pane fails
|
||||
(0, "", ""), # kill-window cleanup
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@4\n", ""), # new-window succeeds
|
||||
(0, "", ""), # set-option succeeds
|
||||
(1, "", "respawn error"), # respawn-pane fails
|
||||
(0, "", ""), # kill-window cleanup
|
||||
]
|
||||
result = await runner.new_window("hqt-4", "/tmp", "claude")
|
||||
assert result is None
|
||||
@@ -314,7 +356,7 @@ async def test_new_window_respawn_failure_kills_window(runner):
|
||||
async def test_new_window_returns_window_id(runner):
|
||||
"""new_window returns the window_id string on full success."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@42\n", ""),
|
||||
(0, "", ""),
|
||||
(0, "", ""),
|
||||
@@ -327,7 +369,7 @@ async def test_new_window_returns_window_id(runner):
|
||||
async def test_new_window_returns_none_on_new_window_failure(runner):
|
||||
"""new_window returns None when the initial new-window command fails."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(1, "", "session not found"), # new-window fails
|
||||
]
|
||||
result = await runner.new_window("hqt-x", "/tmp", "sleep 1")
|
||||
@@ -354,8 +396,8 @@ async def test_verify_window_alive_alive(runner):
|
||||
async def test_verify_window_alive_dead_with_text(runner):
|
||||
"""Pane dies within timeout → (False, captured text)."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "1\n", ""), # is_pane_dead → dead
|
||||
(0, "command not found\n", ""), # capture_pane
|
||||
(0, "1\n", ""), # is_pane_dead → dead
|
||||
(0, "command not found\n", ""), # capture_pane
|
||||
]
|
||||
ok, text = await runner.verify_window_alive("hqt-1", timeout=0.5, interval=0.05)
|
||||
assert ok is False
|
||||
@@ -366,8 +408,8 @@ async def test_verify_window_alive_dead_with_text(runner):
|
||||
async def test_verify_window_alive_dead_empty_text(runner):
|
||||
"""Pane dies with empty capture → (False, '') still False."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "1\n", ""), # is_pane_dead
|
||||
(0, "", ""), # capture_pane empty
|
||||
(0, "1\n", ""), # is_pane_dead
|
||||
(0, "", ""), # capture_pane empty
|
||||
]
|
||||
ok, text = await runner.verify_window_alive("hqt-1", timeout=0.5, interval=0.05)
|
||||
assert ok is False
|
||||
@@ -379,9 +421,9 @@ async def test_verify_window_alive_dies_during_last_interval(runner):
|
||||
"""Pane alive throughout polling loop but dies during the final sleep → (False, text)."""
|
||||
# All in-loop polls return alive (0), final post-loop check returns dead (1)
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n", ""), # is_pane_dead (in-loop) → alive
|
||||
(0, "1\n", ""), # is_pane_dead (post-loop final check) → dead
|
||||
(0, "died at last\n", ""), # capture_pane
|
||||
(0, "0\n", ""), # is_pane_dead (in-loop) → alive
|
||||
(0, "1\n", ""), # is_pane_dead (post-loop final check) → dead
|
||||
(0, "died at last\n", ""), # capture_pane
|
||||
]
|
||||
ok, text = await runner.verify_window_alive("hqt-1", timeout=0.05, interval=0.1)
|
||||
assert ok is False
|
||||
@@ -397,10 +439,10 @@ async def test_verify_window_alive_dies_during_last_interval(runner):
|
||||
async def test_spawn_result_ok(runner):
|
||||
"""spawn returns SpawnResult(ok=True, window_id=...) on success."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@10\n", ""), # new-window
|
||||
(0, "", ""), # set-option
|
||||
(0, "", ""), # respawn-pane
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@10\n", ""), # new-window
|
||||
(0, "", ""), # set-option
|
||||
(0, "", ""), # respawn-pane
|
||||
]
|
||||
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
|
||||
mgr = TmuxManager(runner)
|
||||
@@ -416,10 +458,10 @@ async def test_spawn_result_ok(runner):
|
||||
async def test_spawn_result_failure_verification(runner):
|
||||
"""spawn returns SpawnResult(ok=False, error=...) when pane dies immediately."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@11\n", ""), # new-window
|
||||
(0, "", ""), # set-option
|
||||
(0, "", ""), # respawn-pane
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@11\n", ""), # new-window
|
||||
(0, "", ""), # set-option
|
||||
(0, "", ""), # respawn-pane
|
||||
]
|
||||
runner.verify_window_alive = AsyncMock(return_value=(False, "no such binary\n"))
|
||||
mgr = TmuxManager(runner)
|
||||
@@ -433,14 +475,16 @@ async def test_spawn_result_failure_verification(runner):
|
||||
async def test_spawn_shlex_join(runner):
|
||||
"""spawn uses shlex.join so args with spaces are properly quoted."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@99\n", ""),
|
||||
(0, "", ""),
|
||||
(0, "", ""),
|
||||
]
|
||||
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
|
||||
mgr = TmuxManager(runner)
|
||||
req = SpawnRequest(window_name="hqt-1", command=["my cmd", "arg with space"], cwd="/tmp")
|
||||
req = SpawnRequest(
|
||||
window_name="hqt-1", command=["my cmd", "arg with space"], cwd="/tmp"
|
||||
)
|
||||
await mgr.spawn(req)
|
||||
|
||||
# The respawn-pane call (now the 4th _exec call) should have the shlex-joined string
|
||||
@@ -449,6 +493,7 @@ async def test_spawn_shlex_join(runner):
|
||||
joined_cmd = respawn_call[-1]
|
||||
# shlex.join of ["my cmd", "arg with space"] → "'my cmd' 'arg with space'"
|
||||
import shlex
|
||||
|
||||
expected = shlex.join(["my cmd", "arg with space"])
|
||||
assert joined_cmd == expected
|
||||
|
||||
@@ -457,14 +502,16 @@ async def test_spawn_shlex_join(runner):
|
||||
async def test_spawn_passes_env(runner):
|
||||
"""spawn passes env dict through to respawn-pane (step 3), NOT to new-window (step 1)."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@20\n", ""),
|
||||
(0, "", ""),
|
||||
(0, "", ""),
|
||||
]
|
||||
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
|
||||
mgr = TmuxManager(runner)
|
||||
req = SpawnRequest(window_name="hqt-env", command=["bash"], cwd="/tmp", env={"MY_VAR": "hello"})
|
||||
req = SpawnRequest(
|
||||
window_name="hqt-env", command=["bash"], cwd="/tmp", env={"MY_VAR": "hello"}
|
||||
)
|
||||
result = await mgr.spawn(req)
|
||||
assert result.ok is True
|
||||
|
||||
@@ -480,11 +527,12 @@ async def test_spawn_passes_env(runner):
|
||||
assert "MY_VAR=hello" in respawn_args
|
||||
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_respawn_pane_with_env(runner):
|
||||
"""respawn_pane passes -e KEY=VALUE flags when env is provided."""
|
||||
await runner.respawn_pane("hqt-1", "claude", "/tmp", env={"MYKEY": "myval", "A": "b"})
|
||||
await runner.respawn_pane(
|
||||
"hqt-1", "claude", "/tmp", env={"MYKEY": "myval", "A": "b"}
|
||||
)
|
||||
call_args = runner._exec.call_args_list[0].args
|
||||
assert "respawn-pane" in call_args
|
||||
assert "-e" in call_args
|
||||
@@ -501,15 +549,14 @@ async def test_respawn_pane_no_env(runner):
|
||||
assert "-e" not in call_args
|
||||
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_spawn_verify_dead_empty_text_returns_fallback_error(runner):
|
||||
"""When verify_window_alive returns (False, ''), spawn returns the fallback error message."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@30\n", ""), # new-window
|
||||
(0, "", ""), # set-option
|
||||
(0, "", ""), # respawn-pane
|
||||
(0, "0\n", ""), # list-windows (next-index query)
|
||||
(0, "@30\n", ""), # new-window
|
||||
(0, "", ""), # set-option
|
||||
(0, "", ""), # respawn-pane
|
||||
]
|
||||
# Empty pane text — verify_window_alive signals immediate death with no output
|
||||
runner.verify_window_alive = AsyncMock(return_value=(False, ""))
|
||||
@@ -531,7 +578,7 @@ async def test_respawn_verified_success_path(runner):
|
||||
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
|
||||
runner._exec.side_effect = [
|
||||
(0, "hqt-1\n", ""), # has_window → True
|
||||
(0, "", ""), # respawn-pane
|
||||
(0, "", ""), # respawn-pane
|
||||
]
|
||||
mgr = TmuxManager(runner)
|
||||
result = await mgr.respawn_verified("hqt-1", ["claude", "--resume", "abc"], "/tmp")
|
||||
@@ -545,7 +592,7 @@ async def test_respawn_verified_pane_dies_after_respawn(runner):
|
||||
runner.verify_window_alive = AsyncMock(return_value=(False, "no such transcript\n"))
|
||||
runner._exec.side_effect = [
|
||||
(0, "hqt-1\n", ""), # has_window → True
|
||||
(0, "", ""), # respawn-pane
|
||||
(0, "", ""), # respawn-pane
|
||||
]
|
||||
mgr = TmuxManager(runner)
|
||||
result = await mgr.respawn_verified("hqt-1", ["claude", "--resume", "abc"], "/tmp")
|
||||
@@ -559,7 +606,7 @@ async def test_respawn_verified_pane_dies_empty_text_fallback_message(runner):
|
||||
runner.verify_window_alive = AsyncMock(return_value=(False, ""))
|
||||
runner._exec.side_effect = [
|
||||
(0, "hqt-1\n", ""), # has_window → True
|
||||
(0, "", ""), # respawn-pane
|
||||
(0, "", ""), # respawn-pane
|
||||
]
|
||||
mgr = TmuxManager(runner)
|
||||
result = await mgr.respawn_verified("hqt-1", ["claude", "--resume", "abc"], "/tmp")
|
||||
@@ -572,14 +619,16 @@ async def test_respawn_verified_window_gone_branch(runner):
|
||||
"""respawn_verified: window is gone → new_window + verify → SpawnResult(ok=True, window_id=...)."""
|
||||
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
|
||||
runner._exec.side_effect = [
|
||||
(0, "other\n", ""), # has_window → False
|
||||
(0, "0\n", ""), # new_window: list-windows (next-index query)
|
||||
(0, "@9\n", ""), # new-window step 1
|
||||
(0, "", ""), # set-option step 2
|
||||
(0, "", ""), # respawn-pane step 3
|
||||
(0, "other\n", ""), # has_window → False
|
||||
(0, "0\n", ""), # new_window: list-windows (next-index query)
|
||||
(0, "@9\n", ""), # new-window step 1
|
||||
(0, "", ""), # set-option step 2
|
||||
(0, "", ""), # respawn-pane step 3
|
||||
]
|
||||
mgr = TmuxManager(runner)
|
||||
result = await mgr.respawn_verified("hqt-gone", ["claude", "--resume", "abc"], "/tmp")
|
||||
result = await mgr.respawn_verified(
|
||||
"hqt-gone", ["claude", "--resume", "abc"], "/tmp"
|
||||
)
|
||||
assert result.ok is True
|
||||
assert result.window_id == "@9"
|
||||
|
||||
@@ -588,12 +637,14 @@ async def test_respawn_verified_window_gone_branch(runner):
|
||||
async def test_respawn_verified_window_gone_new_window_fails(runner):
|
||||
"""respawn_verified: window gone, new_window fails → SpawnResult(ok=False)."""
|
||||
runner._exec.side_effect = [
|
||||
(0, "other\n", ""), # has_window → False
|
||||
(0, "0\n", ""), # new_window: list-windows (next-index query)
|
||||
(1, "", "session not found"), # new-window fails
|
||||
(0, "other\n", ""), # has_window → False
|
||||
(0, "0\n", ""), # new_window: list-windows (next-index query)
|
||||
(1, "", "session not found"), # new-window fails
|
||||
]
|
||||
mgr = TmuxManager(runner)
|
||||
result = await mgr.respawn_verified("hqt-gone", ["claude", "--resume", "abc"], "/tmp")
|
||||
result = await mgr.respawn_verified(
|
||||
"hqt-gone", ["claude", "--resume", "abc"], "/tmp"
|
||||
)
|
||||
assert result.ok is False
|
||||
assert result.error != ""
|
||||
|
||||
@@ -604,7 +655,7 @@ async def test_respawn_verified_forwards_env(runner):
|
||||
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
|
||||
runner._exec.side_effect = [
|
||||
(0, "hqt-1\n", ""), # has_window → True
|
||||
(0, "", ""), # respawn-pane
|
||||
(0, "", ""), # respawn-pane
|
||||
]
|
||||
mgr = TmuxManager(runner)
|
||||
result = await mgr.respawn_verified(
|
||||
@@ -621,10 +672,11 @@ async def test_respawn_verified_forwards_env(runner):
|
||||
async def test_respawn_verified_uses_shlex_join(runner):
|
||||
"""respawn_verified uses shlex.join so args with spaces are properly quoted."""
|
||||
import shlex
|
||||
|
||||
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
|
||||
runner._exec.side_effect = [
|
||||
(0, "hqt-1\n", ""), # has_window → True
|
||||
(0, "", ""), # respawn-pane
|
||||
(0, "", ""), # respawn-pane
|
||||
]
|
||||
mgr = TmuxManager(runner)
|
||||
await mgr.respawn_verified("hqt-1", ["cmd with space", "arg"], "/tmp")
|
||||
@@ -641,13 +693,15 @@ async def test_respawn_verified_window_gone_forwards_env(runner):
|
||||
runner.verify_window_alive = AsyncMock(return_value=(True, ""))
|
||||
runner._exec.side_effect = [
|
||||
(0, "other-window\n", ""), # has_window → False
|
||||
(0, "0\n", ""), # new_window: list-windows (next-index query)
|
||||
(0, "@5\n", ""), # new-window step 1
|
||||
(0, "", ""), # set-option step 2
|
||||
(0, "", ""), # respawn-pane step 3
|
||||
(0, "0\n", ""), # new_window: list-windows (next-index query)
|
||||
(0, "@5\n", ""), # new-window step 1
|
||||
(0, "", ""), # set-option step 2
|
||||
(0, "", ""), # respawn-pane step 3
|
||||
]
|
||||
mgr = TmuxManager(runner)
|
||||
result = await mgr.respawn_verified("hqt-gone", ["bash"], "/tmp", env={"GONE_VAR": "x"})
|
||||
result = await mgr.respawn_verified(
|
||||
"hqt-gone", ["bash"], "/tmp", env={"GONE_VAR": "x"}
|
||||
)
|
||||
assert result.ok is True
|
||||
|
||||
# The respawn-pane (step 3 of new_window) should carry the env
|
||||
@@ -667,7 +721,12 @@ async def test_send_text_uses_literal_flag(runner):
|
||||
"""send_text must pass -l and '--' before the text."""
|
||||
await runner.send_text("hqt-1", "Enter; echo hi C-c")
|
||||
runner._exec.assert_called_once_with(
|
||||
"send-keys", "-t", "hqt-main:=hqt-1", "-l", "--", "Enter; echo hi C-c",
|
||||
"send-keys",
|
||||
"-t",
|
||||
"hqt-main:=hqt-1",
|
||||
"-l",
|
||||
"--",
|
||||
"Enter; echo hi C-c",
|
||||
)
|
||||
|
||||
|
||||
@@ -676,7 +735,10 @@ async def test_send_enter_no_literal_flag(runner):
|
||||
"""send_enter intentionally sends the Enter key name — must NOT have -l."""
|
||||
await runner.send_enter("hqt-1")
|
||||
runner._exec.assert_called_once_with(
|
||||
"send-keys", "-t", "hqt-main:=hqt-1", "Enter",
|
||||
"send-keys",
|
||||
"-t",
|
||||
"hqt-main:=hqt-1",
|
||||
"Enter",
|
||||
)
|
||||
|
||||
|
||||
@@ -690,11 +752,16 @@ async def test_list_windows_info_alive_and_activity(runner):
|
||||
"""list_windows_info parses 3-column output; alive=True when pane_dead=0."""
|
||||
runner._exec.return_value = (0, "hqt-1\t0\t1000000\nhqt-2\t1\t999999\n", "")
|
||||
from hqt.tmux.runner import WindowInfo
|
||||
|
||||
result = await runner.list_windows_info()
|
||||
assert result["hqt-1"] == WindowInfo(alive=True, last_activity=1000000)
|
||||
assert result["hqt-2"] == WindowInfo(alive=False, last_activity=999999)
|
||||
runner._exec.assert_called_once_with(
|
||||
"list-panes", "-s", "-t", "hqt-main", "-F",
|
||||
"list-panes",
|
||||
"-s",
|
||||
"-t",
|
||||
"hqt-main",
|
||||
"-F",
|
||||
"#{window_name}\t#{pane_dead}\t#{window_activity}",
|
||||
)
|
||||
|
||||
@@ -708,6 +775,7 @@ async def test_list_windows_info_multi_pane_alive_max_activity(runner):
|
||||
"",
|
||||
)
|
||||
from hqt.tmux.runner import WindowInfo
|
||||
|
||||
result = await runner.list_windows_info()
|
||||
assert result["hqt-1"] == WindowInfo(alive=True, last_activity=2000)
|
||||
assert result["hqt-2"] == WindowInfo(alive=False, last_activity=600)
|
||||
@@ -747,7 +815,12 @@ async def test_send_text_includes_double_dash(runner):
|
||||
"""send_text must pass '--' before the text to prevent flag parsing."""
|
||||
await runner.send_text("hqt-1", "-flag-looking text")
|
||||
runner._exec.assert_called_once_with(
|
||||
"send-keys", "-t", "hqt-main:=hqt-1", "-l", "--", "-flag-looking text",
|
||||
"send-keys",
|
||||
"-t",
|
||||
"hqt-main:=hqt-1",
|
||||
"-l",
|
||||
"--",
|
||||
"-flag-looking text",
|
||||
)
|
||||
|
||||
|
||||
@@ -756,7 +829,12 @@ async def test_send_text_double_dash_with_normal_text(runner):
|
||||
"""send_text includes '--' even for normal text (belt-and-suspenders)."""
|
||||
await runner.send_text("hqt-1", "hello world")
|
||||
runner._exec.assert_called_once_with(
|
||||
"send-keys", "-t", "hqt-main:=hqt-1", "-l", "--", "hello world",
|
||||
"send-keys",
|
||||
"-t",
|
||||
"hqt-main:=hqt-1",
|
||||
"-l",
|
||||
"--",
|
||||
"hello world",
|
||||
)
|
||||
|
||||
|
||||
@@ -765,6 +843,7 @@ async def test_poll_info_single_exec(runner):
|
||||
"""poll_info calls _exec exactly once and returns WindowInfo per name."""
|
||||
runner._exec.return_value = (0, "hqt-1\t0\t1234\nhqt-2\t1\t5678\n", "")
|
||||
from hqt.tmux.runner import WindowInfo
|
||||
|
||||
mgr = TmuxManager(runner)
|
||||
result = await mgr.poll_info(["hqt-1", "hqt-2", "hqt-missing"])
|
||||
assert runner._exec.call_count == 1
|
||||
@@ -881,7 +960,8 @@ async def test_apply_theme_paints_window_options_on_every_window(runner):
|
||||
for window in ("⌂ HQT", "hqt-1", "hqt-2"):
|
||||
target = f"hqt-main:={window}"
|
||||
idxs = [
|
||||
i for i, a in enumerate(args)
|
||||
i
|
||||
for i, a in enumerate(args)
|
||||
if a == "window-status-current-style" and args[i - 1] == target
|
||||
]
|
||||
assert idxs, f"no current-style set for {window}"
|
||||
|
||||
Reference in New Issue
Block a user