From 779b89a804739280e5c84b3032d504d87259ef46 Mon Sep 17 00:00:00 2001 From: M Date: Mon, 31 Aug 2026 14:01:07 +0200 Subject: [PATCH] Tests for what an operand's draw actually fixes Widening the fence used containment for both kinds of hold, which is right for a level a path reads and wrong for a sibling a calc reads. A path may read into anything the level contains; an operand's draw fixes only the value it renders. So containment rejects a reference to a sibling the operand never renders, which loads fine today and cannot disagree, and misses a reference reaching what the operand renders through, which disagreed in 20 of 40 draws. Also pins the repeated reference arm, the one kind that reaches the repeat check by passing the per-arm checks rather than falling through them. Co-Authored-By: Claude Opus 5 --- bound_test.go | 35 +++++++++++++++++++++++++++++------ edge_test.go | 6 ++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/bound_test.go b/bound_test.go index be92046..0739af9 100644 --- a/bound_test.go +++ b/bound_test.go @@ -158,12 +158,35 @@ func TestACalcOperandIsHeldAgainstEveryRoute(t *testing.T) { t.Errorf("%s: New = %v, want the second route to the operand rejected", name, err) } } - // The spellings that read the one draw stay legal: the operand itself, and a - // bare token naming it. - if _, err := New([]string{writeData(t, map[string]string{ - "ok": `{"format":"{net} x 2 = {calc(net * 2, 2)}","net":["10.00","20.00"]}`, - })}); err != nil { - t.Errorf("New = %v, want a format that only reads the operand accepted", err) + // A reference reaching what the operand renders *through* is a second route + // too, however the operand comes by its value. + _, err := New([]string{writeData(t, map[string]string{ + "common": `["1","2"]`, + "cat": `{"format":"{calc(net * 2, 2)} {..common}","net":{"format":"{..common}"}}`, + })}) + if err == nil || !strings.Contains(err.Error(), "a {calc()} also reads") { + t.Errorf("New = %v, want the reference to what the operand renders rejected", err) + } + + accepted := map[string]map[string]string{ + // The spellings that read the one draw: the operand itself, and a bare token. + "only the operand and a bare token": { + "cat": `{"format":"{net} x 2 = {calc(net * 2, 2)}","net":["10.00","20.00"]}`, + }, + // An operand's draw fixes the value it renders and nothing else, so a sibling + // its format never renders cannot disagree with it. A path head differs: a + // path may read into anything the level contains, which is why that half of + // the fence still covers containment. + "a reference to a sibling the operand never renders": { + "cat": `{"format":"{calc(net * 2, 2)} {unit}",` + + `"net":{"format":"{v}","v":["1","2"],"spare":["kg","lb"]},` + + `"unit":{"format":"{..cat.net.spare}"}}`, + }, + } + for name, files := range accepted { + if _, err := New([]string{writeData(t, files)}); err != nil { + t.Errorf("%s: New = %v, want it accepted", name, err) + } } } diff --git a/edge_test.go b/edge_test.go index 144fe36..806416c 100644 --- a/edge_test.go +++ b/edge_test.go @@ -137,6 +137,12 @@ func TestNewErrors(t *testing.T) { map[string]string{"a": `{"format":"{p.v|p.v}","p":{"format":"{v}","v":["1"]}}`}, `arm "p.v" is repeated`, }, + // A reference arm is the only kind that reaches the repeat check by passing + // the per-arm checks rather than falling through them. + "repeated reference arm": { + map[string]string{"a": `["x"]`, "b": `{"format":"{..a|..a}"}`}, + `arm "..a" is repeated`, + }, // An arm that is broken on its own terms is reported as that, not as a // repeat: the repeat is a consequence of the real mistake. "repeated arm with no path": {