From 835cdb57d474738a8712662c57eadb9741533b0c Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 5 Oct 2026 15:42:31 +0200 Subject: [PATCH] Test every package's panics for the one invariant phrase, and a Source built from a directory --- imports_test.go | 56 ++++++++++++++++------------ internal/datafiles/datafiles_test.go | 35 ++++++++++++----- naming_test.go | 37 ++++++++++++------ 3 files changed, 82 insertions(+), 46 deletions(-) diff --git a/imports_test.go b/imports_test.go index 32fe9e7..f73cd64 100644 --- a/imports_test.go +++ b/imports_test.go @@ -29,37 +29,23 @@ func TestPackageImports(t *testing.T) { } checked := 0 allowed := map[string][]string{ - ".": {"internal/builtinfunc", "internal/datafiles", "internal/datatype", "internal/drawstate", "internal/grammar", "internal/proven"}, + ".": {"internal/builtinfunc", "internal/datafiles", "internal/datatype", "internal/drawstate", "internal/grammar", "internal/invariant", "internal/proven"}, "cmd/fejkdata": {"."}, - "internal/builtinfunc": {"internal/datatype", "internal/drawstate", "internal/grammar", "internal/proven"}, + "internal/builtinfunc": {"internal/datatype", "internal/drawstate", "internal/grammar", "internal/invariant", "internal/proven"}, "internal/datafiles": {"internal/grammar"}, "internal/datatype": nil, "internal/drawstate": nil, "internal/grammar": nil, - "internal/proven": {"internal/datatype", "internal/grammar"}, + "internal/invariant": nil, + "internal/proven": {"internal/datatype", "internal/grammar", "internal/invariant"}, } seen := map[string]bool{} - err = filepath.WalkDir(".", func(dir string, d fs.DirEntry, err error) error { - if err != nil || !d.IsDir() { - return err - } - if dir != "." && (strings.HasPrefix(d.Name(), ".") || d.Name() == "testdata") { - return filepath.SkipDir - } - pkg, err := build.ImportDir(dir, 0) - var noGo *build.NoGoError - if errors.As(err, &noGo) { - return nil - } - if err != nil { - return err - } - dir = filepath.ToSlash(dir) + eachPackage(t, func(dir string, pkg *build.Package) { seen[dir] = true may, listed := allowed[dir] if !listed { t.Errorf("package %s is not listed: add the module packages it may import", dir) - return nil + return } for _, imp := range slices.Concat(pkg.Imports, pkg.TestImports, pkg.XTestImports) { if imp != module && !strings.HasPrefix(imp, module+"/") { @@ -74,11 +60,7 @@ func TestPackageImports(t *testing.T) { t.Errorf("package %s imports %s, which its list does not allow", dir, rel) } } - return nil }) - if err != nil { - t.Fatal(err) - } if checked == 0 { t.Fatalf("no package imports a package of module %s, which go.mod names", module) } @@ -88,3 +70,29 @@ func TestPackageImports(t *testing.T) { } } } + +// eachPackage calls fn with every Go package in the module and its slash-separated directory. +func eachPackage(t *testing.T, fn func(dir string, pkg *build.Package)) { + t.Helper() + err := filepath.WalkDir(".", func(dir string, d fs.DirEntry, err error) error { + if err != nil || !d.IsDir() { + return err + } + if dir != "." && (strings.HasPrefix(d.Name(), ".") || d.Name() == "testdata") { + return filepath.SkipDir + } + pkg, err := build.ImportDir(dir, 0) + var noGo *build.NoGoError + if errors.As(err, &noGo) { + return nil + } + if err != nil { + return err + } + fn(filepath.ToSlash(dir), pkg) + return nil + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/internal/datafiles/datafiles_test.go b/internal/datafiles/datafiles_test.go index 282d11a..41d62b1 100644 --- a/internal/datafiles/datafiles_test.go +++ b/internal/datafiles/datafiles_test.go @@ -2,6 +2,7 @@ package datafiles import ( "errors" + "os" "path/filepath" "reflect" "strings" @@ -26,7 +27,7 @@ func walk(t *testing.T, src Source) ([]handed, error) { } func TestWalkHandsEachCategoryInOrder(t *testing.T) { - got, err := walk(t, Source{FS: fstest.MapFS{ + got, err := walk(t, Source{fsys: fstest.MapFS{ "a.json": {Data: []byte(`{"x":"1"}`)}, "sub/b.json": {Data: []byte(`"y"`)}, "sub/notes.txt": {Data: []byte("not data")}, @@ -49,19 +50,19 @@ func TestWalkHandsEachCategoryInOrder(t *testing.T) { } func TestWalkStartsAtTheBaseDir(t *testing.T) { - got, err := walk(t, Source{FS: fstest.MapFS{"data/a.json": {Data: []byte(`"x"`)}, "b.json": {Data: []byte(`"y"`)}}, BaseDir: "data"}) + got, err := walk(t, Source{fsys: fstest.MapFS{"data/a.json": {Data: []byte(`"x"`)}, "b.json": {Data: []byte(`"y"`)}}, base: "data"}) if err != nil || len(got) != 1 || got[0].name != "a" || got[0].dir != nil { t.Errorf("Walk = %#v, %v, want a alone at the base", got, err) } } func TestReadRowsReadsOnlyAFileBesideTheCategory(t *testing.T) { - src := Source{FS: fstest.MapFS{ + src := Source{fsys: fstest.MapFS{ "t.json": {Data: []byte(`{"rows":"t.tsv"}`)}, "t.tsv": {Data: []byte("key\nA\n")}, "sub/u.tsv": {Data: []byte("key\nB\n")}, "sub/u.json": {Data: []byte(`{"rows":"u.tsv"}`)}, - }, Label: "lbl"} + }, label: "lbl"} rows := map[string]string{} err := src.Walk(func(c Category) error { data, err := c.ReadRows(c.Name + ".tsv") @@ -80,7 +81,7 @@ func TestReadRowsReadsOnlyAFileBesideTheCategory(t *testing.T) { } func TestWalkRefusesARowsFileNoCategoryNames(t *testing.T) { - _, err := walk(t, Source{FS: fstest.MapFS{"a.json": {Data: []byte(`"x"`)}, "a.tsv": {Data: []byte("key\n")}}, Label: "lbl"}) + _, err := walk(t, Source{fsys: fstest.MapFS{"a.json": {Data: []byte(`"x"`)}, "a.tsv": {Data: []byte("key\n")}}, label: "lbl"}) if err == nil || !strings.HasPrefix(err.Error(), "lbl/a.tsv: no category names it in its rows") { t.Errorf("Walk = %v, want a.tsv refused as named by no category", err) } @@ -98,7 +99,7 @@ func TestWalkLabelsWhatACategoryFileGetsWrong(t *testing.T) { {"its name", fstest.MapFS{"a.b.json": {Data: []byte(`"x"`)}}, nil, "lbl/a.b.json: category "}, {"its folder's name", fstest.MapFS{"a.b/c.json": {Data: []byte(`"x"`)}}, nil, "lbl/a.b: folder "}, } { - err := Source{FS: c.fs, Label: "lbl"}.Walk(func(Category) error { return c.compile }) + err := Source{fsys: c.fs, label: "lbl"}.Walk(func(Category) error { return c.compile }) if err == nil || !strings.HasPrefix(err.Error(), c.want) { t.Errorf("%s: Walk = %v, want it to start %q", c.name, err, c.want) } @@ -106,7 +107,7 @@ func TestWalkLabelsWhatACategoryFileGetsWrong(t *testing.T) { } func TestWalkChecksAFolderNameOnlyWhenItHoldsData(t *testing.T) { - if _, err := walk(t, Source{FS: fstest.MapFS{"a.b/notes.txt": {Data: []byte("x")}, "c.json": {Data: []byte(`"x"`)}}}); err != nil { + if _, err := walk(t, Source{fsys: fstest.MapFS{"a.b/notes.txt": {Data: []byte("x")}, "c.json": {Data: []byte(`"x"`)}}}); err != nil { t.Errorf("Walk = %v, want a folder holding no data skipped whatever its name", err) } } @@ -117,7 +118,7 @@ func TestWalkRefusesADiskPathThatIsNoDirectory(t *testing.T) { {"", "a data path is empty"}, {filepath.Join(dir, "none"), "data path " + filepath.Join(dir, "none") + ": "}, } { - _, err := walk(t, Source{FS: fstest.MapFS{}, Label: c.path, OnDisk: true}) + _, err := walk(t, Dir(c.path)) if err == nil || !strings.HasPrefix(err.Error(), c.want) { t.Errorf("Walk(%q) = %v, want it to start %q", c.path, err, c.want) } @@ -125,11 +126,11 @@ func TestWalkRefusesADiskPathThatIsNoDirectory(t *testing.T) { } func TestLoadHandsOneCategoryAndItsRows(t *testing.T) { - src := Source{FS: fstest.MapFS{ + src := Source{fsys: fstest.MapFS{ "data/sub/t.json": {Data: []byte(`{"rows":"t.tsv"}`)}, "data/sub/t.tsv": {Data: []byte("key\nA\n")}, "data/sub/u.json": {Data: []byte(`{`)}, - }, BaseDir: "data"} + }, base: "data"} var got handed var rows string err := src.Load([]string{"sub"}, "t", func(c Category) error { @@ -142,3 +143,17 @@ func TestLoadHandsOneCategoryAndItsRows(t *testing.T) { t.Errorf("Load = %v, handed %#v with rows %q", err, got, rows) } } + +func TestDirReadsTheDirectoryAndLabelsItsErrorsWithIt(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "a.json"), []byte(`{`), 0o600); err != nil { + t.Fatal(err) + } + _, err := walk(t, Dir(dir)) + if err == nil || !strings.HasPrefix(err.Error(), filepath.ToSlash(filepath.Join(dir, "a.json"))+": ") { + t.Errorf("Walk = %v, want the error labelled with the directory", err) + } + if _, err := walk(t, Dir(filepath.Join(dir, "a.json"))); err == nil || err.Error() != filepath.Join(dir, "a.json")+" is not a directory" { + t.Errorf("Walk = %v, want a file refused as no directory", err) + } +} diff --git a/naming_test.go b/naming_test.go index 84c80ea..2dc55ad 100644 --- a/naming_test.go +++ b/naming_test.go @@ -2,6 +2,8 @@ package fejkdata import ( "go/ast" + "go/build" + "path/filepath" "slices" "sort" "strings" @@ -20,19 +22,30 @@ func TestNoFunctionSpellsAMethod(t *testing.T) { } func TestEveryPanicIsAnInternalError(t *testing.T) { - fset, files := sourceFiles(t) - for _, f := range files { - ast.Inspect(f, func(n ast.Node) bool { - call, isCall := n.(*ast.CallExpr) - if !isCall || !isIdent(call.Fun, "panic") { + eachPackage(t, func(dir string, pkg *build.Package) { + var names []string + for _, name := range pkg.GoFiles { + names = append(names, filepath.Join(dir, name)) + } + fset, files := parseFiles(t, names) + for _, f := range files { + ast.Inspect(f, func(n ast.Node) bool { + call, isCall := n.(*ast.CallExpr) + if !isCall || !isIdent(call.Fun, "panic") { + return true + } + if arg, isArgCall := call.Args[0].(*ast.CallExpr); !isArgCall || !isBroken(arg.Fun) { + t.Errorf("%s: panic with invariant.Broken(…), so every invariant break greps to one phrase", fset.Position(call.Pos())) + } return true - } - if arg, isArgCall := call.Args[0].(*ast.CallExpr); !isArgCall || !isIdent(arg.Fun, "internalError") { - t.Errorf("%s: panic with internalError(…), so every invariant break greps to one phrase", fset.Position(call.Pos())) - } - return true - }) - } + }) + } + }) +} + +func isBroken(e ast.Expr) bool { + sel, isSel := e.(*ast.SelectorExpr) + return isSel && isIdent(sel.X, "invariant") && sel.Sel.Name == "Broken" } func isIdent(e ast.Expr, name string) bool {