Test every package's panics for the one invariant phrase, and a Source built from a directory
This commit is contained in:
+32
-24
@@ -29,37 +29,23 @@ func TestPackageImports(t *testing.T) {
|
|||||||
}
|
}
|
||||||
checked := 0
|
checked := 0
|
||||||
allowed := map[string][]string{
|
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": {"."},
|
"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/datafiles": {"internal/grammar"},
|
||||||
"internal/datatype": nil,
|
"internal/datatype": nil,
|
||||||
"internal/drawstate": nil,
|
"internal/drawstate": nil,
|
||||||
"internal/grammar": 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{}
|
seen := map[string]bool{}
|
||||||
err = filepath.WalkDir(".", func(dir string, d fs.DirEntry, err error) error {
|
eachPackage(t, func(dir string, pkg *build.Package) {
|
||||||
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)
|
|
||||||
seen[dir] = true
|
seen[dir] = true
|
||||||
may, listed := allowed[dir]
|
may, listed := allowed[dir]
|
||||||
if !listed {
|
if !listed {
|
||||||
t.Errorf("package %s is not listed: add the module packages it may import", dir)
|
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) {
|
for _, imp := range slices.Concat(pkg.Imports, pkg.TestImports, pkg.XTestImports) {
|
||||||
if imp != module && !strings.HasPrefix(imp, module+"/") {
|
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)
|
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 {
|
if checked == 0 {
|
||||||
t.Fatalf("no package imports a package of module %s, which go.mod names", module)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package datafiles
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -26,7 +27,7 @@ func walk(t *testing.T, src Source) ([]handed, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWalkHandsEachCategoryInOrder(t *testing.T) {
|
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"}`)},
|
"a.json": {Data: []byte(`{"x":"1"}`)},
|
||||||
"sub/b.json": {Data: []byte(`"y"`)},
|
"sub/b.json": {Data: []byte(`"y"`)},
|
||||||
"sub/notes.txt": {Data: []byte("not data")},
|
"sub/notes.txt": {Data: []byte("not data")},
|
||||||
@@ -49,19 +50,19 @@ func TestWalkHandsEachCategoryInOrder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWalkStartsAtTheBaseDir(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 {
|
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)
|
t.Errorf("Walk = %#v, %v, want a alone at the base", got, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadRowsReadsOnlyAFileBesideTheCategory(t *testing.T) {
|
func TestReadRowsReadsOnlyAFileBesideTheCategory(t *testing.T) {
|
||||||
src := Source{FS: fstest.MapFS{
|
src := Source{fsys: fstest.MapFS{
|
||||||
"t.json": {Data: []byte(`{"rows":"t.tsv"}`)},
|
"t.json": {Data: []byte(`{"rows":"t.tsv"}`)},
|
||||||
"t.tsv": {Data: []byte("key\nA\n")},
|
"t.tsv": {Data: []byte("key\nA\n")},
|
||||||
"sub/u.tsv": {Data: []byte("key\nB\n")},
|
"sub/u.tsv": {Data: []byte("key\nB\n")},
|
||||||
"sub/u.json": {Data: []byte(`{"rows":"u.tsv"}`)},
|
"sub/u.json": {Data: []byte(`{"rows":"u.tsv"}`)},
|
||||||
}, Label: "lbl"}
|
}, label: "lbl"}
|
||||||
rows := map[string]string{}
|
rows := map[string]string{}
|
||||||
err := src.Walk(func(c Category) error {
|
err := src.Walk(func(c Category) error {
|
||||||
data, err := c.ReadRows(c.Name + ".tsv")
|
data, err := c.ReadRows(c.Name + ".tsv")
|
||||||
@@ -80,7 +81,7 @@ func TestReadRowsReadsOnlyAFileBesideTheCategory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWalkRefusesARowsFileNoCategoryNames(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") {
|
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)
|
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 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 "},
|
{"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) {
|
if err == nil || !strings.HasPrefix(err.Error(), c.want) {
|
||||||
t.Errorf("%s: Walk = %v, want it to start %q", c.name, err, 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) {
|
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)
|
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"},
|
{"", "a data path is empty"},
|
||||||
{filepath.Join(dir, "none"), "data path " + filepath.Join(dir, "none") + ": "},
|
{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) {
|
if err == nil || !strings.HasPrefix(err.Error(), c.want) {
|
||||||
t.Errorf("Walk(%q) = %v, want it to start %q", c.path, err, 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) {
|
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.json": {Data: []byte(`{"rows":"t.tsv"}`)},
|
||||||
"data/sub/t.tsv": {Data: []byte("key\nA\n")},
|
"data/sub/t.tsv": {Data: []byte("key\nA\n")},
|
||||||
"data/sub/u.json": {Data: []byte(`{`)},
|
"data/sub/u.json": {Data: []byte(`{`)},
|
||||||
}, BaseDir: "data"}
|
}, base: "data"}
|
||||||
var got handed
|
var got handed
|
||||||
var rows string
|
var rows string
|
||||||
err := src.Load([]string{"sub"}, "t", func(c Category) error {
|
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)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+25
-12
@@ -2,6 +2,8 @@ package fejkdata
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
|
"go/build"
|
||||||
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -20,19 +22,30 @@ func TestNoFunctionSpellsAMethod(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEveryPanicIsAnInternalError(t *testing.T) {
|
func TestEveryPanicIsAnInternalError(t *testing.T) {
|
||||||
fset, files := sourceFiles(t)
|
eachPackage(t, func(dir string, pkg *build.Package) {
|
||||||
for _, f := range files {
|
var names []string
|
||||||
ast.Inspect(f, func(n ast.Node) bool {
|
for _, name := range pkg.GoFiles {
|
||||||
call, isCall := n.(*ast.CallExpr)
|
names = append(names, filepath.Join(dir, name))
|
||||||
if !isCall || !isIdent(call.Fun, "panic") {
|
}
|
||||||
|
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
|
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 {
|
func isIdent(e ast.Expr, name string) bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user