mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-10-16 01:29:55 -07:00
The cases that tested a mock, a timer, a wording or a table, or that
replayed a judgment a host test already holds, leave forgetest/tests.
What catches a false PASS, keeps the bench safe, or guards the gate
stays.
- test_baseline: the fake sysfs tree is a mixin with no test_ methods,
so ExtensionFreeTests and BaselineModeTests no longer re-run
BaselineTests' 25 cases (133 runs, now 45). CoolPublishTests, the
lamp-fade cases and TransientNotLeftoverTests go; PositionDeadbandTests
keeps a reading past the band and an unreadable reading, both never
excused. Both latch-relock cases stay.
- test_fixture: MdnsTests, the three timing cases, the probe's log
wording, and nine prompt-routing variants go. A live test never runs
unattended, the arm-press opt-in, the short-press edge, the presence
press handing over the arm press, and the arm notice dropping at the
press stay (35 cases, now 21).
- test_cloud_suite: three false-PASS guards stay: the latch open at a
dark print's button, a lid stop that is not edge-driven, the kernel
refusing the resume. The guards for the latch unlocking in the hold
and a paused print resuming left with their catalog tests in 6a434bb.
The rest go, with the harness pieces and the hunt-lid fixture only
they read (41 cases, now 3).
- test_cooling_suite: GateOffTests, CriticalTierTests, FanGateTests and
FansQuietTests go with their harness; the flow verdict's log reads
stay: only the text after the offset, never an older verdict before it
(the flow-under-load false-PASS guard), a rotated log read whole.
- test_queue: RunClockTests and three ordering cases go, and refusals
are judged by a reason given, not its words; both live
acknowledgment cases stay.
- test_takeover_position: the six cases that judge the head and move
nothing stay.
- test_camera_home_return: the five cases where the head must not move
stay, and so do the three that check the hand-back jog's direction and
distance (homed, unhomed with the counters not counted twice, after
cloud mode): a wrong sign or a double count drives the head into the
gantry stops (15 cases, now 8).
- test_server: the keep-alive and ETag cases, the takeover and scope
half of the bench case, and the page's self-contained asserts
(test_page.py's) go.
- test_operator: the wording cases, the fixture case (test_fixture.py's)
and JournalTests go.
- test_mode: the supervisor timeout table goes.
- test_takeover_client: the listener, the offline service back, the
online client without the hunt, and GRBL under no marker stay.
- test_responsiveness: PageTests and ImplementationCostTests go; the
log cache keeps appends, a partial line, a replaced file and a fresh
list, and the fingerprint cache a changed manifest and one with no
content hash.
- test_bench_registry: GfbenchTests and two description and argument
pins go.
- test_installer: the log-format and TLS-wording cases and
InstallerShapeTests go; the die case checks the reason is in the log,
not its format.
- test_image: KernelIdentTests and the registration pin go.
Each file's __main__ guard now ends the file, and the docstrings say what
each still proves.
Proof: each of the fifteen files passes under Linux as forgetest-ci
discovers it, and pyflakes reports nothing new in them.
Acceptance: none; test-only.
161 lines
7.4 KiB
Python
161 lines
7.4 KiB
Python
# Copyright 2026 514 LLC d/b/a OpenGlow
|
|
# Written by Scott Wiederhold
|
|
# https://community.openglow.org
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
"""A takeover hands back the cloud client it found. In cloud mode
|
|
forgectrl's start at the end of a takeover starts a cloud client, and what
|
|
that client is comes only from gfcloud's one-start markers. Started bare it
|
|
is the online client with the service's connect-time hunt, and the moves
|
|
the service sends after the hunt outlast the run. The start is made under
|
|
the no-hunt marker, and under the offline marker when the takeover found
|
|
the offline service, which it then proves came back. Runs against the
|
|
fake forgectrl, a fake /proc, and a fake init script that plays the
|
|
client's start."""
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import unittest
|
|
|
|
from forgetest import baseline, hw, runner
|
|
|
|
from helpers import FakeForgectrl
|
|
|
|
|
|
class FakeRun:
|
|
def __init__(self, cap):
|
|
self.baseline_captured = cap
|
|
self.lines = []
|
|
|
|
def log(self, s):
|
|
self.lines.append(s)
|
|
|
|
|
|
class TakeoverClientTests(unittest.TestCase):
|
|
OLD_PID, NEW_PID = 200, 201
|
|
|
|
def setUp(self):
|
|
self.tmp = tempfile.mkdtemp(prefix="forgetest-tkc-")
|
|
self.proc = os.path.join(self.tmp, "proc")
|
|
os.makedirs(os.path.join(self.proc, "net"))
|
|
self.sysfs = os.path.join(self.tmp, "sysfs") + os.sep
|
|
for group in ("cnc", "pic", "head"):
|
|
os.makedirs(self.sysfs + group)
|
|
os.environ["GF_SYSFS_ROOT"] = self.sysfs
|
|
os.environ["FORGETEST_PROC_ROOT"] = self.proc
|
|
os.environ["FORGETEST_MARKER"] = os.path.join(self.tmp, "forgetest.active")
|
|
self.saved = (baseline.NOHUNT_MARKER, baseline.OFFLINE_MARKER, baseline.OFFLINE_SOCKET,
|
|
hw.initd, runner.Takeover.OFFLINE_BACK_S)
|
|
baseline.NOHUNT_MARKER = os.path.join(self.tmp, "gfcloud-nohunt")
|
|
baseline.OFFLINE_MARKER = os.path.join(self.tmp, "gfcloud-offline")
|
|
baseline.OFFLINE_SOCKET = "/run/gfcloud-offline.sock"
|
|
runner.Takeover.OFFLINE_BACK_S = 0
|
|
baseline.Baseline._unreachable_until = 0.0
|
|
self.unix = [] # (pid, inode, path, listening)
|
|
self.fc = FakeForgectrl().start()
|
|
self.fc.state["mode"] = {"mode": "cloud", "controller": "running", "pid": self.OLD_PID,
|
|
"motion": "verified"}
|
|
self.calls = []
|
|
self.at_start = None # the markers present when forgectrl started
|
|
self.comes_up = "running" # the controller forgectrl's start settles on
|
|
hw.initd = self._initd
|
|
|
|
def tearDown(self):
|
|
(baseline.NOHUNT_MARKER, baseline.OFFLINE_MARKER, baseline.OFFLINE_SOCKET,
|
|
hw.initd, runner.Takeover.OFFLINE_BACK_S) = self.saved
|
|
self.fc.stop()
|
|
for k in ("GF_SYSFS_ROOT", "FORGETEST_PROC_ROOT", "FORGETEST_MARKER"):
|
|
os.environ.pop(k, None)
|
|
shutil.rmtree(self.tmp, ignore_errors=True)
|
|
|
|
# -- the fakes ------------------------------------------------------
|
|
def _socket(self, pid, inode, path, listening=True):
|
|
self.unix.append((pid, inode, path, listening))
|
|
fd_dir = os.path.join(self.proc, str(pid), "fd")
|
|
os.makedirs(fd_dir, exist_ok=True)
|
|
os.symlink("socket:[%d]" % inode, os.path.join(fd_dir, str(len(os.listdir(fd_dir)) + 3)))
|
|
with open(os.path.join(self.proc, "net", "unix"), "w") as f:
|
|
f.write("Num RefCount Protocol Flags Type St Inode Path\n")
|
|
for _pid, ino, p, lis in self.unix:
|
|
f.write("00000000: 00000002 00000000 %08X 0001 %s %d %s\n"
|
|
% (0x10000 if lis else 0, "01" if lis else "03", ino, p))
|
|
|
|
def _offline_client(self, pid):
|
|
self._socket(pid, 9000 + pid, baseline.OFFLINE_SOCKET)
|
|
|
|
def _initd(self, service, action, timeout=60):
|
|
"""forgectrl stop/start; the start plays gfcloud's: it reads and
|
|
takes down the markers, and it is the offline service when the
|
|
offline marker was there."""
|
|
self.calls.append((service, action))
|
|
if action != "start":
|
|
return 0, ""
|
|
self.at_start = sorted(os.path.basename(p) for p in (baseline.NOHUNT_MARKER, baseline.OFFLINE_MARKER)
|
|
if os.path.exists(p))
|
|
mode = self.fc.state["mode"]
|
|
if self.comes_up != "running":
|
|
self.fc.state["mode"] = dict(mode, controller=self.comes_up, pid=None, motion="unverified")
|
|
return 0, ""
|
|
offline = os.path.exists(baseline.OFFLINE_MARKER)
|
|
for p in (baseline.NOHUNT_MARKER, baseline.OFFLINE_MARKER):
|
|
if os.path.exists(p):
|
|
os.remove(p)
|
|
self.fc.state["mode"] = dict(mode, controller="running", pid=self.NEW_PID, motion="verified")
|
|
if offline and mode.get("mode") == "cloud":
|
|
self._offline_client(self.NEW_PID)
|
|
return 0, ""
|
|
|
|
def takeover(self):
|
|
cap = {"position": None, "mode": self.fc.state["mode"]["mode"]}
|
|
run = FakeRun(cap)
|
|
with runner.Takeover(run.log, "test.id", run=run):
|
|
pass
|
|
return cap, run
|
|
|
|
def markers_left(self):
|
|
return [p for p in (baseline.NOHUNT_MARKER, baseline.OFFLINE_MARKER) if os.path.exists(p)]
|
|
|
|
# -- the socket judge -------------------------------------------------
|
|
def test_the_listener_is_the_process_own_descriptor(self):
|
|
self._offline_client(self.OLD_PID)
|
|
self.assertTrue(hw.listens_on(self.OLD_PID, baseline.OFFLINE_SOCKET))
|
|
# the path alone is not enough: another process's socket, a
|
|
# connected (not listening) socket, another path, no pid
|
|
self.assertFalse(hw.listens_on(self.NEW_PID, baseline.OFFLINE_SOCKET))
|
|
self._socket(self.NEW_PID, 7001, baseline.OFFLINE_SOCKET, listening=False)
|
|
self.assertFalse(hw.listens_on(self.NEW_PID, baseline.OFFLINE_SOCKET))
|
|
self.assertFalse(hw.listens_on(self.OLD_PID, "/run/forgefirm/grbl.ctl"))
|
|
self.assertFalse(hw.listens_on(None, baseline.OFFLINE_SOCKET))
|
|
|
|
# -- the takeover ------------------------------------------------------
|
|
def test_the_offline_service_comes_back_offline(self):
|
|
# exthost.armed-freeze's case: the setup record put back under a
|
|
# takeover while the offline service ran
|
|
self._offline_client(self.OLD_PID)
|
|
cap, run = self.takeover()
|
|
self.assertEqual(self.calls, [("forgectrl", "stop"), ("forgectrl", "start")])
|
|
self.assertEqual(self.at_start, ["gfcloud-nohunt", "gfcloud-offline"])
|
|
self.assertTrue(hw.listens_on(self.NEW_PID, baseline.OFFLINE_SOCKET))
|
|
self.assertNotIn("restart_clients", cap)
|
|
self.assertEqual(self.markers_left(), [])
|
|
self.assertTrue(any("back as the offline service (pid %d)" % self.NEW_PID in l for l in run.lines),
|
|
run.lines)
|
|
|
|
def test_an_online_client_comes_back_without_the_hunt(self):
|
|
cap, run = self.takeover()
|
|
self.assertEqual(self.at_start, ["gfcloud-nohunt"])
|
|
self.assertFalse(hw.listens_on(self.NEW_PID, baseline.OFFLINE_SOCKET))
|
|
self.assertNotIn("restart_clients", cap)
|
|
self.assertEqual(self.markers_left(), [])
|
|
|
|
def test_grbl_mode_starts_under_no_marker(self):
|
|
self.fc.state["mode"] = {"mode": "grbl", "controller": "running", "pid": self.OLD_PID,
|
|
"motion": "verified"}
|
|
cap, run = self.takeover()
|
|
self.assertEqual(self.at_start, [])
|
|
self.assertNotIn("restart_clients", cap)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|