From 8429f757550023b3f1021e97789205ce5e1f6b59 Mon Sep 17 00:00:00 2001 From: claudeMB Date: Fri, 18 Sep 2026 18:30:23 +0200 Subject: [PATCH] tests: make the dash-loop tests actually run (CI red since 15 Sep) CI has failed on main for three days, every run the same way: ModuleNotFoundError: No module named 'pytest' Ran 115 tests ... FAILED (errors=1) test_dash_loop.py imported pytest for two `@pytest.mark.skipif` decorators. pytest is not installed on the runner, and the workflow runs `python3 -m unittest discover`, so the import blew up before anything ran. Worse than a red tick: these are bare `def test_*` functions, which unittest never collects. Even with pytest installed they would not have run under the command CI actually uses. The dash-loop regressions from #57/#59/#61 - a stalled poll killing the scheduler, and F's own timeout being overwritten by the caller's options - have never once executed in CI. The only thing this file ever reported was its own import error. Converted to a unittest.TestCase with unittest.skipUnless(node), and the pytest `tmp_path` fixture replaced with tempfile. Both tests now run and pass. before Ran 115 tests FAILED (errors=1) dash-loop: never executed after Ran 121 tests OK dash-loop: 2 running A permanently red CI is as useless as a green one that checks nothing: nobody can see a real break in it. Three PRs merged into that state. Co-Authored-By: Claude Opus 5 --- tests/test_dash_loop.py | 80 ++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/tests/test_dash_loop.py b/tests/test_dash_loop.py index b2d22eb..420e628 100644 --- a/tests/test_dash_loop.py +++ b/tests/test_dash_loop.py @@ -3,7 +3,13 @@ fn() with no bound, so one stalled voice request stopped that loop for good, which setInterval never did. The scheduler is extracted from webchat.py and run under node with fake timers; skipped when node is not installed.""" -import pathlib, re, shutil, subprocess, pytest +import json +import pathlib +import re +import shutil +import subprocess +import tempfile +import unittest SRC = pathlib.Path(__file__).resolve().parents[1] / "carwatch" / "webchat.py" @@ -71,30 +77,48 @@ def _scheduler_js(): })(); """ -@pytest.mark.skipif(shutil.which("node") is None, reason="node not installed") -def test_stalled_poll_does_not_stop_the_loop(tmp_path): - js = tmp_path / "loop.js" - js.write_text(HARNESS % _scheduler_js()) - out = subprocess.run(["node", str(js)], capture_output=True, text=True, timeout=30) - assert out.returncode == 0, out.stderr - r = __import__("json").loads(out.stdout.strip().splitlines()[-1]) - assert r["c1"] == 1, r - assert r["c2"] == 1, "a second poll started while the first was still pending" - assert r["c3"] == 2, "the loop stopped after a poll that never settled" - assert r["settled"] == 1, "the deadline did not abort the stalled poll" - assert r["order"] == ["call1", "settle1", "call2"], f"second run started before the first settled: {r['order']}" - - -@pytest.mark.skipif(shutil.which("node") is None, reason="node not installed") -def test_fetch_helper_abort_reaches_the_request(tmp_path): - """codexmb, #61: Object.assign({signal}, o) let the caller's options overwrite - the helper's own signal, so its timeout never aborted the request.""" - js = tmp_path / "f.js" - js.write_text(F_HARNESS % _fetch_helper_js()) - out = subprocess.run(["node", str(js)], capture_output=True, text=True, timeout=30) - assert out.returncode == 0, out.stderr - r = __import__("json").loads(out.stdout.strip().splitlines()[-1]) - assert r["ownTimeoutAborts"], "F's own timeout did not abort the request (no outer signal)" - assert r["undefinedOuterOk"], "a caller passing {signal: undefined} disabled F's timeout" - assert r["outerAborts"], "an aborted outer signal did not abort the request" - assert r["distinct"], "F passed the outer signal itself instead of its linked one" +def _run_node(harness: str, body: str) -> dict: + """Write the harness to a temp file, run it under node, return its JSON.""" + with tempfile.TemporaryDirectory() as td: + js = pathlib.Path(td) / "t.js" + js.write_text(harness % body) + out = subprocess.run(["node", str(js)], capture_output=True, + text=True, timeout=30) + assert out.returncode == 0, out.stderr + return json.loads(out.stdout.strip().splitlines()[-1]) + + +@unittest.skipUnless(shutil.which("node"), "node not installed") +class DashLoop(unittest.TestCase): + """These ran under pytest only, and pytest is not installed in CI, so the + import error was the ONLY thing CI ever reported from this file. As + unittest they actually execute.""" + + def test_stalled_poll_does_not_stop_the_loop(self): + r = _run_node(HARNESS, _scheduler_js()) + self.assertEqual(r["c1"], 1, r) + self.assertEqual(r["c2"], 1, + "a second poll started while the first was still pending") + self.assertEqual(r["c3"], 2, + "the loop stopped after a poll that never settled") + self.assertEqual(r["settled"], 1, + "the deadline did not abort the stalled poll") + self.assertEqual(r["order"], ["call1", "settle1", "call2"], + f"second run started before the first settled: {r['order']}") + + def test_fetch_helper_abort_reaches_the_request(self): + """codexmb, #61: Object.assign({signal}, o) let the caller's options + overwrite the helper's own signal, so its timeout never aborted.""" + r = _run_node(F_HARNESS, _fetch_helper_js()) + self.assertTrue(r["ownTimeoutAborts"], + "F's own timeout did not abort the request (no outer signal)") + self.assertTrue(r["undefinedOuterOk"], + "a caller passing {signal: undefined} disabled F's timeout") + self.assertTrue(r["outerAborts"], + "an aborted outer signal did not abort the request") + self.assertTrue(r["distinct"], + "F passed the outer signal itself instead of its linked one") + + +if __name__ == "__main__": + unittest.main()