Hold only the {token} grammar's tests in format_test.go, the rest in files named for what they test, and drop the done item

This commit is contained in:
2026-09-25 18:18:57 +02:00
committed by Lillem4n
parent 44a824ccf5
commit 5e84b26a5f
8 changed files with 301 additions and 264 deletions
+70
View File
@@ -368,3 +368,73 @@ func TestBuiltinDateSpansOneDay(t *testing.T) {
t.Fatalf("one day in a date-only layout = %v, want it named a constant", err)
}
}
func TestClassBuiltins(t *testing.T) {
cases := map[string]*regexp.Regexp{
`"{digits(1)}"`: regexp.MustCompile(`^[0-9]$`),
`"{digits(3)}"`: regexp.MustCompile(`^[0-9]{3}$`),
`"{int(1,9)}"`: regexp.MustCompile(`^[1-9]$`),
`"{upper(1)}"`: regexp.MustCompile(`^[A-Z]$`),
`"{lower(1)}"`: regexp.MustCompile(`^[a-z]$`),
`"{upper(2)}{lower(2)}"`: regexp.MustCompile(`^[A-Z]{2}[a-z]{2}$`),
}
f := engine(7)
for tmpl, re := range cases {
for i := 0; i < 100; i++ {
if got := mustRender(t, f, tmpl); !re.MatchString(got) {
t.Fatalf("%s produced %q, want %s", tmpl, got, re)
}
}
}
}
func TestFunctionTokenLuhn(t *testing.T) {
// {luhn()} appends a Luhn check digit over the digits emitted so far in the
// current expansion (non-digits skipped but kept). It reads the output
// buffer, so a value is never re-rendered. Bodies are escaped to fix input.
f := engine(1)
cases := map[string]string{
`"811218987{luhn()}"`: "8112189876", // personnummer body
`"7992739871{luhn()}"`: "79927398713", // classic Luhn vector
`"811218-987{luhn()}"`: "811218-9876", // '-' skipped, kept
`{"format":"{n}{luhn()}","n":"811218987"}`: "8112189876", // over a rendered token
}
for tmpl, want := range cases {
if got := mustRender(t, f, tmpl); got != want {
t.Fatalf("%s = %q, want %q", tmpl, got, want)
}
}
}
func TestBuiltinCompileErrors(t *testing.T) {
for _, bad := range []string{
`"{digits(0)}"`, // count must be positive
`"{upper(x)}"`, // count must be an integer
`"{lower()}"`, // wrong arity
`"{nope()}"`, // unknown function
`"{luhn(x)}"`, // function given args it takes none of
`"{int(1)}"`, // wrong arity
`"{int(a,b)}"`, // non-integer args
`"{int(5,1)}"`, // min > max
`"{hex(0)}"`, // count must be positive
`"{nanoid(-1)}"`, // negative count
`"{base64(0)}"`, // count must be positive
`"{float(1,2)}"`, // wrong arity
`"{float(1,2,-1)}"`, // negative decimals
`"{float(NaN,NaN,2)}"`, // bounds must be finite
`"{float(Inf,Inf,2)}"`, // same-sign infinities
`"{float(1,NaN,2)}"`, // one NaN bound
`"{float(-Inf,1,2)}"`, // one infinite bound
`"{digits(+5)}"`, // a count is a plain integer
`"{digits(05)}"`, // no leading zero
`"{int(+1,5)}"`, // a bound is a plain integer
`"{int(5,5)}"`, // a constant is written as text
`"{float(1,1,2)}"`, // a constant is written as text
`"{iban(US)}"`, // unsupported country
`"{seq(a,b)}"`, // seq takes at most one name
} {
if _, err := compile(parse(t, bad)); err == nil {
t.Errorf("compile(%s) = nil error, want error", bad)
}
}
}
+19
View File
@@ -270,3 +270,22 @@ func TestCalcConstantZeroDivisorIsRejected(t *testing.T) {
}
t.Fatal("a sometimes-zero divisor never printed +Inf in 50 draws")
}
func TestCalcCompileErrors(t *testing.T) {
for _, bad := range []string{
`"{calc(1/0)}"`, // a constant zero divisor
`{"format":"{calc(x/y)}","x":"1","y":"0"}`, // a fixed zero divisor
`"{calc()}"`, // calc needs an expression
`"{calc(1 +)}"`, // dangling operator
`"{calc((1 + 2)}"`, // unbalanced parenthesis
`"{calc(1 2)}"`, // two operands, no operator
`"{calc(price)}"`, // operand names no field
`"{calc(1, 2, 3)}"`, // too many args
`"{calc(1, x)}"`, // decimals arg not an integer
`"{calc(1, -1)}"`, // decimals negative
} {
if _, err := compile(parse(t, bad)); err == nil {
t.Errorf("compile(%s) = nil error, want error", bad)
}
}
}
+35
View File
@@ -1,6 +1,7 @@
package fejkdata
import (
"encoding/json"
"errors"
"io/fs"
"strings"
@@ -92,3 +93,37 @@ func fakeTemplate(t *testing.T, f *Generator, s string) string {
}
return got
}
// engine builds a seeded generator with no loaded categories, for rendering tests.
func engine(seed uint64) *Generator {
s, err := newRand(seed, true)
if err != nil {
panic(err)
}
return &Generator{rand: s}
}
// parse unmarshals a JSON template fragment into its dynamic form.
func parse(t *testing.T, s string) any {
t.Helper()
var v any
if err := json.Unmarshal([]byte(s), &v); err != nil {
t.Fatalf("parse %q: %v", s, err)
}
return v
}
// compiled parses and compiles a JSON fragment into a node.
func compiled(t *testing.T, s string) node {
t.Helper()
n, err := compile(parse(t, s))
if err != nil {
t.Fatalf("compile %q: %v", s, err)
}
return n
}
func mustRender(t *testing.T, f *Generator, s string) string {
t.Helper()
return renderOnce(f.rand, compiled(t, s))
}
+20 -261
View File
@@ -1,47 +1,12 @@
package fejkdata
import (
"encoding/json"
"reflect"
"regexp"
"strings"
"testing"
)
// engine builds a seeded generator with no loaded categories, for rendering tests.
func engine(seed uint64) *Generator {
s, err := newRand(seed, true)
if err != nil {
panic(err)
}
return &Generator{rand: s}
}
// parse unmarshals a JSON template fragment into its dynamic form.
func parse(t *testing.T, s string) any {
t.Helper()
var v any
if err := json.Unmarshal([]byte(s), &v); err != nil {
t.Fatalf("parse %q: %v", s, err)
}
return v
}
// compiled parses and compiles a JSON fragment into a node.
func compiled(t *testing.T, s string) node {
t.Helper()
n, err := compile(parse(t, s))
if err != nil {
t.Fatalf("compile %q: %v", s, err)
}
return n
}
func mustRender(t *testing.T, f *Generator, s string) string {
t.Helper()
return renderOnce(f.rand, compiled(t, s))
}
func TestStringIsAFormat(t *testing.T) {
f := engine(1)
for src, want := range map[string]string{
@@ -61,25 +26,6 @@ func TestStringIsAFormat(t *testing.T) {
}
}
func TestClassBuiltins(t *testing.T) {
cases := map[string]*regexp.Regexp{
`"{digits(1)}"`: regexp.MustCompile(`^[0-9]$`),
`"{digits(3)}"`: regexp.MustCompile(`^[0-9]{3}$`),
`"{int(1,9)}"`: regexp.MustCompile(`^[1-9]$`),
`"{upper(1)}"`: regexp.MustCompile(`^[A-Z]$`),
`"{lower(1)}"`: regexp.MustCompile(`^[a-z]$`),
`"{upper(2)}{lower(2)}"`: regexp.MustCompile(`^[A-Z]{2}[a-z]{2}$`),
}
f := engine(7)
for tmpl, re := range cases {
for i := 0; i < 100; i++ {
if got := mustRender(t, f, tmpl); !re.MatchString(got) {
t.Fatalf("%s produced %q, want %s", tmpl, got, re)
}
}
}
}
func TestTextIsLiteral(t *testing.T) {
if got := mustRender(t, engine(1), `{"format":"100 Main St #1 {x}","x":"A"}`); got != "100 Main St #1 A" {
t.Fatalf("text = %q, want it verbatim", got)
@@ -128,210 +74,17 @@ func TestAlternationPicksOneField(t *testing.T) {
}
}
func TestWeightZeroIsRejected(t *testing.T) {
_, err := compile(parse(t, `[{"format":"X","weight":0},"Y"]`))
if err == nil || !strings.Contains(err.Error(), "weight 0") || !strings.Contains(err.Error(), "remove") {
t.Fatalf("compile(weight 0) = %v, want it rejected naming the fix", err)
}
}
func TestWeightSkewsDistribution(t *testing.T) {
f := engine(9)
heavy := 0
for i := 0; i < 1000; i++ {
if mustRender(t, f, `[{"format":"H","weight":10},"L"]`) == "H" {
heavy++
}
}
if heavy < 800 { // expected ~909
t.Fatalf("heavy variant chosen %d/1000, want a clear majority", heavy)
}
}
func TestRepeatRendersFormatNTimes(t *testing.T) {
// repeat N concatenates N independent renders of the format ('x','y' are
// literal, not character classes).
if got := mustRender(t, engine(1), `{"format":"xy","repeat":3}`); got != "xyxyxy" {
t.Fatalf("repeat literal = %q, want xyxyxy", got)
}
// separator joins renders (N-1 of them, no trailing one).
if got := mustRender(t, engine(1), `{"format":"xy","repeat":3,"separator":"-"}`); got != "xy-xy-xy" {
t.Fatalf("repeat with separator = %q, want xy-xy-xy", got)
}
f, re := engine(7), regexp.MustCompile(`^[0-9]{4}$`)
func TestAlternationThreeWay(t *testing.T) {
f := engine(4)
seen := map[string]bool{}
for i := 0; i < 50; i++ {
got := mustRender(t, f, `{"format":"{digits(1)}","repeat":4}`)
if !re.MatchString(got) {
t.Fatalf("repeat-4 = %q, want 4 digits", got)
}
seen[got] = true
for i := 0; i < 200; i++ {
seen[mustRender(t, f, `{"format":"{a|b|c}","a":"A","b":"B","c":"C"}`)] = true
}
if len(seen) < 2 {
t.Fatalf("repeat never varied across renders: %v", seen)
if !seen["A"] || !seen["B"] || !seen["C"] || len(seen) != 3 {
t.Fatalf("3-way alternation produced %v, want A, B and C", seen)
}
}
func TestFunctionTokenLuhn(t *testing.T) {
// {luhn()} appends a Luhn check digit over the digits emitted so far in the
// current expansion (non-digits skipped but kept). It reads the output
// buffer, so a value is never re-rendered. Bodies are escaped to fix input.
f := engine(1)
cases := map[string]string{
`"811218987{luhn()}"`: "8112189876", // personnummer body
`"7992739871{luhn()}"`: "79927398713", // classic Luhn vector
`"811218-987{luhn()}"`: "811218-9876", // '-' skipped, kept
`{"format":"{n}{luhn()}","n":"811218987"}`: "8112189876", // over a rendered token
}
for tmpl, want := range cases {
if got := mustRender(t, f, tmpl); got != want {
t.Fatalf("%s = %q, want %q", tmpl, got, want)
}
}
}
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)
}
}
func TestCompileErrors(t *testing.T) {
// Every structural problem is caught up front, at compile/New time, never
// deferred to a random render that happens to hit the bad branch.
for _, bad := range []string{
`{"x":"Q"}`, // object without "format"
`{"format":"{y}","x":1}`, // a field is a bare number
`[1, 2]`, // a choice of numbers
`5`, // unsupported node type
`[]`, // empty choice
`"{x"`, // unterminated brace
`"x}y"`, // a lone } must be written }}
`["x"]`, // a one-item choice is its item
`["a","a"]`, // a repeated item is a weight
`{"format":"x"}`, // an object holding only a format is a string
`[{"format":"a","weight":1},"b"]`, // weight 1 is the default
`{"format":"{x}","x":"v","repeat":1}`, // repeat 1 is the default
`{"format":"{a{b}","a":"Q"}`, // a brace inside a token
`"{x}"`, // a bare string has no fields to name
`"{digits(0)}"`, // count must be positive
`"{upper(x)}"`, // count must be an integer
`"{lower()}"`, // wrong arity
`{"format":"{y}","x":"Q"}`, // token names a missing field
`"{}"`, // empty token name
`{"format":"{a|}","a":"Q"}`, // empty alternation segment
`[{"format":"A","weight":-1},"B"]`, // negative weight
`[{"format":"A","weight":0},{"format":"B","weight":0}]`, // weights sum to zero
`[{"format":"A","weight":1e308},{"format":"B","weight":1e308}]`, // weights overflow to +Inf
`{"format":"A","weight":"heavy"}`, // non-numeric weight
`{"format":"x","repeat":0}`, // repeat below 1
`{"format":"x","repeat":-2}`, // negative repeat
`{"format":"x","repeat":1.5}`, // non-integer repeat
`{"format":"x","repeat":"two"}`, // non-numeric repeat
`{"format":"x","repeat":2,"separator":5}`, // non-string separator
`"{nope()}"`, // unknown function
`"{luhn(x)}"`, // function given args it takes none of
`"{luhn(}"`, // malformed function token
`"{int(1)}"`, // wrong arity
`"{int(a,b)}"`, // non-integer args
`"{int(5,1)}"`, // min > max
`"{hex(0)}"`, // count must be positive
`"{nanoid(-1)}"`, // negative count
`"{base64(0)}"`, // count must be positive
`"{float(1,2)}"`, // wrong arity
`"{float(1,2,-1)}"`, // negative decimals
`"{float(NaN,NaN,2)}"`, // bounds must be finite
`"{float(Inf,Inf,2)}"`, // same-sign infinities
`"{float(1,NaN,2)}"`, // one NaN bound
`"{float(-Inf,1,2)}"`, // one infinite bound
`"{digits(+5)}"`, // a count is a plain integer
`"{digits(05)}"`, // no leading zero
`"{int(+1,5)}"`, // a bound is a plain integer
`"{int(5,5)}"`, // a constant is written as text
`"{float(1,1,2)}"`, // a constant is written as text
`{"format":"{x}","x":"v","repeat":2,"separator":""}`, // separator "" is the default
`"{calc(1/0)}"`, // a constant zero divisor
`{"format":"{calc(x/y)}","x":"1","y":"0"}`, // a fixed zero divisor
`"{iban(US)}"`, // unsupported country
`"{seq(a,b)}"`, // seq takes at most one name
`"{calc()}"`, // calc needs an expression
`"{calc(1 +)}"`, // dangling operator
`"{calc((1 + 2)}"`, // unbalanced parenthesis
`"{calc(1 2)}"`, // two operands, no operator
`"{calc(price)}"`, // operand names no field
`"{calc(1, 2, 3)}"`, // too many args
`"{calc(1, x)}"`, // decimals arg not an integer
`"{calc(1, -1)}"`, // decimals negative
} {
if _, err := compile(parse(t, bad)); err == nil {
t.Errorf("compile(%s) = nil error, want error", bad)
}
}
}
func TestFakePathNavigation(t *testing.T) {
f := engine(1)
f.categories = map[string]node{
"addr": compiled(t, `{"format":"{street}","street":"Main"}`),
}
if got, err := f.Fake("addr"); err != nil || !strings.Contains(got, "Main") {
t.Fatalf("Fake(addr) = %q, %v", got, err)
}
if got, err := f.Fake("addr.street"); err != nil || got != "Main" {
t.Fatalf("Fake(addr.street) = %q, %v, want Main", got, err)
}
if _, err := f.Fake("addr.nope"); err == nil {
t.Error("Fake(addr.nope) = nil error, want missing-field error")
}
if _, err := f.Fake("missing"); err == nil {
t.Error("Fake(missing) = nil error, want unknown-category error")
}
}
// 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 set holdSet
if got := len(expand(f.rand, tmpl, renderScope{set: &set})); got < tmpl.grow {
t.Errorf("format %q: expand emitted %d bytes, below grow %d", format, got, tmpl.grow)
}
}
}
}
func quote(s string) string {
b, err := json.Marshal(s)
if err != nil {
panic(err)
}
return string(b)
}
func TestHashIsLiteral(t *testing.T) {
f := engine(1)
cases := map[string]string{
@@ -357,14 +110,20 @@ func TestMultibyteFormat(t *testing.T) {
}
}
func TestAlternationThreeWay(t *testing.T) {
f := engine(4)
seen := map[string]bool{}
for i := 0; i < 200; i++ {
seen[mustRender(t, f, `{"format":"{a|b|c}","a":"A","b":"B","c":"C"}`)] = true
}
if !seen["A"] || !seen["B"] || !seen["C"] || len(seen) != 3 {
t.Fatalf("3-way alternation produced %v, want A, B and C", seen)
func TestFormatCompileErrors(t *testing.T) {
for _, bad := range []string{
`"{x"`, // unterminated brace
`"x}y"`, // a lone } must be written }}
`{"format":"{a{b}","a":"Q"}`, // a brace inside a token
`"{x}"`, // a bare string has no fields to name
`{"format":"{y}","x":"Q"}`, // token names a missing field
`"{}"`, // empty token name
`{"format":"{a|}","a":"Q"}`, // empty alternation segment
`"{luhn(}"`, // malformed function token
} {
if _, err := compile(parse(t, bad)); err == nil {
t.Errorf("compile(%s) = nil error, want error", bad)
}
}
}
+82
View File
@@ -0,0 +1,82 @@
package fejkdata
import (
"regexp"
"strings"
"testing"
)
func TestWeightZeroIsRejected(t *testing.T) {
_, err := compile(parse(t, `[{"format":"X","weight":0},"Y"]`))
if err == nil || !strings.Contains(err.Error(), "weight 0") || !strings.Contains(err.Error(), "remove") {
t.Fatalf("compile(weight 0) = %v, want it rejected naming the fix", err)
}
}
func TestWeightSkewsDistribution(t *testing.T) {
f := engine(9)
heavy := 0
for i := 0; i < 1000; i++ {
if mustRender(t, f, `[{"format":"H","weight":10},"L"]`) == "H" {
heavy++
}
}
if heavy < 800 { // expected ~909
t.Fatalf("heavy variant chosen %d/1000, want a clear majority", heavy)
}
}
func TestRepeatRendersFormatNTimes(t *testing.T) {
// repeat N concatenates N independent renders of the format ('x','y' are
// literal, not character classes).
if got := mustRender(t, engine(1), `{"format":"xy","repeat":3}`); got != "xyxyxy" {
t.Fatalf("repeat literal = %q, want xyxyxy", got)
}
// separator joins renders (N-1 of them, no trailing one).
if got := mustRender(t, engine(1), `{"format":"xy","repeat":3,"separator":"-"}`); got != "xy-xy-xy" {
t.Fatalf("repeat with separator = %q, want xy-xy-xy", got)
}
f, re := engine(7), regexp.MustCompile(`^[0-9]{4}$`)
seen := map[string]bool{}
for i := 0; i < 50; i++ {
got := mustRender(t, f, `{"format":"{digits(1)}","repeat":4}`)
if !re.MatchString(got) {
t.Fatalf("repeat-4 = %q, want 4 digits", got)
}
seen[got] = true
}
if len(seen) < 2 {
t.Fatalf("repeat never varied across renders: %v", seen)
}
}
func TestNodeCompileErrors(t *testing.T) {
// Every structural problem is caught up front, at compile/New time, never
// deferred to a random render that happens to hit the bad branch.
for _, bad := range []string{
`{"x":"Q"}`, // object without "format"
`{"format":"{y}","x":1}`, // a field is a bare number
`[1, 2]`, // a choice of numbers
`5`, // unsupported node type
`[]`, // empty choice
`["x"]`, // a one-item choice is its item
`["a","a"]`, // a repeated item is a weight
`{"format":"x"}`, // an object holding only a format is a string
`[{"format":"a","weight":1},"b"]`, // weight 1 is the default
`{"format":"{x}","x":"v","repeat":1}`, // repeat 1 is the default
`[{"format":"A","weight":-1},"B"]`, // negative weight
`[{"format":"A","weight":0},{"format":"B","weight":0}]`, // weights sum to zero
`[{"format":"A","weight":1e308},{"format":"B","weight":1e308}]`, // weights overflow to +Inf
`{"format":"A","weight":"heavy"}`, // non-numeric weight
`{"format":"x","repeat":0}`, // repeat below 1
`{"format":"x","repeat":-2}`, // negative repeat
`{"format":"x","repeat":1.5}`, // non-integer repeat
`{"format":"x","repeat":"two"}`, // non-numeric repeat
`{"format":"x","repeat":2,"separator":5}`, // non-string separator
`{"format":"{x}","x":"v","repeat":2,"separator":""}`, // separator "" is the default
} {
if _, err := compile(parse(t, bad)); err == nil {
t.Errorf("compile(%s) = nil error, want error", bad)
}
}
}
+19
View File
@@ -170,3 +170,22 @@ func TestBindingKeyIsNotAPathSegment(t *testing.T) {
t.Errorf("Fake(name./color) = %v, want a no-field error", err)
}
}
func TestFakePathNavigation(t *testing.T) {
f := engine(1)
f.categories = map[string]node{
"addr": compiled(t, `{"format":"{street}","street":"Main"}`),
}
if got, err := f.Fake("addr"); err != nil || !strings.Contains(got, "Main") {
t.Fatalf("Fake(addr) = %q, %v", got, err)
}
if got, err := f.Fake("addr.street"); err != nil || got != "Main" {
t.Fatalf("Fake(addr.street) = %q, %v, want Main", got, err)
}
if _, err := f.Fake("addr.nope"); err == nil {
t.Error("Fake(addr.nope) = nil error, want missing-field error")
}
if _, err := f.Fake("missing"); err == nil {
t.Error("Fake(missing) = nil error, want unknown-category error")
}
}
+56
View File
@@ -0,0 +1,56 @@
package fejkdata
import (
"encoding/json"
"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 set holdSet
if got := len(expand(f.rand, tmpl, renderScope{set: &set})); got < tmpl.grow {
t.Errorf("format %q: expand emitted %d bytes, below grow %d", format, got, tmpl.grow)
}
}
}
}
func quote(s string) string {
b, err := json.Marshal(s)
if err != nil {
panic(err)
}
return string(b)
}
-3
View File
@@ -19,9 +19,6 @@ Self-sufficiency 5.6), every seat capped by Locality or Self-sufficiency.
This round, in order:
- Hold only the `{token}` grammar's tests in `format_test.go`: move the helpers seven
other test files use (`engine`, `parse`, `compiled`, `mustRender`) and the weight,
recursion and path-navigation tests to files named for what they test.
- Give `rows` and `field` one meaning each, per the decision "One name, one meaning":
`categoryFiles.rows` returns a TSV file name where `table.rows` counts rows, and
`formatOps.field` compiles a `{field}` token where `structFields.field` reads a Go