Files
fejkdata/render_test.go
T

144 lines
4.2 KiB
Go

package fejkdata
import (
"encoding/json"
"go/ast"
"reflect"
"slices"
"testing"
)
func TestRecursionHasNoDepthLimit(t *testing.T) {
// Build {format:{a}, a:[{format:{a}, a:[ ... "deep" ]]}} 50 levels deep.
tmpl := `"deep"`
for i := 0; i < 50; i++ {
tmpl = `{"format":"{a}","a":` + tmpl + `}`
}
if got := mustRender(t, engine(1), tmpl); got != "deep" {
t.Fatalf("deep recursion = %q, want deep", got)
}
}
// TestGrowIsALowerBound pins the property that makes the pre-sized render buffer
// safe: grow must never exceed what expand emits. render multiplies it by repeat,
// so an over-estimate would amplify up to a million-fold.
func TestGrowIsALowerBound(t *testing.T) {
f := engine(3)
for _, format := range []string{
"",
"plain literal",
"{digits(2)}-{int(1,9)}{int(1,9)}-{upper(2)}-{lower(2)}",
"01Aa# literal",
"{{}} {{{x}}}",
"Ö dag åäö 日本語",
"{x}{x}{x}",
"{hex(8)}-{int(10,99)}-{nanoid(5)}",
"9{d}{luhn()}",
"{a|b} and {a|b}",
} {
src := `{"format":` + quote(format) + `,"x":"1","a":"A","b":"B","d":"012345678901234"}`
tmpl, ok := compiled(t, src).(*template)
if !ok {
t.Fatalf("format %q did not compile to a template", format)
}
for i := 0; i < 50; i++ {
var draws renderDraws
if got := len(expand(f.rand, tmpl, renderScope{draws: &draws})); got < tmpl.compiled.grow {
t.Errorf("format %q: expand emitted %d bytes, below grow %d", format, got, tmpl.compiled.grow)
}
}
}
}
func quote(s string) string {
b, err := json.Marshal(s)
if err != nil {
panic(err)
}
return string(b)
}
type unhandledNode struct{}
func (*unhandledNode) isNode() {}
// nodeSwitchSkips is the kinds a switch is never handed: its callers step past them first.
var nodeSwitchSkips = map[string][]string{
"columnItems": {"folder", "table", "tableRow"},
"prove": {"folder"},
"render": {"folder"},
"stepInto": {"choice", "table", "tableRow"},
}
func TestNodeSwitchesHandleEveryKind(t *testing.T) {
f := newGenerator(t, writeFiles(t, map[string]string{
"r.json": `{"format":"{name}","rows":"r.tsv","key":"code"}`,
"r.tsv": "code\tname\n01\t{digits(2)}\n02\tB\n",
}))
tbl := f.root.children["r"].(*table)
samples := map[string]node{}
for _, n := range []node{
compiled(t, `["a","b"]`),
compiled(t, `{"format":"{x}","x":"1"}`),
&f.root,
&nullItem{},
tbl,
tbl.formatTemplate.fields["name"],
tbl.rowNode,
} {
samples[reflect.TypeOf(n).Elem().Name()] = n
}
var kinds []string
_, files := sourceFiles(t)
for _, file := range files {
for _, decl := range file.Decls {
if d, isFunc := decl.(*ast.FuncDecl); isFunc && d.Name.Name == "isNode" {
kinds = append(kinds, receiverType(d.Recv))
}
}
}
slices.Sort(kinds)
sampled := make([]string, 0, len(samples))
for kind := range samples {
sampled = append(sampled, kind)
}
slices.Sort(sampled)
if !slices.Equal(kinds, sampled) {
t.Fatalf("node kinds %v, samples %v; give every kind a sample here", kinds, sampled)
}
for name, call := range map[string]func(node){
"columnItems": func(n node) { columnItems(n) },
"contained": func(n node) { contained(n) },
"paths": func(n node) { paths(n) },
"prove": func(n node) { (&valueProof{}).prove(n) },
"render": func(n node) {
var draws renderDraws
render(engine(1).rand, n, renderScope{draws: &draws, row: renderedRow{tbl, 0}})
},
"renderEdges": func(n node) { renderEdges(n) },
"stepInto": func(n node) { _, _ = stepInto(n, "x") },
} {
mustPanic(t, name+" on an unhandled node", func() { call(&unhandledNode{}) })
for _, kind := range kinds {
if slices.Contains(nodeSwitchSkips[name], kind) {
mustPanic(t, name+" on a skipped "+kind, func() { call(samples[kind]) })
continue
}
func() {
defer func() {
if r := recover(); r != nil {
t.Errorf("%s on %s: %v", name, kind, r)
}
}()
call(samples[kind])
}()
}
}
for kind, n := range samples {
recurses := kind != "nullItem" && !slices.Contains(nodeSwitchSkips["render"], kind)
if edges := renderEdges(n); recurses != (len(edges) > 0) {
t.Errorf("%s: render recurses into it %v, renderEdges lists %d edges; a kind render recurses into has edges, and no other", kind, recurses, len(edges))
}
}
}