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
|
#!/usr/bin/env python3
"""Offline unit tests for tnc_serial_recovery (no serial hardware)."""
from __future__ import annotations
import importlib.util
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent
spec = importlib.util.spec_from_file_location(
"tnc_serial_recovery", ROOT / "tnc_serial_recovery.py"
)
assert spec and spec.loader
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
class MockPort:
def __init__(self) -> None:
self.written: list[bytes] = []
self.responses: list[bytes] = []
self._read_idx = 0
def write_flush(self, data: bytes) -> None:
self.written.append(data)
def read_for(self, seconds: float) -> bytes:
del seconds
if self._read_idx < len(self.responses):
chunk = self.responses[self._read_idx]
self._read_idx += 1
return chunk
return b""
def test_has_banner() -> None:
assert mod.has_banner(b"NORD><LINK TheFirmware Version 2.7")
assert mod.has_banner(b"cmd: ")
assert not mod.has_banner(b"INFO\r\nINFO")
def test_format_rx_brief_and_classify() -> None:
assert mod.format_rx_brief(b"") == "0 B silent"
brief = mod.format_rx_brief(b"kiss off\rINFO\r")
assert "14 B" in brief
assert "markers=none" in brief
assert "echo-only" in mod.classify_recovery_failure(b"kiss off\rINFO\r")
assert "silent" in mod.classify_recovery_failure(b"")
diag = mod.classify_recovery_failure(b"TheFirmware cmd:")
assert "banner" in diag
def test_probe_tf_terminal() -> None:
port = MockPort()
port.responses = [b"TheFirmware Version 2.7 DAMA\r\nChecksum"]
ok, combined, only_echo = mod.probe_tf_terminal(port.write_flush, port.read_for)
assert ok
assert not only_echo
assert port.written[0] == mod.TF_ESC_V
def test_tf_mycall_frame() -> None:
assert mod.tf_mycall_frame("cb-0") == b"\x1bI CB-0\r"
def test_recover_terminal_ok_on_passive_banner() -> None:
port = MockPort()
port.responses = [b"NORD TheFirmware Version 2.7\r\n"]
ok, rx = mod.recover_terminal(
port.write_flush, port.read_for, skip_kiss_frame=True, log=lambda _m: None
)
assert ok
assert b"TheFirmware" in rx
assert b"\xc0\xff\xc0" not in port.written
def test_recover_terminal_ok_after_kiss_return() -> None:
port = MockPort()
port.responses = [
b"",
b"TheFirmware Version 2.7\r\nChecksum (0000) = DC0A\r\n",
]
ok, rx = mod.recover_terminal(
port.write_flush, port.read_for, skip_kiss_frame=False, log=lambda _m: None
)
assert ok
assert b"TheFirmware" in rx
assert port.written[0] == b"\xc0\xff\xc0"
def test_recover_terminal_sends_qres_on_failure() -> None:
port = MockPort()
port.responses = [b""] * 40
ok, _ = mod.recover_terminal(
port.write_flush, port.read_for, skip_kiss_frame=True, log=lambda _m: None
)
assert not ok
assert mod.TF_ESC_QRES in port.written
assert b"RESTART\r" not in port.written
def test_enter_kiss_native() -> None:
port = MockPort()
port.responses = [b""]
mod.enter_kiss(port.write_flush, port.read_for, entry="kiss_on")
assert port.written[0] == mod.TF_ESC_AT_K
def main() -> int:
tests = [
test_has_banner,
test_format_rx_brief_and_classify,
test_probe_tf_terminal,
test_tf_mycall_frame,
test_recover_terminal_ok_on_passive_banner,
test_recover_terminal_ok_after_kiss_return,
test_recover_terminal_sends_qres_on_failure,
test_enter_kiss_native,
]
for fn in tests:
fn()
print(f"OK {fn.__name__}")
print(f"All {len(tests)} tests passed")
return 0
if __name__ == "__main__":
sys.exit(main())
|