From 6a62840373cde596d82d4910900651beedc72a60 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sun, 11 Oct 2026 13:23:30 +0200 Subject: [PATCH] Test that a mounted module names a broken file beside its namespace, that a manifest refuses reads, and that each shipped module renders beside the modules its categories reach --- imports_test.go | 2 +- index.go | 10 ++--- inline.go | 9 ++-- reference.go | 21 --------- reference_test.go | 2 +- shipped_test.go | 110 ++++++++-------------------------------------- struct.go | 9 ++-- 7 files changed, 32 insertions(+), 131 deletions(-) diff --git a/imports_test.go b/imports_test.go index a43043b..1429d95 100644 --- a/imports_test.go +++ b/imports_test.go @@ -38,7 +38,7 @@ func TestPackageImports(t *testing.T) { "data/misc": nil, "data/svse": nil, "internal/builtinfunc": {"internal/datatype", "internal/drawstate", "internal/grammar", "internal/invariant", "internal/proven"}, - "internal/datafiles": {"internal/grammar", "internal/jsonvalue"}, + "internal/datafiles": {"internal/grammar", "internal/invariant", "internal/jsonvalue"}, "internal/datatype": nil, "internal/drawstate": nil, "internal/grammar": nil, diff --git a/index.go b/index.go index debdf42..fa1c67e 100644 --- a/index.go +++ b/index.go @@ -58,7 +58,7 @@ func (f *Generator) loadCallerPath(path string) (string, []string, error) { return "", nil, fmt.Errorf("fejkdata: %w", err) } if c, ok := categoryOn(&f.root, segs); ok && c.unloaded() { - if err := loadReached(&f.root, f.manifestReads, nil, []categoryAt{c}); err != nil { + if err := loadReached(&f.root, nil, []categoryAt{c}); err != nil { return "", nil, fmt.Errorf("fejkdata: %w", loadError{err}) } } @@ -105,9 +105,8 @@ func needs(root *folder, s categorySite) []categoryAt { // loadReached loads the unloaded categories among wanted and every unloaded category they // or sites need, then runs sites and all it loaded through a whole load's pipeline. On an -// error it puts back what it loaded, so the next call fails the same way, and notes which -// namespace of reads, every namespace the loaded manifests read, nothing in root provides. -func loadReached(root *folder, reads []string, sites []categorySite, wanted []categoryAt) error { +// error it puts back what it loaded, so the next call fails the same way. +func loadReached(root *folder, sites []categorySite, wanted []categoryAt) error { queue := wanted for _, s := range sites { queue = append(queue, needs(root, s)...) @@ -119,9 +118,8 @@ func loadReached(root *folder, reads []string, sites []categorySite, wanted []ca } if err != nil { b.putBack() - return withMissingModules(err, root, reads) } - return nil + return err } // batch is the categories one loadReached loaded, each beside the entry it was loaded from. diff --git a/inline.go b/inline.go index 0e3fdfc..a9e15b3 100644 --- a/inline.go +++ b/inline.go @@ -36,7 +36,7 @@ func (f *Generator) NewTemplate(input string) (*Template, error) { } f.mu.Lock() defer f.mu.Unlock() - if err := loadInline(&f.root, f.manifestReads, n, "template", false); err != nil { + if err := loadInline(&f.root, n, "template", false); err != nil { return nil, f.noDataNote(fmt.Errorf("fejkdata: %w", err)) } return &Template{g: f, n: n}, nil @@ -84,14 +84,13 @@ func inputValue(input string) jsonvalue.Value { return raw } -// loadInline loads the unloaded categories n reads, then runs n through the pipeline against root; -// reads is every namespace the loaded manifests read. -func loadInline(root *folder, reads []string, n node, label string, typedByGo bool) error { +// loadInline loads the unloaded categories n reads, then runs n through the pipeline against root. +func loadInline(root *folder, n node, label string, typedByGo bool) error { nodes := inlineNodes(n, label) if err := refuseFolderRefs(nodes); err != nil { return err } - if err := loadReached(root, reads, nil, referenced(root, nil, nodes)); err != nil { + if err := loadReached(root, nil, referenced(root, nil, nodes)); err != nil { return loadError{err} } return pipeline{ diff --git a/reference.go b/reference.go index 8313e54..60e7433 100644 --- a/reference.go +++ b/reference.go @@ -121,27 +121,6 @@ type noEntry struct{ seg string } func (e noEntry) Error() string { return fmt.Sprintf("no entry %q", e.seg) } -// withMissingModules adds to err, where a path names nothing, each namespace of reads that -// nothing in root provides, as the modules a loaded manifest reads and none loaded; or else -// that no loaded manifest names a module to add, the mistake being the path's own. -func withMissingModules(err error, root *folder, reads []string) error { - var ne noEntry - if !errors.As(err, &ne) { - return err - } - var missing []string - for _, namespace := range reads { - segs := strings.Split(namespace, ".") - if _, i := folderAt(root, segs); i < len(segs) { - missing = append(missing, namespace) - } - } - if len(missing) == 0 { - return fmt.Errorf("%w, and no loaded manifest names a module to add", err) - } - return fmt.Errorf("%w; is a module missing? The loaded modules read %s, which nothing loaded provides", err, strings.Join(missing, ", ")) -} - // noDataNote adds to err, where a segment names nothing and f holds no data at all, that no // data is loaded. func (f *Generator) noDataNote(err error) error { diff --git a/reference_test.go b/reference_test.go index 7b04e3e..5c6b392 100644 --- a/reference_test.go +++ b/reference_test.go @@ -206,7 +206,7 @@ func TestNewErrorPathIsCanonical(t *testing.T) { { "bad reference reached through another reference", map[string]string{"a": `"{/b}"`, "b": `"{/nope}"`}, - `fejkdata: b: reference {/nope}: no entry "nope", and no loaded manifest names a module to add`, + `fejkdata: b: reference {/nope}: no entry "nope"`, }, } for _, c := range cases { diff --git a/shipped_test.go b/shipped_test.go index 65c342d..2eedcfd 100644 --- a/shipped_test.go +++ b/shipped_test.go @@ -1,7 +1,6 @@ package fejkdata import ( - "bytes" "encoding/json" "errors" "fmt" @@ -110,7 +109,7 @@ func loadedCategories(f *Generator) []string { // REGENERATE=1 rewrites the manifests. func TestShippedManifestsAreCurrent(t *testing.T) { modules := shippedModules(t) - manifests, err := shippedManifests(newShippedWhole(t), modules) + manifests, _, err := shippedManifests(newShippedWhole(t), modules) if err != nil { t.Fatal(err) } @@ -133,8 +132,9 @@ func TestShippedManifestsAreCurrent(t *testing.T) { } // shippedManifests is each module's manifest, read from whole's tree: its index, one category a -// line, and every other module it reads, directly or through another. -func shippedManifests(whole *Generator, modules []shippedModule) ([][]byte, error) { +// line, and its namespace; and the namespace of every other module each reads, directly or +// through another. +func shippedManifests(whole *Generator, modules []shippedModule) ([][]byte, [][]string, error) { owner := func(path string) int { return slices.IndexFunc(modules, func(m shippedModule) bool { return m.holds(path) }) } @@ -143,11 +143,11 @@ func shippedManifests(whole *Generator, modules []shippedModule) ([][]byte, erro for _, s := range categorySites(&whole.root) { i := owner(s.path) if i < 0 { - return nil, fmt.Errorf("no shipped module holds %s", s.path) + return nil, nil, fmt.Errorf("no shipped module holds %s", s.path) } line, err := json.Marshal(indexEntry(s)) if err != nil { - return nil, err + return nil, nil, err } key, _ := json.Marshal(s.path) index[i] = append(index[i], "\n\t\t"+string(key)+": "+string(line)) @@ -158,18 +158,13 @@ func shippedManifests(whole *Generator, modules []shippedModule) ([][]byte, erro } } out := make([][]byte, len(modules)) + reads := make([][]string, len(modules)) for i := range modules { - var b bytes.Buffer namespace, _ := json.Marshal(modules[i].namespace) - b.WriteString("{\n\t\"index\": {" + strings.Join(index[i], ",") + "\n\t},\n\t\"namespace\": " + string(namespace)) - if reads := readsOf(i, direct, modules); len(reads) > 0 { - line, _ := json.Marshal(reads) - b.WriteString(",\n\t\"reads\": " + string(line)) - } - b.WriteString("\n}\n") - out[i] = b.Bytes() + out[i] = []byte("{\n\t\"index\": {" + strings.Join(index[i], ",") + "\n\t},\n\t\"namespace\": " + string(namespace) + "\n}\n") + reads[i] = readsOf(i, direct, modules) } - return out, nil + return out, reads, nil } // readsOf is the namespace of every module module i reads, directly or through another. @@ -205,13 +200,17 @@ func shippedIndex(t *testing.T) map[string]datafiles.IndexEntry { func TestEachShippedModuleRendersBesideTheModulesItReads(t *testing.T) { modules := shippedModules(t) - for _, m := range modules { + _, reads, err := shippedManifests(newShippedWhole(t), modules) + if err != nil { + t.Fatal(err) + } + for i, m := range modules { manifest, err := datafiles.FS(m.fsys).Manifest() if err != nil { t.Fatal(err) } set := []fs.FS{m.fsys} - for _, read := range manifest.Reads { + for _, read := range reads[i] { i := slices.IndexFunc(modules, func(r shippedModule) bool { return r.namespace == read }) if i < 0 { t.Fatalf("%s reads %s, which no shipped module is", m.dir, read) @@ -230,83 +229,12 @@ func TestEachShippedModuleRendersBesideTheModulesItReads(t *testing.T) { } } -func TestAReadNoModuleProvidesNamesTheReadersModules(t *testing.T) { - f, err := New(WithDataFS(svse.FS)) - if err != nil { - t.Fatalf("New(WithDataFS(svse.FS)) = %v", err) - } - if _, err := f.Fake("sv_SE.address"); err == nil || !strings.Contains(err.Error(), "is a module missing? The loaded modules read geo.SE, which nothing loaded provides") { - t.Errorf(`Fake("sv_SE.address") = %v, want the module it reads named`, err) - } - for name, opt := range map[string]Option{ - "no manifest": WithDataPath(writeData(t, map[string]string{"x": `"{/y}"`})), - "no reads in one": WithDataFS(fstest.MapFS{".fejkdata.json": {Data: []byte(`{"index": {"x": {"paths": [""]}}}`)}, "x.json": {Data: []byte(`"{/y}"`)}}), - } { - f, err := New(opt) - if err == nil { - _, err = f.Fake("x") - } - if err == nil || !strings.Contains(err.Error(), "no loaded manifest names a module to add") { - t.Errorf("%s: x = %v, want it said that no loaded module names one", name, err) - } - } -} - -func TestADataPathManifestNamesWhatItReads(t *testing.T) { - dir := writeFiles(t, map[string]string{ - ".fejkdata.json": `{"reads": ["places"]}`, - "x.json": `"{/place}"`, - }) - if _, err := New(WithDataPath(dir)); err == nil || !strings.Contains(err.Error(), "is a module missing? The loaded modules read places, which nothing loaded provides") { - t.Errorf("New = %v, want the module x reads named", err) - } -} - -func TestAnIndexedModuleNamesWhatItReads(t *testing.T) { - f, err := New(WithDataFS(fstest.MapFS{ - ".fejkdata.json": {Data: []byte(`{"index": {"x": {"paths": [""]}}, "reads": ["places"]}`)}, - "x.json": {Data: []byte(`"{/place}"`)}, - })) - if err != nil { - t.Fatalf("New = %v", err) - } - const note = "is a module missing? The loaded modules read places, which nothing loaded provides" - for name, reach := range map[string]func() error{ - "Fake": func() error { _, err := f.Fake("x"); return err }, - "FakeTemplate": func() error { _, err := f.FakeTemplate("{/x}"); return err }, - "FakeStruct": func() error { - return f.FakeStruct(&struct { - A string `fake:"/x"` - }{}) - }, - } { - if err := reach(); err == nil || strings.Count(err.Error(), note) != 1 { - t.Errorf("%s reaching x = %v, want the module x reads named once", name, err) - } - } - if _, err := f.FakeTemplate("{/nope}"); err == nil || strings.Contains(err.Error(), "module") { - t.Errorf("FakeTemplate({/nope}) = %v, want no module named: no load failed", err) - } -} - -func TestATypoUnderLoadedModulesNamesNoModule(t *testing.T) { - for name, opts := range map[string][]Option{ - "every shipped module": {withShipped()}, - "a stand-in providing what a module reads": {WithDataFS(svse.FS, fstest.MapFS{"geo/SE/address.json": {Data: []byte(`"x"`)}})}, - } { - _, err := New(append(opts, WithDataPath(writeData(t, map[string]string{"x": `"{/sv_SE.persn}"`})))...) - if err == nil || !strings.HasSuffix(err.Error(), `no entry "persn", and no loaded manifest names a module to add`) { - t.Errorf("%s: New = %v, want the typo reported with no module to add", name, err) - } - } -} - func TestAMountedModuleNamesItsFilesByItsNamespace(t *testing.T) { _, err := New(WithDataFS(fstest.MapFS{ ".fejkdata.json": {Data: []byte(`{"namespace": "my.data"}`)}, "word.json": {Data: []byte(`{`)}, })) - if err == nil || !strings.Contains(err.Error(), "my/data/word.json") { + if err == nil || !strings.Contains(err.Error(), "my.data (word.json)") { t.Errorf("New = %v, want the broken file named under its namespace", err) } } @@ -505,9 +433,7 @@ func TestABrokenManifestFailsNewNamingEveryMistake(t *testing.T) { `{"index": {"a": {"paths": ["", "x", "x"]}}}`: {`paths item 3 repeats "x"`}, `{"index": {"a": {"paths": [""], "parent": "b"}}}`: {`parent "b" names no entry`}, `{"index": {"a": {"paths": [""], "reads": ["b"]}}}`: {`unknown key "reads"`}, - `{"reads": "b"}`: {"reads must be a list, not a string"}, - `{"reads": ["", 3]}`: {"reads item 1:", "reads item 2 must be a string, not a number"}, - `{"reads": ["a", "a"]}`: {`reads item 2 repeats "a"`}, + `{"reads": ["b"]}`: {`unknown key "reads"; a manifest holds index and namespace`}, `{"namespace": 5}`: {"namespace must be a string, not a number"}, `{"namespace": "a..b"}`: {`namespace "a..b"`}, `{"index": {"b.x": {"paths": [""]}}, "namespace": "a"}`: {`index entry "b.x" lies outside the namespace "a"`}, diff --git a/struct.go b/struct.go index 033b5e9..61a41eb 100644 --- a/struct.go +++ b/struct.go @@ -55,7 +55,7 @@ func (f *Generator) structShapeOf(t reflect.Type) (*structShape, error) { if label == "" { label = "struct" } - sc := &structCompile{root: &f.root, reads: f.manifestReads, visiting: map[reflect.Type]bool{}, structsLeft: maxStructs} + sc := &structCompile{root: &f.root, visiting: map[reflect.Type]bool{}, structsLeft: maxStructs} shape, err := sc.compileShape(t, label) if f.structs == nil { f.structs = map[reflect.Type]structResult{} @@ -85,7 +85,6 @@ func (s *structShape) empty() bool { return s.record == nil && len(s.nested) == // rather than filled without end, and how many more structs it may walk. type structCompile struct { root *folder - reads []string visiting map[reflect.Type]bool structsLeft int } @@ -112,7 +111,7 @@ func (sc *structCompile) compileShape(t reflect.Type, label string) (*structShap return nil, err } if len(c.tags) > 0 { - if err := c.shape.compileRecord(sc.root, sc.reads, t, label, c.tags); err != nil { + if err := c.shape.compileRecord(sc.root, t, label, c.tags); err != nil { return nil, err } } @@ -256,13 +255,13 @@ func checkTaggedType(sf reflect.StructField) error { // compileRecord compiles the tagged fields of t as one record, and proves each column holds // only what its field's Go type can. -func (s *structShape) compileRecord(root *folder, reads []string, t reflect.Type, label string, tags map[string]jsonvalue.Value) error { +func (s *structShape) compileRecord(root *folder, t reflect.Type, label string, tags map[string]jsonvalue.Value) error { tags["format"] = jsonvalue.String("") n, err := compileFrom(jsonvalue.Object(tags), atGoTop) if err != nil { return fmt.Errorf("%s: %w", label, err) } - if err := loadInline(root, reads, n, label, true); err != nil { + if err := loadInline(root, n, label, true); err != nil { return err } record, err := recordOf(n)