mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-10-15 17:19: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.
111 lines
4.4 KiB
Python
111 lines
4.4 KiB
Python
# Copyright 2026 514 LLC d/b/a OpenGlow
|
|
# Written by Scott Wiederhold
|
|
# https://community.openglow.org
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
"""The caches behind the page's state.
|
|
|
|
Every poll used to re-read and re-parse the whole result log and recompute
|
|
every test's domain fingerprint; a result record carries its run log, so
|
|
the file reaches megabytes over a campaign. Both are now kept between
|
|
polls, and a stale cache would misjudge the gate: the log cache picks up
|
|
what is appended or replaced and never counts a partial line, and the
|
|
fingerprint cache never serves a changed manifest or one it cannot key.
|
|
"""
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import unittest
|
|
|
|
import helpers
|
|
from forgetest import manifest as manifest_mod
|
|
from forgetest.log import Log
|
|
|
|
|
|
def t_noop(ctx):
|
|
pass
|
|
|
|
|
|
class LogCacheTests(unittest.TestCase):
|
|
"""The log is append-only, so a read parses each line exactly once."""
|
|
|
|
def setUp(self):
|
|
self.tmp = tempfile.mkdtemp(prefix="forgetest-log-")
|
|
self.path = os.path.join(self.tmp, "results.jsonl")
|
|
self.log = Log(self.path)
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(self.tmp, ignore_errors=True)
|
|
|
|
def write(self, *lines):
|
|
with open(self.path, "a", encoding="utf-8") as f:
|
|
for line in lines:
|
|
f.write(line + "\n")
|
|
|
|
def test_appends_are_picked_up(self):
|
|
self.log.append({"t": "campaign", "id": "c1"})
|
|
self.assertEqual([r["id"] for r in self.log.read()], ["c1"])
|
|
self.log.append({"t": "result", "test": "a.b"})
|
|
self.log.append({"t": "result", "test": "c.d"})
|
|
recs = self.log.read()
|
|
self.assertEqual([r["t"] for r in recs], ["campaign", "result", "result"])
|
|
self.assertEqual(recs[2]["test"], "c.d")
|
|
|
|
def test_read_returns_a_fresh_list(self):
|
|
"""Callers filter and sort the result; the cache must not be theirs."""
|
|
self.log.append({"t": "result", "test": "a.b"})
|
|
first = self.log.read()
|
|
first.append({"t": "bogus"})
|
|
self.assertEqual(len(self.log.read()), 1)
|
|
|
|
def test_partial_trailing_line_is_held_not_counted_corrupt(self):
|
|
self.write('{"t":"result","test":"a.b"}')
|
|
with open(self.path, "a", encoding="utf-8") as f:
|
|
f.write('{"t":"result","te') # a line still being written
|
|
recs = self.log.read()
|
|
self.assertEqual(len(recs), 1)
|
|
self.assertEqual(self.log.corrupt, 0)
|
|
with open(self.path, "a", encoding="utf-8") as f:
|
|
f.write('st":"c.d"}\n') # ... now finished
|
|
recs = self.log.read()
|
|
self.assertEqual([r["test"] for r in recs], ["a.b", "c.d"])
|
|
self.assertEqual(self.log.corrupt, 0)
|
|
|
|
def test_replaced_file_is_read_again(self):
|
|
self.write('{"t":"result","test":"a.b"}', '{"t":"result","test":"c.d"}')
|
|
self.assertEqual(len(self.log.read()), 2)
|
|
with open(self.path, "w", encoding="utf-8") as f:
|
|
f.write('{"t":"campaign","id":"c9"}\n')
|
|
recs = self.log.read()
|
|
self.assertEqual([r.get("id") for r in recs], ["c9"])
|
|
|
|
|
|
class FingerprintCacheTests(unittest.TestCase):
|
|
"""Memoized per manifest: the page recomputes every test's fingerprint
|
|
on every poll, and a manifest never changes under a running tool."""
|
|
|
|
def setUp(self):
|
|
self.man = helpers.make_manifest()
|
|
self.t = helpers.make_test("fake.fp", [("forgectrl", "src/ui.c")], fn=t_noop)
|
|
|
|
def test_a_changed_manifest_is_not_served_from_the_cache(self):
|
|
first = self.t.fingerprint(self.man)
|
|
man2 = helpers.with_file(self.man, "forgectrl", "src/ui.c", "ui v2")
|
|
self.assertNotEqual(self.t.fingerprint(man2), first)
|
|
# and back again: the cache must not have latched the new one either
|
|
self.assertEqual(self.t.fingerprint(self.man), first)
|
|
|
|
def test_manifest_without_a_content_hash_is_not_cached(self):
|
|
"""The cache is keyed by the manifest's content hash. A manifest
|
|
that has none must still fingerprint correctly, not collide."""
|
|
a = manifest_mod.Manifest({"components": {"forgectrl": {"files": [["src/ui.c", "aaa"]]}},
|
|
"platform": {}})
|
|
b = manifest_mod.Manifest({"components": {"forgectrl": {"files": [["src/ui.c", "bbb"]]}},
|
|
"platform": {}})
|
|
self.assertIsNone(a.content_sha)
|
|
self.assertNotEqual(self.t.fingerprint(a), self.t.fingerprint(b))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|