forgetest: cloud.service-protocol, the service answered by the emulator in this machine's identity

The service-protocol half of the cloud catalog on its own test: the cloud
client restarted as gfutilities' emulator under the /run/gfcloud-emulate
marker signs in, passes the firmware check, opens the WebSocket, answers
the connect-time hunt and the image requests with the dev image's canned
frames, and runs a print from the app through the real download path to
':completed' - nothing moves, nothing arms, and only the app has to be
driven, by a person or an agent through the prompt API. The real client
is restarted afterward and its hunt waited out. session_live now knows
the emulator's session is not the machine's, so enter_cloud restarts it
rather than reusing it; restart_client is the one restart the offline
and emulator entries share.

The dev image adds python3-gfutilities-emulator (the fixtures, packaged
on their own in meta-openglow); forgefirm-app moves to 12ad3b1 (gfcloud
--emulate). Catalog: 44 tests, 27 auto / 9 operator / 8 live; the new
test covers the gfutilities service layer and examples/, which step 4
will take off the other cloud tests. Replays over the prompt script;
contract and BRINGUP updated. A layer change (the dev image recipe):
everything re-requires on the next image.
This commit is contained in:
ScottW514
2026-08-22 20:19:46 -04:00
parent 0cb9044e1d
commit 1c8197faa3
6 changed files with 281 additions and 40 deletions
+83
View File
@@ -13,6 +13,7 @@ reuse an existing cloud session and never switch back, restart the client
for a fresh hunt, judge the print's own finish line (not another action's),
wait the service's deferred moves out, and fail for the right reasons."""
import contextlib
import json
import os
import shutil
import struct
@@ -170,6 +171,9 @@ class CloudSuiteTests(unittest.TestCase):
cloud.Offline = FakeOffline
cloud.OFFLINE_MARKER = os.path.join(self.tmp, "offline-marker")
cloud.JOB_DIR = os.path.join(self.tmp, "jobs")
self.saved_emu = (cloud.EMULATE_MARKER, cloud.EMULATOR_WORK)
cloud.EMULATE_MARKER = os.path.join(self.tmp, "emulate-marker")
cloud.EMULATOR_WORK = os.path.join(self.tmp, "emu-work")
FakeOffline.sent = []
FakeOffline.on_send = None
self.engine_line = EFFECTIVE_LINE # what the engine logs at the print; None = nothing
@@ -188,6 +192,7 @@ class CloudSuiteTests(unittest.TestCase):
(cloud.GFCLOUD_LOG, cloud.FORGECTRL_LOG, cloud.QUIET_S, cloud.QUIET_TIMEOUT_S,
cloud.HUNT_TIMEOUT_S, cloud.GFHOME_LOG, cloud.Offline, cloud.OFFLINE_MARKER,
cloud.JOB_DIR) = self.saved
(cloud.EMULATE_MARKER, cloud.EMULATOR_WORK) = self.saved_emu
os.environ.pop("GF_SYSFS_ROOT", None)
os.environ.pop("GF_LEDS_ROOT", None)
shutil.rmtree(self.tmp, ignore_errors=True)
@@ -262,6 +267,84 @@ class CloudSuiteTests(unittest.TestCase):
self.assertIn("offline", detail)
self.assertTrue(cloud.client_offline(3100))
# -- the emulator ------------------------------------------------------------------
EMU_PID = 4100
EMU_START = ["2026-08-22T23:50:00.100000+00:00 gfcloud[4100] INFO ffmachine:build_emulator EMULATE: the "
"emulator in this machine's identity, frames from /usr/share/gfutilities/emulator, no hardware",
"2026-08-22T23:50:02.000000+00:00 gfcloud[4100] INFO authentication:authenticate_machine SUCCESS",
"2026-08-22T23:50:03.000000+00:00 gfcloud[4100] INFO websocket:ws_connect ESTABLISHED",
"2026-08-22T23:50:05.000000+00:00 gfcloud[4100] INFO basemachine:_finish_action hunt [1578300001]: "
"finished with event \":completed\"",
"2026-08-22T23:50:06.000000+00:00 gfcloud[4100] INFO websocket:img_upload COMPLETE",
"2026-08-22T23:50:06.500000+00:00 gfcloud[4100] INFO basemachine:_finish_action lid_image [1578300002]: "
"finished with event \":completed\""]
EMU_PRINT = ["2026-08-22T23:51:00.000000+00:00 gfcloud[4100] INFO gfuiservice:run service action request: print (ready)",
"2026-08-22T23:51:01.000000+00:00 gfcloud[4100] INFO websocket:fetch_motion pulse data is uncompressed, "
"47848 byte body",
"2026-08-22T23:51:02.000000+00:00 gfcloud[4100] INFO basemachine:_finish_action print [1578300003]: "
"finished with event \":completed\""]
REAL_BACK = ["2026-08-22T23:52:02.000000+00:00 gfcloud[4200] INFO authentication:authenticate_machine SUCCESS",
"2026-08-22T23:52:03.000000+00:00 gfcloud[4200] INFO websocket:ws_connect ESTABLISHED",
"2026-08-22T23:52:03.500000+00:00 gfcloud[4200] INFO websocket:_on_open RX-EVENT: ready",
"2026-08-22T23:52:15.000000+00:00 gfcloud[4200] INFO basemachine:_finish_action hunt [1578300010]: "
"finished with event \":completed\""]
def emulator_setup(self, print_lines=None, info=True):
self.in_cloud(pid=2278)
self.append(["2026-08-22T23:49:00.500000+00:00 gfcloud[2278] INFO websocket:_on_open RX-EVENT: ready"])
starts = []
def on_post(path, form):
if path == "/controller/stop":
self.fc.state["mode"] = dict(self.fc.state["mode"], controller="standby", pid=0)
elif path == "/controller/start":
starts.append(os.path.exists(cloud.EMULATE_MARKER))
if len(starts) == 1:
self.fc.state["mode"] = dict(self.fc.state["mode"], controller="running", pid=self.EMU_PID)
self.append(self.EMU_START, delay=0.2)
else:
self.fc.state["mode"] = dict(self.fc.state["mode"], controller="running", pid=4200)
self.append(self.REAL_BACK, delay=0.2)
return None
self.fc.on_post = on_post
def at_print():
os.makedirs(cloud.EMULATOR_WORK, exist_ok=True)
if info:
with open(os.path.join(cloud.EMULATOR_WORK, "2026-08-22_235101_print.info"), "w") as f:
json.dump({"header_data": {"STfr": 10000, "MCsn": 0, "PDfm": 0}}, f)
self.append(print_lines if print_lines is not None else self.EMU_PRINT, delay=0.1)
return starts, {"Set up any small job": at_print}
def test_service_protocol_against_the_emulator(self):
starts, hooks = self.emulator_setup()
run = self.run_test(cloud.service_protocol, hooks=hooks, test_id="cloud.service-protocol")
ev = run.evidence
self.assertEqual(starts, [True, False]) # emulator under the marker, the real client without
self.assertFalse(os.path.exists(cloud.EMULATE_MARKER))
self.assertEqual([p for p, _ in self.fc.posts],
["/controller/stop", "/controller/start", "/controller/stop", "/controller/start"])
self.assertIn(":completed", ev["hunt"])
self.assertIn(":completed", ev["print"])
self.assertEqual(ev["images_uploaded"], 1)
self.assertEqual(ev["header_stfr"], 10000)
self.assertEqual(self.script.asked, ["Does the app show the print complete?"])
self.assertTrue(any("PASS: session, hunt" in l for l in run.lines), run.lines[-5:])
self.assertFalse(os.path.exists(cloud.EMULATOR_WORK))
def test_service_protocol_fails_when_the_print_does_not_complete_and_still_restores_the_client(self):
failed = [l.replace(':completed"', ':failed"') for l in self.EMU_PRINT]
starts, hooks = self.emulator_setup(print_lines=failed)
self.assertFails(cloud.service_protocol, "did not complete", hooks=hooks)
self.assertEqual(starts, [True, False]) # the real client came back regardless
def test_the_emulators_session_is_not_the_machines(self):
self.append(self.EMU_START + ["2026-08-22T23:50:07.000000+00:00 gfcloud[4100] INFO websocket:_on_open "
"RX-EVENT: ready"])
live, detail = cloud.session_live(4100)
self.assertFalse(live)
self.assertIn("emulator", detail)
# -- the offline entry ----------------------------------------------------------
def test_enter_offline_reuses_a_running_offline_client(self):
self.in_offline()