Trace the row a render stands in, skip a fieldless record, and cover a cell reading through a template
This commit is contained in:
+31
-30
@@ -5,16 +5,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// fenceCorpus layers over the shipped set what it lacks: draw groups, a repeat beside one, token
|
|
||||||
// cells selecting a row, a table read whole and records reading a family.
|
|
||||||
func fenceCorpus(t *testing.T) *Generator {
|
func fenceCorpus(t *testing.T) *Generator {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
dir := writeFiles(t, with(geo(), map[string]string{
|
dir := writeFiles(t, with(geo(), map[string]string{
|
||||||
"addr.json": `{"format":"{a} {b} {c}","a":"{/locality.name}, {/municipality.name}","b":{"format":"{/region[12].name} {/place}","drawGroup":"g"},"c":{"format":"{/place.zip} ","repeat":2}}`,
|
"addr.json": `{"format":"{a} {b} {c}","a":"{/locality.name}, {/municipality.name}","b":{"format":"{/region[12].name} {/place}","drawGroup":"g"},"c":{"format":"{/place.zip} ","repeat":2}}`,
|
||||||
"place.json": `{"format":"{zip} {name} {tag}","rows":"place.tsv","key":"name"}`,
|
"place.json": `{"format":"{zip} {name} {tag}","rows":"place.tsv","key":"name"}`,
|
||||||
"place.tsv": "name\tzip\ttag\nStockholm\t1{digits(2)} {digits(2)}\t{/w}\nTranås\t573 {digits(2)}\t{/region[12].name}\n",
|
"place.tsv": "name\tzip\ttag\nStockholm\t1{digits(2)} {digits(2)}\t{/x}\nTranås\t573 {digits(2)}\t{/region[12].name}\n",
|
||||||
"rec.json": `{"format":"","l":"{/locality.name}","m":"{/municipality.name}","t":"{/place[Stockholm].tag}"}`,
|
"rec.json": `{"format":"","l":"{/locality.name}","m":"{/municipality.name}","t":"{/place[Stockholm].tag}"}`,
|
||||||
"w.json": `["x","y"]`,
|
"w.json": `["x","y"]`,
|
||||||
|
"x.json": `"{/w}"`,
|
||||||
}))
|
}))
|
||||||
f, err := New(WithDataPath(dir), WithSeed(1))
|
f, err := New(WithDataPath(dir), WithSeed(1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -28,12 +27,16 @@ type fenceRoot struct {
|
|||||||
t *template
|
t *template
|
||||||
}
|
}
|
||||||
|
|
||||||
func fenceRoots(t *testing.T, f *Generator) []fenceRoot {
|
func fenceRoots(t *testing.T, f *Generator) ([]fenceRoot, map[string]*table) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var roots []fenceRoot
|
var roots []fenceRoot
|
||||||
|
tables := map[string]*table{}
|
||||||
_ = walkNodes(f.categories, func(path string, n node) error {
|
_ = walkNodes(f.categories, func(path string, n node) error {
|
||||||
if tm, ok := n.(*template); ok {
|
switch n := n.(type) {
|
||||||
roots = append(roots, fenceRoot{path, tm})
|
case *template:
|
||||||
|
roots = append(roots, fenceRoot{path, n})
|
||||||
|
case *table:
|
||||||
|
tables[n.path] = n
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -51,35 +54,39 @@ func fenceRoots(t *testing.T, f *Generator) []fenceRoot {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return append(roots, fenceRoot{"inline record", record.template})
|
return append(roots, fenceRoot{"inline record", record.template}), tables
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEveryReadARenderMakesIsGathered(t *testing.T) {
|
func TestEveryReadARenderMakesIsGathered(t *testing.T) {
|
||||||
f := fenceCorpus(t)
|
f := fenceCorpus(t)
|
||||||
for _, root := range fenceRoots(t, f) {
|
roots, tables := fenceRoots(t, f)
|
||||||
wantGathered(t, f.rand, root.label, renderDraws(root.t).reads, func() { renderRoot(f.rand, root.t) })
|
for _, root := range roots {
|
||||||
if !root.t.isRecord {
|
wantGathered(t, f.rand, tables, root.label, renderDraws(root.t).reads, func() { renderRoot(f.rand, root.t) })
|
||||||
|
if !root.t.isRecord || len(root.t.fields) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
columns := sortedNames(root.t.fields)
|
_, columns, err := recordOf(root.t)
|
||||||
wantGathered(t, f.rand, root.label+" as a record", columnDraws(root.t, columns).reads, func() { renderRecordRoot(f.rand, root.t) })
|
if err != nil {
|
||||||
|
t.Fatalf("%s: recordOf = %v", root.label, err)
|
||||||
|
}
|
||||||
|
wantGathered(t, f.rand, tables, root.label+" as a record", columnDraws(root.t, sortedNames(root.t.fields)).reads, func() { renderRecordRoot(f.rand, root.t, columns) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// wantGathered renders a root five times over the seeded stream and fails where a reference the
|
// wantGathered renders a root five times over the seeded stream and fails where a reference the
|
||||||
// render read is not among gathered: same draw group and path, and where the read sits in a cell,
|
// render read is not among gathered: same draw group and path, and where the render stood in a
|
||||||
// gathered from that row or from no row of its table.
|
// table's row, gathered from that row or from no row of the table.
|
||||||
func wantGathered(t *testing.T, s *session, label string, gathered []pathRead, renderOnce func()) {
|
func wantGathered(t *testing.T, s *session, tables map[string]*table, label string, gathered []pathRead, renderOnce func()) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var reads []pathRead
|
var reads []pathRead
|
||||||
seen := map[readKey]bool{}
|
seen := map[readKey]bool{}
|
||||||
s.trace = &renderTrace{read: func(from *template, group string, a arm) {
|
s.trace = &renderTrace{read: func(group, table string, row int, a arm) {
|
||||||
if s.trace.depth != 0 || !isRef(a.key) {
|
if s.trace.repeatDepth != 0 || !isRef(a.key) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r := pathRead{at: drawAt{group: group}, a: a}
|
r := pathRead{at: drawAt{group: group}, a: a}
|
||||||
if from.cellOf != nil {
|
if table != "" {
|
||||||
r.at.pins.add(from.cellOf, from.cellRow)
|
r.at.pins.add(tables[table], row)
|
||||||
}
|
}
|
||||||
if k := (readKey{group, a.path, r.at.rowsKey()}); !seen[k] {
|
if k := (readKey{group, a.path, r.at.rowsKey()}); !seen[k] {
|
||||||
seen[k] = true
|
seen[k] = true
|
||||||
@@ -110,13 +117,11 @@ func spellPins(p *pinSet) string {
|
|||||||
var rows []string
|
var rows []string
|
||||||
p.each(func(t *table, r int) { rows = append(rows, t.selectorSpelling(r)) })
|
p.each(func(t *table, r int) { rows = append(rows, t.selectorSpelling(r)) })
|
||||||
if len(rows) == 0 {
|
if len(rows) == 0 {
|
||||||
return "no cell"
|
return "no row"
|
||||||
}
|
}
|
||||||
return "the cell of " + strings.Join(rows, ", ")
|
return "row " + strings.Join(rows, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderRoot renders t the way the render reaches it: a table's format through its table, a cell in
|
|
||||||
// its row, one iteration of a repeat in t's own draw group, and anything else as itself.
|
|
||||||
func renderRoot(s *session, t *template) {
|
func renderRoot(s *session, t *template) {
|
||||||
var set holdSet
|
var set holdSet
|
||||||
sc := renderScope{set: &set}
|
sc := renderScope{set: &set}
|
||||||
@@ -127,21 +132,17 @@ func renderRoot(s *session, t *template) {
|
|||||||
sc.t, sc.row = t.cellOf, t.cellRow
|
sc.t, sc.row = t.cellOf, t.cellRow
|
||||||
render(s, t, sc)
|
render(s, t, sc)
|
||||||
case t.repeat > 1:
|
case t.repeat > 1:
|
||||||
expand(s, t, sc.in(t))
|
expand(s, t, sc)
|
||||||
default:
|
default:
|
||||||
render(s, t, sc)
|
render(s, t, sc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderRecordRoot(s *session, t *template) {
|
func renderRecordRoot(s *session, t *template, columns []Column) {
|
||||||
set := eagerHoldSet()
|
set := eagerHoldSet()
|
||||||
sc := renderScope{set: &set}
|
sc := renderScope{set: &set}
|
||||||
if t.table != nil {
|
if t.table != nil {
|
||||||
t.table.drawIn(s, &sc.in(t).hold().pins)
|
t.table.drawIn(s, &sc.hold().pins)
|
||||||
}
|
|
||||||
_, columns, err := recordOf(t)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
renderRecord(s, t, columns, sc)
|
renderRecord(s, t, columns, sc)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ type draw struct {
|
|||||||
// linkRefs prove every step, so the walk cannot fail.
|
// linkRefs prove every step, so the walk cannot fail.
|
||||||
func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw {
|
func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw {
|
||||||
if s.trace != nil {
|
if s.trace != nil {
|
||||||
s.trace.read(t, strings.Clone(sc.group), a)
|
var table string
|
||||||
|
if sc.t != nil {
|
||||||
|
table = strings.Clone(sc.t.path)
|
||||||
|
}
|
||||||
|
s.trace.read(strings.Clone(sc.group), table, sc.row, a)
|
||||||
}
|
}
|
||||||
if !t.held[a.key] {
|
if !t.held[a.key] {
|
||||||
if len(a.tail) > 0 {
|
if len(a.tail) > 0 {
|
||||||
@@ -49,11 +53,10 @@ func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderTrace is a test's view of a render: each field a format reads, and how many repeat
|
// renderTrace is a test's view of the fields a render reads; nil outside a test.
|
||||||
// iterations, each a render of its own, the read sits inside. nil outside a test.
|
|
||||||
type renderTrace struct {
|
type renderTrace struct {
|
||||||
read func(from *template, group string, a arm)
|
read func(group, table string, row int, a arm)
|
||||||
depth int
|
repeatDepth int
|
||||||
}
|
}
|
||||||
|
|
||||||
// readHold is the hold a held read keeps its draw in: for a reference that reads a path,
|
// readHold is the hold a held read keeps its draw in: for a reference that reads a path,
|
||||||
|
|||||||
+2
-2
@@ -36,8 +36,8 @@ func renderOnce(s *session, n node) string {
|
|||||||
func expandAnew(s *session, t *template) string {
|
func expandAnew(s *session, t *template) string {
|
||||||
var set holdSet
|
var set holdSet
|
||||||
if s.trace != nil {
|
if s.trace != nil {
|
||||||
s.trace.depth++
|
s.trace.repeatDepth++
|
||||||
defer func() { s.trace.depth-- }()
|
defer func() { s.trace.repeatDepth-- }()
|
||||||
}
|
}
|
||||||
return expand(s, t, renderScope{set: &set})
|
return expand(s, t, renderScope{set: &set})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user