Find every node switch with a panicking default from the source, and test routeSteps beside the seven listed by hand
This commit is contained in:
+87
-14
@@ -64,10 +64,11 @@ func (*unhandledNode) isNode() {}
|
||||
|
||||
// nodeSwitchSkips is the kinds a switch is never handed: its callers step past them first.
|
||||
var nodeSwitchSkips = map[string][]string{
|
||||
"childNamed": {"choice", "table", "tableRow"},
|
||||
"columnItems": {"folder", "table", "tableRow"},
|
||||
"prove": {"folder"},
|
||||
"render": {"folder"},
|
||||
"childNamed": {"choice", "table", "tableRow"},
|
||||
"routeSteps": {"choice", "folder", "nullItem", "template"},
|
||||
}
|
||||
|
||||
func TestNodeSwitchesHandleEveryKind(t *testing.T) {
|
||||
@@ -88,16 +89,7 @@ func TestNodeSwitchesHandleEveryKind(t *testing.T) {
|
||||
} {
|
||||
samples[reflect.TypeOf(n).Elem().Name()] = n
|
||||
}
|
||||
var kinds []string
|
||||
_, files := sourceFiles(t)
|
||||
for _, file := range files {
|
||||
for _, decl := range file.Decls {
|
||||
if d, isFunc := decl.(*ast.FuncDecl); isFunc && d.Name.Name == "isNode" {
|
||||
kinds = append(kinds, receiverType(d.Recv))
|
||||
}
|
||||
}
|
||||
}
|
||||
slices.Sort(kinds)
|
||||
kinds := nodeKinds(t)
|
||||
sampled := make([]string, 0, len(samples))
|
||||
for kind := range samples {
|
||||
sampled = append(sampled, kind)
|
||||
@@ -106,7 +98,8 @@ func TestNodeSwitchesHandleEveryKind(t *testing.T) {
|
||||
if !slices.Equal(kinds, sampled) {
|
||||
t.Fatalf("node kinds %v, samples %v; give every kind a sample here", kinds, sampled)
|
||||
}
|
||||
for name, call := range map[string]func(node){
|
||||
switches := map[string]func(node){
|
||||
"childNamed": func(n node) { _, _ = childNamed(n, "x") },
|
||||
"columnItems": func(n node) { columnItems(n) },
|
||||
"contained": func(n node) { contained(n) },
|
||||
"paths": func(n node) { paths(n, false) },
|
||||
@@ -116,8 +109,18 @@ func TestNodeSwitchesHandleEveryKind(t *testing.T) {
|
||||
render(engine(1).drawState, n, renderEnv{frames: &frames, row: renderedRow{tbl, 0}})
|
||||
},
|
||||
"renderEdges": func(n node) { renderEdges(n) },
|
||||
"childNamed": func(n node) { _, _ = childNamed(n, "x") },
|
||||
} {
|
||||
"routeSteps": func(n node) { _, _ = routeSteps(nil, nil, tbl, tableRoute{next: n}, 0) },
|
||||
}
|
||||
found := exhaustiveNodeSwitches(t, kinds)
|
||||
listed := make([]string, 0, len(switches))
|
||||
for name := range switches {
|
||||
listed = append(listed, name)
|
||||
}
|
||||
slices.Sort(listed)
|
||||
if !slices.Equal(found, listed) {
|
||||
t.Fatalf("the functions switching over node kinds with a panicking default are %v, this test calls %v; call each here", found, listed)
|
||||
}
|
||||
for name, call := range switches {
|
||||
mustPanic(t, name+" on an unhandled node", func() { call(&unhandledNode{}) })
|
||||
for _, kind := range kinds {
|
||||
if slices.Contains(nodeSwitchSkips[name], kind) {
|
||||
@@ -141,3 +144,73 @@ func TestNodeSwitchesHandleEveryKind(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func nodeKinds(t *testing.T) []string {
|
||||
t.Helper()
|
||||
var kinds []string
|
||||
_, files := sourceFiles(t)
|
||||
for _, file := range files {
|
||||
for _, decl := range file.Decls {
|
||||
if d, isFunc := decl.(*ast.FuncDecl); isFunc && d.Name.Name == "isNode" {
|
||||
kinds = append(kinds, receiverType(d.Recv))
|
||||
}
|
||||
}
|
||||
}
|
||||
slices.Sort(kinds)
|
||||
return kinds
|
||||
}
|
||||
|
||||
// exhaustiveNodeSwitches names each function holding a type switch that has a case for a
|
||||
// node kind and a default that panics.
|
||||
func exhaustiveNodeSwitches(t *testing.T, kinds []string) []string {
|
||||
t.Helper()
|
||||
var names []string
|
||||
_, files := sourceFiles(t)
|
||||
for _, file := range files {
|
||||
for _, decl := range file.Decls {
|
||||
d, isFunc := decl.(*ast.FuncDecl)
|
||||
if !isFunc || d.Body == nil {
|
||||
continue
|
||||
}
|
||||
ast.Inspect(d.Body, func(n ast.Node) bool {
|
||||
if sw, isTypeSwitch := n.(*ast.TypeSwitchStmt); isTypeSwitch && switchesOverNode(sw, kinds) {
|
||||
names = append(names, d.Name.Name)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
slices.Sort(names)
|
||||
return slices.Compact(names)
|
||||
}
|
||||
|
||||
func switchesOverNode(sw *ast.TypeSwitchStmt, kinds []string) bool {
|
||||
casesNode, panics := false, false
|
||||
for _, stmt := range sw.Body.List {
|
||||
clause := stmt.(*ast.CaseClause)
|
||||
if clause.List == nil {
|
||||
panics = slices.ContainsFunc(clause.Body, isPanic)
|
||||
}
|
||||
for _, e := range clause.List {
|
||||
if star, isPointer := e.(*ast.StarExpr); isPointer {
|
||||
if id, isIdent := star.X.(*ast.Ident); isIdent && slices.Contains(kinds, id.Name) {
|
||||
casesNode = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return casesNode && panics
|
||||
}
|
||||
|
||||
func isPanic(stmt ast.Stmt) bool {
|
||||
expr, isExpr := stmt.(*ast.ExprStmt)
|
||||
if !isExpr {
|
||||
return false
|
||||
}
|
||||
call, isCall := expr.X.(*ast.CallExpr)
|
||||
if !isCall {
|
||||
return false
|
||||
}
|
||||
id, isIdent := call.Fun.(*ast.Ident)
|
||||
return isIdent && id.Name == "panic"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user