diff --git a/cmd/go-printf-func-name/main.go b/cmd/go-printf-func-name/main.go deleted file mode 100644 index a3d4116..0000000 --- a/cmd/go-printf-func-name/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "flag" - - "github.com/jirfag/go-printf-func-name/pkg/analyzer" - "golang.org/x/tools/go/analysis/singlechecker" -) - -func main() { - // Don't use it: just to not crash on -unsafeptr flag from go vet - flag.Bool("unsafeptr", false, "") - - singlechecker.Main(analyzer.Analyzer) -} diff --git a/exmaple_test.go b/example_test.go similarity index 100% rename from exmaple_test.go rename to example_test.go diff --git a/main.go b/main.go new file mode 100644 index 0000000..1970448 --- /dev/null +++ b/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/jirfag/go-printf-func-name/pkg/paralleltest" + "golang.org/x/tools/go/analysis/singlechecker" +) + +func main() { + singlechecker.Main(paralleltest.NewAnalyzer()) +} diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go deleted file mode 100644 index e87d080..0000000 --- a/pkg/analyzer/analyzer.go +++ /dev/null @@ -1,82 +0,0 @@ -package analyzer - -import ( - "go/ast" - "strings" - - "golang.org/x/tools/go/analysis/passes/inspect" - "golang.org/x/tools/go/ast/inspector" - - "golang.org/x/tools/go/analysis" -) - -var Analyzer = &analysis.Analyzer{ - Name: "paralleltest", - Doc: "Checks that tests have t.Parallel enabled", - Run: run, - Requires: []*analysis.Analyzer{inspect.Analyzer}, -} - -func run(pass *analysis.Pass) (interface{}, error) { - inspector := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) - nodeFilter := []ast.Node{ - (*ast.FuncDecl)(nil), - } - - inspector.Preorder(nodeFilter, func(node ast.Node) { - funcDecl := node.(*ast.FuncDecl) - funcHasParallelMethod := false - rangeStatementExists := false - rangeHasParallelMethod := false - - // TODO : Only run for test files - if strings.HasPrefix(funcDecl.Name.Name, "Test_") { - for _, l := range funcDecl.Body.List { - switch v := l.(type) { - - case *ast.ExprStmt: - ast.Inspect(v, func(n ast.Node) bool { - if funcHasParallelMethod == false { - funcHasParallelMethod = callExprCallsMethodParallel(n) - } - return true - }) - - case *ast.RangeStmt: - // TODO: Check range statements is over testcases - - rangeStatementExists= true - // TODO: Also check for the assignment tc:tc - ast.Inspect(v, func(n ast.Node) bool { - if rangeHasParallelMethod == false { - rangeHasParallelMethod = callExprCallsMethodParallel(n) - } - return true - }) - } - } - - if !funcHasParallelMethod { - pass.Reportf(node.Pos(), "Function %s missing the call to method parallel \n", funcDecl.Name.Name) - } - if rangeStatementExists && !rangeHasParallelMethod { - pass.Reportf(node.Pos(), "Range statement %s missing the call to method parallel \n", funcDecl.Name.Name) - } - } - }) - - return nil, nil -} - -func callExprCallsMethodParallel(node ast.Node) bool { - methodName := "Parallel" - - switch n := node.(type) { - default: - case *ast.CallExpr: - if fun, ok := n.Fun.(*ast.SelectorExpr); ok { - return fun.Sel.Name == methodName - } - } - return false -} diff --git a/pkg/paralleltest/paralleltest.go b/pkg/paralleltest/paralleltest.go new file mode 100644 index 0000000..e28ce66 --- /dev/null +++ b/pkg/paralleltest/paralleltest.go @@ -0,0 +1,128 @@ +package paralleltest + +import ( + "fmt" + "go/ast" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" + "strings" +) + +// TODO add ignoring ability flag +func NewAnalyzer() *analysis.Analyzer { + return &analysis.Analyzer{ + Name: "paralleltest", + Doc: "Checks that tests have t.Parallel enabled", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + } +} + +func run(pass *analysis.Pass) (interface{}, error) { + // Run only for test files + inspector := inspector.New(getTestFiles(pass)) + + nodeFilter := []ast.Node{ + (*ast.FuncDecl)(nil), + } + + inspector.Preorder(nodeFilter, func(node ast.Node) { + funcDecl := node.(*ast.FuncDecl) + funcHasParallelMethod := false + rangeStatementExists := false + rangeHasParallelMethod := false + + // Check runs for test functions only + if !isItATestFunction(funcDecl) { + return + } + + for _, l := range funcDecl.Body.List { + switch v := l.(type) { + + // Check if the test method is calling t.parallel + case *ast.ExprStmt: + ast.Inspect(v, func(n ast.Node) bool { + if funcHasParallelMethod == false { + funcHasParallelMethod = callExprCallsMethodParallel(n) + } + return true + }) + + case *ast.RangeStmt: + // Check if the range over testcases is calling t.parallel + // TODO: Check range statements is over testcases and not any other ranges + + rangeStatementExists = true + // TODO: Also check for the assignment tc:tc + ast.Inspect(v, func(n ast.Node) bool { + if rangeHasParallelMethod == false { + rangeHasParallelMethod = callExprCallsMethodParallel(n) + } + return true + }) + } + } + + if !funcHasParallelMethod { + pass.Reportf(node.Pos(), "Function %s missing the call to method parallel \n", funcDecl.Name.Name) + } + if rangeStatementExists && !rangeHasParallelMethod { + pass.Reportf(node.Pos(), "Range statement for test %s missing the call to method parallel \n", funcDecl.Name.Name) + } + + }) + + return nil, nil +} + +func getTestFiles(pass *analysis.Pass) []*ast.File { + testFileSuffix := "_test.go" + + var testFiles []*ast.File + for _, f := range pass.Files { + fileName := pass.Fset.Position(f.Pos()).Filename + if strings.HasSuffix(fileName,testFileSuffix ) { + testFiles = append(testFiles, f) + } + } + return testFiles +} + +func callExprCallsMethodParallel(node ast.Node) bool { + methodName := "Parallel" + + switch n := node.(type) { + default: + case *ast.CallExpr: + if fun, ok := n.Fun.(*ast.SelectorExpr); ok { + return fun.Sel.Name == methodName + } + } + return false +} + +func isItATestFunction(funcDecl *ast.FuncDecl) bool { + testMethodParamName := "t" + testMethodPackageType := "testing" + testMethodStruct := "T" + + if funcDecl.Type.Params != nil { + if len(funcDecl.Type.Params.List) > 1 { + return false + } + + for _, param := range funcDecl.Type.Params.List { + if param.Names[0].String() == testMethodParamName { + if starExp, ok := param.Type.(*ast.StarExpr); ok { + if selectExpr, ok := starExp.X.(*ast.SelectorExpr); ok { + return fmt.Sprint(selectExpr.X) == testMethodPackageType && + fmt.Sprint(selectExpr.Sel) == testMethodStruct + } + } + } + } + } + return false +} diff --git a/pkg/analyzer/analyzer_test.go b/pkg/paralleltest/paralleltest_test.go similarity index 68% rename from pkg/analyzer/analyzer_test.go rename to pkg/paralleltest/paralleltest_test.go index d613ffe..c526e8e 100644 --- a/pkg/analyzer/analyzer_test.go +++ b/pkg/paralleltest/paralleltest_test.go @@ -1,11 +1,10 @@ -package analyzer_test +package paralleltest import ( "os" "path/filepath" "testing" - "github.com/jirfag/go-printf-func-name/pkg/analyzer" "golang.org/x/tools/go/analysis/analysistest" ) @@ -16,5 +15,5 @@ func TestAll(t *testing.T) { } testdata := filepath.Join(filepath.Dir(filepath.Dir(wd)), "testdata") - analysistest.Run(t, testdata, analyzer.Analyzer, "p") + analysistest.Run(t, testdata, NewAnalyzer(), "p") }