diff --git a/pkg/paralleltest/paralleltest.go b/pkg/paralleltest/paralleltest.go index 77fc5e3..d08495f 100644 --- a/pkg/paralleltest/paralleltest.go +++ b/pkg/paralleltest/paralleltest.go @@ -14,7 +14,6 @@ It also checks that the t.Parallel is used if multiple tests cases are run as pa As part of ensuring parallel tests works as expected it checks for reinitialising of the range value over the test cases.(https://tinyurl.com/y6555cy6)` -// TODO add ignoring ability flag func NewAnalyzer() *analysis.Analyzer { return &analysis.Analyzer{ Name: "paralleltest", @@ -37,10 +36,10 @@ func run(pass *analysis.Pass) (interface{}, error) { var funcHasParallelMethod, rangeStatementOverTestCasesExists, rangeStatementHasParallelMethod, - testLoopVariableReinitialised bool var testRunLoopIdentifier string - + var numberOfTestRun int + var positionOfTestRunNode []ast.Node var rangeNode ast.Node // Check runs for test functions only @@ -51,12 +50,27 @@ func run(pass *analysis.Pass) (interface{}, error) { 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 { + // Check if the test method is calling t.parallel if !funcHasParallelMethod { funcHasParallelMethod = methodParallelIsCalledInTestFunction(n) } + + // Check if the t.Run within the test function is calling t.parallel + if methodRunIsCalledInTestFunction(n) { + hasParallel := false + numberOfTestRun++ + ast.Inspect(v, func(p ast.Node) bool { + if !hasParallel { + hasParallel = methodParallelIsCalledInTestFunction(p) + } + return true + }) + if !hasParallel { + positionOfTestRunNode = append(positionOfTestRunNode, n) + } + } return true }) @@ -99,15 +113,22 @@ func run(pass *analysis.Pass) (interface{}, error) { if rangeStatementOverTestCasesExists && rangeNode != nil { if !rangeStatementHasParallelMethod { - pass.Reportf(rangeNode.Pos(), "Range statement for test %s missing the call to method parallel in t.Run\n", funcDecl.Name.Name) + pass.Reportf(rangeNode.Pos(), "Range statement for test %s missing the call to method parallel in test Run\n", funcDecl.Name.Name) } else { if testRunLoopIdentifier == "" { - pass.Reportf(rangeNode.Pos(), "Range statement for test %s does not use range value in t.Run\n", funcDecl.Name.Name) + pass.Reportf(rangeNode.Pos(), "Range statement for test %s does not use range value in test Run\n", funcDecl.Name.Name) } else if !testLoopVariableReinitialised { pass.Reportf(rangeNode.Pos(), "Range statement for test %s does not reinitialise the variable %s\n", funcDecl.Name.Name, testRunLoopIdentifier) } } } + + // Check if the t.Run is more than one as there is no point making one test parallel + if numberOfTestRun > 1 && len(positionOfTestRunNode) > 0 { + for _, n := range positionOfTestRunNode { + pass.Reportf(n.Pos(), "Function %s has missing the call to method parallel in the test run\n", funcDecl.Name.Name) + } + } }) return nil, nil @@ -190,6 +211,9 @@ func methodRunIsCalledInRangeStatement(node ast.Node) bool { return exprCallHasMethod(node, "Run") } +func methodRunIsCalledInTestFunction(node ast.Node) bool { + return exprCallHasMethod(node, "Run") +} func exprCallHasMethod(node ast.Node, methodName string) bool { // nolint: gocritic switch n := node.(type) { diff --git a/pkg/paralleltest/testdata/src/t/t_test.go b/pkg/paralleltest/testdata/src/t/t_test.go index 916eb06..cfb8d5e 100644 --- a/pkg/paralleltest/testdata/src/t/t_test.go +++ b/pkg/paralleltest/testdata/src/t/t_test.go @@ -5,12 +5,12 @@ import ( "testing" ) -func NoATestFunction() {} -func TestingFunctionLooksLikeATestButIsNotWithParam() {} -func TestingFunctionLooksLikeATestButIsWithParam(i int) {} -func AbcFunctionSuccessful(t *testing.T) {} +func NoATestFunction() {} +func TestingFunctionLooksLikeATestButIsNotWithParam() {} +func TestingFunctionLooksLikeATestButIsWithParam(i int) {} +func AbcFunctionSuccessful(t *testing.T) {} -func TestFunctionSuccessful(t *testing.T) { +func TestFunctionSuccessfulRangeTest(t *testing.T) { t.Parallel() testCases := []struct { @@ -25,6 +25,24 @@ func TestFunctionSuccessful(t *testing.T) { } } +func TestFunctionSuccessfulNoRangeTests(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + }{{name: "foo"}, {name: "bar"}} + + t.Run(testCases[0].name, func(t *testing.T) { + t.Parallel() + fmt.Println(testCases[0].name) + }) + t.Run(testCases[1].name, func(t *testing.T) { + t.Parallel() + fmt.Println(testCases[1].name) + }) + +} + func TestFunctionMissingCallToParallel(t *testing.T) {} // want "Function TestFunctionMissingCallToParallel missing the call to method parallel" func TestFunctionRangeMissingCallToParallel(t *testing.T) { t.Parallel() @@ -33,12 +51,12 @@ func TestFunctionRangeMissingCallToParallel(t *testing.T) { name string }{{name: "foo"}} - // this range loop should be okay as it does not have t.Run + // this range loop should be okay as it does not have test Run for _, tc := range testCases { fmt.Println(tc.name) } - for _, tc := range testCases { // want "Range statement for test TestFunctionRangeMissingCallToParallel missing the call to method parallel in t.Run" + for _, tc := range testCases { // want "Range statement for test TestFunctionRangeMissingCallToParallel missing the call to method parallel in test Run" t.Run(tc.name, func(t *testing.T) { fmt.Println(tc.name) }) @@ -50,7 +68,7 @@ func TestFunctionMissingCallToParallelAndRangeNotUsingRangeValueInTDotRun(t *tes name string }{{name: "foo"}} - for _, tc := range testCases { // want "Range statement for test TestFunctionMissingCallToParallelAndRangeNotUsingRangeValueInTDotRun missing the call to method parallel in t.Run" + for _, tc := range testCases { // want "Range statement for test TestFunctionMissingCallToParallelAndRangeNotUsingRangeValueInTDotRun missing the call to method parallel in test Run" t.Run(tc.name, func(t *testing.T) { fmt.Println(tc.name) }) @@ -63,7 +81,7 @@ func TestFunctionRangeNotUsingRangeValueInTDotRun(t *testing.T) { testCases := []struct { name string }{{name: "foo"}} - for _, tc := range testCases { // want "Range statement for test TestFunctionRangeNotUsingRangeValueInTDotRun does not use range value in t.Run" + for _, tc := range testCases { // want "Range statement for test TestFunctionRangeNotUsingRangeValueInTDotRun does not use range value in test Run" t.Run("tc.name", func(t *testing.T) { t.Parallel() fmt.Println(tc.name) @@ -85,14 +103,37 @@ func TestFunctionRangeNotReInitialisingVariable(t *testing.T) { } } -// TODO this test should fail when the missing functionality is implemented as t.Run does not call t.Parallel -func TestFunctionSuccessful_maybe(t *testing.T) { +func TestFunctionTwoTestRunMissingCallToParallel(t *testing.T) { t.Parallel() - t.Run("1", func(t *testing.T) { + t.Run("1", func(t *testing.T) { // want "Function TestFunctionTwoTestRunMissingCallToParallel has missing the call to method parallel in the test run" + fmt.Println("1") + }) + t.Run("2", func(t *testing.T) { // want "Function TestFunctionTwoTestRunMissingCallToParallel has missing the call to method parallel in the test run" + fmt.Println("2") + }) +} + +func TestFunctionFirstOneTestRunMissingCallToParallel(t *testing.T) { + t.Parallel() + + t.Run("1", func(t *testing.T) { // want "Function TestFunctionFirstOneTestRunMissingCallToParallel has missing the call to method parallel in the test run" fmt.Println("1") }) t.Run("2", func(t *testing.T) { + t.Parallel() + fmt.Println("2") + }) +} + +func TestFunctionSecondOneTestRunMissingCallToParallel(t *testing.T) { + t.Parallel() + + t.Run("1", func(t *testing.T) { + t.Parallel() + fmt.Println("1") + }) + t.Run("2", func(t *testing.T) { // want "Function TestFunctionSecondOneTestRunMissingCallToParallel has missing the call to method parallel in the test run" fmt.Println("2") }) }