From a548abcc02b3d17c80f3ada9c7a4ff321aa34ba8 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Tue, 15 Sep 2026 16:53:56 +0200 Subject: [PATCH] Tests: embedded fields join their struct's record, fake:"-" leaves a struct unfilled, integer ranges round inward, and a lone reference is refused as a path --- cmd/fejkdata/main_test.go | 47 +++++++++------------------- inline_test.go | 38 +++++++++++++++++++++++ struct_test.go | 64 +++++++++++++++++++++++++++++++++------ 3 files changed, 106 insertions(+), 43 deletions(-) diff --git a/cmd/fejkdata/main_test.go b/cmd/fejkdata/main_test.go index fc2c485..2e17b5c 100644 --- a/cmd/fejkdata/main_test.go +++ b/cmd/fejkdata/main_test.go @@ -305,42 +305,22 @@ func TestRunShippedDataByDefault(t *testing.T) { } func TestClassify(t *testing.T) { - for arg, want := range map[string]argKind{ - "sv_SE.person": argPath, - "person.last": argPath, - "name: {x}": argTemplate, // a { token: a path can never carry a brace - `{"format":"x"}`: argTemplate, - `["a","b"]`: argTemplate, // a JSON array carries no brace - `[1, 2]`: argTemplate, - ` ["a","b"]`: argTemplate, // padding is the template's own error, not a shape verdict - `"hello"`: argTemplate, // a JSON string, the spelling a format-only object names - } { - got, err := classify(arg) - if err != nil || got != want { + for arg, want := range map[string]argKind{"sv_SE.person": argPath, "name: {x}": argTemplate} { + if got, err := classify(arg); err != nil || got != want { t.Errorf("classify(%q) = %v, %v; want %v", arg, got, err, want) } } - for arg, want := range map[string]string{ - "[abc]": `holds a "["`, - "[abc].field": `holds a "["`, - "x[1]": `holds a "["`, - "a]b": `holds a "]"`, - "a}b": `holds a "}"`, - `"abc`: `holds a "\""`, - `"a]b`: `holds a "\""`, // the opener the reader typed, not the bracket behind it - } { - _, err := classify(arg) - if err == nil || !strings.Contains(err.Error(), want) { - t.Errorf("classify(%q) = %v; want it rejected naming %s", arg, err, want) - } + if _, err := classify("[abc]"); err == nil || !strings.Contains(err.Error(), `holds a "["`) { + t.Errorf("classify([abc]) = %v; want it rejected naming the bracket", err) } } func TestUsageReferencesResolve(t *testing.T) { for _, token := range regexp.MustCompile(`\{/[^}]+\}`).FindAllString(usage, -1) { - code, out, errb := runOut("--seed", "1", token) + path := token[2 : len(token)-1] + code, out, errb := runOut("--seed", "1", path) if code != 0 || strings.TrimSpace(out) == "" { - t.Errorf("usage advertises %s: run = %d, %q, stderr %q", token, code, out, errb) + t.Errorf("usage advertises %s: run %s = %d, %q, stderr %q", token, path, code, out, errb) } } } @@ -373,12 +353,13 @@ func TestRunInlineTemplate(t *testing.T) { func TestRunTemplateMisuse(t *testing.T) { for arg, want := range map[string]string{ - "{bad": "unterminated", - "[red,green]": "names no template either", - `{"format":"x"}`: "is a string", - "{/no.such.path}": "no entry", - "x[1]": "names no template either", - ` ["a","b"] `: "may not be padded", + "{bad": "unterminated", + "[red,green]": "names no template either", + `{"format":"x"}`: "is a string", + "name: {/no.such.path}": "no entry", + "{/sv_SE.person}": "write sv_SE.person", + "x[1]": "names no template either", + ` ["a","b"] `: "may not be padded", } { code, out, errb := runOut("--seed", "1", arg) if code != 2 || out != "" || !strings.Contains(errb, "try 'fejkdata --help'") || !strings.Contains(errb, want) { diff --git a/inline_test.go b/inline_test.go index f44e164..bb5b7f5 100644 --- a/inline_test.go +++ b/inline_test.go @@ -150,6 +150,44 @@ func TestNewTemplateReusable(t *testing.T) { } } +func TestIsTemplate(t *testing.T) { + for arg, want := range map[string]bool{ + "sv_SE.person": false, + "person.last": false, + "name: {x}": true, + `{"format":"x"}`: true, + `["a","b"]`: true, + `[1, 2]`: true, + ` ["a","b"]`: true, // padding is the template's own error, not a shape verdict + `"hello"`: true, + "{/a}{/b}": true, + "{/a|/b}": true, + "{uppercase(/a)}": true, + "{{/a}}": true, + `"{/a} x"`: true, + } { + if got, err := IsTemplate(arg); err != nil || got != want { + t.Errorf("IsTemplate(%q) = %v, %v; want %v", arg, got, err, want) + } + } + for arg, want := range map[string]string{ + "[abc]": `holds a "["`, + "[abc].field": `holds a "["`, + "x[1]": `holds a "["`, + "a]b": `holds a "]"`, + "a}b": `holds a "}"`, + `"abc`: `holds a "\""`, + `"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}"`: "write sv_SE.person", + "{.person.last}": "write person.last", + } { + if _, err := IsTemplate(arg); err == nil || !strings.Contains(err.Error(), want) { + t.Errorf("IsTemplate(%q) = %v; want it rejected naming %s", arg, err, want) + } + } +} + func TestFakeTemplateRepeatBound(t *testing.T) { f := shipped(t) _, err := f.FakeTemplate(`{"format":"{x}","repeat":200,"x":{"format":"{y}","repeat":200,"y":{"format":"z","repeat":200}}}`) diff --git a/struct_test.go b/struct_test.go index 3fbaa37..7e5c18b 100644 --- a/struct_test.go +++ b/struct_test.go @@ -19,14 +19,30 @@ type structUser struct { Home structPlace ID int64 `fake:"{seq()}"` Last string `fake:"person.last"` + Level uint8 `fake:"{float(0,255,0)}"` Nick *string `fake:"[null,\"bo\"]"` Note string - Rank *int `fake:"{\"format\":\"{r}\",\"r\":[\"1\",\"2\"]}"` - Score float32 `fake:"{float(0,1,2)}"` + Rank *int `fake:"{\"format\":\"{r}\",\"r\":[\"1\",\"2\"]}"` + Score float32 `fake:"{float(0,1,2)}"` + Skip *structPlace `fake:"-"` Work *structPlace hidden structPlace } +type structGiven struct { + First string `fake:"person.first"` +} + +type StructFamily struct { + Last string `fake:"person.last"` +} + +type structEmployee struct { + structGiven + *StructFamily + Email string `fake:"{lowercase(/person.first)}@example.com"` +} + type structLink struct { Name string `fake:"person.first"` Next *structLink @@ -63,7 +79,7 @@ func TestFakeStructFillsTaggedFields(t *testing.T) { 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{}): + case u.Nick != nil && *u.Nick != "bo", u.Note != "keep", u.hidden != (structPlace{}), u.Skip != nil: t.Fatalf("%+v: want Nick nil or bo, and the untagged fields left as they were", u) } if u.Active { @@ -78,6 +94,20 @@ func TestFakeStructFillsTaggedFields(t *testing.T) { } } +func TestFakeStructFillsEmbeddedFieldsIntoItsRecord(t *testing.T) { + f := structData(t) + people := map[string]string{"Ada": "Lovelace", "Bo": "Ek"} + for i := 0; i < 100; i++ { + var e structEmployee + if err := f.FakeStruct(&e); err != nil { + t.Fatal(err) + } + if e.StructFamily == nil || people[e.First] != e.Last || e.Email != strings.ToLower(e.First)+"@example.com" { + t.Fatalf("%+v, %+v: want the promoted fields one person with the struct's own, the embedded pointer allocated", e, e.StructFamily) + } + } +} + func TestFakeStructDrawsANestedStructApart(t *testing.T) { f := structData(t) for i := 0; i < 100; i++ { @@ -136,13 +166,16 @@ func TestFakeStructErrors(t *testing.T) { }{}, `"Ada" is not an integer`}, {&struct { A int8 `fake:"{int(0,300)}"` - }{}, `"{int(0,300)}" is not proven within int8`}, + }{}, `"{int(0,300)}" can reach 300, past int8; make it int64`}, {&struct { A uint `fake:"{int(-1,5)}"` - }{}, `"{int(-1,5)}" is not proven within uint`}, + }{}, `"{int(-1,5)}" can reach -1, past uint; make it int64`}, {&struct { A float32 `fake:"[\"1\",\"1e39\"]"` - }{}, `"1e39" is not proven within float32`}, + }{}, `"1e39" can reach 1e+39, past float32; make it float64`}, + {&struct { + A int32 `fake:"{seq()}"` + }{}, `"{seq()}" can reach 9.223372036854776e+18, past int32; make it int64`}, {&struct { A bool `fake:"{int(0,1)}"` }{}, "prints an integer, not a boolean"}, @@ -151,10 +184,10 @@ func TestFakeStructErrors(t *testing.T) { }{}, `no entry "nope"`}, {&struct { A string `fake:"{/person.first}"` - }{}, `write fake:"person.first"`}, + }{}, "is the path person.first written as a template; write person.first"}, {&struct { A string `fake:"\"{/person.first}\""` - }{}, `write fake:"person.first"`}, + }{}, "is the path person.first written as a template; write person.first"}, {&struct { A string `fake:"a|b"` }{}, `contains "|"`}, @@ -165,8 +198,19 @@ func TestFakeStructErrors(t *testing.T) { A string `fake:"[abc]"` }{}, `holds a "["`}, {&struct { - A string `fake:"{.person.first}"` + A string `fake:"{.person.first} x"` }{}, "write {/person.first}"}, + {&struct { + A string `fake:"/person.first"` + }{}, "write person.first"}, + {&struct { + A string `fake:"-"` + }{}, `fake:"-" leaves a struct field unfilled`}, + {&struct{ *structGiven }{}, "an embedded pointer to an unexported type"}, + {&struct { + structGiven + First string `fake:"person.last"` + }{}, "struct.structGiven.First: hidden by another field named First"}, {&struct { A string `fake:"{x}"` }{}, `no field "x"`}, @@ -178,7 +222,7 @@ func TestFakeStructErrors(t *testing.T) { Trip struct { A int `fake:"{digits(3)}"` } - }{}, ".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) { t.Errorf("FakeStruct(%T) = %v, want an error containing %q", c.v, err, c.want)