diff --git a/drawfence_test.go b/drawfence_test.go index d4e0eb9..6f9a0aa 100644 --- a/drawfence_test.go +++ b/drawfence_test.go @@ -146,3 +146,117 @@ func renderRecordRoot(s *session, t *template, columns []Column) { } renderRecord(s, t, columns, sc) } + +func TestReplayPairsFindsWhatAWholeReplayFinds(t *testing.T) { + corpus := fenceCorpus(t) + roots, _ := fenceRoots(t, corpus) + shipped, err := New() + if err != nil { + t.Fatalf("New() = %v", err) + } + _ = walkNodes(shipped.categories, func(path string, n node) error { + if tm, ok := n.(*template); ok { + roots = append(roots, fenceRoot{path, tm}) + } + return nil + }) + for _, root := range roots { + wantPairsFindAll(t, root.label, renderDraws(root.t).reads) + if root.t.isRecord && len(root.t.fields) > 0 { + wantPairsFindAll(t, root.label+" as a record", columnDraws(root.t, sortedNames(root.t.fields)).reads) + } + } +} + +func TestReplayPairsFindsWhatAWholeReplayOfSelectedRowsFinds(t *testing.T) { + f, err := New(WithDataPath(writeFiles(t, siblings()))) + if err != nil { + t.Fatalf("New = %v", err) + } + var reads []pathRead + _ = walkNodes(f.categories, func(_ string, n node) error { + tb, isTable := n.(*table) + if !isTable || tb.keyIndex < 0 || tb.familyRoot().path != "region" { + return nil + } + for r := 0; r < tb.rowCount(); r++ { + inline, err := f.NewTemplate(`"{/` + tb.selectorSpelling(r) + `}"`) + if err != nil { + t.Fatalf("NewTemplate(%s) = %v", tb.selectorSpelling(r), err) + } + root, isTemplate := inline.n.(*template) + if !isTemplate { + t.Fatalf("NewTemplate(%s) compiled to %T", tb.selectorSpelling(r), inline.n) + } + reads = append(reads, renderDraws(root).reads...) + } + return nil + }) + conflicts := 0 + for _, a := range reads { + for _, b := range reads { + for _, c := range reads { + triple := []pathRead{a, b, c} + wantPairsFindAll(t, a.a.spelling+", "+b.a.spelling+", "+c.a.spelling, triple) + if replayPairs(triple) != nil { + conflicts++ + } + } + } + } + if conflicts == 0 { + t.Error("no triple of selected rows conflicts, so the comparison proved nothing") + } +} + +func wantPairsFindAll(t *testing.T, label string, reads []pathRead) { + t.Helper() + var tabled []pathRead + for _, r := range reads { + if r.tr != nil { + tabled = append(tabled, r) + } + } + pairs := replayPairs(reads) + var whole error + eachRenderSet(tabled, func(set []pathRead) { + var d pinSet + for _, r := range set { + if err := r.tr.replay(&d); err != nil && whole == nil { + whole = err + } + } + }) + if (pairs == nil) != (whole == nil) { + t.Errorf("%s: replayPairs = %v, a whole replay of every read set rendering together = %v", label, pairs, whole) + } +} + +// eachRenderSet calls fn with every maximal set of reads that render together, by Bron–Kerbosch. +func eachRenderSet(reads []pathRead, fn func([]pathRead)) { + together := func(a, b pathRead) bool { return a.at.group == b.at.group && !alternatives(a.at, b.at) } + var grow func(set, candidates, excluded []pathRead) + grow = func(set, candidates, excluded []pathRead) { + if len(candidates) == 0 && len(excluded) == 0 { + fn(set) + return + } + for len(candidates) > 0 { + r := candidates[0] + var nextC, nextX []pathRead + for _, c := range candidates[1:] { + if together(r, c) { + nextC = append(nextC, c) + } + } + for _, x := range excluded { + if together(r, x) { + nextX = append(nextX, x) + } + } + grow(append(set[:len(set):len(set)], r), nextC, nextX) + candidates, excluded = candidates[1:], append(excluded, r) + } + } + grow(nil, reads, nil) +} diff --git a/todo.md b/todo.md index 4fca912..ffa2a4f 100644 --- a/todo.md +++ b/todo.md @@ -23,9 +23,6 @@ Shape 5.8, Self-sufficiency 5.7), every seat capped by Locality, all nine naming This round, in order: -- Hold `replayPairs`' claim that pairs find every conflict a full replay would to a - test replaying every read set whole, over the fixtures and the shipped data: six - seats could only take the comment's proof on trust. - Key `drawWalk`'s visits by the pins, not `rowsKey`'s `%p` strings, and make leaving `wholeTable` out of the key fail loudly where `checkOwnFamily` has not run: the walk is sound only because another fence ran first, and nothing at the walk enforces it.