Files
nix-config/scripts/tests/test_wake.py
T

158 lines
4.6 KiB
Python

from __future__ import annotations
import io
import unittest
from scripts.wake import (
Host,
HostSelectionError,
Reporter,
WakeConfig,
WakeSession,
magic_packet,
resolve_hosts,
)
class FakeClock:
def __init__(self) -> None:
self.now = 0.0
def __call__(self) -> float:
return self.now
def sleep(self, seconds: float) -> None:
self.now += seconds
class RecordingSender:
def __init__(self) -> None:
self.calls: list[tuple[str, int]] = []
def __call__(self, host: Host, packets_per_broadcast: int) -> None:
self.calls.append((host.name, packets_per_broadcast))
class SequenceProbe:
def __init__(self, online_on_call: int) -> None:
self._online_on_call = online_on_call
self._calls = 0
def __call__(self, _host: Host) -> bool:
self._calls += 1
return self._calls >= self._online_on_call
class MagicPacketTests(unittest.TestCase):
def test_builds_standard_packet(self) -> None:
mac = "8c:32:23:36:81:84"
packet = magic_packet(mac)
self.assertEqual(len(packet), 102)
self.assertEqual(packet[:6], b"\xff" * 6)
self.assertEqual(packet[6:], bytes.fromhex(mac.replace(":", "")) * 16)
def test_rejects_invalid_mac(self) -> None:
with self.assertRaises(ValueError):
magic_packet("not-a-mac")
class ResolveHostsTests(unittest.TestCase):
def setUp(self) -> None:
self.hosts = {
"alpha": Host("alpha", "00:00:00:00:00:01", "192.0.2.1"),
"beta": Host("beta", "00:00:00:00:00:02", "192.0.2.2"),
}
def test_defaults_to_all_hosts(self) -> None:
self.assertEqual(resolve_hosts([], hosts=self.hosts), list(self.hosts.values()))
def test_resolves_alias_and_removes_duplicates(self) -> None:
selected = resolve_hosts(
["a", "alpha", "beta"],
hosts=self.hosts,
aliases={"a": "alpha"},
)
self.assertEqual(selected, [self.hosts["alpha"], self.hosts["beta"]])
def test_rejects_unknown_host(self) -> None:
with self.assertRaises(HostSelectionError):
resolve_hosts(["missing"], hosts=self.hosts, aliases={})
class WakeSessionTests(unittest.TestCase):
def test_retries_until_host_is_online(self) -> None:
clock = FakeClock()
sender = RecordingSender()
stdout = io.StringIO()
stderr = io.StringIO()
reporter = Reporter(stream=stdout, error_stream=stderr, clock=clock)
host = Host("test", "00:00:00:00:00:01", "192.0.2.1")
config = WakeConfig(
timeout=30,
retry_interval=5,
probe_interval=1,
progress_interval=5,
packets_per_broadcast=2,
)
session = WakeSession(
[host],
config,
reporter,
sender=sender,
probe=SequenceProbe(online_on_call=10),
clock=clock,
sleep=clock.sleep,
)
self.assertEqual(session.run(), 0)
self.assertEqual(sender.calls, [("test", 2), ("test", 2)])
output = stdout.getvalue()
self.assertIn("attempt 2", output)
self.assertIn("still waiting for: test (attempt 2, 192.0.2.1)", output)
self.assertIn("test is online", output)
def test_exits_immediately_when_host_is_already_online(self) -> None:
clock = FakeClock()
sender = RecordingSender()
reporter = Reporter(
stream=io.StringIO(), error_stream=io.StringIO(), clock=clock
)
host = Host("test", "00:00:00:00:00:01", "192.0.2.1")
session = WakeSession(
[host],
WakeConfig(),
reporter,
sender=sender,
probe=lambda _host: True,
clock=clock,
sleep=clock.sleep,
)
self.assertEqual(session.run(), 0)
self.assertEqual(sender.calls, [])
def test_times_out_when_host_never_comes_online(self) -> None:
clock = FakeClock()
sender = RecordingSender()
stdout = io.StringIO()
stderr = io.StringIO()
reporter = Reporter(stream=stdout, error_stream=stderr, clock=clock)
host = Host("test", "00:00:00:00:00:01", "192.0.2.1")
session = WakeSession(
[host],
WakeConfig(timeout=3, retry_interval=5),
reporter,
sender=sender,
probe=lambda _host: False,
clock=clock,
sleep=clock.sleep,
)
self.assertEqual(session.run(), 1)
self.assertIn("timed out waiting for: test", stderr.getvalue())
if __name__ == "__main__":
unittest.main()