File: test_software_version.py

package info (click to toggle)
python-blessed 1.25-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 8,812 kB
  • sloc: python: 14,645; makefile: 13; sh: 7
file content (220 lines) | stat: -rw-r--r-- 7,663 bytes parent folder | download
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
r"""Tests for SoftwareVersion class and Terminal.get_software_version().

XTVERSION Query Format
======================

The XTVERSION query (CSI > q or ESC [ > q) requests the terminal software
name and version. Supported by modern terminal emulators including xterm,
mintty, iTerm2, tmux, kitty, WezTerm, foot, and VTE-based terminals.

Terminal response: DCS > | text ST  (ESC P > | text ESC \)

Text format varies by terminal:
  - XTerm(367)
  - kitty(0.24.2)
  - tmux 3.2a
  - WezTerm 20220207-230252-0826fb06
  - X.Org 7.7.0(370)
"""
# std
import time

# 3rd party
import pytest

# local
from .conftest import TEST_KEYBOARD, IS_WINDOWS
from .accessories import (
    TestTerminal,
    pty_test,
    as_subprocess,
)
from blessed.keyboard import SoftwareVersion

pytestmark = pytest.mark.skipif(
    not TEST_KEYBOARD or IS_WINDOWS,
    reason="Timing-sensitive tests please do not run on build farms.")


@pytest.mark.parametrize("response,expected_name,expected_version", [
    ('\x1bP>|kitty(0.24.2)\x1b\\', 'kitty', '0.24.2'),
    ('\x1bP>|tmux 3.2a\x1b\\', 'tmux', '3.2a'),
    ('\x1bP>|foot\x1b\\', 'foot', ''),
    ('\x1bP>|WezTerm 20220207-230252-0826fb06\x1b\\', 'WezTerm', '20220207-230252-0826fb06'),
    ('\x1bP>|XTerm(367)\x1b\\', 'XTerm', '367'),
    ('\x1bP>|X.Org 7.7.0(370)\x1b\\', 'X.Org', '7.7.0(370)'),
])
def test_software_version_from_match(response, expected_name, expected_version):
    """Test SoftwareVersion.from_match() with various response formats."""
    match = SoftwareVersion.RE_RESPONSE.match(response)
    sv = SoftwareVersion.from_match(match)
    assert sv is not None
    assert sv.name == expected_name
    assert sv.version == expected_version
    assert sv.raw == response


@pytest.mark.parametrize("invalid_input", ['invalid', ''])
def test_software_version_from_match_invalid(invalid_input):
    """Test SoftwareVersion.from_match() with invalid input."""
    match = SoftwareVersion.RE_RESPONSE.match(invalid_input)
    assert match is None


def test_software_version_repr():
    """Test SoftwareVersion.__repr__()."""
    sv = SoftwareVersion('\x1bP>|kitty(0.24.2)\x1b\\', 'kitty', '0.24.2')
    repr_str = repr(sv)
    assert 'SoftwareVersion' in repr_str
    assert "name='kitty'" in repr_str
    assert "version='0.24.2'" in repr_str


@pytest.mark.parametrize("text,expected_name,expected_version", [
    ('kitty(0.24.2)', 'kitty', '0.24.2'),
    ('tmux 3.2a', 'tmux', '3.2a'),
    ('foot', 'foot', ''),
    ('WezTerm 20220207-230252-0826fb06', 'WezTerm', '20220207-230252-0826fb06'),
])
def test_software_version_parse_text(text, expected_name, expected_version):
    """Test SoftwareVersion._parse_text() with various formats."""
    name, version = SoftwareVersion._parse_text(text)
    assert name == expected_name
    assert version == expected_version


@pytest.mark.parametrize("response,expected_name,expected_version,test_suffix", [
    ('\x1bP>|kitty(0.24.2)\x1b\\', 'kitty', '0.24.2', 'OK'),
    ('\x1bP>|XTerm(367)\x1b\\', 'XTerm', '367', 'XTERM'),
    ('\x1bP>|tmux 3.2a\x1b\\', 'tmux', '3.2a', 'TMUX'),
    ('\x1bP>|WezTerm 20220207-230252-0826fb06\x1b\\',
     'WezTerm', '20220207-230252-0826fb06', 'WEZTERM'),
])
def test_get_software_version_via_ungetch(response, expected_name, expected_version, test_suffix):
    """Test get_software_version() with various terminal responses via ungetch."""
    def child(term):
        term.ungetch(response)
        sv = term.get_software_version(timeout=0.01)
        assert sv is not None
        assert sv.name == expected_name
        assert sv.version == expected_version
        return test_suffix.encode('ascii')

    output = pty_test(child, parent_func=None,
                      test_name=f'test_get_software_version_{test_suffix.lower()}')
    assert output == f'\x1b[>q{test_suffix}'


def test_get_software_version_timeout():
    """Test get_software_version() timeout without response."""
    def child(term):
        stime = time.time()
        sv = term.get_software_version(timeout=0.1)
        elapsed = time.time() - stime
        assert sv is None
        assert 0.08 <= elapsed <= 0.15
        return b'TIMEOUT'

    output = pty_test(child, parent_func=None, test_name='test_get_software_version_timeout')
    assert output == '\x1b[>qTIMEOUT'


def test_get_software_version_force_bypass_cache():
    """Test get_software_version() with force=True bypasses cache."""
    def child(term):
        # First response: kitty 0.24.2
        term.ungetch('\x1bP>|kitty(0.24.2)\x1b\\')
        sv1 = term.get_software_version(timeout=0.01)

        # Second response: XTerm 367 with force=True
        term.ungetch('\x1bP>|XTerm(367)\x1b\\')
        sv2 = term.get_software_version(timeout=0.01, force=True)

        assert sv1 is not None
        assert sv2 is not None
        assert sv1.name == 'kitty'
        assert sv2.name == 'XTerm'
        assert sv1 is not sv2

        return b'FORCED'

    output = pty_test(child, parent_func=None,
                      test_name='test_get_software_version_force_bypass_cache')
    assert output == '\x1b[>q\x1b[>qFORCED'


def test_get_software_version_no_force_uses_cache():
    """Test get_software_version() without force uses cached result."""
    def child(term):
        # First response: kitty 0.24.2
        term.ungetch('\x1bP>|kitty(0.24.2)\x1b\\')
        sv1 = term.get_software_version(timeout=0.01)

        # Second query without force should use cache even with different ungetch data
        # Response: XTerm 367 - but this is ignored due to cache
        term.ungetch('\x1bP>|XTerm(367)\x1b\\')
        sv2 = term.get_software_version(timeout=0.01, force=False)

        assert sv1 is not None
        assert sv2 is not None
        assert sv1 is sv2
        assert sv1.name == 'kitty'
        assert sv2.name == 'kitty'

        return b'NO_FORCE'

    output = pty_test(child, parent_func=None,
                      test_name='test_get_software_version_no_force_uses_cache')
    assert output == '\x1b[>qNO_FORCE'


def test_get_software_version_retry_after_timeout():
    """Test get_software_version() can retry after timeout."""
    def child(term):
        # First query fails (timeout)
        sv1 = term.get_software_version(timeout=0.01)

        # Second query succeeds: kitty 0.24.2
        term.ungetch('\x1bP>|kitty(0.24.2)\x1b\\')
        sv2 = term.get_software_version(timeout=0.01)

        assert sv1 is None
        assert sv2 is not None
        assert sv2.name == 'kitty'
        assert sv2.version == '0.24.2'

        return b'RETRY'

    output = pty_test(child, parent_func=None,
                      test_name='test_get_software_version_retry_after_timeout')
    assert output == '\x1b[>q\x1b[>qRETRY'


def test_get_software_version_raw_stored():
    """Test SoftwareVersion stores raw response string."""
    raw = '\x1bP>|kitty(0.24.2)\x1b\\'
    match = SoftwareVersion.RE_RESPONSE.match(raw)
    sv = SoftwareVersion.from_match(match)
    assert sv is not None
    assert sv.raw == raw


def test_get_software_version_not_a_tty():
    """Test get_software_version() returns None when not a TTY."""
    @as_subprocess
    def child():
        import io
        term = TestTerminal(stream=io.StringIO(), force_styling=True)
        term._is_a_tty = False

        sv = term.get_software_version(timeout=0.01)
        assert sv is None
    child()


def test_software_version_init():
    """Test SoftwareVersion.__init__() stores all parameters."""
    sv = SoftwareVersion('\x1bP>|kitty(0.24.2)\x1b\\', 'kitty', '0.24.2')
    assert sv.raw == '\x1bP>|kitty(0.24.2)\x1b\\'
    assert sv.name == 'kitty'
    assert sv.version == '0.24.2'