diff --git a/calc_test.go b/calc_test.go index b24696c..2efdf89 100644 --- a/calc_test.go +++ b/calc_test.go @@ -245,3 +245,18 @@ func TestCalcOverANeverNumericOperandIsRejected(t *testing.T) { } } } + +func TestCalcConstantZeroDivisorIsRejected(t *testing.T) { + for _, bad := range []string{`"{calc(1/0)}"`, `"{calc(2/(1-1))}"`, `{"format":"{calc(x/y)}","x":"1","y":"0"}`, `{"format":"{calc(x/(y*2))}","x":"1","y":" 0 "}`} { + if _, err := compile(parse(t, bad)); err == nil || !strings.Contains(err.Error(), "zero") { + t.Errorf("compile(%s) = %v, want the constant zero divisor rejected", bad, err) + } + } + f := engine(1) + for i := 0; i < 50; i++ { + if got := mustRender(t, f, `{"format":"{calc(x/y)}","x":"1","y":["0","1"]}`); got == "+Inf" { + return + } + } + t.Fatal("a sometimes-zero divisor never printed +Inf in 50 draws") +} diff --git a/hold_test.go b/hold_test.go index 76f2053..3827d55 100644 --- a/hold_test.go +++ b/hold_test.go @@ -711,3 +711,13 @@ func TestRepeatedBareTokenOfAHeldNameIsRejected(t *testing.T) { } } } + +func TestReadFieldPanicsOnAnUnheldPath(t *testing.T) { + tm, ok := compiled(t, `{"format":"{w}","w":{"format":"{x}","x":"1"}}`).(*template) + if !ok { + t.Fatal("not a template") + } + mustPanic(t, "unheld arm with a path", func() { + readField(engine(1).rand, tm, nil, arm{name: "w.x", key: "w", tail: []string{"x"}, path: "w.x"}) + }) +} diff --git a/one_spelling_test.go b/one_spelling_test.go index e2d0fe1..f866609 100644 --- a/one_spelling_test.go +++ b/one_spelling_test.go @@ -30,11 +30,12 @@ func TestRepeatedChoiceItemIsRejected(t *testing.T) { func TestInertObjectIsRejected(t *testing.T) { for src, want := range map[string]string{ - `{"format":"Malmö"}`: `write "Malmö"`, - `{"format":"{digits(3)}"}`: `write "{digits(3)}"`, - `[{"format":"a","weight":1},"b"]`: "weight 1", - `{"format":"{x}","x":"v","repeat":1}`: "repeat 1", - `{"format":"{x}","x":"v","separator":","}`: "separator", + `{"format":"Malmö"}`: `write "Malmö"`, + `{"format":"{digits(3)}"}`: `write "{digits(3)}"`, + `[{"format":"a","weight":1},"b"]`: "weight 1", + `{"format":"{x}","x":"v","repeat":1}`: "repeat 1", + `{"format":"{x}","x":"v","separator":","}`: "separator", + `{"format":"{x}","x":"v","repeat":2,"separator":""}`: "default", } { if _, err := compile(parse(t, src)); err == nil || !strings.Contains(err.Error(), want) { t.Errorf("compile(%s) = %v, want an error mentioning %s", src, err, want) diff --git a/path_test.go b/path_test.go index 57a385f..b784793 100644 --- a/path_test.go +++ b/path_test.go @@ -136,3 +136,17 @@ func TestMissingFieldNamesItself(t *testing.T) { t.Errorf("Fake(person.typo) = %v, want it to name the missing field", err) } } + +func TestBindingKeyIsNotAPathSegment(t *testing.T) { + dir := writeData(t, map[string]string{ + "color": `["red","blue"]`, + "name": `{"format":"{/color} {w}","w":"x"}`, + }) + f := newGenerator(t, dir, WithSeed(1)) + if slices.Contains(f.List(), "name./color") { + t.Error("List() advertises a binding key") + } + if _, err := f.Fake("name./color"); err == nil || !strings.Contains(err.Error(), `no field "/color"`) { + t.Errorf("Fake(name./color) = %v, want a no-field error", err) + } +} diff --git a/template_test.go b/template_test.go index f1ddcb8..0ce3237 100644 --- a/template_test.go +++ b/template_test.go @@ -227,17 +227,29 @@ func TestCompileErrors(t *testing.T) { `{"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 + `"{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 diff --git a/transform_test.go b/transform_test.go index d201427..0272856 100644 --- a/transform_test.go +++ b/transform_test.go @@ -51,3 +51,9 @@ func TestTransformArgs(t *testing.T) { } } } + +func TestAsciiKeepsDEL(t *testing.T) { + if got := mustRender(t, engine(1), `{"format":"{ascii(x)}","x":"a\u007fb"}`); got != "a\u007fb" { + t.Errorf("ascii over DEL = %q, want it kept: DEL is ASCII", got) + } +}