Compare commits
2 Commits
79827a66d6
...
85a4cd4475
| Author | SHA1 | Date | |
|---|---|---|---|
| 85a4cd4475 | |||
| b99526aeb8 |
@@ -123,9 +123,10 @@ string and a backslash as an escape.
|
|||||||
A record written only to emit columns still needs a `format` — the grammar's one
|
A record written only to emit columns still needs a `format` — the grammar's one
|
||||||
required key — so `"format": ""` carries the fields with an inert format: it
|
required key — so `"format": ""` carries the fields with an inert format: it
|
||||||
renders nothing by `Fake`, and is compiled only so the tree's fences still run.
|
renders nothing by `Fake`, and is compiled only so the tree's fences still run.
|
||||||
The columns are the point, and their facts stay together: two columns that
|
The columns are the point, and their facts stay together: two columns that read a
|
||||||
reference one category — `{/currency.code}` and `{/currency.symbol}` — share one
|
path into one category — `{/currency.code}` and `{/currency.symbol}` — share one
|
||||||
draw of it, so the record is internally consistent. That one draw is also why two
|
draw of it, so the record is internally consistent. A bare `{/currency}` names no
|
||||||
|
field, so it keeps drawing on its own. That one draw is also why two
|
||||||
columns may not read overlapping reference *paths* — `{/cat.a}` beside
|
columns may not read overlapping reference *paths* — `{/cat.a}` beside
|
||||||
`{/cat.a.b}` is refused, naming the fields to write instead, as
|
`{/cat.a.b}` is refused, naming the fields to write instead, as
|
||||||
[One draw, one spelling](#one-draw-one-spelling) refuses that pair inside a single
|
[One draw, one spelling](#one-draw-one-spelling) refuses that pair inside a single
|
||||||
|
|||||||
+4
-2
@@ -52,8 +52,7 @@ func TestNoRenderAllocRegression(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A record's fences read the compiled tree, so they belong to New, not to a draw.
|
// A record's fences read the compiled tree, so they belong to New, not to a draw.
|
||||||
// A per-draw walk costs allocations in proportion to the tree; this pins that the
|
// A per-draw walk costs allocations in proportion to the tree.
|
||||||
// count does not move with the column count.
|
|
||||||
func TestNoRecordAllocRegression(t *testing.T) {
|
func TestNoRecordAllocRegression(t *testing.T) {
|
||||||
for _, s := range []struct{ name, json string }{
|
for _, s := range []struct{ name, json string }{
|
||||||
{"record 3 columns", `{"format":"","a":"x","b":"y","c":"z"}`},
|
{"record 3 columns", `{"format":"","a":"x","b":"y","c":"z"}`},
|
||||||
@@ -63,6 +62,9 @@ func TestNoRecordAllocRegression(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("New(%s): %v", s.name, err)
|
t.Fatalf("New(%s): %v", s.name, err)
|
||||||
}
|
}
|
||||||
|
if _, err := f.Record("x"); err != nil {
|
||||||
|
t.Fatalf("Record(%s): %v", s.name, err) // else the gate would measure the error path
|
||||||
|
}
|
||||||
const base = 4.0
|
const base = 4.0
|
||||||
if allocs := testing.AllocsPerRun(10000, func() { f.Record("x") }); allocs > base*1.10 {
|
if allocs := testing.AllocsPerRun(10000, func() { f.Record("x") }); allocs > base*1.10 {
|
||||||
t.Errorf("%s: %.1f allocs/op regressed past %.1f (baseline %.1f + 10%%); a record fence running per draw is the usual cause", s.name, allocs, base*1.10, base)
|
t.Errorf("%s: %.1f allocs/op regressed past %.1f (baseline %.1f + 10%%); a record fence running per draw is the usual cause", s.name, allocs, base*1.10, base)
|
||||||
|
|||||||
@@ -120,9 +120,8 @@ type recordShape struct {
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
// recordShapeOf fences a node once and remembers the answer. The fences read the
|
// recordShapeOf fences a node once and remembers the answer. Callers hold the
|
||||||
// compiled tree, which New fixed, so a repeated Record call on one path pays them
|
// generator's lock.
|
||||||
// once rather than per draw. Callers hold the generator's lock.
|
|
||||||
func (f *Generator) recordShapeOf(n node) recordShape {
|
func (f *Generator) recordShapeOf(n node) recordShape {
|
||||||
if shape, done := f.records[n]; done {
|
if shape, done := f.records[n]; done {
|
||||||
return shape
|
return shape
|
||||||
@@ -225,10 +224,10 @@ type columnRef struct {
|
|||||||
a arm
|
a arm
|
||||||
}
|
}
|
||||||
|
|
||||||
// columnRefs lists every reference read that reads a path, anywhere a column
|
// columnRefs lists every reference that reads a path, anywhere a column renders,
|
||||||
// renders, following the same edges expand does. A read that lands back on the
|
// following the same edges expand does. A reference landing back on the record
|
||||||
// record itself names a sibling column, which no draw of the record can answer
|
// itself is reported rather than collected, whether it reads a path or the record
|
||||||
// for, so it is reported here rather than collected.
|
// whole: either way the column describes a draw other than its neighbours'.
|
||||||
func columnRefs(t *template, columns []string) ([]columnRef, error) {
|
func columnRefs(t *template, columns []string) ([]columnRef, error) {
|
||||||
var out []columnRef
|
var out []columnRef
|
||||||
var err error
|
var err error
|
||||||
@@ -241,11 +240,8 @@ func columnRefs(t *template, columns []string) ([]columnRef, error) {
|
|||||||
}
|
}
|
||||||
seen[n] = true
|
seen[n] = true
|
||||||
if tm, ok := n.(*template); ok {
|
if tm, ok := n.(*template); ok {
|
||||||
for _, r := range boundReaders(tm.format, tm.bound, tm.refs) {
|
for _, ref := range refTokens(tm.format) {
|
||||||
a := splitArm(r.name, tm.refs)
|
a := splitArm(ref, tm.refs)
|
||||||
if !isRef(a.key) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if tm.fields[a.key] == node(t) {
|
if tm.fields[a.key] == node(t) {
|
||||||
err = fmt.Errorf("column %q reads {%s}, which points back at this record; a column cannot read another column — move the shared value into its own category and reference that", name, a.name)
|
err = fmt.Errorf("column %q reads {%s}, which points back at this record; a column cannot read another column — move the shared value into its own category and reference that", name, a.name)
|
||||||
return
|
return
|
||||||
|
|||||||
+9
-5
@@ -184,12 +184,16 @@ func TestInlineRecordRejectsOverlappingColumns(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRecordRejectsAColumnReadingItsOwnRecord(t *testing.T) {
|
func TestRecordRejectsAColumnReadingItsOwnRecord(t *testing.T) {
|
||||||
dir := writeData(t, map[string]string{
|
for _, c := range []struct{ name, column string }{
|
||||||
"person": `{"format":"{first} {last}","first":["Ada","Bo"],"last":["Lovelace","Ek"],"full":"{/person.first} {/person.last}"}`,
|
{"a path into itself", `"full":"{/person.first} {/person.last}"`},
|
||||||
})
|
{"the record read whole", `"whole":"{/person}"`},
|
||||||
f := newGenerator(t, dir, WithSeed(1))
|
{"the record as an operand", `"up":"{uppercase(/person)}"`},
|
||||||
|
} {
|
||||||
|
person := `{"format":"{first} {last}","first":["Ada","Bo"],"last":["Lovelace","Ek"],` + c.column + `}`
|
||||||
|
f := newGenerator(t, writeData(t, map[string]string{"person": person}), WithSeed(1))
|
||||||
if _, err := f.Record("person"); err == nil || !strings.Contains(err.Error(), "points back at this record") {
|
if _, err := f.Record("person"); err == nil || !strings.Contains(err.Error(), "points back at this record") {
|
||||||
t.Fatalf("a column referencing its own record = %v, want it refused; it would contradict the columns it reads", err)
|
t.Errorf("%s: Record = %v, want it refused; the column would contradict the columns beside it", c.name, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,10 @@ func TestFakeIsSafeForConcurrentUse(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
f.List()
|
f.List()
|
||||||
|
if _, err := f.Record("sv_SE.person"); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
tmpl, err := f.NewTemplate("{/sv_SE.person.last}")
|
tmpl, err := f.NewTemplate("{/sv_SE.person.last}")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
|||||||
Reference in New Issue
Block a user