Skip to content

Commit 12f60e3

Browse files
committed
tests(test_options): Test OptionMixin
1 parent ff41819 commit 12f60e3

File tree

1 file changed

+388
-0
lines changed

1 file changed

+388
-0
lines changed

tests/test_options.py

Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
"""Test for libtmux options management."""
2+
3+
import dataclasses
4+
import textwrap
5+
import typing as t
6+
7+
import pytest
8+
9+
from libtmux._internal.constants import (
10+
Options,
11+
PaneOptions,
12+
ServerOptions,
13+
SessionOptions,
14+
TmuxArray,
15+
WindowOptions,
16+
)
17+
from libtmux.common import has_gte_version
18+
from libtmux.constants import OptionScope
19+
from libtmux.exc import OptionError
20+
from libtmux.pane import Pane
21+
22+
if t.TYPE_CHECKING:
23+
from typing_extensions import LiteralString
24+
25+
from libtmux.server import Server
26+
27+
28+
def test_options(server: "Server") -> None:
29+
"""Test basic options."""
30+
session = server.new_session(session_name="test")
31+
window = session.new_window(window_name="test")
32+
pane = window.split_window(attach=False)
33+
34+
for obj in [server, session, window, pane]:
35+
obj._show_options()
36+
obj._show_options(_global=True)
37+
obj._show_options(include_inherited=True)
38+
obj._show_options(include_hooks=True)
39+
with pytest.raises(OptionError):
40+
obj._show_option("test")
41+
if has_gte_version("3.0"):
42+
obj._show_option("test", ignore_errors=True)
43+
with pytest.raises(OptionError):
44+
obj.set_option("test", "invalid")
45+
if isinstance(obj, Pane):
46+
if has_gte_version("3.0"):
47+
obj.set_option("test", "invalid", ignore_errors=True)
48+
else:
49+
with pytest.raises(OptionError):
50+
obj.set_option("test", "invalid", ignore_errors=True)
51+
else:
52+
obj.set_option("test", "invalid", ignore_errors=True)
53+
54+
55+
def test_options_server(server: "Server") -> None:
56+
"""Test server options."""
57+
session = server.new_session(session_name="test")
58+
window = session.new_window(window_name="test")
59+
pane = window.split_window(attach=False)
60+
61+
server.set_option("buffer-limit", 100)
62+
assert server._show_option("buffer-limit") == 100
63+
if has_gte_version("3.0"):
64+
server.set_option("buffer-limit", 150, scope=OptionScope.Pane)
65+
66+
if has_gte_version("3.0"):
67+
# set-option and show-options w/ Pane (-p) does not exist until 3.0+
68+
server.set_option(
69+
"buffer-limit",
70+
150,
71+
scope=OptionScope.Pane,
72+
ignore_errors=True,
73+
)
74+
server.set_option("buffer-limit", 150, scope=OptionScope.Server)
75+
76+
if has_gte_version("3.0"):
77+
assert session._show_option("buffer-limit") == 150
78+
79+
# Server option in deeper objects
80+
if has_gte_version("3.0"):
81+
pane.set_option("buffer-limit", 100)
82+
else:
83+
with pytest.raises(OptionError):
84+
pane.set_option("buffer-limit", 100)
85+
86+
if has_gte_version("3.0"):
87+
assert pane._show_option("buffer-limit") == 100
88+
assert window._show_option("buffer-limit") == 100
89+
assert server._show_option("buffer-limit") == 100
90+
91+
server_options = ServerOptions(**server._show_options(scope=OptionScope.Server))
92+
if has_gte_version("3.0"):
93+
assert server._show_option("buffer-limit") == 100
94+
95+
assert server_options.buffer_limit == 100
96+
97+
server.set_option("buffer-limit", 150, scope=OptionScope.Server)
98+
99+
assert server._show_option("buffer-limit") == 150
100+
101+
server.unset_option("buffer-limit")
102+
103+
assert server._show_option("buffer-limit") == 50
104+
105+
106+
def test_options_session(server: "Server") -> None:
107+
"""Test session options."""
108+
session = server.new_session(session_name="test")
109+
session.new_window(window_name="test")
110+
111+
_session_options = session._show_options(scope=OptionScope.Session)
112+
113+
session_options = SessionOptions(**_session_options)
114+
assert session_options.default_size == _session_options.get("default-size")
115+
116+
117+
def test_options_window(server: "Server") -> None:
118+
"""Test window options."""
119+
session = server.new_session(session_name="test")
120+
window = session.new_window(window_name="test")
121+
window.split_window(attach=False)
122+
123+
_window_options = window._show_options(scope=OptionScope.Window)
124+
125+
window_options = WindowOptions(**_window_options)
126+
assert window_options.automatic_rename == _window_options.get("automatic-rename")
127+
128+
129+
def test_options_pane(server: "Server") -> None:
130+
"""Test pane options."""
131+
session = server.new_session(session_name="test")
132+
window = session.new_window(window_name="test")
133+
pane = window.split_window(attach=False)
134+
135+
_pane_options = pane._show_options(scope=OptionScope.Pane)
136+
137+
pane_options = PaneOptions(**_pane_options)
138+
assert pane_options.window_active_style == _pane_options.get("window-active-style")
139+
140+
141+
def test_options_grid(server: "Server") -> None:
142+
"""Test options against grid."""
143+
session = server.new_session(session_name="test")
144+
window = session.new_window(window_name="test")
145+
pane = window.split_window(attach=False)
146+
147+
for include_inherited in [True, False]:
148+
for _global in [True, False]:
149+
for obj in [server, session, window, pane]:
150+
for scope in [
151+
OptionScope.Server,
152+
OptionScope.Session,
153+
OptionScope.Window,
154+
]:
155+
_obj_global_options = obj._show_options(
156+
scope=scope,
157+
include_inherited=include_inherited,
158+
_global=_global,
159+
)
160+
obj_global_options = Options(**_obj_global_options)
161+
for field in dataclasses.fields(obj_global_options):
162+
expected = _obj_global_options.get(field.name.replace("_", "-"))
163+
164+
if include_inherited and expected is None:
165+
expected = _obj_global_options.get(
166+
f'{field.name.replace("_", "-")}*',
167+
None,
168+
)
169+
170+
default_value = None
171+
if field.default_factory is not dataclasses.MISSING:
172+
default_value = field.default_factory()
173+
if expected is None:
174+
default_value = None
175+
assert (
176+
getattr(obj_global_options, field.name, default_value)
177+
== expected
178+
), (
179+
f"Expect {field.name} to be {expected} when "
180+
+ f"scope={scope}, _global={_global}"
181+
)
182+
if (
183+
has_gte_version("3.0")
184+
and scope == OptionScope.Window
185+
and _global
186+
):
187+
assert obj_global_options.pane_base_index == 0
188+
189+
190+
def test_custom_options(
191+
server: "Server",
192+
) -> None:
193+
"""Test tmux's user (custom) options."""
194+
session = server.new_session(session_name="test")
195+
session.set_option("@custom-option", "test")
196+
assert session._show_option("@custom-option") == "test"
197+
198+
199+
MOCKED_GLOBAL_OPTIONS: t.List["LiteralString"] = """
200+
backspace C-?
201+
buffer-limit 50
202+
command-alias[0] split-pane=split-window
203+
command-alias[1] splitp=split-window
204+
command-alias[2] "server-info=show-messages -JT"
205+
command-alias[3] "info=show-messages -JT"
206+
command-alias[4] "choose-window=choose-tree -w"
207+
command-alias[5] "choose-session=choose-tree -s"
208+
copy-command ''
209+
default-terminal xterm-256color
210+
editor vim
211+
escape-time 50
212+
exit-empty on
213+
exit-unattached off
214+
extended-keys off
215+
focus-events off
216+
history-file ''
217+
message-limit 1000
218+
prompt-history-limit 100
219+
set-clipboard external
220+
terminal-overrides[0] xterm-256color:Tc
221+
terminal-features[0] xterm*:clipboard:ccolour:cstyle:focus
222+
terminal-features[1] screen*:title
223+
user-keys
224+
""".strip().split("\n")
225+
226+
227+
@dataclasses.dataclass
228+
class MockedCmdResponse:
229+
"""Mocked tmux_cmd response."""
230+
231+
stdout: t.Optional[t.List["LiteralString"]]
232+
stderr: t.Optional[t.List[str]]
233+
234+
235+
def cmd_mocked(*args: object) -> MockedCmdResponse:
236+
"""Mock command response for show-options -s (server)."""
237+
return MockedCmdResponse(
238+
stdout=MOCKED_GLOBAL_OPTIONS,
239+
stderr=None,
240+
)
241+
242+
243+
def fake_cmd(
244+
stdout: t.Optional[t.List[str]],
245+
stderr: t.Optional[t.List[str]] = None,
246+
) -> t.Callable[
247+
[t.Tuple[object, ...]],
248+
MockedCmdResponse,
249+
]:
250+
"""Mock command response for show-options -s (server)."""
251+
252+
def _cmd(*args: object) -> MockedCmdResponse:
253+
return MockedCmdResponse(
254+
stdout=stdout,
255+
stderr=stderr,
256+
)
257+
258+
return _cmd
259+
260+
261+
def test_terminal_features(
262+
server: "Server",
263+
monkeypatch: pytest.MonkeyPatch,
264+
) -> None:
265+
"""Test tmux's terminal-feature option destructuring."""
266+
monkeypatch.setattr(server, "cmd", fake_cmd(stdout=MOCKED_GLOBAL_OPTIONS))
267+
_options = server._show_options()
268+
assert any("terminal-features" in k for k in _options)
269+
options = Options(**_options)
270+
assert options
271+
assert options.terminal_features
272+
assert options.terminal_features["screen*"] == ["title"]
273+
assert options.terminal_features["xterm*"] == [
274+
"clipboard",
275+
"ccolour",
276+
"cstyle",
277+
"focus",
278+
]
279+
280+
281+
def test_terminal_overrides(
282+
server: "Server",
283+
monkeypatch: pytest.MonkeyPatch,
284+
) -> None:
285+
"""Test tmux's terminal-overrides option destructuring."""
286+
monkeypatch.setattr(server, "cmd", cmd_mocked)
287+
_options = server._show_options()
288+
assert any("terminal-overrides" in k for k in _options)
289+
options = Options(**_options)
290+
assert options
291+
assert options.terminal_overrides
292+
assert _options["terminal-overrides"] is not None
293+
assert isinstance(_options["terminal-overrides"], dict)
294+
assert not isinstance(_options["terminal-overrides"], TmuxArray)
295+
assert "xterm-256color" in _options["terminal-overrides"]
296+
assert isinstance(_options["terminal-overrides"]["xterm-256color"], dict)
297+
assert _options["terminal-overrides"]["xterm-256color"] == {"Tc": None}
298+
299+
300+
def test_command_alias(
301+
server: "Server",
302+
monkeypatch: pytest.MonkeyPatch,
303+
) -> None:
304+
"""Test tmux's command-alias option destructuring."""
305+
monkeypatch.setattr(server, "cmd", cmd_mocked)
306+
_options = server._show_options()
307+
assert any("command-alias" in k for k in _options)
308+
options = Options(**_options)
309+
assert options
310+
assert options.command_alias
311+
assert isinstance(_options["command-alias"], dict)
312+
assert not isinstance(_options["command-alias"], TmuxArray)
313+
assert isinstance(_options["command-alias"]["split-pane"], str)
314+
assert _options["command-alias"]["split-pane"] == "split-window"
315+
316+
317+
def test_user_keys(
318+
server: "Server",
319+
monkeypatch: pytest.MonkeyPatch,
320+
) -> None:
321+
"""Test tmux's user-keys option destructuring."""
322+
monkeypatch.setattr(server, "cmd", cmd_mocked)
323+
_options = server._show_options()
324+
assert any("user-keys" in k for k in _options)
325+
options = Options(**_options)
326+
assert options
327+
assert options.user_keys is None
328+
329+
330+
class OptionDataclassTestFixture(t.NamedTuple):
331+
"""Test fixture raw show_option(s) data into typed libtmux data."""
332+
333+
# pytest internal
334+
test_id: str
335+
336+
# test data
337+
option_data: t.List[str] # option data (raw)
338+
tmux_option: str # e.g. terminal-features
339+
340+
# results
341+
expected: t.Any # e.g. 50, TerminalFeatures({}), etc.
342+
dataclass_attribute: str # e.g. terminal_features
343+
344+
345+
TEST_FIXTURES: t.List[OptionDataclassTestFixture] = [
346+
OptionDataclassTestFixture(
347+
test_id="terminal-features",
348+
option_data=textwrap.dedent(
349+
"""
350+
terminal-features[0] xterm*:clipboard:ccolour:cstyle:focus
351+
terminal-features[1] screen*:title
352+
""",
353+
)
354+
.strip()
355+
.split("\n"),
356+
dataclass_attribute="terminal_features",
357+
tmux_option="terminal-features",
358+
expected={
359+
"screen*": ["title"],
360+
"xterm*": ["clipboard", "ccolour", "cstyle", "focus"],
361+
},
362+
),
363+
]
364+
365+
366+
@pytest.mark.parametrize(
367+
list(OptionDataclassTestFixture._fields),
368+
TEST_FIXTURES,
369+
ids=[test.test_id for test in TEST_FIXTURES],
370+
)
371+
def test_option_dataclass_fixture(
372+
monkeypatch: pytest.MonkeyPatch,
373+
test_id: str,
374+
option_data: t.List[str],
375+
tmux_option: str,
376+
expected: t.Any,
377+
dataclass_attribute: str,
378+
server: "Server",
379+
) -> None:
380+
"""Parametrized test grid for options."""
381+
monkeypatch.setattr(server, "cmd", fake_cmd(stdout=option_data))
382+
383+
_options = server._show_options()
384+
assert any(tmux_option in k for k in _options)
385+
options = Options(**_options)
386+
assert options
387+
assert hasattr(options, dataclass_attribute)
388+
assert getattr(options, dataclass_attribute, None) == expected

0 commit comments

Comments
 (0)