Skip to content

Commit 3415a70

Browse files
committed
test(Session.attach): Add xfail regression test for refresh after kill
why: Reproduce tmuxp issue #1002 where attach() raises exception if session is killed while user is attached. what: - Add test that simulates session killed during attach-session - Mark as xfail since attach() currently calls refresh() which fails - Documents expected behavior: attach() should not raise if session gone
1 parent 64276e4 commit 3415a70

File tree

1 file changed

+98
-0
lines changed

1 file changed

+98
-0
lines changed

tests/test_session.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import pathlib
77
import shutil
88
import typing as t
9+
from contextlib import nullcontext as does_not_raise
910

1011
import pytest
1112

@@ -18,6 +19,15 @@
1819
from libtmux.window import Window
1920

2021
if t.TYPE_CHECKING:
22+
from typing import TypeAlias
23+
24+
try:
25+
from _pytest.raises import RaisesExc
26+
except ImportError:
27+
from _pytest.python_api import RaisesContext # type: ignore[attr-defined]
28+
29+
RaisesExc: TypeAlias = RaisesContext[Exception] # type: ignore[no-redef]
30+
2131
from libtmux._internal.types import StrPath
2232
from libtmux.server import Server
2333

@@ -481,3 +491,91 @@ def test_new_window_start_directory_pathlib(
481491
actual_path = str(pathlib.Path(active_pane.pane_current_path).resolve())
482492
expected_path = str(user_path.resolve())
483493
assert actual_path == expected_path
494+
495+
496+
class SessionAttachRefreshFixture(t.NamedTuple):
497+
"""Test fixture for Session.attach() refresh behavior regression.
498+
499+
This tests the scenario where a session is killed while the user is attached,
500+
and then attach() tries to call refresh() which fails because the session
501+
no longer exists.
502+
503+
See: https://github.com/tmux-python/tmuxp/issues/1002
504+
"""
505+
506+
test_id: str
507+
raises: type[Exception] | bool
508+
509+
510+
SESSION_ATTACH_REFRESH_FIXTURES: list[SessionAttachRefreshFixture] = [
511+
SessionAttachRefreshFixture(
512+
test_id="session_killed_during_attach_should_not_raise",
513+
raises=False, # attach() should NOT raise if session gone
514+
),
515+
]
516+
517+
518+
@pytest.mark.xfail(
519+
reason="Bug: attach() calls refresh() which fails if session killed during attach. "
520+
"See: https://github.com/tmux-python/tmuxp/issues/1002",
521+
raises=Exception,
522+
)
523+
@pytest.mark.parametrize(
524+
list(SessionAttachRefreshFixture._fields),
525+
SESSION_ATTACH_REFRESH_FIXTURES,
526+
ids=[test.test_id for test in SESSION_ATTACH_REFRESH_FIXTURES],
527+
)
528+
def test_session_attach_does_not_fail_if_session_killed_during_attach(
529+
server: Server,
530+
monkeypatch: pytest.MonkeyPatch,
531+
test_id: str,
532+
raises: type[Exception] | bool,
533+
) -> None:
534+
"""Regression test: Session.attach() should not fail if session is killed.
535+
536+
When a user is attached to a tmux session via `tmuxp load`, then kills the
537+
session from within tmux (e.g., kills all windows), and then detaches,
538+
the attach() method should not raise an exception.
539+
540+
Currently, attach() calls self.refresh() after attach-session returns, which
541+
fails with TmuxObjectDoesNotExist if the session no longer exists.
542+
543+
The fix is to remove the refresh() call from attach() since:
544+
1. attach-session is a blocking interactive command
545+
2. Session state can change arbitrarily while the user is attached
546+
3. Refreshing after such a command makes no semantic sense
547+
"""
548+
from libtmux.common import tmux_cmd
549+
550+
# Create a new session specifically for this test
551+
test_session = server.new_session(detach=True)
552+
553+
# Store original cmd method
554+
original_cmd = test_session.cmd
555+
556+
# Create a mock tmux_cmd result that simulates successful attach-session
557+
class MockTmuxCmd:
558+
def __init__(self) -> None:
559+
self.stdout: list[str] = []
560+
self.stderr: list[str] = []
561+
self.cmd: list[str] = ["tmux", "attach-session"]
562+
563+
def patched_cmd(cmd_name: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd:
564+
"""Patched cmd that kills session after attach-session."""
565+
if cmd_name == "attach-session":
566+
# Simulate: attach-session succeeded, user worked, then killed session
567+
# This happens BEFORE refresh() is called
568+
test_session.kill()
569+
return MockTmuxCmd() # type: ignore[return-value]
570+
return original_cmd(cmd_name, *args, **kwargs)
571+
572+
monkeypatch.setattr(test_session, "cmd", patched_cmd)
573+
574+
# Use context manager pattern for exception handling
575+
raises_ctx: RaisesExc = (
576+
pytest.raises(t.cast("type[Exception]", raises))
577+
if raises
578+
else t.cast("RaisesExc", does_not_raise())
579+
)
580+
with raises_ctx:
581+
test_session.attach()

0 commit comments

Comments
 (0)