Devices: fixes from review round 17

- A command that fails after a switch doesn't make the connector drop the new
  headset's connection.
- On first import, the app keeps using the `frame` headset even when Set Up
  Connection put another block above it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
saphidandClaude Opus 5.5 committed 2026-09-29 00:08:36 +10:00
1 parent fa05a4b310
commit 90fd2684ba
5 files changed
+35 -3

No files matched your search

+11
View File
@@ -113,6 +113,17 @@ class Migration(Base):
self.assertEqual(len(data["devices"]), 2)
self.assertEqual(fd.Registry(self.dir / "devices.json").devices(), self.reg.devices())
def test_first_import_keeps_using_frame(self):
# Set Up Connection puts each new block first; the app used `frame` before.
blocks = CONFIG.split("# >>> steam-frame (frame-2) >>>")
head, first = blocks[0].split("# >>> steam-frame (frame) >>>")
second, tail = blocks[1].split("# <<< steam-frame (frame-2) <<<")
(self.ssh / "config").write_text(head + "# >>> steam-frame (frame-2) >>>" + second + "# <<< steam-frame (frame-2) <<<\n"
+ "# >>> steam-frame (frame) >>>" + first + tail)
self.reg.sync_from_config(seed=False)
self.assertEqual([d["alias"] for d in self.reg.devices()], ["frame-2", "frame"])
self.assertEqual(self.reg.active(), self.reg.by_alias("frame")["id"])
def test_setup_finding_a_new_address_adds_it(self):
self.reg.sync_from_config(seed=False)
(self.ssh / "config").write_text(CONFIG.replace("HostName frame.tail1234.ts.net", "HostName 192.168.1.237"))
+8
View File
@@ -304,6 +304,14 @@ class Connecting(unittest.TestCase):
self.assertIn("User deck", cfg.read_text()) # changed meanwhile: left as it is
self.assertIn("left as it is", out["message"])
def test_a_late_failure_from_the_last_headset_is_ignored(self):
self.link.state["phase"] = "connected"
self.link.gen = 3
self.link.lost("ssh: connect to host a port 22: Operation timed out", 2) # sent before the switch
self.assertEqual(self.link.kicks, [])
self.link.lost("ssh: connect to host b port 22: Operation timed out", 3)
self.assertEqual(len(self.link.kicks), 1)
def test_probes_from_an_earlier_attempt_leave_the_new_rows_alone(self):
self.link.state.update(attempt=2, probes=[{"host": "b", "state": "waiting"}])
self.link.probe_update(0, 1, state="answered", ip="10.0.0.2")
+8
View File
@@ -672,6 +672,7 @@ class Registry:
blocks = parse_blocks(read_config(self.config))
changed = False
with self.lock:
first = not self.data["devices"] and not self.data.get("active")
for b in blocks:
host = b["hostname"] if b["hostname"] and HOST_RE.fullmatch(b["hostname"]) else None
user = b["user"] if b["user"] and NAME_RE.fullmatch(b["user"]) else DEFAULT_USER
@@ -714,6 +715,13 @@ class Registry:
aliases = {b["alias"] for b in blocks}
for d in self.data["devices"]:
d["managed"] = d["alias"] in aliases
if first:
# First import: the headset the app used before is `frame`, even if Set Up
# Connection put another block above it.
frame = next((d for d in self.data["devices"] if d["alias"] == "frame"), None)
if frame and self.data.get("active") != frame["id"]:
self.data["active"] = frame["id"]
changed = True
if changed:
self.save()
return changed
+6 -2
View File
@@ -325,8 +325,12 @@ class Link:
def route_key(device):
return device["id"], device.get("user"), device.get("port"), tuple(device.get("frozen") or ())
def lost(self, message):
"""A command couldn't reach the headset (Windows has no master to watch)."""
def lost(self, message, gen=None):
"""A command couldn't reach the headset (Windows has no master to watch). `gen`:
the route it was sent on; one to a headset since switched away from says nothing
about this one."""
if gen is not None and gen != self.gen:
return
if self.state["phase"] == "connected":
self.kick(f"lost: {message}")
+2 -1
View File
@@ -254,6 +254,7 @@ def ensure_master():
def ssh(remote, *, stdin=None, timeout=30, text=True):
route_gen = LINK.gen if LINK else None # which headset this command is for
try:
ensure_master()
# Never let ssh inherit our stdin: under the app it's the pipe held open for
@@ -266,7 +267,7 @@ def ssh(remote, *, stdin=None, timeout=30, text=True):
if r.returncode != 0:
err = (r.stderr or r.stdout) if text else (r.stderr or r.stdout).decode(errors="replace")
if r.returncode == 255 and LINK and unreachable(err):
LINK.lost(err) # ssh itself failed: the connector reconnects
LINK.lost(err, route_gen) # ssh itself failed: the connector reconnects
failure = Failure(strip_ansi(err).strip() or f"ssh exited {r.returncode}")
failure.stdout = r.stdout if text else r.stdout.decode(errors="replace")
raise failure