diff --git a/drawfence_test.go b/drawfence_test.go index 113a316..85bddbf 100644 --- a/drawfence_test.go +++ b/drawfence_test.go @@ -57,10 +57,7 @@ func fenceRoots(t *testing.T, f *Generator) []fenceRoot { func TestPairsFindWhatAWholeReplayFinds(t *testing.T) { corpus := fenceCorpus(t) roots := fenceRoots(t, corpus) - shipped, err := New() - if err != nil { - t.Fatalf("New() = %v", err) - } + shipped := newShippedWhole(t) _ = walkNodes(shipped.root.children, func(path string, n node) error { if tm, ok := n.(*template); ok { roots = append(roots, fenceRoot{path, tm}) diff --git a/shape_test.go b/shape_test.go index 50d48f2..a912b67 100644 --- a/shape_test.go +++ b/shape_test.go @@ -12,11 +12,7 @@ const shapePin = "testdata/shipped_shape.txt" // REPIN=1 rewrites the pin. func TestShippedShapeIsPinned(t *testing.T) { - f, err := New(WithSeed(1)) - if err != nil { - t.Fatal(err) - } - got := shippedShape(f) + got := shippedShape(newShippedWhole(t, WithSeed(1))) if os.Getenv("REPIN") == "1" { if err := os.WriteFile(shapePin, []byte(got), 0o644); err != nil { t.Fatal(err) diff --git a/shipped_test.go b/shipped_test.go new file mode 100644 index 0000000..13145dc --- /dev/null +++ b/shipped_test.go @@ -0,0 +1,209 @@ +package fejkdata + +import ( + "bytes" + "fmt" + "go/format" + "io/fs" + "os" + "reflect" + "slices" + "sort" + "sync" + "testing" +) + +const shippedIndexFile = "shippedindex.go" + +// newShippedWhole loads every shipped category in New, as a --data-path load does, so a test +// reads the whole shipped tree. +func newShippedWhole(t testing.TB, opts ...Option) *Generator { + t.Helper() + data, err := fs.Sub(shippedFS, "data") + if err != nil { + t.Fatal(err) + } + f, err := New(append([]Option{WithoutShippedData(), WithDataFS(data)}, opts...)...) + if err != nil { + t.Fatalf("New() = %v", err) + } + return f +} + +// loadedCategories is the dot path of every category f has parsed and linked. +func loadedCategories(f *Generator) []string { + var out []string + var walk func(prefix string, g *folder) + walk = func(prefix string, g *folder) { + for _, name := range sortedNames(g.children) { + if sub, isFolder := g.children[name].(*folder); isFolder { + walk(join(prefix, name), sub) + continue + } + out = append(out, join(prefix, name)) + } + } + walk("", &f.root) + return out +} + +// REPIN=1 rewrites the index. +func TestShippedIndexIsCurrent(t *testing.T) { + got, err := shippedIndexSource(newShippedWhole(t)) + if err != nil { + t.Fatal(err) + } + if os.Getenv("REPIN") == "1" { + if err := os.WriteFile(shippedIndexFile, got, 0o644); err != nil { + t.Fatal(err) + } + return + } + want, err := os.ReadFile(shippedIndexFile) + if err != nil { + t.Fatal(err) + } + if string(got) != string(want) { + t.Fatalf("%s is stale for data/; rewrite it with REPIN=1", shippedIndexFile) + } +} + +func shippedIndexSource(whole *Generator) ([]byte, error) { + var b bytes.Buffer + b.WriteString("// Code generated by TestShippedIndexIsCurrent with REPIN=1. DO NOT EDIT.\n\npackage fejkdata\n\nvar shippedIndex = map[string]shippedCategory{\n") + for _, s := range categorySites(&whole.root) { + fmt.Fprintf(&b, "%q: {", s.path) + if t, isTable := s.n.(*table); isTable && t.parentIndex >= 0 { + fmt.Fprintf(&b, "parent: %q, ", t.header[t.parentIndex]) + } + ps := paths(s.n) + sort.Strings(ps) + fmt.Fprintf(&b, "paths: %#v},\n", ps) + } + b.WriteString("}\n") + return format.Source(b.Bytes()) +} + +func TestNewLoadsNoShippedCategory(t *testing.T) { + f, err := New() + if err != nil { + t.Fatalf("New() = %v", err) + } + if got := loadedCategories(f); len(got) > 0 { + t.Fatalf("New() loaded %v", got) + } +} + +func TestReachingACategoryLoadsWhatItReads(t *testing.T) { + for _, tc := range []struct { + reach func(f *Generator) error + want, absent []string + }{ + { + reach: func(f *Generator) error { _, err := f.Fake("sv_SE.person.first"); return err }, + want: []string{"sv_SE.first-name", "sv_SE.last-name", "sv_SE.person", "sv_SE.sex", "sv_SE.title"}, + }, + { + reach: func(f *Generator) error { _, err := f.Fake("geo.SE.region"); return err }, + want: []string{"geo.SE.locality", "geo.SE.municipality", "geo.SE.postal-code", "geo.SE.region", "geo.SE.street"}, + absent: []string{"geo.SE.address", "geo.US.region"}, + }, + { + reach: func(f *Generator) error { _, err := f.FakeTemplate("{/misc.territory[SE].name}"); return err }, + want: []string{"misc.territory", "misc.timezone"}, + absent: []string{"misc.tld"}, + }, + { + reach: func(f *Generator) error { _, err := f.FakeRecord("misc.uuid"); return err }, + want: []string{"misc.uuid"}, + absent: []string{"misc.objectid"}, + }, + { + reach: func(f *Generator) error { + var v struct { + Name string `fake:"sv_SE.first-name"` + } + return f.FakeStruct(&v) + }, + want: []string{"sv_SE.first-name"}, + absent: []string{"sv_SE.person"}, + }, + } { + f, err := New() + if err != nil { + t.Fatalf("New() = %v", err) + } + if err := tc.reach(f); err != nil { + t.Fatal(err) + } + got := loadedCategories(f) + for _, w := range tc.want { + if !slices.Contains(got, w) { + t.Errorf("loaded %v, want %s among them", got, w) + } + } + for _, a := range tc.absent { + if slices.Contains(got, a) { + t.Errorf("loaded %v, want %s left unloaded", got, a) + } + } + } +} + +func TestADataPathLoadsTheShippedSetWhole(t *testing.T) { + f, err := New(WithDataPath(writeData(t, map[string]string{"x": `"y"`}))) + if err != nil { + t.Fatalf("New() = %v", err) + } + if got, want := loadedCategories(f), loadedCategories(newShippedWhole(t, WithDataPath(writeData(t, map[string]string{"x": `"y"`})))); !reflect.DeepEqual(got, want) { + t.Fatalf("loaded %v, want %v", got, want) + } +} + +func TestOnDemandRendersAsTheWholeLoad(t *testing.T) { + whole := newShippedWhole(t) + paths := whole.List() + lazy, err := New() + if err != nil { + t.Fatalf("New() = %v", err) + } + if got := lazy.List(); !reflect.DeepEqual(got, paths) { + t.Fatalf("List() on demand = %v, want %v", got, paths) + } + a, err := New(WithSeed(7)) + if err != nil { + t.Fatal(err) + } + b := newShippedWhole(t, WithSeed(7)) + for _, p := range paths { + for range 3 { + got, gotErr := a.Fake(p) + want, wantErr := b.Fake(p) + if got != want || (gotErr == nil) != (wantErr == nil) { + t.Fatalf("Fake(%q) on demand = %q, %v; whole = %q, %v", p, got, gotErr, want, wantErr) + } + } + } +} + +func TestListRunsBesideAnOnDemandLoad(t *testing.T) { + f, err := New() + if err != nil { + t.Fatalf("New() = %v", err) + } + var wg sync.WaitGroup + for _, p := range []string{"sv_SE.person", "geo.US.address", "misc.territory"} { + wg.Add(2) + go func() { + defer wg.Done() + if _, err := f.Fake(p); err != nil { + t.Error(err) + } + }() + go func() { + defer wg.Done() + f.List() + }() + } + wg.Wait() +}