From 53e4fa20e9c852931a4aabe3fe6e0388af64aa47 Mon Sep 17 00:00:00 2001 From: ScottW514 Date: Sun, 23 Aug 2026 12:54:55 -0400 Subject: [PATCH] forgetest: the cloud tests' coverage maps follow the split The six job tests covered all three cloud components whole, so a one-line change anywhere re-required one real print and five attended tests. The maps now say what each test proves: the protocol test the web session, the emulator and its fixtures; the offline tests the run loop, the hardware it drives, the offline dispatch and the pulse path; cloud.mode-switch the homing path; every one of them the client's common ground. The one real print keeps the coarse maps as the integration and the lint's floor. Two entries of the protocol test named app files without the recipe's subdirectory and selected nothing; the paths are spelled out now. --- forgetest/forgetest/suite/cloud.py | 92 ++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/forgetest/forgetest/suite/cloud.py b/forgetest/forgetest/suite/cloud.py index 301920a..0126621 100644 --- a/forgetest/forgetest/suite/cloud.py +++ b/forgetest/forgetest/suite/cloud.py @@ -13,8 +13,71 @@ from .. import hw from .. import puls from ..baseline import read_position, read_program_total -_CLOUD_COVERS = [("forgefirm-app", "**"), ("python3-gfhardware", "**"), ("python3-gfutilities", "**"), - ("forgectrl", "src/super.c"), ("forgectrl", "src/main.c")] +# Coverage maps of the cloud tests, by what each proves. Globs anchor at +# the repository root; forgefirm-app and python3-gfhardware are two +# recipes over the one repository (the same file list under both names), +# so app paths are named under forgefirm-app and library paths under +# python3-gfhardware. The one real print keeps the coarse maps: it is +# the integration and the floor the coverage lint needs, so whatever +# the finer maps leave out still re-requires it. +_SUPERVISOR = [("forgectrl", "src/super.c"), ("forgectrl", "src/main.c")] +_CLOUD_ALL = [("forgefirm-app", "**"), ("python3-gfhardware", "**"), ("python3-gfutilities", "**")] + _SUPERVISOR + +# The cloud client's common ground: its entry, the machine glue, the +# config it starts from, the library's package and identity, the +# cooling reporter every controller runs, and gfutilities' core. +_CLIENT = [("forgefirm-app", "forgefirm-app/gfcloud.py"), + ("forgefirm-app", "forgefirm-app/ffmachine.py"), + ("forgefirm-app", "forgefirm-app/gfhome.conf.sample"), + ("python3-gfhardware", "gfhardware/__init__.py"), + ("python3-gfhardware", "gfhardware/_common.py"), + ("python3-gfhardware", "gfhardware/id.py"), + ("python3-gfhardware", "gfhardware/coolsvc.py"), + ("python3-gfutilities", "gfutilities/__init__.py"), + ("python3-gfutilities", "gfutilities/_common.py"), + ("python3-gfutilities", "gfutilities/configuration.py"), + ("python3-gfutilities", "gfutilities/device/__init__.py"), + ("python3-gfutilities", "gfutilities/device/basemachine.py"), + ("python3-gfutilities", "gfutilities/device/settings.py"), + ("python3-gfutilities", "gfutilities/service/__init__.py"), + ("python3-gfutilities", "gfutilities/service/dispatch.py"), + ("python3-gfutilities", "gfutilities/service/websocket.py")] + +# The web session: sign-in, the firmware check, the service loop. +_WEB_SESSION = [("python3-gfutilities", "gfutilities/service/authentication.py"), + ("python3-gfutilities", "gfutilities/service/gfuiservice.py")] + +# The service protocol, answered by the emulator: the web session, the +# emulator and its fixtures, no hardware behind it. +_SERVICE_LAYER = _CLIENT + _WEB_SESSION + [ + ("python3-gfutilities", "gfutilities/device/emulator.py"), + ("python3-gfutilities", "examples/**")] + _SUPERVISOR + +# The machine's print behavior under the offline service: the run loop +# and the hardware it drives, the offline dispatch, the pulse path. Not +# the web session, not the emulator, not the cameras. +_MACHINE_RUN = _CLIENT + [ + ("python3-gfhardware", "gfhardware/machine.py"), + ("python3-gfhardware", "gfhardware/feeder.py"), + ("python3-gfhardware", "gfhardware/cnc.py"), + ("python3-gfhardware", "gfhardware/switches.py"), + ("python3-gfhardware", "gfhardware/input/**"), + ("python3-gfhardware", "gfhardware/src/evdev.c"), + ("python3-gfhardware", "gfhardware/z_axis.py"), + ("python3-gfhardware", "gfhardware/leds.py"), + ("python3-gfhardware", "gfhardware/cooling.py"), + ("python3-gfutilities", "gfutilities/service/offline.py"), + ("python3-gfutilities", "gfutilities/puls/**")] + _SUPERVISOR + +# The service and the machine on the homing path: the real session, the +# hunt with its captures and motions, the web-service homing from GRBL +# mode (gfhome). The whole hardware library, since a hunt drives the +# cameras, the lens, the switches and the feeder. Not the offline +# service, not the emulator. +_HOMING_PATH = _CLIENT + _WEB_SESSION + [ + ("forgefirm-app", "forgefirm-app/**"), + ("python3-gfhardware", "gfhardware/**"), + ("python3-gfutilities", "gfutilities/puls/**")] + _SUPERVISOR GF_LATEST = "/data/forgefirm/gf-latest.json" GFCLOUD_LOG = "/data/log/forgefirm/gfcloud/gfcloud.log" @@ -180,8 +243,8 @@ def gfhome_homing(ctx, ev, g): @test("cloud.mode-switch", title="Controller mode switch grbl -> cloud -> grbl, the connect-time hunt " "with the lid open, and the web-service homing", subsystem="cloud", kind="operator", mode="grbl", est_min=10, - covers=_CLOUD_COVERS + [("forgectrl", "src/cool.*"), ("forgectrl", "src/airflow.*"), - ("grblhal-glowforge", "src/**")], + covers=_HOMING_PATH + [("forgectrl", "src/cool.*"), ("forgectrl", "src/airflow.*"), + ("grblhal-glowforge", "src/**")], requires=["forgectrl.auth", "motion.pacing"], actions=["lid"], precheck=homing_mode_is_gfcloud, steps=["Bed clear; cloud credentials configured and homing_mode = gfcloud; the machine on " @@ -321,16 +384,7 @@ def mode_switch(ctx): @test("cloud.service-protocol", title="The service protocol, answered by the emulator in this " "machine's identity", subsystem="cloud", kind="operator", est_min=5, - covers=[("python3-gfutilities", "gfutilities/service/**"), - ("python3-gfutilities", "gfutilities/device/basemachine.py"), - ("python3-gfutilities", "gfutilities/device/emulator.py"), - ("python3-gfutilities", "gfutilities/device/settings.py"), - ("python3-gfutilities", "gfutilities/configuration.py"), - ("python3-gfutilities", "gfutilities/_common.py"), - ("python3-gfutilities", "gfutilities/__init__.py"), - ("python3-gfutilities", "examples/**"), - ("forgefirm-app", "gfcloud.py"), ("forgefirm-app", "ffmachine.py"), - ("forgectrl", "src/super.c")], + covers=_SERVICE_LAYER, requires=["forgectrl.auth"], steps=["Cloud credentials configured; the machine on the network; the app open in a browser " "(anyone can drive it, at the machine or not: nothing here moves, arms, or fires).", @@ -1047,7 +1101,7 @@ def judge_abort_tail(ctx, ev, offset, tag, fin): @test("cloud.lid-interlock-abort", title="Lid and interlock each abort a cloud print; the park ignores " "an open lid", subsystem="cloud", kind="live", est_min=11, - covers=_CLOUD_COVERS + [("forgectrl", "src/super.c")], + covers=_MACHINE_RUN, requires=["laser.emission-witness"], actions=["button", "lid", "interlock"], steps=[OFFLINE_STEP, ARM_STEP, LID_STEP, "The head needs 40 mm of free +X and +Y travel. The test runs TWO prints.", @@ -1156,7 +1210,7 @@ def lid_interlock_abort_body(ctx, ev, off, job, offset): @test("cloud.lid-during-button-wait", title="Lid open at the cloud button prompt cancels the print", subsystem="cloud", kind="operator", est_min=6, - covers=_CLOUD_COVERS, requires=[], actions=["lid"], + covers=_MACHINE_RUN, requires=[], actions=["lid"], steps=[OFFLINE_STEP, LID_STEP, "When the button lights white, do NOT press it - open the lid, and close it when told. " "Nothing moves and nothing fires."], @@ -1216,7 +1270,7 @@ def lid_during_button_wait_body(ctx, ev, off, job, offset): @test("cloud.pause-resume", title="Button pauses and resumes a cloud print (factory backtrack + lead)", subsystem="cloud", kind="live", est_min=8, - covers=_CLOUD_COVERS + [("forgectrl", "src/main.c")], + covers=_CLOUD_ALL, requires=["laser.emission-witness"], actions=["button"], steps=[CLOUD_STEP, "The app open; scrap on the bed and a small engrave/score job (about 60 s) ready.", "Print from the app and press the button when it lights; a few seconds into the run " @@ -1327,7 +1381,7 @@ def pause_resume(ctx): @test("cloud.oversize-stream", title="A print longer than the ring is fed while it plays", subsystem="cloud", kind="live", est_min=12, - covers=_CLOUD_COVERS, + covers=_MACHINE_RUN, requires=["cloud.lid-interlock-abort", "cloud.pause-resume"], actions=["button"], steps=[OFFLINE_STEP, ARM_STEP, "The head needs 40 mm of free +X and +Y travel. The job is an hour of squares, " @@ -1449,7 +1503,7 @@ def oversize_stream_body(ctx, ev, off, job, offset, before): @test("cloud.paused-lid-cancel", title="A paused cloud print is cancelled by the lid", subsystem="cloud", kind="live", est_min=6, - covers=_CLOUD_COVERS, requires=["cloud.pause-resume", "cloud.lid-interlock-abort"], + covers=_MACHINE_RUN, requires=["cloud.pause-resume", "cloud.lid-interlock-abort"], actions=["button", "lid"], steps=[OFFLINE_STEP, ARM_STEP, LID_STEP, "The head needs 40 mm of free +X and +Y travel.",