Compare commits
2 Commits
bbe6c5b1d9
...
e4ebfdb28d
| Author | SHA1 | Date | |
|---|---|---|---|
| e4ebfdb28d | |||
| ef00000436 |
@@ -538,10 +538,10 @@ tokens add cost in proportion to the output.
|
|||||||
whole — though only a leading one could collide — keeps one simple name rule
|
whole — though only a leading one could collide — keeps one simple name rule
|
||||||
instead of a leading-position special case. The JSON string is what makes the
|
instead of a leading-position special case. The JSON string is what makes the
|
||||||
library's own advice reachable: the error for an object holding only a format
|
library's own advice reachable: the error for an object holding only a format
|
||||||
names `"…"`, and that spelling has to work where it is printed. One reference alone,
|
names `"…"`, and that spelling has to work where it is printed. An argument or struct
|
||||||
`{/users}`, is refused naming the path `users`: both render the same text, and only
|
tag of one reference alone, `{/users}`, is refused naming the path `users`: both
|
||||||
the path names a record. `IsTemplate` exports the rule, so the CLI, struct tags and
|
render the same text, and only the path names a record. `IsTemplate` exports the
|
||||||
any other caller read one.
|
rule, so the CLI, struct tags and any other caller read one.
|
||||||
- **An inline template skips the cycle fence, and only that one.** `New` proves the
|
- **An inline template skips the cycle fence, and only that one.** `New` proves the
|
||||||
loaded tree acyclic, an inline node is a finite tree of its own, and nothing in
|
loaded tree acyclic, an inline node is a finite tree of its own, and nothing in
|
||||||
the tree can reference it, so no render of it reaches itself. Every other fence
|
the tree can reference it, so no render of it reaches itself. Every other fence
|
||||||
|
|||||||
@@ -418,7 +418,7 @@ const (
|
|||||||
func classify(arg string) (argKind, error) {
|
func classify(arg string) (argKind, error) {
|
||||||
inline, err := fejkdata.IsTemplate(arg)
|
inline, err := fejkdata.IsTemplate(arg)
|
||||||
if inline {
|
if inline {
|
||||||
return argTemplate, err
|
return argTemplate, nil
|
||||||
}
|
}
|
||||||
return argPath, err
|
return argPath, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,8 +72,8 @@ func isTemplate(arg string) (bool, error) {
|
|||||||
if i := strings.IndexAny(arg, `[]}"`); i >= 0 {
|
if i := strings.IndexAny(arg, `[]}"`); i >= 0 {
|
||||||
return false, fmt.Errorf("%q holds a %q, which no path may, and it is not valid JSON, so it names no template either", arg, arg[i:i+1])
|
return false, fmt.Errorf("%q holds a %q, which no path may, and it is not valid JSON, so it names no template either", arg, arg[i:i+1])
|
||||||
}
|
}
|
||||||
if len(arg) > 1 && strings.HasPrefix(arg, "/") {
|
if path := strings.TrimLeft(arg, "/"); path != arg && path != "" {
|
||||||
return false, fmt.Errorf("path %s starts with /, and every path starts at the root already; write %s", arg, arg[1:])
|
return false, fmt.Errorf("path %s starts with /, and every path starts at the root already; write %s", arg, path)
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@@ -82,14 +82,19 @@ func isJSONStart(arg string) bool {
|
|||||||
return strings.HasPrefix(arg, "[") || strings.HasPrefix(arg, `"`)
|
return strings.HasPrefix(arg, "[") || strings.HasPrefix(arg, `"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// loneReference is the path a template spells when it is one reference token and nothing else.
|
// loneReference is the path a template spells when the value it holds — a format string, a
|
||||||
|
// JSON string, or an object holding only a format — is one reference token and nothing else.
|
||||||
func loneReference(arg string) (string, bool) {
|
func loneReference(arg string) (string, bool) {
|
||||||
format := arg
|
var raw any
|
||||||
if strings.HasPrefix(arg, `"`) && json.Unmarshal([]byte(arg), &format) != nil {
|
if json.Unmarshal([]byte(arg), &raw) != nil {
|
||||||
return "", false
|
raw = arg
|
||||||
}
|
}
|
||||||
|
if m, isObject := raw.(map[string]any); isObject && len(m) == 1 {
|
||||||
|
raw = m["format"]
|
||||||
|
}
|
||||||
|
format, isString := raw.(string)
|
||||||
var units []ftoken
|
var units []ftoken
|
||||||
if eachToken(format, func(t ftoken) error { units = append(units, t); return nil }) != nil || len(units) != 1 {
|
if !isString || eachToken(format, func(t ftoken) error { units = append(units, t); return nil }) != nil || len(units) != 1 {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
body := units[0].body
|
body := units[0].body
|
||||||
|
|||||||
+14
-11
@@ -171,17 +171,20 @@ func TestIsTemplate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for arg, want := range map[string]string{
|
for arg, want := range map[string]string{
|
||||||
"[abc]": `holds a "["`,
|
"[abc]": `holds a "["`,
|
||||||
"[abc].field": `holds a "["`,
|
"[abc].field": `holds a "["`,
|
||||||
"x[1]": `holds a "["`,
|
"x[1]": `holds a "["`,
|
||||||
"a]b": `holds a "]"`,
|
"a]b": `holds a "]"`,
|
||||||
"a}b": `holds a "}"`,
|
"a}b": `holds a "}"`,
|
||||||
`"abc`: `holds a "\""`,
|
`"abc`: `holds a "\""`,
|
||||||
`"a]b`: `holds a "\""`, // the opener the reader typed, not the bracket behind it
|
`"a]b`: `holds a "\""`, // the opener the reader typed, not the bracket behind it
|
||||||
"{/sv_SE.person.last}": "{/sv_SE.person.last} is the path sv_SE.person.last written as a template; write sv_SE.person.last",
|
"{/sv_SE.person.last}": "{/sv_SE.person.last} is the path sv_SE.person.last written as a template; write sv_SE.person.last",
|
||||||
`"{/sv_SE.person}"`: "write sv_SE.person",
|
`"{/sv_SE.person}"`: "write sv_SE.person",
|
||||||
"{.person.last}": "write person.last",
|
"{.person.last}": "write person.last",
|
||||||
"/sv_SE.person": "write sv_SE.person",
|
"/sv_SE.person": "write sv_SE.person",
|
||||||
|
` "{/sv_SE.person}"`: "write sv_SE.person",
|
||||||
|
`{"format":"{/sv_SE.person}"}`: "write sv_SE.person",
|
||||||
|
"//sv_SE.person": "write sv_SE.person",
|
||||||
} {
|
} {
|
||||||
if _, err := IsTemplate(arg); err == nil || !strings.Contains(err.Error(), want) {
|
if _, err := IsTemplate(arg); err == nil || !strings.Contains(err.Error(), want) {
|
||||||
t.Errorf("IsTemplate(%q) = %v; want it rejected naming %s", arg, err, want)
|
t.Errorf("IsTemplate(%q) = %v; want it rejected naming %s", arg, err, want)
|
||||||
|
|||||||
@@ -118,6 +118,13 @@ func TestFakeIsSafeForConcurrentUse(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
tmpl.Fake()
|
tmpl.Fake()
|
||||||
|
var u struct {
|
||||||
|
Last string `fake:"sv_SE.person.last"`
|
||||||
|
}
|
||||||
|
if err := f.FakeStruct(&u); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,12 +31,15 @@ func (f *Generator) FakeStruct(v any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// structResult is what compiling a struct type settled: its shape, or why it cannot be filled.
|
|
||||||
type structResult struct {
|
type structResult struct {
|
||||||
shape *structShape
|
shape *structShape
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// maxStructRecords caps the records one struct type fills, which pointers between struct
|
||||||
|
// types multiply along every path.
|
||||||
|
const maxStructRecords = 1 << 10
|
||||||
|
|
||||||
// structShapeOf compiles a struct type once and remembers the answer. Callers hold the
|
// structShapeOf compiles a struct type once and remembers the answer. Callers hold the
|
||||||
// generator's lock.
|
// generator's lock.
|
||||||
func (f *Generator) structShapeOf(t reflect.Type) (*structShape, error) {
|
func (f *Generator) structShapeOf(t reflect.Type) (*structShape, error) {
|
||||||
@@ -47,7 +50,8 @@ func (f *Generator) structShapeOf(t reflect.Type) (*structShape, error) {
|
|||||||
if label == "" {
|
if label == "" {
|
||||||
label = "struct"
|
label = "struct"
|
||||||
}
|
}
|
||||||
shape, err := compileStruct(f.categories, t, label, map[reflect.Type]bool{})
|
sc := &structCompile{root: f.categories, visiting: map[reflect.Type]bool{}, records: maxStructRecords}
|
||||||
|
shape, err := sc.record(t, label)
|
||||||
if err == nil && shape.empty() {
|
if err == nil && shape.empty() {
|
||||||
err = fmt.Errorf("%s has no fake tags, so nothing to fill", t)
|
err = fmt.Errorf("%s has no fake tags, so nothing to fill", t)
|
||||||
}
|
}
|
||||||
@@ -75,29 +79,38 @@ type nestedStruct struct {
|
|||||||
|
|
||||||
func (s *structShape) empty() bool { return s.record == nil && len(s.nested) == 0 }
|
func (s *structShape) empty() bool { return s.record == nil && len(s.nested) == 0 }
|
||||||
|
|
||||||
// structFields gathers what one struct type fills: its tagged fields, those its embedded
|
// structCompile is what compiling one struct type shares across the records it reaches: the
|
||||||
// structs promote included, as the tags of one record, and its named struct fields as nested
|
// loaded tree, the types compiling or embedded above, so a pointer back to one is left alone
|
||||||
// records. visiting holds the types compiling or embedded above, so a pointer back to one is
|
// rather than filled without end, and how many more records it may build.
|
||||||
// left alone rather than filled without end.
|
type structCompile struct {
|
||||||
type structFields struct {
|
|
||||||
root map[string]node
|
root map[string]node
|
||||||
t reflect.Type
|
|
||||||
label string
|
|
||||||
visiting map[reflect.Type]bool
|
visiting map[reflect.Type]bool
|
||||||
tags map[string]any
|
records int
|
||||||
shape *structShape
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// compileStruct compiles struct type t, naming its fields from label.
|
// structFields gathers what one struct type fills: its tagged fields, those its embedded
|
||||||
func compileStruct(root map[string]node, t reflect.Type, label string, visiting map[reflect.Type]bool) (*structShape, error) {
|
// structs promote included, as the tags of one record, and its named struct fields as nested
|
||||||
visiting[t] = true
|
// records.
|
||||||
defer delete(visiting, t)
|
type structFields struct {
|
||||||
c := &structFields{root: root, t: t, label: label, visiting: visiting, tags: map[string]any{}, shape: &structShape{}}
|
*structCompile
|
||||||
|
t reflect.Type
|
||||||
|
label string
|
||||||
|
tags map[string]any
|
||||||
|
shape *structShape
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *structCompile) record(t reflect.Type, label string) (*structShape, error) {
|
||||||
|
if sc.records--; sc.records < 0 {
|
||||||
|
return nil, fmt.Errorf(`%s: the struct fields reach more than %d records; leave a pointer unfilled with fake:"-"`, label, maxStructRecords)
|
||||||
|
}
|
||||||
|
sc.visiting[t] = true
|
||||||
|
defer delete(sc.visiting, t)
|
||||||
|
c := &structFields{structCompile: sc, t: t, label: label, tags: map[string]any{}, shape: &structShape{}}
|
||||||
if err := c.walk(t, nil); err != nil {
|
if err := c.walk(t, nil); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(c.tags) > 0 {
|
if len(c.tags) > 0 {
|
||||||
if err := c.shape.compileRecord(root, t, label, c.tags); err != nil {
|
if err := c.shape.compileRecord(sc.root, t, label, c.tags); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,7 +183,6 @@ func fieldPath(t reflect.Type, index []int) string {
|
|||||||
return strings.Join(names, ".")
|
return strings.Join(names, ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
// embed gathers the fields an embedded struct promotes into c's record.
|
|
||||||
func (c *structFields) embed(sf reflect.StructField, elem reflect.Type) error {
|
func (c *structFields) embed(sf reflect.StructField, elem reflect.Type) error {
|
||||||
c.visiting[elem] = true
|
c.visiting[elem] = true
|
||||||
defer delete(c.visiting, elem)
|
defer delete(c.visiting, elem)
|
||||||
@@ -179,14 +191,13 @@ func (c *structFields) embed(sf reflect.StructField, elem reflect.Type) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if sf.Type.Kind() == reflect.Pointer && !sf.IsExported() && (len(c.tags) > tags || len(c.shape.nested) > nested) {
|
if sf.Type.Kind() == reflect.Pointer && !sf.IsExported() && (len(c.tags) > tags || len(c.shape.nested) > nested) {
|
||||||
return fmt.Errorf("%s.%s: an embedded pointer to an unexported type cannot be allocated, so the tags beneath it cannot fill; embed %s by value", c.label, fieldPath(c.t, sf.Index), elem)
|
return fmt.Errorf("%s.%s: an unexported embedded pointer field cannot be set, so the tags beneath it cannot fill; embed %s by value", c.label, fieldPath(c.t, sf.Index), elem)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// nest adds a named struct field, or a pointer to one, that carries tags as a record of its own.
|
|
||||||
func (c *structFields) nest(sf reflect.StructField, elem reflect.Type) error {
|
func (c *structFields) nest(sf reflect.StructField, elem reflect.Type) error {
|
||||||
nested, err := compileStruct(c.root, elem, c.label+"."+sf.Name, c.visiting)
|
nested, err := c.record(elem, c.label+"."+sf.Name)
|
||||||
if err != nil || nested.empty() {
|
if err != nil || nested.empty() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -264,7 +275,7 @@ func (s *structShape) compileRecord(root map[string]node, t reflect.Type, label
|
|||||||
}
|
}
|
||||||
|
|
||||||
// columnKind is what a field of one Go kind holds: the datatype its text proves as, the range a
|
// columnKind is what a field of one Go kind holds: the datatype its text proves as, the range a
|
||||||
// number of it stays in, and the kind to name when a value can pass that range.
|
// number of it stays in, and the kind to name when a value is not proven within that range.
|
||||||
type columnKind struct {
|
type columnKind struct {
|
||||||
datatype DataType
|
datatype DataType
|
||||||
lo, hi float64
|
lo, hi float64
|
||||||
@@ -288,23 +299,16 @@ var columnKinds = map[reflect.Kind]columnKind{
|
|||||||
reflect.Uint8: {DataTypeInteger, 0, math.MaxUint8, reflect.Int64},
|
reflect.Uint8: {DataTypeInteger, 0, math.MaxUint8, reflect.Int64},
|
||||||
}
|
}
|
||||||
|
|
||||||
// past is the bound of v a field of this kind cannot hold, if either is. An integer prints
|
// holds reports whether a field of this kind holds every value v proves. An integer prints
|
||||||
// whole, so its bounds round inward first.
|
// whole, so its bounds round inward first.
|
||||||
func (k columnKind) past(v proven) (float64, bool) {
|
func (k columnKind) holds(v proven) bool {
|
||||||
lo, hi := v.lo, v.hi
|
|
||||||
switch k.datatype {
|
switch k.datatype {
|
||||||
case DataTypeString, DataTypeBoolean:
|
case DataTypeString, DataTypeBoolean:
|
||||||
return 0, false
|
return true
|
||||||
case DataTypeInteger:
|
case DataTypeInteger:
|
||||||
lo, hi = math.Ceil(lo), math.Floor(hi)
|
return math.Ceil(v.lo) >= k.lo && math.Floor(v.hi) <= k.hi
|
||||||
}
|
}
|
||||||
switch {
|
return v.lo >= k.lo && v.hi <= k.hi
|
||||||
case lo < k.lo:
|
|
||||||
return lo, true
|
|
||||||
case hi > k.hi:
|
|
||||||
return hi, true
|
|
||||||
}
|
|
||||||
return 0, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkField rejects a column some render of which a field of Go type ft cannot hold: a null
|
// checkField rejects a column some render of which a field of Go type ft cannot hold: a null
|
||||||
@@ -326,8 +330,8 @@ func (p *valueProof) checkField(label string, ft reflect.Type, column node) erro
|
|||||||
if reason := v.not[kind.datatype]; reason != "" {
|
if reason := v.not[kind.datatype]; reason != "" {
|
||||||
return fmt.Errorf("%s (%s): %s", label, ft, reason)
|
return fmt.Errorf("%s (%s): %s", label, ft, reason)
|
||||||
}
|
}
|
||||||
if bound, over := kind.past(v); over {
|
if !kind.holds(v) {
|
||||||
return fmt.Errorf("%s (%s): %q can reach %s, past %s; make it %s", label, ft, it.format, strconv.FormatFloat(bound, 'g', -1, 64), elem.Kind(), kind.wider)
|
return fmt.Errorf("%s (%s): %q is not proven within %s; make it %s", label, ft, it.format, elem.Kind(), kind.wider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+29
-8
@@ -63,7 +63,8 @@ func TestFakeStructFillsTaggedFields(t *testing.T) {
|
|||||||
zips := map[string]string{"Stockholm": "111 22", "Tranås": "573 31"}
|
zips := map[string]string{"Stockholm": "111 22", "Tranås": "573 31"}
|
||||||
actives, nils := 0, 0
|
actives, nils := 0, 0
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
u, twin := structUser{Note: "keep"}, structUser{Note: "keep"}
|
oldNick, oldTwinNick := "old", "old"
|
||||||
|
u, twin := structUser{Nick: &oldNick, Note: "keep"}, structUser{Nick: &oldTwinNick, Note: "keep"}
|
||||||
if err := a.FakeStruct(&u); err != nil {
|
if err := a.FakeStruct(&u); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -79,7 +80,7 @@ func TestFakeStructFillsTaggedFields(t *testing.T) {
|
|||||||
t.Fatalf("places %+v, %+v, want each nested struct one place, the pointer allocated", u.Home, u.Work)
|
t.Fatalf("places %+v, %+v, want each nested struct one place, the pointer allocated", u.Home, u.Work)
|
||||||
case u.ID != int64(i+1) || u.Age < 18 || u.Age > 99 || u.Score < 0 || u.Score > 1 || u.Rank == nil || (*u.Rank != 1 && *u.Rank != 2):
|
case u.ID != int64(i+1) || u.Age < 18 || u.Age > 99 || u.Score < 0 || u.Score > 1 || u.Rank == nil || (*u.Rank != 1 && *u.Rank != 2):
|
||||||
t.Fatalf("typed fields %+v, want each the value its tag draws", u)
|
t.Fatalf("typed fields %+v, want each the value its tag draws", u)
|
||||||
case u.Nick != nil && *u.Nick != "bo", u.Note != "keep", u.hidden != (structPlace{}), u.Skip != nil:
|
case u.Nick != nil && *u.Nick != "bo", u.Note != "keep", u.hidden != (structPlace{}), u.Skip != nil, oldNick != "old":
|
||||||
t.Fatalf("%+v: want Nick nil or bo, and the untagged fields left as they were", u)
|
t.Fatalf("%+v: want Nick nil or bo, and the untagged fields left as they were", u)
|
||||||
}
|
}
|
||||||
if u.Active {
|
if u.Active {
|
||||||
@@ -173,16 +174,16 @@ func TestFakeStructErrors(t *testing.T) {
|
|||||||
}{}, `"Ada" is not an integer`},
|
}{}, `"Ada" is not an integer`},
|
||||||
{&struct {
|
{&struct {
|
||||||
A int8 `fake:"{int(0,300)}"`
|
A int8 `fake:"{int(0,300)}"`
|
||||||
}{}, `"{int(0,300)}" can reach 300, past int8; make it int64`},
|
}{}, `"{int(0,300)}" is not proven within int8; make it int64`},
|
||||||
{&struct {
|
{&struct {
|
||||||
A uint `fake:"{int(-1,5)}"`
|
A uint `fake:"{int(-1,5)}"`
|
||||||
}{}, `"{int(-1,5)}" can reach -1, past uint; make it int64`},
|
}{}, `"{int(-1,5)}" is not proven within uint; make it int64`},
|
||||||
{&struct {
|
{&struct {
|
||||||
A float32 `fake:"[\"1\",\"1e39\"]"`
|
A float32 `fake:"[\"1\",\"1e39\"]"`
|
||||||
}{}, `"1e39" can reach 1e+39, past float32; make it float64`},
|
}{}, `"1e39" is not proven within float32; make it float64`},
|
||||||
{&struct {
|
{&struct {
|
||||||
A int32 `fake:"{seq()}"`
|
A int32 `fake:"{seq()}"`
|
||||||
}{}, `"{seq()}" can reach 9.223372036854776e+18, past int32; make it int64`},
|
}{}, `"{seq()}" is not proven within int32; make it int64`},
|
||||||
{&struct {
|
{&struct {
|
||||||
A bool `fake:"{int(0,1)}"`
|
A bool `fake:"{int(0,1)}"`
|
||||||
}{}, "prints an integer, not a boolean"},
|
}{}, "prints an integer, not a boolean"},
|
||||||
@@ -213,7 +214,7 @@ func TestFakeStructErrors(t *testing.T) {
|
|||||||
{&struct {
|
{&struct {
|
||||||
A string `fake:"-"`
|
A string `fake:"-"`
|
||||||
}{}, `fake:"-" leaves a struct field unfilled`},
|
}{}, `fake:"-" leaves a struct field unfilled`},
|
||||||
{&struct{ *structGiven }{}, "an embedded pointer to an unexported type"},
|
{&struct{ *structGiven }{}, "an unexported embedded pointer field cannot be set"},
|
||||||
{&struct {
|
{&struct {
|
||||||
structGiven
|
structGiven
|
||||||
First string `fake:"person.last"`
|
First string `fake:"person.last"`
|
||||||
@@ -231,8 +232,28 @@ func TestFakeStructErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}{}, ": struct.Trip.A (int): {digits(3)} prints text"},
|
}{}, ": struct.Trip.A (int): {digits(3)} prints text"},
|
||||||
} {
|
} {
|
||||||
if err := f.FakeStruct(c.v); err == nil || !strings.Contains(err.Error(), c.want) {
|
err := f.FakeStruct(c.v)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), c.want) {
|
||||||
t.Errorf("FakeStruct(%T) = %v, want an error containing %q", c.v, err, c.want)
|
t.Errorf("FakeStruct(%T) = %v, want an error containing %q", c.v, err, c.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if again := f.FakeStruct(c.v); again == nil || again.Error() != err.Error() {
|
||||||
|
t.Errorf("FakeStruct(%T) again = %v, want the first call's error, %v", c.v, again, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeStructBoundsTheRecordsATypeReaches(t *testing.T) {
|
||||||
|
f := structData(t)
|
||||||
|
tree := reflect.TypeOf(structPlace{})
|
||||||
|
for depth := 1; depth <= 10; depth++ {
|
||||||
|
tree = reflect.StructOf([]reflect.StructField{{Name: "L", Type: reflect.PointerTo(tree)}, {Name: "R", Type: reflect.PointerTo(tree)}})
|
||||||
|
err := f.FakeStruct(reflect.New(tree).Interface())
|
||||||
|
switch {
|
||||||
|
case depth < 10 && err != nil:
|
||||||
|
t.Fatalf("a tree %d deep, %d records: FakeStruct = %v, want it filled", depth, 1<<(depth+1)-1, err)
|
||||||
|
case depth == 10 && (err == nil || !strings.Contains(err.Error(), "more than 1024 records") || !strings.Contains(err.Error(), `fake:"-"`)):
|
||||||
|
t.Errorf("a tree 10 deep, 2047 records: FakeStruct = %v, want it refused naming the cap and fake:\"-\"", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user