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/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