diff --git a/forgetest/forgetest/suite/cooling.py b/forgetest/forgetest/suite/cooling.py index 041fff0..b9e2d5b 100644 --- a/forgetest/forgetest/suite/cooling.py +++ b/forgetest/forgetest/suite/cooling.py @@ -20,61 +20,9 @@ _COOL_COVERS = [("forgectrl", "src/cool.*"), ("forgectrl", "src/coolfmt.*"), ("f ("kernel-module-glowforge", "src/pic*")] -@test("cooling.flow-verify", title="Coolant flow check separates flow from no-flow", - subsystem="cooling", kind="auto", est_min=4, - covers=_COOL_COVERS, requires=["kernel.latch-locked-idle"], - steps=["Coolant loop normal (pump on); the machine idle. The controller is suspended by " - "forgectrl for the duration (about 3 minutes)."], - description="forgectrl's flow-verify diagnostic: one heater window with the pump on and " - "one with it commanded off, judged against the configured threshold. PASS = " - "the threshold separates the two readings with the margins forgectrl reports; " - "a thin margin is recorded as a warning.") -def flow_verify(ctx): - fc = ctx.forgectrl - ev = ctx.evidence - st, body = fc.get("/diag/status") - ctx.check(st == 200 and isinstance(body, dict), "GET /diag/status -> %s", st) - ctx.check(not body.get("running"), "a diagnostic is already running (%s)", body.get("tool")) - st, body = fc.post("/diag/flow-verify") - ctx.log("POST /diag/flow-verify -> %s %s", st, body if isinstance(body, dict) else "") - ctx.check(st == 202 and isinstance(body, dict) and body.get("started") is True, - "could not start flow-verify (%s %s)", st, body) - last_phase = None - result = None - t0 = time.time() - try: - while time.time() - t0 < 900: - ctx.checkpoint() - st, d = fc.get("/diag/status") - if st == 200 and isinstance(d, dict): - if d.get("phase") != last_phase: - last_phase = d.get("phase") - ctx.log("phase: %s (down %.1f C, up %.1f C)", last_phase, d.get("down_c", 0), d.get("up_c", 0)) - if not d.get("running") and d.get("result") is not None: - result = d.get("result") - for line in d.get("log", [])[-12:]: - ctx.log(" diag: %s", line) - break - time.sleep(2) - except BaseException: - fc.post("/diag/abort") - raise - ctx.check(result is not None, "flow-verify did not finish within 15 minutes") - ev["result"] = result - ctx.check("error" not in result, "flow-verify error: %s", result.get("error")) - ctx.log("verdict: pass=%s threshold=%s flow_rise=%s noflow_rise=%s margins %s/%s thin=%s", - result.get("pass"), result.get("threshold"), result.get("flow_rise"), - result.get("noflow_rise"), result.get("margin_flow"), result.get("margin_noflow"), - result.get("thin_margin")) - ctx.check(result.get("pass") is True, "the threshold does not separate flow from no-flow: %s", result) - if result.get("thin_margin"): - ctx.log("WARNING: thin margin - run flow-calibrate") - ctx.check(fc.wait_idle(120, abort=ctx.aborted), "machine did not return to idle after the diagnostic") - - -AA_CAL_TIMEOUT_S = 540 # the tool's 420 s settle limit plus its six edges - - +# Registered before the heater tools: a queue runs the catalog in this +# order, and a heater trial leaves a warm slug circulating past the +# coolant sensors for minutes, which the tool's edges read as disagreement. @test("cooling.aa-offset-calibrate", title="The air-assist ground shift on the coolant readings measures cleanly", subsystem="cooling", kind="auto", est_min=6, covers=_COOL_COVERS, requires=["kernel.latch-locked-idle"], @@ -137,6 +85,61 @@ IDLE_REF_TIMEOUT_S = 180 # a previous test's cooldown + spin-down COOLDOWN_TIMEOUT_S = 240 # the engine's smoke phase + idle drop + spin-down +@test("cooling.flow-verify", title="Coolant flow check separates flow from no-flow", + subsystem="cooling", kind="auto", est_min=4, + covers=_COOL_COVERS, requires=["kernel.latch-locked-idle"], + steps=["Coolant loop normal (pump on); the machine idle. The controller is suspended by " + "forgectrl for the duration (about 3 minutes)."], + description="forgectrl's flow-verify diagnostic: one heater window with the pump on and " + "one with it commanded off, judged against the configured threshold. PASS = " + "the threshold separates the two readings with the margins forgectrl reports; " + "a thin margin is recorded as a warning.") +def flow_verify(ctx): + fc = ctx.forgectrl + ev = ctx.evidence + st, body = fc.get("/diag/status") + ctx.check(st == 200 and isinstance(body, dict), "GET /diag/status -> %s", st) + ctx.check(not body.get("running"), "a diagnostic is already running (%s)", body.get("tool")) + st, body = fc.post("/diag/flow-verify") + ctx.log("POST /diag/flow-verify -> %s %s", st, body if isinstance(body, dict) else "") + ctx.check(st == 202 and isinstance(body, dict) and body.get("started") is True, + "could not start flow-verify (%s %s)", st, body) + last_phase = None + result = None + t0 = time.time() + try: + while time.time() - t0 < 900: + ctx.checkpoint() + st, d = fc.get("/diag/status") + if st == 200 and isinstance(d, dict): + if d.get("phase") != last_phase: + last_phase = d.get("phase") + ctx.log("phase: %s (down %.1f C, up %.1f C)", last_phase, d.get("down_c", 0), d.get("up_c", 0)) + if not d.get("running") and d.get("result") is not None: + result = d.get("result") + for line in d.get("log", [])[-12:]: + ctx.log(" diag: %s", line) + break + time.sleep(2) + except BaseException: + fc.post("/diag/abort") + raise + ctx.check(result is not None, "flow-verify did not finish within 15 minutes") + ev["result"] = result + ctx.check("error" not in result, "flow-verify error: %s", result.get("error")) + ctx.log("verdict: pass=%s threshold=%s flow_rise=%s noflow_rise=%s margins %s/%s thin=%s", + result.get("pass"), result.get("threshold"), result.get("flow_rise"), + result.get("noflow_rise"), result.get("margin_flow"), result.get("margin_noflow"), + result.get("thin_margin")) + ctx.check(result.get("pass") is True, "the threshold does not separate flow from no-flow: %s", result) + if result.get("thin_margin"): + ctx.log("WARNING: thin margin - run flow-calibrate") + ctx.check(fc.wait_idle(120, abort=ctx.aborted), "machine did not return to idle after the diagnostic") + + +AA_CAL_TIMEOUT_S = 540 # the tool's 420 s settle limit plus its six edges + + def _duties(): return {k: hw.sysfs_int(k) for k in IDLE_DUTY} diff --git a/forgetest/tests/test_cooling_order.py b/forgetest/tests/test_cooling_order.py new file mode 100644 index 0000000..e0f92ba --- /dev/null +++ b/forgetest/tests/test_cooling_order.py @@ -0,0 +1,19 @@ +"""cooling.aa-offset-calibrate is registered before the heater tools, so +a queue runs it first: a heater trial leaves a warm slug circulating past +the coolant sensors for minutes, and the tool's edges read it as +disagreement.""" +import unittest + +from forgetest import catalog + + +class CoolingOrderTests(unittest.TestCase): + def test_aa_offset_precedes_flow_verify(self): + reg = catalog.load_suite() + tests = catalog.all_tests(reg) + order = catalog.order_by_requires(tests, [t.id for t in tests]) + self.assertLess(order.index("cooling.aa-offset-calibrate"), order.index("cooling.flow-verify")) + + +if __name__ == "__main__": + unittest.main()