Skip to content

Commit

Permalink
Merge pull request #5 from kunwardeep/add-rule-parallel-for-test-run
Browse files Browse the repository at this point in the history
added missing scenario for test runs
  • Loading branch information
kunwardeep authored Nov 2, 2020
2 parents 0656b59 + 3ec3cd7 commit db82655
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 18 deletions.
36 changes: 30 additions & 6 deletions pkg/paralleltest/paralleltest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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
Expand All @@ -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
})

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
65 changes: 53 additions & 12 deletions pkg/paralleltest/testdata/src/t/t_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()
Expand All @@ -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)
})
Expand All @@ -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)
})
Expand All @@ -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)
Expand All @@ -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")
})
}

0 comments on commit db82655

Please sign in to comment.