Struct-filling #14
@@ -75,6 +75,25 @@ func TestNoRecordAllocRegression(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoStructAllocRegression(t *testing.T) {
|
||||||
|
f, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{"x.json": {Data: []byte(`{"format":"","a":"x","b":"y","c":"z"}`)}}))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var v struct {
|
||||||
|
A string `fake:"x.a"`
|
||||||
|
B string `fake:"x.b"`
|
||||||
|
C string `fake:"x.c"`
|
||||||
|
}
|
||||||
|
if err := f.FakeStruct(&v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
const base = 5.0
|
||||||
|
if allocs := testing.AllocsPerRun(10000, func() { f.FakeStruct(&v) }); allocs > base*1.10 {
|
||||||
|
t.Errorf("FakeStruct: %.1f allocs/op regressed past %.1f (baseline %.1f + 10%%); compiling the type per call is the usual cause", allocs, base*1.10, base)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkNestedDepth25(b *testing.B) { benchPath(b, tmpData(b, "deep", nestedJSON(25)), "deep") }
|
func BenchmarkNestedDepth25(b *testing.B) { benchPath(b, tmpData(b, "deep", nestedJSON(25)), "deep") }
|
||||||
func BenchmarkNestedDepth100(b *testing.B) { benchPath(b, tmpData(b, "deep", nestedJSON(100)), "deep") }
|
func BenchmarkNestedDepth100(b *testing.B) { benchPath(b, tmpData(b, "deep", nestedJSON(100)), "deep") }
|
||||||
func BenchmarkWideTokens100(b *testing.B) {
|
func BenchmarkWideTokens100(b *testing.B) {
|
||||||
|
|||||||
+187
@@ -0,0 +1,187 @@
|
|||||||
|
package fejkdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type structPlace struct {
|
||||||
|
City string `fake:"place.city"`
|
||||||
|
Zip string `fake:"place.zip"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type structUser struct {
|
||||||
|
Active bool `fake:"[\"true\",\"false\"]"`
|
||||||
|
Age uint8 `fake:"{int(18,99)}"`
|
||||||
|
Email string `fake:"{lowercase(/person.first)}@example.com"`
|
||||||
|
First string `fake:"person.first"`
|
||||||
|
Home structPlace
|
||||||
|
ID int64 `fake:"{seq()}"`
|
||||||
|
Last string `fake:"person.last"`
|
||||||
|
Nick *string `fake:"[null,\"bo\"]"`
|
||||||
|
Note string
|
||||||
|
Rank *int `fake:"{\"format\":\"{r}\",\"r\":[\"1\",\"2\"]}"`
|
||||||
|
Score float32 `fake:"{float(0,1,2)}"`
|
||||||
|
Work *structPlace
|
||||||
|
hidden structPlace
|
||||||
|
}
|
||||||
|
|
||||||
|
type structLink struct {
|
||||||
|
Name string `fake:"person.first"`
|
||||||
|
Next *structLink
|
||||||
|
}
|
||||||
|
|
||||||
|
func structData(t *testing.T) *Generator {
|
||||||
|
t.Helper()
|
||||||
|
return newGenerator(t, writeData(t, map[string]string{
|
||||||
|
"person": `[{"format":"{first} {last}","first":"Ada","last":"Lovelace"},{"format":"{first} {last}","first":"Bo","last":"Ek"}]`,
|
||||||
|
"place": `[{"format":"{city}","city":"Stockholm","zip":"111 22"},{"format":"{city}","city":"Tranås","zip":"573 31"}]`,
|
||||||
|
"trip": `{"format":"","leg":[{"format":"{to}","to":"Oslo"},{"format":"{to}","to":"Rome"}]}`,
|
||||||
|
}), WithSeed(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeStructFillsTaggedFields(t *testing.T) {
|
||||||
|
a, b := structData(t), structData(t)
|
||||||
|
people := map[string]string{"Ada": "Lovelace", "Bo": "Ek"}
|
||||||
|
zips := map[string]string{"Stockholm": "111 22", "Tranås": "573 31"}
|
||||||
|
actives, nils := 0, 0
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
u, twin := structUser{Note: "keep"}, structUser{Note: "keep"}
|
||||||
|
if err := a.FakeStruct(&u); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.FakeStruct(&twin); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case !reflect.DeepEqual(u, twin):
|
||||||
|
t.Fatalf("same seed diverged: %+v != %+v", u, twin)
|
||||||
|
case people[u.First] != u.Last || u.Email != strings.ToLower(u.First)+"@example.com":
|
||||||
|
t.Fatalf("person fields %q %q %q, want one person drawn across the struct", u.First, u.Last, u.Email)
|
||||||
|
case zips[u.Home.City] != u.Home.Zip || u.Work == nil || zips[u.Work.City] != u.Work.Zip:
|
||||||
|
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):
|
||||||
|
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{}):
|
||||||
|
t.Fatalf("%+v: want Nick nil or bo, and the untagged fields left as they were", u)
|
||||||
|
}
|
||||||
|
if u.Active {
|
||||||
|
actives++
|
||||||
|
}
|
||||||
|
if u.Nick == nil {
|
||||||
|
nils++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if actives == 0 || actives == 100 || nils == 0 || nils == 100 {
|
||||||
|
t.Errorf("100 draws gave %d active and %d nil nicks, want both outcomes of each", actives, nils)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeStructDrawsANestedStructApart(t *testing.T) {
|
||||||
|
f := structData(t)
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
var trip struct{ From, To structPlace }
|
||||||
|
if err := f.FakeStruct(&trip); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if trip.From.City != trip.To.City {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Error("From and To drew one place in 100 trips; a nested struct is a record of its own, so each draws apart")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeStructLeavesAPointerBackAlone(t *testing.T) {
|
||||||
|
var l structLink
|
||||||
|
if err := structData(t).FakeStruct(&l); err != nil || l.Name == "" || l.Next != nil {
|
||||||
|
t.Errorf("FakeStruct = %v, %+v; want Name filled and Next, a pointer back to the struct being filled, left nil", err, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeStructErrors(t *testing.T) {
|
||||||
|
f := structData(t)
|
||||||
|
for _, c := range []struct {
|
||||||
|
v any
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{structUser{}, "fills a struct through a non-nil pointer, got fejkdata.structUser"},
|
||||||
|
{(*structUser)(nil), "through a non-nil pointer"},
|
||||||
|
{new(int), "through a non-nil pointer"},
|
||||||
|
{nil, "through a non-nil pointer"},
|
||||||
|
{&struct{ A string }{}, "has no fake tags"},
|
||||||
|
{&struct {
|
||||||
|
a string `fake:"person.first"`
|
||||||
|
}{}, ".a: unexported"},
|
||||||
|
{&struct {
|
||||||
|
A []string `fake:"person.first"`
|
||||||
|
}{}, ".A: a fake tag fills a string, bool, integer or float field, or a pointer to one, not []string"},
|
||||||
|
{&struct {
|
||||||
|
A **int `fake:"{int(1,9)}"`
|
||||||
|
}{}, "not **int"},
|
||||||
|
{&struct {
|
||||||
|
A structPlace `fake:"place"`
|
||||||
|
}{}, ".A: a struct field fills from the tags on its own fields"},
|
||||||
|
{&struct {
|
||||||
|
A int `fake:"{\"format\":\"{int(1,9)}\",\"datatype\":\"integer\"}"`
|
||||||
|
}{}, `its Go type int sets the datatype; drop "datatype"`},
|
||||||
|
{&struct {
|
||||||
|
A int `fake:"[null,\"{int(1,9)}\"]"`
|
||||||
|
}{}, "can draw null, which int cannot hold; make it *int"},
|
||||||
|
{&struct {
|
||||||
|
A int `fake:"{digits(3)}"`
|
||||||
|
}{}, ".A (int): {digits(3)} prints text, not an integer"},
|
||||||
|
{&struct {
|
||||||
|
A int `fake:"person.first"`
|
||||||
|
}{}, `"Ada" is not an integer`},
|
||||||
|
{&struct {
|
||||||
|
A int8 `fake:"{int(0,300)}"`
|
||||||
|
}{}, `"{int(0,300)}" is not proven within int8`},
|
||||||
|
{&struct {
|
||||||
|
A uint `fake:"{int(-1,5)}"`
|
||||||
|
}{}, `"{int(-1,5)}" is not proven within uint`},
|
||||||
|
{&struct {
|
||||||
|
A float32 `fake:"[\"1\",\"1e39\"]"`
|
||||||
|
}{}, `"1e39" is not proven within float32`},
|
||||||
|
{&struct {
|
||||||
|
A bool `fake:"{int(0,1)}"`
|
||||||
|
}{}, "prints an integer, not a boolean"},
|
||||||
|
{&struct {
|
||||||
|
A string `fake:"nope.x"`
|
||||||
|
}{}, `no entry "nope"`},
|
||||||
|
{&struct {
|
||||||
|
A string `fake:"{/person.first}"`
|
||||||
|
}{}, `write fake:"person.first"`},
|
||||||
|
{&struct {
|
||||||
|
A string `fake:"\"{/person.first}\""`
|
||||||
|
}{}, `write fake:"person.first"`},
|
||||||
|
{&struct {
|
||||||
|
A string `fake:"a|b"`
|
||||||
|
}{}, `contains "|"`},
|
||||||
|
{&struct {
|
||||||
|
A string `fake:""`
|
||||||
|
}{}, "is empty"},
|
||||||
|
{&struct {
|
||||||
|
A string `fake:"[abc]"`
|
||||||
|
}{}, `holds a "["`},
|
||||||
|
{&struct {
|
||||||
|
A string `fake:"{.person.first}"`
|
||||||
|
}{}, "write {/person.first}"},
|
||||||
|
{&struct {
|
||||||
|
A string `fake:"{x}"`
|
||||||
|
}{}, `no field "x"`},
|
||||||
|
{&struct {
|
||||||
|
A string `fake:"trip.leg"`
|
||||||
|
B string `fake:"trip.leg.to"`
|
||||||
|
}{}, "reads a path into"},
|
||||||
|
{&struct {
|
||||||
|
Trip struct {
|
||||||
|
A int `fake:"{digits(3)}"`
|
||||||
|
}
|
||||||
|
}{}, ".Trip.A (int): {digits(3)} prints text"},
|
||||||
|
} {
|
||||||
|
if err := f.FakeStruct(c.v); err == nil || !strings.Contains(err.Error(), c.want) {
|
||||||
|
t.Errorf("FakeStruct(%T) = %v, want an error containing %q", c.v, err, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user