Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -4,13 +4,11 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/larvit/fejkdata/internal/grammar"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuiltinIDGenerators(t *testing.T) {
|
func TestBuiltinIDGenerators(t *testing.T) {
|
||||||
@@ -191,31 +189,6 @@ func ibanValid(s string) bool {
|
|||||||
return rem == 1
|
return rem == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRegistryShapes pins the builtin contract compileFormat relies on: every entry
|
|
||||||
// supplies prep, and args parsed at compile only behind a check.
|
|
||||||
func TestRegistryShapes(t *testing.T) {
|
|
||||||
for name, b := range builtins {
|
|
||||||
if b.prep == nil {
|
|
||||||
t.Errorf("builtin %q has no prep: compileFormat would call a nil func", name)
|
|
||||||
}
|
|
||||||
if b.arity != 0 && b.checkArgs == nil {
|
|
||||||
t.Errorf("builtin %q parses args in prep with no check", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestArgGuardsPanic pins the guards that report a builtin arg its check should
|
|
||||||
// have rejected. No data reaches them — checkFunc runs a builtin's check before
|
|
||||||
// compileFormat ever calls prep — so they are exercised directly.
|
|
||||||
func TestArgGuardsPanic(t *testing.T) {
|
|
||||||
for name, call := range map[string]func(){
|
|
||||||
"atoi on an unvalidated arg": func() { atoi("nope") },
|
|
||||||
"atof on an unvalidated arg": func() { atof("nope") },
|
|
||||||
} {
|
|
||||||
mustPanic(t, name, call)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mustPanic fails unless call panics with internalError's phrase, which is what
|
// mustPanic fails unless call panics with internalError's phrase, which is what
|
||||||
// separates a reported invariant break from a silently wrong value.
|
// separates a reported invariant break from a silently wrong value.
|
||||||
func mustPanic(t *testing.T, name string, call func()) {
|
func mustPanic(t *testing.T, name string, call func()) {
|
||||||
@@ -450,14 +423,3 @@ func TestArgErrorsNameTheSpelling(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoRefOperandsMatchesTheOperandGrammar(t *testing.T) {
|
|
||||||
for name, b := range builtins {
|
|
||||||
if b.operands == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if takesRef := slices.ContainsFunc(b.operands([]string{"/x"}), grammar.IsRef); b.noRefOperands == takesRef {
|
|
||||||
t.Errorf("%s: noRefOperands = %v, but its operands func reads a reference: %v", name, b.noRefOperands, takesRef)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -113,12 +113,6 @@ func TestParsedCallReadsOnlyAnOperandBuiltin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCalcPrepPanicsOnAnUncheckedExpression pins the guard on calc's own invariant:
|
|
||||||
// checkCalc parsed the expression before prep sees it.
|
|
||||||
func TestCalcPrepPanicsOnAnUncheckedExpression(t *testing.T) {
|
|
||||||
mustPanic(t, "prep on an expression that does not parse", func() { calcPrep([]string{"1 +"}) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- one draw, one value: a calc operand reads the expansion's draw ---
|
// --- one draw, one value: a calc operand reads the expansion's draw ---
|
||||||
|
|
||||||
// TestANamedCalcOperandIsTheOneShown pins how a calc computes from the value its format
|
// TestANamedCalcOperandIsTheOneShown pins how a calc computes from the value its format
|
||||||
|
|||||||
+5
-1
@@ -29,10 +29,14 @@ func TestPackageImports(t *testing.T) {
|
|||||||
}
|
}
|
||||||
checked := 0
|
checked := 0
|
||||||
allowed := map[string][]string{
|
allowed := map[string][]string{
|
||||||
".": {"internal/drawstate", "internal/grammar"},
|
".": {"internal/builtinfunc", "internal/datafiles", "internal/datatype", "internal/drawstate", "internal/grammar", "internal/proven"},
|
||||||
"cmd/fejkdata": {"."},
|
"cmd/fejkdata": {"."},
|
||||||
|
"internal/builtinfunc": {"internal/datatype", "internal/drawstate", "internal/grammar", "internal/proven"},
|
||||||
|
"internal/datafiles": {"internal/grammar"},
|
||||||
|
"internal/datatype": nil,
|
||||||
"internal/drawstate": nil,
|
"internal/drawstate": nil,
|
||||||
"internal/grammar": nil,
|
"internal/grammar": nil,
|
||||||
|
"internal/proven": {"internal/datatype", "internal/grammar"},
|
||||||
}
|
}
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
err = filepath.WalkDir(".", func(dir string, d fs.DirEntry, err error) error {
|
err = filepath.WalkDir(".", func(dir string, d fs.DirEntry, err error) error {
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package builtinfunc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/larvit/fejkdata/internal/grammar"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestRegistryShapes pins the builtin contract Prep relies on: every entry supplies prep,
|
||||||
|
// and args parsed at compile only behind a check.
|
||||||
|
func TestRegistryShapes(t *testing.T) {
|
||||||
|
for name, b := range builtins {
|
||||||
|
if b.prep == nil {
|
||||||
|
t.Errorf("builtin %q has no prep: Prep would call a nil func", name)
|
||||||
|
}
|
||||||
|
if b.arity != 0 && b.checkArgs == nil {
|
||||||
|
t.Errorf("builtin %q parses args in prep with no check", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoRefOperandsMatchesTheOperandGrammar(t *testing.T) {
|
||||||
|
for name, b := range builtins {
|
||||||
|
if b.operands == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if takesRef := slices.ContainsFunc(b.operands([]string{"/x"}), grammar.IsRef); b.noRefOperands == takesRef {
|
||||||
|
t.Errorf("%s: noRefOperands = %v, but its operands func reads a reference: %v", name, b.noRefOperands, takesRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckNamesWhatACallGetsWrong(t *testing.T) {
|
||||||
|
for _, c := range []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"nope", nil, `unknown function "nope"`},
|
||||||
|
{"luhn", []string{"1"}, "luhn takes 0 arguments, got 1"},
|
||||||
|
{"hex", nil, "hex takes 1 argument, got 0"},
|
||||||
|
{"calc", []string{"a +"}, `calc("a +"): `},
|
||||||
|
{"calc", []string{"a", "-1"}, "calc decimals "},
|
||||||
|
{"calc", nil, "calc takes an expression and an optional decimals count, got 0 args"},
|
||||||
|
} {
|
||||||
|
if err := Check(c.name, c.args); err == nil || !strings.HasPrefix(err.Error(), c.want) {
|
||||||
|
t.Errorf("Check(%s, %q) = %v, want it to start %q", c.name, c.args, err, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := Check("calc", []string{"a / b", "2"}); err != nil {
|
||||||
|
t.Errorf("Check(calc) = %v, want an expression over any operand accepted", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestArgGuardsPanic pins the guards that report a builtin arg its check should have
|
||||||
|
// rejected. No data reaches them, since Check runs before Prep, so they are called directly.
|
||||||
|
func TestArgGuardsPanic(t *testing.T) {
|
||||||
|
for name, call := range map[string]func(){
|
||||||
|
"atoi on an unvalidated arg": func() { atoi("nope") },
|
||||||
|
"atof on an unvalidated arg": func() { atof("nope") },
|
||||||
|
"prep on an expression that won't parse": func() { calcPrep([]string{"1 +"}) },
|
||||||
|
} {
|
||||||
|
mustPanic(t, name, call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustPanic fails unless call panics with internalError's phrase, which is what
|
||||||
|
// separates a reported invariant break from a silently wrong value.
|
||||||
|
func mustPanic(t *testing.T, name string, call func()) {
|
||||||
|
t.Helper()
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r == nil || !strings.HasPrefix(fmt.Sprint(r), "fejkdata: internal error: ") {
|
||||||
|
t.Errorf("%s: recovered %v, want the invariant reported as an internal error", name, r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
call()
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package datafiles
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type handed struct {
|
||||||
|
dir []string
|
||||||
|
name string
|
||||||
|
json any
|
||||||
|
}
|
||||||
|
|
||||||
|
func walk(t *testing.T, src Source) ([]handed, error) {
|
||||||
|
t.Helper()
|
||||||
|
var got []handed
|
||||||
|
err := src.Walk(func(c Category) error {
|
||||||
|
got = append(got, handed{c.Dir, c.Name, c.JSON})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return got, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalkHandsEachCategoryInOrder(t *testing.T) {
|
||||||
|
got, err := walk(t, Source{FS: fstest.MapFS{
|
||||||
|
"a.json": {Data: []byte(`{"x":"1"}`)},
|
||||||
|
"sub/b.json": {Data: []byte(`"y"`)},
|
||||||
|
"sub/notes.txt": {Data: []byte("not data")},
|
||||||
|
"empty/x.txt": {Data: []byte("not data")},
|
||||||
|
".git/c.json": {Data: []byte(`"hidden"`)},
|
||||||
|
"sub/.d.json": {Data: []byte(`"hidden"`)},
|
||||||
|
"sub/deep/e.json": {Data: []byte(`"z"`)},
|
||||||
|
}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want := []handed{
|
||||||
|
{nil, "a", map[string]any{"x": "1"}},
|
||||||
|
{[]string{"sub"}, "b", "y"},
|
||||||
|
{[]string{"sub", "deep"}, "e", "z"},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("Walk handed %#v, want %#v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalkStartsAtTheBaseDir(t *testing.T) {
|
||||||
|
got, err := walk(t, Source{FS: fstest.MapFS{"data/a.json": {Data: []byte(`"x"`)}, "b.json": {Data: []byte(`"y"`)}}, BaseDir: "data"})
|
||||||
|
if err != nil || len(got) != 1 || got[0].name != "a" || got[0].dir != nil {
|
||||||
|
t.Errorf("Walk = %#v, %v, want a alone at the base", got, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadRowsReadsOnlyAFileBesideTheCategory(t *testing.T) {
|
||||||
|
src := Source{FS: fstest.MapFS{
|
||||||
|
"t.json": {Data: []byte(`{"rows":"t.tsv"}`)},
|
||||||
|
"t.tsv": {Data: []byte("key\nA\n")},
|
||||||
|
"sub/u.tsv": {Data: []byte("key\nB\n")},
|
||||||
|
"sub/u.json": {Data: []byte(`{"rows":"u.tsv"}`)},
|
||||||
|
}, Label: "lbl"}
|
||||||
|
rows := map[string]string{}
|
||||||
|
err := src.Walk(func(c Category) error {
|
||||||
|
data, err := c.ReadRows(c.Name + ".tsv")
|
||||||
|
rows[c.Name] = data
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := c.ReadRows("x.tsv"); err == nil || err.Error() != "rows names x.tsv, which is not beside it in "+map[string]string{"t": "lbl", "u": "lbl/sub"}[c.Name] {
|
||||||
|
t.Errorf("ReadRows(x.tsv) = %v, want the file named missing beside %s", err, c.Name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil || rows["t"] != "key\nA\n" || rows["u"] != "key\nB\n" {
|
||||||
|
t.Errorf("Walk = %v, read %q", err, rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalkRefusesARowsFileNoCategoryNames(t *testing.T) {
|
||||||
|
_, err := walk(t, Source{FS: fstest.MapFS{"a.json": {Data: []byte(`"x"`)}, "a.tsv": {Data: []byte("key\n")}}, Label: "lbl"})
|
||||||
|
if err == nil || !strings.HasPrefix(err.Error(), "lbl/a.tsv: no category names it in its rows") {
|
||||||
|
t.Errorf("Walk = %v, want a.tsv refused as named by no category", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalkLabelsWhatACategoryFileGetsWrong(t *testing.T) {
|
||||||
|
for _, c := range []struct {
|
||||||
|
name string
|
||||||
|
fs fstest.MapFS
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"its compile", fstest.MapFS{"a.json": {Data: []byte(`"x"`)}}, "lbl/a.json: bad"},
|
||||||
|
{"its JSON", fstest.MapFS{"a.json": {Data: []byte(`{`)}}, "lbl/a.json: unexpected end of JSON input"},
|
||||||
|
{"its name", fstest.MapFS{"a b.json": {Data: []byte(`"x"`)}}, "lbl/a b.json: category "},
|
||||||
|
{"its folder's name", fstest.MapFS{"a b/c.json": {Data: []byte(`"x"`)}}, "lbl/a b: folder "},
|
||||||
|
} {
|
||||||
|
err := Source{FS: c.fs, Label: "lbl"}.Walk(func(Category) error { return errors.New("bad") })
|
||||||
|
if err == nil || !strings.HasPrefix(err.Error(), c.want) {
|
||||||
|
t.Errorf("%s: Walk = %v, want it to start %q", c.name, err, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalkChecksAFolderNameOnlyWhenItHoldsData(t *testing.T) {
|
||||||
|
if _, err := walk(t, Source{FS: fstest.MapFS{"a b/notes.txt": {Data: []byte("x")}, "c.json": {Data: []byte(`"x"`)}}}); err != nil {
|
||||||
|
t.Errorf("Walk = %v, want a folder holding no data skipped whatever its name", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalkRefusesADiskPathThatIsNoDirectory(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
for _, c := range []struct{ path, want string }{
|
||||||
|
{"", "a data path is empty"},
|
||||||
|
{filepath.Join(dir, "none"), "data path " + filepath.Join(dir, "none") + ": "},
|
||||||
|
} {
|
||||||
|
_, err := walk(t, Source{FS: fstest.MapFS{}, Label: c.path, OnDisk: true})
|
||||||
|
if err == nil || !strings.HasPrefix(err.Error(), c.want) {
|
||||||
|
t.Errorf("Walk(%q) = %v, want it to start %q", c.path, err, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadHandsOneCategoryAndItsRows(t *testing.T) {
|
||||||
|
src := Source{FS: fstest.MapFS{
|
||||||
|
"data/sub/t.json": {Data: []byte(`{"rows":"t.tsv"}`)},
|
||||||
|
"data/sub/t.tsv": {Data: []byte("key\nA\n")},
|
||||||
|
"data/sub/u.json": {Data: []byte(`{`)},
|
||||||
|
}, BaseDir: "data"}
|
||||||
|
var got handed
|
||||||
|
var rows string
|
||||||
|
err := src.Load([]string{"sub"}, "t", func(c Category) error {
|
||||||
|
got = handed{c.Dir, c.Name, c.JSON}
|
||||||
|
var err error
|
||||||
|
rows, err = c.ReadRows("t.tsv")
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil || got.name != "t" || !reflect.DeepEqual(got.dir, []string{"sub"}) || rows != "key\nA\n" {
|
||||||
|
t.Errorf("Load = %v, handed %#v with rows %q", err, got, rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package datatype
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestEachDatatypeIsSpelledAsDataWritesIt(t *testing.T) {
|
||||||
|
for d, want := range map[DataType][2]string{
|
||||||
|
String: {"string", "text"},
|
||||||
|
Integer: {"integer", "an integer"},
|
||||||
|
Number: {"number", "a number"},
|
||||||
|
Boolean: {"boolean", "a boolean"},
|
||||||
|
} {
|
||||||
|
if d.String() != want[0] || Noun(d) != want[1] {
|
||||||
|
t.Errorf("%d: String = %q, Noun = %q, want %q", int(d), d.String(), Noun(d), want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if Count != 4 {
|
||||||
|
t.Errorf("Count = %d, want the four datatypes", Count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringNamesADatatypeNoDataWrites(t *testing.T) {
|
||||||
|
if got := DataType(9).String(); got != "DataType(9)" {
|
||||||
|
t.Errorf("String = %q, want DataType(9)", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package proven
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/larvit/fejkdata/internal/datatype"
|
||||||
|
"github.com/larvit/fejkdata/internal/grammar"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLiteralSaysWhichDatatypesItsTextIs(t *testing.T) {
|
||||||
|
for _, c := range []struct {
|
||||||
|
text string
|
||||||
|
not [datatype.Count]bool
|
||||||
|
}{
|
||||||
|
{"42", [datatype.Count]bool{datatype.Boolean: true}},
|
||||||
|
{"1.5", [datatype.Count]bool{datatype.Integer: true, datatype.Boolean: true}},
|
||||||
|
{"true", [datatype.Count]bool{datatype.Integer: true, datatype.Number: true}},
|
||||||
|
{"007", [datatype.Count]bool{datatype.Integer: true, datatype.Number: true, datatype.Boolean: true}},
|
||||||
|
{"-0", [datatype.Count]bool{datatype.Integer: true, datatype.Number: true, datatype.Boolean: true}},
|
||||||
|
} {
|
||||||
|
v := Literal(c.text)
|
||||||
|
for d := datatype.Integer; d <= datatype.Boolean; d++ {
|
||||||
|
if (v.Not[d] != "") != c.not[d] {
|
||||||
|
t.Errorf("Literal(%q).Not[%s] = %q", c.text, d, v.Not[d])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := Literal("-0"); v.Not[datatype.Integer] != `"-0" is zero written with a sign; write "0"` {
|
||||||
|
t.Errorf("Literal(-0) = %q, want the unsigned spelling named", v.Not[datatype.Integer])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrKeepsTheBoundsOfBothAndTheFirstReason(t *testing.T) {
|
||||||
|
v := Bounded(1, 2, true).Or(Bounded(-3, 0.5, false))
|
||||||
|
if v.Lo != -3 || v.Hi != 2 || v.Integral || v.NonZero != 0 {
|
||||||
|
t.Errorf("Or = %+v, want [-3, 2], not integral, possibly zero", v)
|
||||||
|
}
|
||||||
|
if v := Unproven("first").Or(Unproven("second")); v.NotOperand != "first" || v.Not[datatype.Number] != "first" {
|
||||||
|
t.Errorf("Or = %+v, want the first reason kept", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalcBoundsAnExpressionFromItsOperands(t *testing.T) {
|
||||||
|
operand := func(name string) Value {
|
||||||
|
return map[string]Value{"a": Bounded(1, 9, true), "b": Bounded(0, 9, true), "big": Bounded(0, 1e299, true), "word": Unproven("it is text")}[name]
|
||||||
|
}
|
||||||
|
for expr, want := range map[string]string{
|
||||||
|
"a * 2 + 1": "",
|
||||||
|
"a / b": "divides by b, which is not proven nonzero",
|
||||||
|
"big * 100": "(big * 100) is not proven within 1e300",
|
||||||
|
"big": "",
|
||||||
|
"word + 1": `operand "word": it is text`,
|
||||||
|
"-(a - 10)": "",
|
||||||
|
} {
|
||||||
|
c, err := grammar.ParseCalc(expr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
v, doubt := Calc(c.Expr, operand)
|
||||||
|
if doubt != want {
|
||||||
|
t.Errorf("Calc(%s) doubts %q, want %q", expr, doubt, want)
|
||||||
|
}
|
||||||
|
if expr == "a * 2 + 1" && (v.Lo != 3 || v.Hi != 19 || !v.Integral || v.NonZero != 3) {
|
||||||
|
t.Errorf("Calc(%s) = %+v, want [3, 19], integral, nonzero by 3", expr, v)
|
||||||
|
}
|
||||||
|
if expr == "-(a - 10)" && (v.Lo != 1 || v.Hi != 9) {
|
||||||
|
t.Errorf("Calc(%s) = %+v, want [1, 9]", expr, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lone, _ := grammar.ParseCalc("x")
|
||||||
|
if _, doubt := Calc(lone.Expr, func(string) Value { return Bounded(0, 1e301, true) }); doubt != "x is not proven within 1e300" {
|
||||||
|
t.Errorf("Calc(x) doubts %q, want a lone operand held to the limit", doubt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintedNumberWidensItsBoundsByTheRounding(t *testing.T) {
|
||||||
|
v := PrintedNumber("float(0,1,1)", Bounded(0.2, 0.8, false), 1)
|
||||||
|
if math.Abs(v.Lo-0.15) > 1e-12 || math.Abs(v.Hi-0.85) > 1e-12 || v.Not[datatype.Integer] == "" || v.Not[datatype.Number] != "" {
|
||||||
|
t.Errorf("PrintedNumber = %+v, want [0.15, 0.85], a number and no integer", v)
|
||||||
|
}
|
||||||
|
if v := PrintedNumber("calc(a)", Bounded(1, 2, true), ShortestDecimals); v.Not[datatype.Integer] != "" {
|
||||||
|
t.Errorf("PrintedNumber = %+v, want a whole value printed as an integer", v)
|
||||||
|
}
|
||||||
|
if v := Printing("digits(3)", datatype.String, Bounded(0, 999, true)); v.Not[datatype.Integer] != "{digits(3)} prints text, not an integer" {
|
||||||
|
t.Errorf("Printing = %q, want the text named", v.Not[datatype.Integer])
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user