diff options
| author | Taras Madan <tarasmadan@google.com> | 2022-09-05 14:27:54 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-09-05 12:27:54 +0000 |
| commit | b2f2446b46bf02821d90ebedadae2bf7ae0e880e (patch) | |
| tree | 923cf42842918d6bebca1d6bbdc08abed54d274d /vendor/github.com/kulti | |
| parent | e6654faff4bcca4be92e9a8596fd4b77f747c39e (diff) | |
go.mod, vendor: update (#3358)
* go.mod, vendor: remove unnecessary dependencies
Commands:
1. go mod tidy
2. go mod vendor
* go.mod, vendor: update cloud.google.com/go
Commands:
1. go get -u cloud.google.com/go
2. go mod tidy
3. go mod vendor
* go.mod, vendor: update cloud.google.com/*
Commands:
1. go get -u cloud.google.com/storage cloud.google.com/logging
2. go mod tidy
3. go mod vendor
* go.mod, .golangci.yml, vendor: update *lint*
Commands:
1. go get -u golang.org/x/tools github.com/golangci/golangci-lint@v1.47.0
2. go mod tidy
3. go mod vendor
4. edit .golangci.yml to suppress new errors (resolved in the same PR later)
* all: fix lint errors
hash.go: copy() recommended by gosimple
parse.go: ent is never nil
verifier.go: signal.Notify() with unbuffered channel is bad. Have no idea why.
* .golangci.yml: adjust godot rules
check-all is deprecated, but still work
if you're hesitating too - I'll remove this commit
Diffstat (limited to 'vendor/github.com/kulti')
| -rw-r--r-- | vendor/github.com/kulti/thelper/pkg/analyzer/analyzer.go | 370 | ||||
| -rw-r--r-- | vendor/github.com/kulti/thelper/pkg/analyzer/report.go | 18 |
2 files changed, 324 insertions, 64 deletions
diff --git a/vendor/github.com/kulti/thelper/pkg/analyzer/analyzer.go b/vendor/github.com/kulti/thelper/pkg/analyzer/analyzer.go index 369d3ad56..a22fd6aca 100644 --- a/vendor/github.com/kulti/thelper/pkg/analyzer/analyzer.go +++ b/vendor/github.com/kulti/thelper/pkg/analyzer/analyzer.go @@ -26,6 +26,7 @@ Available checks ` + checkTName + ` - check *testing.T param has t name Also available similar checks for benchmark and TB helpers: ` + + checkFBegin + `, ` + checkFFirst + `, ` + checkFName + `,` + checkBBegin + `, ` + checkBFirst + `, ` + checkBName + `,` + checkTBBegin + `, ` + checkTBFirst + `, ` + checkTBName + ` @@ -60,6 +61,7 @@ func (m enabledChecksValue) Set(s string) error { for _, v := range ss { switch v { case checkTBegin, checkTFirst, checkTName, + checkFBegin, checkFFirst, checkFName, checkBBegin, checkBFirst, checkBName, checkTBBegin, checkTBFirst, checkTBName: m[v] = struct{}{} @@ -74,6 +76,9 @@ const ( checkTBegin = "t_begin" checkTFirst = "t_first" checkTName = "t_name" + checkFBegin = "f_begin" + checkFFirst = "f_first" + checkFName = "f_name" checkBBegin = "b_begin" checkBFirst = "b_first" checkBName = "b_name" @@ -94,6 +99,9 @@ func NewAnalyzer() *analysis.Analyzer { checkTBegin: struct{}{}, checkTFirst: struct{}{}, checkTName: struct{}{}, + checkFBegin: struct{}{}, + checkFFirst: struct{}{}, + checkFName: struct{}{}, checkBBegin: struct{}{}, checkBFirst: struct{}{}, checkBName: struct{}{}, @@ -118,29 +126,17 @@ func NewAnalyzer() *analysis.Analyzer { } func (t thelper) run(pass *analysis.Pass) (interface{}, error) { - var ctxType types.Type - ctxObj := analysisutil.ObjectOf(pass, "context", "Context") - if ctxObj != nil { - ctxType = ctxObj.Type() - } - - tCheckOpts, ok := t.buildTestCheckFuncOpts(pass, ctxType) - if !ok { - return nil, nil - } - - bCheckOpts, ok := t.buildBenchmarkCheckFuncOpts(pass, ctxType) + tCheckOpts, fCheckOpts, bCheckOpts, tbCheckOpts, ok := t.buildCheckFuncOpts(pass) if !ok { return nil, nil } - tbCheckOpts, ok := t.buildTBCheckFuncOpts(pass, ctxType) + inspect, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) if !ok { return nil, nil } var reports reports - inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil), @@ -160,14 +156,28 @@ func (t thelper) run(pass *analysis.Pass) (interface{}, error) { fd.Body = n.Body fd.Name = n.Name case *ast.CallExpr: - reports.Filter(subtestPos(pass, n, tCheckOpts.tbRun)) - reports.Filter(subtestPos(pass, n, bCheckOpts.tbRun)) + runSubtestExprs := extractSubtestExp(pass, n, tCheckOpts.subRun, tCheckOpts.subTestFuncType) + if len(runSubtestExprs) == 0 { + runSubtestExprs = extractSubtestExp(pass, n, bCheckOpts.subRun, bCheckOpts.subTestFuncType) + } + if len(runSubtestExprs) == 0 { + runSubtestExprs = extractSubtestFuzzExp(pass, n, fCheckOpts.subRun) + } + + if len(runSubtestExprs) > 0 { + for _, expr := range runSubtestExprs { + reports.Filter(funcDefPosition(pass, expr)) + } + } else { + reports.NoFilter(funcDefPosition(pass, n.Fun)) + } return default: return } checkFunc(pass, &reports, fd, tCheckOpts) + checkFunc(pass, &reports, fd, fCheckOpts) checkFunc(pass, &reports, fd, bCheckOpts) checkFunc(pass, &reports, fd, tbCheckOpts) }) @@ -178,15 +188,46 @@ func (t thelper) run(pass *analysis.Pass) (interface{}, error) { } type checkFuncOpts struct { - skipPrefix string - varName string - tbHelper types.Object - tbRun types.Object - tbType types.Type - ctxType types.Type - checkBegin bool - checkFirst bool - checkName bool + skipPrefix string + varName string + fnHelper types.Object + subRun types.Object + subTestFuncType types.Type + hpType types.Type + ctxType types.Type + checkBegin bool + checkFirst bool + checkName bool +} + +func (t thelper) buildCheckFuncOpts(pass *analysis.Pass) (checkFuncOpts, checkFuncOpts, checkFuncOpts, checkFuncOpts, bool) { + var ctxType types.Type + ctxObj := analysisutil.ObjectOf(pass, "context", "Context") + if ctxObj != nil { + ctxType = ctxObj.Type() + } + + tCheckOpts, ok := t.buildTestCheckFuncOpts(pass, ctxType) + if !ok { + return checkFuncOpts{}, checkFuncOpts{}, checkFuncOpts{}, checkFuncOpts{}, false + } + + fCheckOpts, ok := t.buildFuzzCheckFuncOpts(pass, ctxType) + if !ok { + return checkFuncOpts{}, checkFuncOpts{}, checkFuncOpts{}, checkFuncOpts{}, false + } + + bCheckOpts, ok := t.buildBenchmarkCheckFuncOpts(pass, ctxType) + if !ok { + return checkFuncOpts{}, checkFuncOpts{}, checkFuncOpts{}, checkFuncOpts{}, false + } + + tbCheckOpts, ok := t.buildTBCheckFuncOpts(pass, ctxType) + if !ok { + return checkFuncOpts{}, checkFuncOpts{}, checkFuncOpts{}, checkFuncOpts{}, false + } + + return tCheckOpts, fCheckOpts, bCheckOpts, tbCheckOpts, true } func (t thelper) buildTestCheckFuncOpts(pass *analysis.Pass, ctxType types.Type) (checkFuncOpts, bool) { @@ -205,16 +246,48 @@ func (t thelper) buildTestCheckFuncOpts(pass *analysis.Pass, ctxType types.Type) return checkFuncOpts{}, false } + tType := types.NewPointer(tObj.Type()) + tVar := types.NewVar(token.NoPos, nil, "t", tType) + return checkFuncOpts{ + skipPrefix: "Test", + varName: "t", + fnHelper: tHelper, + subRun: tRun, + hpType: tType, + subTestFuncType: types.NewSignature(nil, types.NewTuple(tVar), nil, false), + ctxType: ctxType, + checkBegin: t.enabledChecks.Enabled(checkTBegin), + checkFirst: t.enabledChecks.Enabled(checkTFirst), + checkName: t.enabledChecks.Enabled(checkTName), + }, true +} + +func (t thelper) buildFuzzCheckFuncOpts(pass *analysis.Pass, ctxType types.Type) (checkFuncOpts, bool) { + fObj := analysisutil.ObjectOf(pass, "testing", "F") + if fObj == nil { + return checkFuncOpts{}, true // fuzzing supports since go1.18, it's ok, that testig.F is missed. + } + + fHelper, _, _ := types.LookupFieldOrMethod(fObj.Type(), true, fObj.Pkg(), "Helper") + if fHelper == nil { + return checkFuncOpts{}, false + } + + tFuzz, _, _ := types.LookupFieldOrMethod(fObj.Type(), true, fObj.Pkg(), "Fuzz") + if tFuzz == nil { + return checkFuncOpts{}, false + } + return checkFuncOpts{ - skipPrefix: "Test", - varName: "t", - tbHelper: tHelper, - tbRun: tRun, - tbType: types.NewPointer(tObj.Type()), + skipPrefix: "Fuzz", + varName: "f", + fnHelper: fHelper, + subRun: tFuzz, + hpType: types.NewPointer(fObj.Type()), ctxType: ctxType, - checkBegin: t.enabledChecks.Enabled(checkTBegin), - checkFirst: t.enabledChecks.Enabled(checkTFirst), - checkName: t.enabledChecks.Enabled(checkTName), + checkBegin: t.enabledChecks.Enabled(checkFBegin), + checkFirst: t.enabledChecks.Enabled(checkFFirst), + checkName: t.enabledChecks.Enabled(checkFName), }, true } @@ -234,16 +307,19 @@ func (t thelper) buildBenchmarkCheckFuncOpts(pass *analysis.Pass, ctxType types. return checkFuncOpts{}, false } + bType := types.NewPointer(bObj.Type()) + bVar := types.NewVar(token.NoPos, nil, "b", bType) return checkFuncOpts{ - skipPrefix: "Benchmark", - varName: "b", - tbHelper: bHelper, - tbRun: bRun, - tbType: types.NewPointer(bObj.Type()), - ctxType: ctxType, - checkBegin: t.enabledChecks.Enabled(checkBBegin), - checkFirst: t.enabledChecks.Enabled(checkBFirst), - checkName: t.enabledChecks.Enabled(checkBName), + skipPrefix: "Benchmark", + varName: "b", + fnHelper: bHelper, + subRun: bRun, + hpType: types.NewPointer(bObj.Type()), + subTestFuncType: types.NewSignature(nil, types.NewTuple(bVar), nil, false), + ctxType: ctxType, + checkBegin: t.enabledChecks.Enabled(checkBBegin), + checkFirst: t.enabledChecks.Enabled(checkBFirst), + checkName: t.enabledChecks.Enabled(checkBName), }, true } @@ -261,8 +337,8 @@ func (t thelper) buildTBCheckFuncOpts(pass *analysis.Pass, ctxType types.Type) ( return checkFuncOpts{ skipPrefix: "", varName: "tb", - tbHelper: tbHelper, - tbType: tbObj.Type(), + fnHelper: tbHelper, + hpType: tbObj.Type(), ctxType: ctxType, checkBegin: t.enabledChecks.Enabled(checkTBBegin), checkFirst: t.enabledChecks.Enabled(checkTBFirst), @@ -278,11 +354,15 @@ type funcDecl struct { } func checkFunc(pass *analysis.Pass, reports *reports, funcDecl funcDecl, opts checkFuncOpts) { + if !opts.checkFirst && !opts.checkBegin && !opts.checkName { + return + } + if opts.skipPrefix != "" && strings.HasPrefix(funcDecl.Name.Name, opts.skipPrefix) { return } - p, pos, ok := searchFuncParam(pass, funcDecl, opts.tbType) + p, pos, ok := searchFuncParam(pass, funcDecl, opts.hpType) if !ok { return } @@ -296,7 +376,7 @@ func checkFunc(pass *analysis.Pass, reports *reports, funcDecl funcDecl, opts ch } if !checkFirstPassed { - reports.Reportf(funcDecl.Pos, "parameter %s should be the first or after context.Context", opts.tbType) + reports.Reportf(funcDecl.Pos, "parameter %s should be the first or after context.Context", opts.hpType) } } } @@ -304,32 +384,30 @@ func checkFunc(pass *analysis.Pass, reports *reports, funcDecl funcDecl, opts ch if len(p.Names) > 0 && p.Names[0].Name != "_" { if opts.checkName { if p.Names[0].Name != opts.varName { - reports.Reportf(funcDecl.Pos, "parameter %s should have name %s", opts.tbType, opts.varName) + reports.Reportf(funcDecl.Pos, "parameter %s should have name %s", opts.hpType, opts.varName) } } if opts.checkBegin { - if len(funcDecl.Body.List) == 0 || !isTHelperCall(pass, funcDecl.Body.List[0], opts.tbHelper) { + if len(funcDecl.Body.List) == 0 || !isTHelperCall(pass, funcDecl.Body.List[0], opts.fnHelper) { reports.Reportf(funcDecl.Pos, "test helper function should start from %s.Helper()", opts.varName) } } } } +// searchFuncParam search a function param with desired type. +// It returns the param field, its position, and true if something is found. func searchFuncParam(pass *analysis.Pass, f funcDecl, p types.Type) (*ast.Field, int, bool) { for i, f := range f.Type.Params.List { - typeInfo, ok := pass.TypesInfo.Types[f.Type] - if !ok { - continue - } - - if types.Identical(typeInfo.Type, p) { + if isExprHasType(pass, f.Type, p) { return f, i, true } } return nil, 0, false } +// isTHelperCall returns true if provided statement 's' is t.Helper() or b.Helper() call. func isTHelperCall(pass *analysis.Pass, s ast.Stmt, tHelper types.Object) bool { exprStmt, ok := s.(*ast.ExprStmt) if !ok { @@ -349,28 +427,122 @@ func isTHelperCall(pass *analysis.Pass, s ast.Stmt, tHelper types.Object) bool { return isSelectorCall(pass, selExpr, tHelper) } -func subtestPos(pass *analysis.Pass, e *ast.CallExpr, tbRun types.Object) token.Pos { +// extractSubtestExp analyzes that call expresion 'e' is t.Run or b.Run +// and returns subtest function. +func extractSubtestExp( + pass *analysis.Pass, e *ast.CallExpr, tbRun types.Object, testFuncType types.Type, +) []ast.Expr { selExpr, ok := e.Fun.(*ast.SelectorExpr) if !ok { - return token.NoPos + return nil } if !isSelectorCall(pass, selExpr, tbRun) { - return token.NoPos + return nil } if len(e.Args) != 2 { - return token.NoPos + return nil + } + + if funcs := unwrapTestingFunctionBuilding(pass, e.Args[1], testFuncType); funcs != nil { + return funcs + } + + return []ast.Expr{e.Args[1]} +} + +// extractSubtestFuzzExp analyzes that call expresion 'e' is f.Fuzz +// and returns subtest function. +func extractSubtestFuzzExp( + pass *analysis.Pass, e *ast.CallExpr, fuzzRun types.Object, +) []ast.Expr { + selExpr, ok := e.Fun.(*ast.SelectorExpr) + if !ok { + return nil + } + + if !isSelectorCall(pass, selExpr, fuzzRun) { + return nil + } + + if len(e.Args) != 1 { + return nil + } + + return []ast.Expr{e.Args[0]} +} + +// unwrapTestingFunctionConstruction checks that expresion is build testing functions +// and returns the result of building. +func unwrapTestingFunctionBuilding(pass *analysis.Pass, expr ast.Expr, testFuncType types.Type) []ast.Expr { + callExpr, ok := expr.(*ast.CallExpr) + if !ok { + return nil + } + + var funcDecl funcDecl + switch f := callExpr.Fun.(type) { + case *ast.FuncLit: + funcDecl.Body = f.Body + funcDecl.Type = f.Type + case *ast.Ident: + funObjDecl := findFunctionDeclaration(pass, f) + if funObjDecl == nil { + return nil + } + + funcDecl.Body = funObjDecl.Body + funcDecl.Type = funObjDecl.Type + case *ast.SelectorExpr: + fd := findSelectorDeclaration(pass, f) + if fd == nil { + return nil + } + + funcDecl.Body = fd.Body + funcDecl.Type = fd.Type + default: + return nil } - anonFunLit, ok := e.Args[1].(*ast.FuncLit) + results := funcDecl.Type.Results.List + if len(results) != 1 || !isExprHasType(pass, results[0].Type, testFuncType) { + return nil + } + + var funcs []ast.Expr + ast.Inspect(funcDecl.Body, func(n ast.Node) bool { + if n == nil { + return false + } + + if retStmt, ok := n.(*ast.ReturnStmt); ok { + if len(retStmt.Results) == 1 { + funcs = append(funcs, retStmt.Results[0]) + } + } + return true + }) + + return funcs +} + +// funcDefPosition returns a function's position. +// It works with anonymous functions as well with function names. +func funcDefPosition(pass *analysis.Pass, e ast.Expr) token.Pos { + anonFunLit, ok := e.(*ast.FuncLit) if ok { return anonFunLit.Pos() } - funIdent, ok := e.Args[1].(*ast.Ident) + funIdent, ok := e.(*ast.Ident) if !ok { - return token.NoPos + selExpr, ok := e.(*ast.SelectorExpr) + if !ok { + return token.NoPos + } + funIdent = selExpr.Sel } funDef, ok := pass.TypesInfo.Uses[funIdent] @@ -381,6 +553,8 @@ func subtestPos(pass *analysis.Pass, e *ast.CallExpr, tbRun types.Object) token. return funDef.Pos() } +// isSelectorCall checks is selExpr is a call expresion on specific callObj. +// Useful to check Run() call for t.Run or b.Run. func isSelectorCall(pass *analysis.Pass, selExpr *ast.SelectorExpr, callObj types.Object) bool { sel, ok := pass.TypesInfo.Selections[selExpr] if !ok { @@ -389,3 +563,77 @@ func isSelectorCall(pass *analysis.Pass, selExpr *ast.SelectorExpr, callObj type return sel.Obj() == callObj } + +// isExprHasType returns true if expr has expected type. +func isExprHasType(pass *analysis.Pass, expr ast.Expr, expType types.Type) bool { + typeInfo, ok := pass.TypesInfo.Types[expr] + if !ok { + return false + } + + return types.Identical(typeInfo.Type, expType) +} + +// findSelectorDeclaration returns function declaration called by selector expression. +func findSelectorDeclaration(pass *analysis.Pass, expr *ast.SelectorExpr) *ast.FuncDecl { + xsel, ok := pass.TypesInfo.Selections[expr] + if !ok { + return nil + } + + for _, file := range pass.Files { + for _, decl := range file.Decls { + fd, ok := decl.(*ast.FuncDecl) + if ok && fd.Recv != nil && len(fd.Recv.List) == 1 { + recvType, ok := fd.Recv.List[0].Type.(*ast.Ident) + if !ok { + continue + } + + recvObj, ok := pass.TypesInfo.Uses[recvType] + if !ok { + continue + } + + if !(types.Identical(recvObj.Type(), xsel.Recv())) { + continue + } + + if fd.Name.Name == expr.Sel.Name { + return fd + } + } + } + } + + return nil +} + +// findFunctionDeclaration returns function declaration called by identity. +func findFunctionDeclaration(pass *analysis.Pass, ident *ast.Ident) *ast.FuncDecl { + if ident.Obj != nil { + if funObjDecl, ok := ident.Obj.Decl.(*ast.FuncDecl); ok { + return funObjDecl + } + } + + obj := pass.TypesInfo.ObjectOf(ident) + if obj == nil { + return nil + } + + for _, file := range pass.Files { + for _, decl := range file.Decls { + funcDecl, ok := decl.(*ast.FuncDecl) + if !ok { + continue + } + + if funcDecl.Name.Pos() == obj.Pos() { + return funcDecl + } + } + } + + return nil +} diff --git a/vendor/github.com/kulti/thelper/pkg/analyzer/report.go b/vendor/github.com/kulti/thelper/pkg/analyzer/report.go index 1dd3eec5f..4a23e36d5 100644 --- a/vendor/github.com/kulti/thelper/pkg/analyzer/report.go +++ b/vendor/github.com/kulti/thelper/pkg/analyzer/report.go @@ -7,8 +7,9 @@ import ( ) type reports struct { - reports []report - filter map[token.Pos]struct{} + reports []report + filter map[token.Pos]struct{} + nofilter map[token.Pos]struct{} } type report struct { @@ -34,10 +35,21 @@ func (rr *reports) Filter(pos token.Pos) { } } +func (rr *reports) NoFilter(pos token.Pos) { + if pos.IsValid() { + if rr.nofilter == nil { + rr.nofilter = make(map[token.Pos]struct{}) + } + rr.nofilter[pos] = struct{}{} + } +} + func (rr reports) Flush(pass *analysis.Pass) { for _, r := range rr.reports { if _, ok := rr.filter[r.pos]; ok { - continue + if _, ok := rr.nofilter[r.pos]; !ok { + continue + } } pass.Reportf(r.pos, r.format, r.args...) } |
