-
Notifications
You must be signed in to change notification settings - Fork 117
Expand file tree
/
Copy pathtest_pytest_plugin.py
More file actions
223 lines (176 loc) · 6.68 KB
/
test_pytest_plugin.py
File metadata and controls
223 lines (176 loc) · 6.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
"""Tests for libtmux pytest plugin."""
from __future__ import annotations
import contextlib
import os
import pathlib
import textwrap
import time
import typing as t
from libtmux.pytest_plugin import _reap_test_server
from libtmux.server import Server
if t.TYPE_CHECKING:
import pytest
def test_plugin(
pytester: pytest.Pytester,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test libtmux pytest plugin."""
# Initialize variables
pytester.plugins = ["pytest_plugin"]
pytester.makefile(
".ini",
pytest=textwrap.dedent(
"""
[pytest]
addopts=-vv
""".strip(),
),
)
pytester.makeconftest(
textwrap.dedent(
r"""
import pathlib
import pytest
@pytest.fixture(autouse=True)
def setup(
request: pytest.FixtureRequest,
) -> None:
pass
""",
),
)
tests_path = pytester.path / "tests"
files = {
"example.py": textwrap.dedent(
"""
import pathlib
def test_repo_git_remote_checkout(
session,
) -> None:
assert session.session_name is not None
assert session.session_id == "$1"
new_window = session.new_window(attach=False, window_name="my window name")
assert new_window.window_name == "my window name"
""",
),
}
first_test_key = next(iter(files.keys()))
first_test_filename = str(tests_path / first_test_key)
tests_path.mkdir()
for file_name, text in files.items():
test_file = tests_path / file_name
test_file.write_text(
text,
encoding="utf-8",
)
# Test
result = pytester.runpytest(str(first_test_filename))
result.assert_outcomes(passed=1)
def test_test_server(TestServer: t.Callable[..., Server]) -> None:
"""Test TestServer creates and cleans up server."""
server = TestServer()
assert server.is_alive() is False # Server not started yet
session = server.new_session()
assert server.is_alive() is True
assert len(server.sessions) == 1
assert session.session_name is not None
# Test socket name is unique
assert server.socket_name is not None
assert server.socket_name.startswith("libtmux_test")
# Each call creates a new server with unique socket
server2 = TestServer()
assert server2.socket_name is not None
assert server2.socket_name.startswith("libtmux_test")
assert server2.socket_name != server.socket_name
def test_test_server_with_config(
TestServer: t.Callable[..., Server],
tmp_path: pathlib.Path,
) -> None:
"""Test TestServer with config file."""
config_file = tmp_path / "tmux.conf"
config_file.write_text("set -g status off", encoding="utf-8")
server = TestServer(config_file=str(config_file))
session = server.new_session()
# Verify config was loaded
assert session.cmd("show-options", "-g", "status").stdout[0] == "status off"
def test_test_server_cleanup(TestServer: t.Callable[..., Server]) -> None:
"""Test TestServer properly cleans up after itself."""
server = TestServer()
socket_name = server.socket_name
assert socket_name is not None
# Create multiple sessions
server.new_session(session_name="test1")
server.new_session(session_name="test2")
assert len(server.sessions) == 2
# Verify server is alive
assert server.is_alive() is True
# Delete server and verify cleanup
server.kill()
time.sleep(0.1) # Give time for cleanup
# Create new server to verify old one was cleaned up
new_server = TestServer()
assert new_server.is_alive() is False # Server not started yet
new_server.new_session() # This should work if old server was cleaned up
assert new_server.is_alive() is True
def test_test_server_multiple(TestServer: t.Callable[..., Server]) -> None:
"""Test multiple TestServer instances can coexist."""
server1 = TestServer()
server2 = TestServer()
# Each server should have a unique socket
assert server1.socket_name != server2.socket_name
# Create sessions in each server
server1.new_session(session_name="test1")
server2.new_session(session_name="test2")
# Verify sessions are in correct servers
assert any(s.session_name == "test1" for s in server1.sessions)
assert any(s.session_name == "test2" for s in server2.sessions)
assert not any(s.session_name == "test1" for s in server2.sessions)
assert not any(s.session_name == "test2" for s in server1.sessions)
def _libtmux_socket_dir() -> pathlib.Path:
"""Resolve the tmux socket directory tmux uses for this uid."""
tmux_tmpdir = pathlib.Path(os.environ.get("TMUX_TMPDIR", "/tmp"))
return tmux_tmpdir / f"tmux-{os.geteuid()}"
def test_reap_test_server_unlinks_socket_file() -> None:
"""``_reap_test_server`` kills the daemon *and* unlinks the socket.
Regression for #660: tmux does not reliably ``unlink(2)`` its socket
on non-graceful exit. Before this fix the plugin's finalizer only
called ``server.kill()``, so ``/tmp/tmux-<uid>/`` accumulated stale
``libtmux_test*`` socket files over time.
This test boots a real tmux daemon on a unique socket, confirms the
socket file exists, invokes the reaper, and asserts the file is
gone.
"""
server = Server(socket_name="libtmux_test_reap_unlink")
server.new_session(session_name="reap_probe")
socket_path = _libtmux_socket_dir() / "libtmux_test_reap_unlink"
try:
assert socket_path.exists(), (
f"expected tmux to have created {socket_path}, but it is missing"
)
_reap_test_server("libtmux_test_reap_unlink")
assert not socket_path.exists(), (
f"_reap_test_server should have unlinked {socket_path}"
)
finally:
# Belt-and-braces: if the assertion above fired before the
# unlink, don't leak the socket the next run of this test.
with contextlib.suppress(OSError):
socket_path.unlink(missing_ok=True)
def test_reap_test_server_is_noop_when_socket_missing() -> None:
"""Reaping a non-existent socket succeeds silently.
Finalizers run even when the fixture failed before the daemon ever
started; the reaper must tolerate the case where the socket file
never existed.
"""
bogus_name = "libtmux_test_reap_never_existed_xyz"
socket_path = _libtmux_socket_dir() / bogus_name
assert not socket_path.exists()
# Should not raise.
_reap_test_server(bogus_name)
def test_reap_test_server_tolerates_none() -> None:
"""``_reap_test_server(None)`` is a no-op, not a crash.
The ``server`` fixture's finalizer passes ``server.socket_name``,
which is typed ``str | None``. Tolerate ``None`` for symmetry with
other nullable paths in the API.
"""
_reap_test_server(None)