33from __future__ import annotations
44
55import logging
6+ import types
67import typing as t
78
89import pytest
@@ -77,6 +78,33 @@ def test_server_new_session_info_logging(
7778 new_session .kill ()
7879
7980
81+ def test_server_kill_info_logging (
82+ caplog : pytest .LogCaptureFixture ,
83+ ) -> None :
84+ """Test that server.kill() emits a lifecycle INFO record."""
85+ from libtmux .server import Server
86+ from libtmux .test .random import namer
87+
88+ with Server (socket_name = f"libtmux_log_{ next (namer )} " ) as temp_server :
89+ temp_server .new_session (session_name = f"log_session_{ next (namer )} " )
90+ caplog .clear ()
91+
92+ with caplog .at_level (logging .INFO , logger = "libtmux.server" ):
93+ temp_server .kill ()
94+
95+ records = [
96+ r
97+ for r in caplog .records
98+ if getattr (r , "tmux_subcommand" , None ) == "kill-server"
99+ and r .levelno == logging .INFO
100+ ]
101+ assert len (records ) >= 1 , "expected INFO record for server kill"
102+
103+ rec = t .cast (t .Any , records [0 ])
104+ assert rec .getMessage () == "server killed"
105+ assert isinstance (rec .tmux_subcommand , str )
106+
107+
80108def test_window_rename_info_logging (
81109 session : Session ,
82110 caplog : pytest .LogCaptureFixture ,
@@ -106,6 +134,39 @@ def test_window_rename_info_logging(
106134 )
107135
108136
137+ def test_window_kill_all_except_logging (
138+ session : Session ,
139+ caplog : pytest .LogCaptureFixture ,
140+ ) -> None :
141+ """Test that window.kill(all_except=True) identifies the surviving window."""
142+ from libtmux .test .random import namer
143+
144+ survivor = session .new_window (window_name = f"log_survivor_{ next (namer )} " )
145+ other_windows = [
146+ session .new_window (window_name = f"log_other_{ next (namer )} " ),
147+ session .new_window (window_name = f"log_other_{ next (namer )} " ),
148+ ]
149+
150+ with caplog .at_level (logging .INFO , logger = "libtmux.window" ):
151+ survivor .kill (all_except = True )
152+
153+ records = [
154+ r
155+ for r in caplog .records
156+ if getattr (r , "tmux_subcommand" , None ) == "kill-window"
157+ and r .levelno == logging .INFO
158+ ]
159+ assert len (records ) >= 1 , "expected INFO record for all-except window kill"
160+
161+ rec = t .cast (t .Any , records [0 ])
162+ assert rec .getMessage () == "other windows killed"
163+ assert rec .tmux_window == survivor .window_name
164+ assert rec .tmux_target == survivor .window_id
165+ remaining_window_ids = {window .window_id for window in session .windows }
166+ assert survivor .window_id in remaining_window_ids
167+ assert all (window .window_id not in remaining_window_ids for window in other_windows )
168+
169+
109170def test_pane_split_info_logging (
110171 session : Session ,
111172 caplog : pytest .LogCaptureFixture ,
@@ -140,6 +201,96 @@ def test_pane_split_info_logging(
140201 new_pane .kill ()
141202
142203
204+ def test_pane_kill_all_except_logging (
205+ session : Session ,
206+ caplog : pytest .LogCaptureFixture ,
207+ ) -> None :
208+ """Test that pane.kill(all_except=True) identifies the surviving pane."""
209+ window = session .active_window
210+ assert window is not None
211+ window .resize (height = 100 , width = 100 )
212+ survivor = window .split ()
213+ other_panes = [window .split (), window .split ()]
214+
215+ with caplog .at_level (logging .INFO , logger = "libtmux.pane" ):
216+ survivor .kill (all_except = True )
217+
218+ records = [
219+ r
220+ for r in caplog .records
221+ if getattr (r , "tmux_subcommand" , None ) == "kill-pane"
222+ and r .levelno == logging .INFO
223+ ]
224+ assert len (records ) >= 1 , "expected INFO record for all-except pane kill"
225+
226+ rec = t .cast (t .Any , records [0 ])
227+ assert rec .getMessage () == "other panes killed"
228+ assert rec .tmux_pane == survivor .pane_id
229+ assert rec .tmux_target == survivor .pane_id
230+ remaining_pane_ids = {p .pane_id for p in window .panes }
231+ assert survivor .pane_id in remaining_pane_ids
232+ assert all (p .pane_id not in remaining_pane_ids for p in other_panes )
233+
234+
235+ def test_session_kill_all_except_logging (
236+ server : Server ,
237+ caplog : pytest .LogCaptureFixture ,
238+ ) -> None :
239+ """Test that session.kill(all_except=True) identifies the surviving session."""
240+ from libtmux .test .random import namer
241+
242+ survivor = server .new_session (session_name = f"log_survivor_{ next (namer )} " )
243+ other_sessions = [
244+ server .new_session (session_name = f"log_other_{ next (namer )} " ),
245+ server .new_session (session_name = f"log_other_{ next (namer )} " ),
246+ ]
247+
248+ with caplog .at_level (logging .INFO , logger = "libtmux.session" ):
249+ survivor .kill (all_except = True )
250+
251+ records = [
252+ r
253+ for r in caplog .records
254+ if getattr (r , "tmux_subcommand" , None ) == "kill-session"
255+ and r .levelno == logging .INFO
256+ ]
257+ assert len (records ) >= 1 , "expected INFO record for all-except session kill"
258+
259+ rec = t .cast (t .Any , records [0 ])
260+ assert rec .getMessage () == "other sessions killed"
261+ assert rec .tmux_session == survivor .session_name
262+ assert rec .tmux_target == survivor .session_id
263+ remaining_session_ids = {session .session_id for session in server .sessions }
264+ assert survivor .session_id in remaining_session_ids
265+ assert all (
266+ session .session_id not in remaining_session_ids for session in other_sessions
267+ )
268+
269+
270+ def test_server_new_session_surfaces_kill_session_stderr (
271+ monkeypatch : pytest .MonkeyPatch ,
272+ ) -> None :
273+ """Test kill-session stderr propagation using monkeypatch for the failure path.
274+
275+ A real tmux fixture is not used here because this path requires forcing a
276+ kill-session command failure before session creation begins.
277+ """
278+ from libtmux import exc
279+ from libtmux .server import Server
280+ from libtmux .test .random import namer
281+
282+ server = Server (socket_name = f"libtmux_log_{ next (namer )} " )
283+ monkeypatch .setattr (server , "has_session" , lambda session_name : True )
284+ monkeypatch .setattr (
285+ server ,
286+ "cmd" ,
287+ lambda * args , ** kwargs : types .SimpleNamespace (stderr = ["kill failed" ]),
288+ )
289+
290+ with pytest .raises (exc .LibTmuxException , match = "kill failed" ):
291+ server .new_session (session_name = "existing_session" , kill_session = True )
292+
293+
143294def test_options_warning_logging_schema (
144295 caplog : pytest .LogCaptureFixture ,
145296) -> None :
@@ -163,3 +314,5 @@ def test_options_warning_logging_schema(
163314
164315 rec = t .cast (t .Any , records [0 ])
165316 assert isinstance (rec .tmux_option_key , str )
317+ assert rec .exc_info is None
318+ assert "Traceback" not in caplog .text
0 commit comments