Tests for a path holding its draw at every level
This commit is contained in:
@@ -82,6 +82,15 @@ func BenchmarkBoundDeep(b *testing.B) {
|
|||||||
benchPath(b, dir, "addr")
|
benchPath(b, dir, "addr")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BenchmarkBoundWide reads ten paths from one row: the point where the maps an
|
||||||
|
// expansion holds outgrow a single bucket.
|
||||||
|
func BenchmarkBoundWide(b *testing.B) {
|
||||||
|
dir := tmpData(b, "row", `{"format":"{r.a}{r.b}{r.c}{r.d}{r.e}{r.f}{r.g}{r.h}{r.i}{r.j}","r":[
|
||||||
|
{"format":"x","a":"1","b":"2","c":"3","d":"4","e":"5","f":"6","g":"7","h":"8","i":"9","j":"0"},
|
||||||
|
{"format":"y","a":"A","b":"B","c":"C","d":"D","e":"E","f":"F","g":"G","h":"H","i":"I","j":"J"}]}`)
|
||||||
|
benchPath(b, dir, "row")
|
||||||
|
}
|
||||||
|
|
||||||
// BenchmarkNew measures load+compile+validate of the whole shipped tree.
|
// BenchmarkNew measures load+compile+validate of the whole shipped tree.
|
||||||
func BenchmarkNew(b *testing.B) {
|
func BenchmarkNew(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|||||||
@@ -202,6 +202,92 @@ func TestDifferentTailsUnderOneHeadShareTheRow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deepPlaces nests the correlated facts a level down: the row holds an addr, and
|
||||||
|
// the addr holds the city and the zip that belongs to it.
|
||||||
|
const deepPlaces = `{"format":"%s","p":[{"format":"{addr}","addr":[
|
||||||
|
{"format":"{city}","city":"Stockholm","zip":"11111"},
|
||||||
|
{"format":"{city}","city":"Kiruna","zip":"98100"}]}]}`
|
||||||
|
|
||||||
|
func TestPathHoldsItsDrawAtEveryLevel(t *testing.T) {
|
||||||
|
// A choice below the head is drawn once too, so two paths sharing a prefix
|
||||||
|
// share every level of it — not just the row they started from.
|
||||||
|
f := engine(11)
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for i := 0; i < 500; i++ {
|
||||||
|
got := mustRender(t, f, strings.Replace(deepPlaces, "%s", "{p.addr.city}/{p.addr.zip}", 1))
|
||||||
|
if got != "Stockholm/11111" && got != "Kiruna/98100" {
|
||||||
|
t.Fatalf("draw %d = %q, want a city and the zip that belongs to it", i, got)
|
||||||
|
}
|
||||||
|
seen[got] = true
|
||||||
|
}
|
||||||
|
if len(seen) < 2 {
|
||||||
|
t.Fatalf("500 draws produced only %v, want both", seen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathHoldsItsDrawThreeLevelsDown(t *testing.T) {
|
||||||
|
// The rule does not run out at two: every level a path passes through is held.
|
||||||
|
f := engine(12)
|
||||||
|
tmpl := `{"format":"{p.geo.town.name}/{p.geo.town.zip} {p.geo.region}","p":[{"format":"{geo}","geo":[
|
||||||
|
{"format":"{town}","region":"Norrbotten","town":[
|
||||||
|
{"format":"{name}","name":"Kiruna","zip":"98100"},
|
||||||
|
{"format":"{name}","name":"Luleå","zip":"97200"}]},
|
||||||
|
{"format":"{town}","region":"Skåne","town":[
|
||||||
|
{"format":"{name}","name":"Malmö","zip":"21100"},
|
||||||
|
{"format":"{name}","name":"Lund","zip":"22100"}]}]}]}`
|
||||||
|
want := map[string]bool{
|
||||||
|
"Kiruna/98100 Norrbotten": true, "Luleå/97200 Norrbotten": true,
|
||||||
|
"Malmö/21100 Skåne": true, "Lund/22100 Skåne": true,
|
||||||
|
}
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for i := 0; i < 600; i++ {
|
||||||
|
got := mustRender(t, f, tmpl)
|
||||||
|
if !want[got] {
|
||||||
|
t.Fatalf("draw %d = %q, want a town with its own zip and region", i, got)
|
||||||
|
}
|
||||||
|
seen[got] = true
|
||||||
|
}
|
||||||
|
if len(seen) != len(want) {
|
||||||
|
t.Fatalf("600 draws produced %v, want all four towns", seen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathWalksVariantsOfDifferentShape(t *testing.T) {
|
||||||
|
// One variant holds addr as a plain template where another holds a choice.
|
||||||
|
// Both carry the path, so both must walk — the walk reads what it drew, never
|
||||||
|
// assuming a shape.
|
||||||
|
f := engine(13)
|
||||||
|
tmpl := `{"format":"{p.addr.city}/{p.addr.zip}","p":[
|
||||||
|
{"format":"{addr}","addr":{"format":"{city}","city":"Kiruna","zip":"98100"}},
|
||||||
|
{"format":"{addr}","addr":[
|
||||||
|
{"format":"{city}","city":"Malmö","zip":"21100"},
|
||||||
|
{"format":"{city}","city":"Lund","zip":"22100"}]}]}`
|
||||||
|
for i := 0; i < 400; i++ {
|
||||||
|
got := mustRender(t, f, tmpl)
|
||||||
|
if got != "Kiruna/98100" && got != "Malmö/21100" && got != "Lund/22100" {
|
||||||
|
t.Fatalf("draw %d = %q, want a city with its own zip", i, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeepPathsUnderOneHeadStayIndependentWhereTheyDiverge(t *testing.T) {
|
||||||
|
// Two paths share only what they actually share: a common prefix is one draw,
|
||||||
|
// and each keeps its own draw past the point they part.
|
||||||
|
f := engine(14)
|
||||||
|
tmpl := `{"format":"{p.a.v}{p.b.v}","p":[{"format":"x",
|
||||||
|
"a":[{"format":"{v}","v":"1"},{"format":"{v}","v":"2"}],
|
||||||
|
"b":[{"format":"{v}","v":"1"},{"format":"{v}","v":"2"}]}]}`
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for i := 0; i < 400; i++ {
|
||||||
|
seen[mustRender(t, f, tmpl)] = true
|
||||||
|
}
|
||||||
|
for _, want := range []string{"11", "12", "21", "22"} {
|
||||||
|
if !seen[want] {
|
||||||
|
t.Fatalf("400 draws produced %v, want every combination including %q", seen, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCycleThroughAPathTokenIsRejected(t *testing.T) {
|
func TestCycleThroughAPathTokenIsRejected(t *testing.T) {
|
||||||
// A path token is a render edge like any other, so a cycle routed through one
|
// A path token is a render edge like any other, so a cycle routed through one
|
||||||
// must be caught at New. Reaching render would be fatal: the recursion never
|
// must be caught at New. Reaching render would be fatal: the recursion never
|
||||||
|
|||||||
Reference in New Issue
Block a user