From a2b4b4290b06c6a3e3ba0d3dba00ca20e7a63f78 Mon Sep 17 00:00:00 2001 From: PJ Date: Sun, 26 Apr 2026 15:38:17 +0700 Subject: [PATCH] test(verifier): expose PredicateError latching across steps The runner logs PredicateError once per step. The current implementation latches the first error per thunk, so the log freezes on step 1 forever even when later steps would observe different errors. This test fails today and locks in the contract: PredicateError must reflect the most recent step. --- internal/verifier/verifier_test.go | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/internal/verifier/verifier_test.go b/internal/verifier/verifier_test.go index f86986c..c441375 100644 --- a/internal/verifier/verifier_test.go +++ b/internal/verifier/verifier_test.go @@ -258,3 +258,35 @@ func TestLoad_AcceptsSpecWithoutPropertiesOrActions(t *testing.T) { t.Errorf("expected ErrNoAction, got %v", err) } } + +// PredicateError must reflect the most recent step's predicate result, not a +// latched first-step error. The runner logs PredicateError once per step; if it +// stays pinned to step 1 forever, downstream debugging looks frozen even though +// the underlying state is changing. +func TestPredicateError_ReflectsCurrentStepNotFirstStep(t *testing.T) { + const spec = ` +globalThis.counter = __sanderling__.extract(state => state.snapshots["count"]); +globalThis.properties = { + reportsCounter: __sanderling__.always(() => { throw new Error("count=" + counter.current); }), +}; +` + verifier := newVerifier(t) + mustLoad(t, verifier, spec) + + for step := 1; step <= 3; step++ { + raw := json.RawMessage([]byte{'"', byte('0' + step), '"'}) + if err := verifier.PushSnapshot(SnapshotInput{Snapshots: Snapshots{"count": raw}}); err != nil { + t.Fatal(err) + } + _ = verifier.EvaluateProperties() + + got := verifier.PredicateError("reportsCounter") + if got == nil { + t.Fatalf("step %d: PredicateError = nil, want non-nil", step) + } + want := "count=" + string(rune('0'+step)) + if !strings.Contains(got.Error(), want) { + t.Errorf("step %d: PredicateError = %q, want to contain %q", step, got.Error(), want) + } + } +}