diff --git a/src/libtmux/server.py b/src/libtmux/server.py index c69463424..c031845b4 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -616,6 +616,10 @@ def new_session( if proc.stderr: raise exc.LibTmuxException(proc.stderr) + if not proc.stdout: + msg = "new-session produced no output" + raise exc.LibTmuxException(msg) + session_stdout = proc.stdout[0] finally: diff --git a/tests/test_server.py b/tests/test_server.py index 38c05636f..73a17db21 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -12,6 +12,7 @@ import pytest +from libtmux import exc from libtmux.server import Server if t.TYPE_CHECKING: @@ -105,6 +106,94 @@ def test_new_session(server: Server) -> None: assert server.has_session("test_new_session") +def test_new_session_empty_stdout( + server: Server, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Server.new_session raises LibTmuxException when tmux returns no output. + + monkeypatch is used to simulate empty stdout, which cannot be triggered + through normal tmux operations on a healthy server. + """ + original_cmd = server.cmd + + def mock_cmd(cmd: str, *args: t.Any, **kwargs: t.Any) -> t.Any: + result = original_cmd(cmd, *args, **kwargs) + if cmd == "new-session": + result.stdout = [] + return result + + monkeypatch.setattr(server, "cmd", mock_cmd) + + with pytest.raises(exc.LibTmuxException, match="new-session produced no output"): + server.new_session(session_name="test_empty_stdout") + + monkeypatch.undo() + if server.has_session("test_empty_stdout"): + server.kill_session("test_empty_stdout") + + +def test_new_session_restores_tmux_env_on_error( + server: Server, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Server.new_session restores TMUX env var when an exception is raised. + + monkeypatch is used to simulate empty stdout (forcing the exception path), + which cannot be triggered through normal tmux operations. + """ + original_cmd = server.cmd + sentinel = "/tmp/libtmux-test-fake-socket,12345,0" + + monkeypatch.setenv("TMUX", sentinel) + + def mock_cmd(cmd: str, *args: t.Any, **kwargs: t.Any) -> t.Any: + result = original_cmd(cmd, *args, **kwargs) + if cmd == "new-session": + result.stdout = [] + return result + + monkeypatch.setattr(server, "cmd", mock_cmd) + + with pytest.raises(exc.LibTmuxException, match="new-session produced no output"): + server.new_session(session_name="test_env_restore") + + assert os.environ.get("TMUX") == sentinel + + monkeypatch.undo() + if server.has_session("test_env_restore"): + server.kill_session("test_env_restore") + + +def test_new_session_restores_tmux_env_on_setup_error( + server: Server, + monkeypatch: pytest.MonkeyPatch, + tmp_path: pathlib.Path, +) -> None: + """Server.new_session restores TMUX env when setup code before cmd() raises. + + monkeypatch makes pathlib.Path.expanduser raise to verify the try/finally + covers the arg-building phase, not just the cmd() call. + """ + sentinel = "/tmp/libtmux-test-fake-socket,99999,0" + + monkeypatch.setenv("TMUX", sentinel) + + def broken_expanduser(self: pathlib.Path) -> t.Any: + msg = "injected setup error" + raise RuntimeError(msg) + + monkeypatch.setattr(pathlib.Path, "expanduser", broken_expanduser) + + with pytest.raises(RuntimeError, match="injected setup error"): + server.new_session( + session_name="test_setup_error", + start_directory=tmp_path, + ) + + assert os.environ.get("TMUX") == sentinel + + def test_new_session_returns_populated_session(server: Server) -> None: """Server.new_session returns Session populated from -P output.""" session = server.new_session(session_name="test_populated")