1package analyzer
2
3import (
4 "go/ast"
5 "go/parser"
6 "go/token"
7 "go/types"
8 "testing"
9
10 "github.com/bnema/hexcheck/config"
11 "golang.org/x/tools/go/analysis"
12)
13
14func TestOversizedFunctionSkipsTypeAndLocalCollection(t *testing.T) {
15 fn := &ast.FuncDecl{Body: &ast.BlockStmt{List: []ast.Stmt{
16 &ast.AssignStmt{Lhs: []ast.Expr{&ast.SelectorExpr{X: ast.NewIdent("repo"), Sel: ast.NewIdent("Name")}}, Rhs: []ast.Expr{ast.NewIdent("value")}},
17 }}}
18 // No analysis pass: any type query before rejection would panic.
19 r := runner{cfg: &config.Config{Heuristics: config.Heuristics{BusinessLogicMaxFunctionNodes: testIntPtr(1)}}}
20 facts := r.collectBusinessFunctionFacts(fn, config.RoleAdapter)
21 if facts.nodeCount <= 1 || facts.constructedLocals != nil || facts.domainMutation {
22 t.Fatalf("oversized function was analyzed: %+v", facts)
23 }
24}
25
26func TestConstructionFactsCachedByObject(t *testing.T) {
27 fset := token.NewFileSet()
28 file, err := parser.ParseFile(fset, "locals.go", `package p
29 type Value struct { N int }
30 func fetch() Value { return Value{} }
31 func build(param Value) {
32 fresh := Value{}
33 fresh.N = 1
34 var zero Value
35 zero.N = 2
36 replaced := Value{}
37 replaced = fetch()
38 replaced.N = 3
39 fetched := fetch()
40 fetched.N = 4
41 param.N = 5
42 _ = fresh; _ = zero; _ = replaced; _ = fetched
43 }`, 0)
44 if err != nil {
45 t.Fatal(err)
46 }
47 info := &types.Info{Defs: map[*ast.Ident]types.Object{}, Uses: map[*ast.Ident]types.Object{}, Types: map[ast.Expr]types.TypeAndValue{}}
48 if _, err := new(types.Config).Check("p", fset, []*ast.File{file}, info); err != nil {
49 t.Fatal(err)
50 }
51 fn := file.Decls[2].(*ast.FuncDecl)
52 facts := businessFunctionFacts{fn: fn, runner: runner{pass: &analysis.Pass{TypesInfo: info}}}
53 facts.collectLocals()
54 want := map[string]bool{"fresh": true, "zero": true, "replaced": false, "fetched": false, "param": false}
55 checked := 0
56 ast.Inspect(fn.Body, func(n ast.Node) bool {
57 if sel, ok := n.(*ast.SelectorExpr); ok {
58 id := sel.X.(*ast.Ident)
59 if got := facts.isConstructedLocal(sel); got != want[id.Name] {
60 t.Errorf("%s constructed=%v, want %v", id.Name, got, want[id.Name])
61 }
62 checked++
63 }
64 return true
65 })
66 if checked != len(want) {
67 t.Fatalf("checked %d receivers, want %d", checked, len(want))
68 }
69}
70
71func TestCollectBusinessFunctionFactsInspectsFunctionBody(t *testing.T) {
72 fn := &ast.FuncDecl{
73 Name: ast.NewIdent("ResolveMode"),
74 Type: &ast.FuncType{},
75 Body: &ast.BlockStmt{List: []ast.Stmt{
76 &ast.IfStmt{
77 Cond: ast.NewIdent("ok"),
78 Body: &ast.BlockStmt{},
79 },
80 }},
81 }
82 r := runner{
83 cfg: &config.Config{Heuristics: config.Heuristics{BusinessLogicMaxFunctionNodes: testIntPtr(2000)}},
84 pass: &analysis.Pass{TypesInfo: &types.Info{Types: map[ast.Expr]types.TypeAndValue{}}},
85 }
86
87 facts := r.collectBusinessFunctionFacts(fn, config.RoleAdapter)
88
89 if facts.fn != fn {
90 t.Fatalf("collectBusinessFunctionFacts() fn = %p, want %p", facts.fn, fn)
91 }
92 if facts.branchCount != 1 {
93 t.Fatalf("collectBusinessFunctionFacts() branchCount = %d, want 1", facts.branchCount)
94 }
95 if facts.nodeCount == 0 {
96 t.Fatal("collectBusinessFunctionFacts() nodeCount = 0, want inspected nodes")
97 }
98}