aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/kunwardeep
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2021-02-22 20:37:25 +0100
committerDmitry Vyukov <dvyukov@google.com>2021-02-22 21:02:12 +0100
commitfcc6d71be2c3ce7d9305c04fc2e87af554571bac (patch)
treeb01dbb3d1e2988e28ea158d2d543d603ec0b9569 /vendor/github.com/kunwardeep
parent8f23c528ad5a943b9ffec5dcaf332fd0f614006e (diff)
go.mod: update golangci-lint to v1.37
Diffstat (limited to 'vendor/github.com/kunwardeep')
-rw-r--r--vendor/github.com/kunwardeep/paralleltest/LICENSE21
-rw-r--r--vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go256
2 files changed, 277 insertions, 0 deletions
diff --git a/vendor/github.com/kunwardeep/paralleltest/LICENSE b/vendor/github.com/kunwardeep/paralleltest/LICENSE
new file mode 100644
index 000000000..d06a809c2
--- /dev/null
+++ b/vendor/github.com/kunwardeep/paralleltest/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Isaev Denis
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go b/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go
new file mode 100644
index 000000000..31f6f2946
--- /dev/null
+++ b/vendor/github.com/kunwardeep/paralleltest/pkg/paralleltest/paralleltest.go
@@ -0,0 +1,256 @@
+package paralleltest
+
+import (
+ "go/ast"
+ "strings"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/ast/inspector"
+)
+
+const Doc = `check that tests use t.Parallel() method
+It also checks that the t.Parallel is used if multiple tests cases are run as part of single test.
+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)`
+
+func NewAnalyzer() *analysis.Analyzer {
+ return &analysis.Analyzer{
+ Name: "paralleltest",
+ Doc: Doc,
+ Run: run,
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ }
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+ inspector := inspector.New(pass.Files)
+
+ nodeFilter := []ast.Node{
+ (*ast.FuncDecl)(nil),
+ }
+
+ inspector.Preorder(nodeFilter, func(node ast.Node) {
+ funcDecl := node.(*ast.FuncDecl)
+ 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
+ if !isTestFunction(funcDecl) {
+ return
+ }
+
+ for _, l := range funcDecl.Body.List {
+ switch v := l.(type) {
+
+ 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
+ })
+
+ // Check if the range over testcases is calling t.parallel
+ case *ast.RangeStmt:
+ rangeNode = v
+
+ ast.Inspect(v, func(n ast.Node) bool {
+ // nolint: gocritic
+ switch r := n.(type) {
+ case *ast.ExprStmt:
+ if methodRunIsCalledInRangeStatement(r.X) {
+ rangeStatementOverTestCasesExists = true
+ testRunLoopIdentifier = methodRunFirstArgumentObjectName(r.X)
+
+ if !rangeStatementHasParallelMethod {
+ rangeStatementHasParallelMethod = methodParallelIsCalledInMethodRun(r.X)
+ }
+ }
+ }
+ return true
+ })
+
+ // Check for the range loop value identifier re assignment
+ // More info here https://gist.github.com/kunwardeep/80c2e9f3d3256c894898bae82d9f75d0
+ if rangeStatementOverTestCasesExists {
+ var rangeValueIdentifier string
+ if i, ok := v.Value.(*ast.Ident); ok {
+ rangeValueIdentifier = i.Name
+ }
+
+ testLoopVariableReinitialised = testCaseLoopVariableReinitialised(v.Body.List, rangeValueIdentifier, testRunLoopIdentifier)
+ }
+ }
+ }
+
+ if !funcHasParallelMethod {
+ pass.Reportf(node.Pos(), "Function %s missing the call to method parallel\n", funcDecl.Name.Name)
+ }
+
+ if rangeStatementOverTestCasesExists && rangeNode != nil {
+ if !rangeStatementHasParallelMethod {
+ 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 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
+}
+
+func testCaseLoopVariableReinitialised(statements []ast.Stmt, rangeValueIdentifier string, testRunLoopIdentifier string) bool {
+ if len(statements) > 1 {
+ for _, s := range statements {
+ leftIdentifier, rightIdentifier := getLeftAndRightIdentifier(s)
+ if leftIdentifier == testRunLoopIdentifier && rightIdentifier == rangeValueIdentifier {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// Return the left hand side and the right hand side identifiers name
+func getLeftAndRightIdentifier(s ast.Stmt) (string, string) {
+ var leftIdentifier, rightIdentifier string
+ // nolint: gocritic
+ switch v := s.(type) {
+ case *ast.AssignStmt:
+ if len(v.Rhs) == 1 {
+ if i, ok := v.Rhs[0].(*ast.Ident); ok {
+ rightIdentifier = i.Name
+ }
+ }
+ if len(v.Lhs) == 1 {
+ if i, ok := v.Lhs[0].(*ast.Ident); ok {
+ leftIdentifier = i.Name
+ }
+ }
+ }
+ return leftIdentifier, rightIdentifier
+}
+
+func methodParallelIsCalledInMethodRun(node ast.Node) bool {
+ var methodParallelCalled bool
+ // nolint: gocritic
+ switch callExp := node.(type) {
+ case *ast.CallExpr:
+ for _, arg := range callExp.Args {
+ if !methodParallelCalled {
+ ast.Inspect(arg, func(n ast.Node) bool {
+ if !methodParallelCalled {
+ methodParallelCalled = methodParallelIsCalledInRunMethod(n)
+ return true
+ }
+ return false
+ })
+ }
+ }
+ }
+ return methodParallelCalled
+}
+
+func methodParallelIsCalledInRunMethod(node ast.Node) bool {
+ return exprCallHasMethod(node, "Parallel")
+}
+
+func methodParallelIsCalledInTestFunction(node ast.Node) bool {
+ return exprCallHasMethod(node, "Parallel")
+}
+
+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) {
+ case *ast.CallExpr:
+ if fun, ok := n.Fun.(*ast.SelectorExpr); ok {
+ return fun.Sel.Name == methodName
+ }
+ }
+ return false
+}
+
+// Gets the object name `tc` from method t.Run(tc.Foo, func(t *testing.T)
+func methodRunFirstArgumentObjectName(node ast.Node) string {
+ // nolint: gocritic
+ switch n := node.(type) {
+ case *ast.CallExpr:
+ for _, arg := range n.Args {
+ if s, ok := arg.(*ast.SelectorExpr); ok {
+ if i, ok := s.X.(*ast.Ident); ok {
+ return i.Name
+ }
+ }
+ }
+ }
+ return ""
+}
+
+// Checks if the function has the param type *testing.T)
+func isTestFunction(funcDecl *ast.FuncDecl) bool {
+ testMethodPackageType := "testing"
+ testMethodStruct := "T"
+ testPrefix := "Test"
+
+ if !strings.HasPrefix(funcDecl.Name.Name, testPrefix) {
+ return false
+ }
+
+ if funcDecl.Type.Params != nil && len(funcDecl.Type.Params.List) != 1 {
+ return false
+ }
+
+ param := funcDecl.Type.Params.List[0]
+ if starExp, ok := param.Type.(*ast.StarExpr); ok {
+ if selectExpr, ok := starExp.X.(*ast.SelectorExpr); ok {
+ if selectExpr.Sel.Name == testMethodStruct {
+ if s, ok := selectExpr.X.(*ast.Ident); ok {
+ return s.Name == testMethodPackageType
+ }
+ }
+ }
+ }
+
+ return false
+}