test(byllm): a ratchet and a written contract for the test suite (#9002 phase 0) - #9004
test(byllm): a ratchet and a written contract for the test suite (#9002 phase 0)#9004MalithaPrabhashana wants to merge 1 commit into
Conversation
…eci-labs#9002 phase 0) The byLLM suite is 10,361 lines holding 242 test blocks, and it grows by roughly one fixture per fix PR. The two consolidation PRs merged on 2026-09-04 (jaseci-labs#8936, jaseci-labs#8938) removed 280 lines; jaseci-labs#8479 added 1,046 back the same day on the old seams, deleting the file jaseci-labs#8936 had just migrated. Net for the effort so far is +766 lines. Consolidation alone does not hold. This adds the piece that does. test_guard.jac is a ratchet, not a wall. Every number in it is today's measured count: ceilings on the things being retired (fixture programs, MockLLM constructions, SimpleNamespace fakes, stdout scraping) and floors on the things being built (assert count, annex adoption). Cleaning up means editing a number down in the same PR, which is the intended direction and needs no explanation. Adding one of the retired patterns fails here, with the file, the delta, and what to use instead. The assert floor is the one that is not obvious. 66 of the suite's 842 assertions live inside fixture programs, guarded by only 10 sentinel greps, so deleting a fixture can drop a dozen checks while the test count does not move. A deleted-test to surviving-row mapping cannot see that; a total assert count can. TESTING.md is the contract the guard enforces: fake the model at model_call_* so make_model_params and parse_response are real code and the test can assert on the request; a fixture is a program under test with no model, entry block, prints or asserts; assert on values, never on stdout. The fixture section documents five things verified against the compiler beforehand, each of which costs an afternoon if missed: `by llm()` resolves its model global at call time, so a test can own the fake, but the fixture needs `glob llm: any = None;` or the assignment fails E1001; graph fixtures need a static import of their archetypes, because `root ++> g.Desk()` on a module from jac_import fails E1097; routing replies are generated handles, not indices; `Jac.jac_import` returns a cached module rather than re-executing it, which contradicts the comment above test_byllm.jac:1618; and per-test subgraphs stay isolated only if you spawn on your own node rather than from root. No existing test or fixture changes. The suite goes from 199 passed, 1 skipped to 207 passed, 1 skipped.
|
One detail a reviewer will probably catch, so stating it up front: the ledger says asserts in test files go 776 to 782, but the floor in That is deliberate. |
|
Closing by decision. We will manage regrowth through review rather than a ratchet, and TESTING.md moves to the final conversion PR, where it can document the finished shape instead of the intended one. Nothing here is lost. The verified mechanics from its fixture section (call-time resolution of the model global, the |
Phase 0 of #9002. No existing test or fixture changes; this only adds a ratchet and the
contract it enforces.
Why this first
Correction to an earlier draft. I first cited #8479 (+1,046 lines, merged 2026-09-04)
as inflow that ignored the new annex. It was opened on 2026-08-22, twelve days before the
annex existed, so its author could not have used it. The example was unfair and I have
withdrawn it. The evidence that matters is the live queue.
Nine open PRs touch this directory right now:
MockLLM(SimpleNamespace(About 2,600 lines, 4 new fixtures, 15
SimpleNamespacefakes and 3MockLLMconstructions are queued to land, and only two of the nine use the annex. Most predate
it; nobody is ignoring a rule. But nothing tells an author at rebase time that the rule
changed, so they merge as written and #8930's consolidation restarts from behind. This
guard tells them, with the file, the delta and the fix. It is 499 lines that change no
behaviour, and every later PR in #9002 depends on it holding.
Scope note. This PR is the gate only. The shape it enforces is not invented here: it
is the one
jaclang/scale/tests/serveralready uses (322 tests, zero fixtures, zerostdout scraping), and the consolidation it protects is the one #8599 already did for the
main test corpus (+5,302/-8,992, 115 files, "no assertion was removed"). #8599 touched
zero byLLM files; byLLM is the suite it missed.
test_guard.jacA ratchet, not a wall. Every number is today's measured count, not a target.
Ceilings, on the patterns being retired. These may only fall:
MockLLMMockLLM(in test filesSimpleNamespace(in test filesFloors, on what is being built. These may only rise:
support_testsLowering a ceiling or raising a floor while cleaning up is a one-line edit in the same
PR and needs no justification; that is the intended direction. Moving one the wrong way
fails with the file, the delta and the fix:
The assert floor is the one that is not obvious
66 of the suite's 842 assertions live inside fixture programs, guarded by only 10
sentinel greps:
logging_stream_reactstream_retryconversation_paramlogging_stream_no_toolsstreaming_conversationDelete
conversation_param.jac, write one test that checks the return value, and thetest count does not move while 12 checks disappear. A deleted-test to surviving-row
mapping cannot see that. A total assert count can, which is why it is a gate rather
than a convention.
TESTING.mdThe contract the guard enforces, in one page: fake the model at
model_call_*somake_model_paramsandparse_responseare real code and the test can assert on therequest; a fixture is a program under test with no model, entry block, prints or
asserts; assert on values, never on stdout.
The fixture section documents five things I verified against the compiler before
writing any of this, each of which costs an afternoon if you meet it in review instead:
by llm()resolves its model global at call time, so a test can own the fake and thefixture stays pure. The fixture needs
glob llm: any = None;; the bare form infersNoneTypeand the test's assignment failsjac checkwith E1001.load_fixture()returnsthe module as
any, soroot ++> g.Desk()fails withE1097: Connection right operand must be a node instance, and CI runsJAC_TEST_STRICT=1. This invalidated my own first draft of the conversion recipe.list[enum[RouteChoice]]and members come from the candidate set: twoAgentsiblings give
Agent_alpha/Agent_beta, a lone one givesAgent. An index retriesthree times and then raises
OutputConversionError.Jac.jac_importreturns a cached module and does not re-execute it. State carriesover between calls. This contradicts the comment above
test_byllm.jac:1618, whichsays the module is imported once per test so mock outputs are consumed exactly once.
Those two tests pass only because they use disjoint
llmobjects.rootaccumulates across tests in a file. Per-test subgraphs stay isolated as long asyou spawn on the node you just created rather than walking from
root.Validation
Each rule was checked by introducing the violation, confirming red, and restoring:
fixtures/zz_probe.jacMockLLM(to a test fileSimpleNamespace(fakeimport from support_testsSuite, this branch,
JAC_TEST_JOBS=2 JAC_TEST_STRICT=1:jac fmt --check --lintfixandjac checkclean on both files.One observation for the record
On a single run, taken while the compiler itself was recompiling after a branch switch,
two pre-existing tests errored inside
JacProgram().compile():async by llm() codegen emits await Jac.acall_llm not call_llmandasync by llm() end-to-end with mocked acall_llm, both intest_byllm.jac. It has notreproduced since: seven subsequent runs are clean (once serial, three times parallel on
the full suite, four times on
test_byllm.jacplus the guard), and a trivial eight-testfile in place of the guard does not trigger it. Neither file here compiles anything, so
this looks like a compiler pass-schedule race that surfaces when those two tests build
their own
JacProgramwhile the compiler is being rebuilt, rather than anything this PRintroduces. Flagging it rather than leaving it in a log; happy to open a separate issue
if it recurs.
Ledger
No test deleted, no assert dropped. The eight added blocks and six added asserts are the
guard itself.
Next
PR 1 in #9002 is the routing cluster, the hard case: five fixtures sharing 47% of their
lines verbatim become one pure graph program, using the mechanics verified above. It
will be the first PR to lower a ceiling in
test_guard.jacrather than only pass it.