Skip to content

Commit

Permalink
feat: support Fuzz
Browse files Browse the repository at this point in the history
  • Loading branch information
kulti committed Apr 2, 2022
1 parent 032d4a4 commit 4b0c0c9
Show file tree
Hide file tree
Showing 7 changed files with 581 additions and 9 deletions.
79 changes: 73 additions & 6 deletions pkg/analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 + `
Expand Down Expand Up @@ -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{}{}
Expand All @@ -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"
Expand All @@ -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{}{},
Expand All @@ -118,7 +126,7 @@ func NewAnalyzer() *analysis.Analyzer {
}

func (t thelper) run(pass *analysis.Pass) (interface{}, error) {
tCheckOpts, bCheckOpts, tbCheckOpts, ok := t.buildCheckFuncOpts(pass)
tCheckOpts, fCheckOpts, bCheckOpts, tbCheckOpts, ok := t.buildCheckFuncOpts(pass)
if !ok {
return nil, nil
}
Expand Down Expand Up @@ -152,6 +160,9 @@ func (t thelper) run(pass *analysis.Pass) (interface{}, error) {
if len(tbRunSubtestExprs) == 0 {
tbRunSubtestExprs = extractSubtestExp(pass, n, bCheckOpts.tbRun, bCheckOpts.tbTestFuncType)
}
if len(tbRunSubtestExprs) == 0 {
tbRunSubtestExprs = extractSubtestFuzzExp(pass, n, fCheckOpts.tbRun)
}

if len(tbRunSubtestExprs) > 0 {
for _, expr := range tbRunSubtestExprs {
Expand All @@ -166,6 +177,7 @@ func (t thelper) run(pass *analysis.Pass) (interface{}, error) {
}

checkFunc(pass, &reports, fd, tCheckOpts)
checkFunc(pass, &reports, fd, fCheckOpts)
checkFunc(pass, &reports, fd, bCheckOpts)
checkFunc(pass, &reports, fd, tbCheckOpts)
})
Expand All @@ -188,7 +200,7 @@ type checkFuncOpts struct {
checkName bool
}

func (t thelper) buildCheckFuncOpts(pass *analysis.Pass) (checkFuncOpts, checkFuncOpts, checkFuncOpts, 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 {
Expand All @@ -197,20 +209,25 @@ func (t thelper) buildCheckFuncOpts(pass *analysis.Pass) (checkFuncOpts, checkFu

tCheckOpts, ok := t.buildTestCheckFuncOpts(pass, ctxType)
if !ok {
return checkFuncOpts{}, checkFuncOpts{}, checkFuncOpts{}, false
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{}, false
return checkFuncOpts{}, checkFuncOpts{}, checkFuncOpts{}, checkFuncOpts{}, false
}

tbCheckOpts, ok := t.buildTBCheckFuncOpts(pass, ctxType)
if !ok {
return checkFuncOpts{}, checkFuncOpts{}, checkFuncOpts{}, false
return checkFuncOpts{}, checkFuncOpts{}, checkFuncOpts{}, checkFuncOpts{}, false
}

return tCheckOpts, bCheckOpts, tbCheckOpts, true
return tCheckOpts, fCheckOpts, bCheckOpts, tbCheckOpts, true
}

func (t thelper) buildTestCheckFuncOpts(pass *analysis.Pass, ctxType types.Type) (checkFuncOpts, bool) {
Expand Down Expand Up @@ -245,6 +262,35 @@ func (t thelper) buildTestCheckFuncOpts(pass *analysis.Pass, ctxType types.Type)
}, true
}

func (t thelper) buildFuzzCheckFuncOpts(pass *analysis.Pass, ctxType types.Type) (checkFuncOpts, bool) {
fObj := analysisutil.ObjectOf(pass, "testing", "F")
if fObj == nil {
return checkFuncOpts{}, false
}

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: "Fuzz",
varName: "f",
tbHelper: fHelper,
tbRun: tFuzz,
tbType: types.NewPointer(fObj.Type()),
ctxType: ctxType,
checkBegin: t.enabledChecks.Enabled(checkFBegin),
checkFirst: t.enabledChecks.Enabled(checkFFirst),
checkName: t.enabledChecks.Enabled(checkFName),
}, true
}

func (t thelper) buildBenchmarkCheckFuncOpts(pass *analysis.Pass, ctxType types.Type) (checkFuncOpts, bool) {
bObj := analysisutil.ObjectOf(pass, "testing", "B")
if bObj == nil {
Expand Down Expand Up @@ -402,6 +448,27 @@ func extractSubtestExp(
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, tbRun types.Object,
) []ast.Expr {
selExpr, ok := e.Fun.(*ast.SelectorExpr)
if !ok {
return nil
}

if !isSelectorCall(pass, selExpr, tbRun) {
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 {
Expand Down
10 changes: 7 additions & 3 deletions pkg/analyzer/analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
)

//go:generate go run github.com/kulti/thelper/scripts/generator --name t --path testdata/src
//go:generate go run github.com/kulti/thelper/scripts/generator --name f --path testdata/src
//go:generate go run github.com/kulti/thelper/scripts/generator --name b --path testdata/src
//go:generate go run github.com/kulti/thelper/scripts/generator --name tb --interface --path testdata/src

Expand All @@ -23,7 +24,7 @@ func TestAllChecks(t *testing.T) {
t.Parallel()

a := analyzer.NewAnalyzer()
analysistest.Run(t, testdata, a, "t", "b", "tb")
analysistest.Run(t, testdata, a, "t", "f", "b", "tb")
})

t.Run("empty checks flag", func(t *testing.T) {
Expand All @@ -34,24 +35,27 @@ func TestAllChecks(t *testing.T) {
if err != nil {
t.Fatalf("failed to set checks empty value: %v", err)
}
analysistest.Run(t, testdata, a, "t", "b", "tb")
analysistest.Run(t, testdata, a, "t", "f", "b", "tb")
})
}

//go:generate go run github.com/kulti/thelper/scripts/generator --name t --check begin --path testdata/src
//go:generate go run github.com/kulti/thelper/scripts/generator --name f --check begin --path testdata/src
//go:generate go run github.com/kulti/thelper/scripts/generator --name b --check begin --path testdata/src
//go:generate go run github.com/kulti/thelper/scripts/generator --name tb --interface --check begin --path testdata/src
//go:generate go run github.com/kulti/thelper/scripts/generator --name t --check first --path testdata/src
//go:generate go run github.com/kulti/thelper/scripts/generator --name f --check first --path testdata/src
//go:generate go run github.com/kulti/thelper/scripts/generator --name b --check first --path testdata/src
//go:generate go run github.com/kulti/thelper/scripts/generator --name tb --interface --check first --path testdata/src
//go:generate go run github.com/kulti/thelper/scripts/generator --name t --check name --path testdata/src
//go:generate go run github.com/kulti/thelper/scripts/generator --name f --check name --path testdata/src
//go:generate go run github.com/kulti/thelper/scripts/generator --name b --check name --path testdata/src
//go:generate go run github.com/kulti/thelper/scripts/generator --name tb --interface --check name --path testdata/src

func TestSingleCheck(t *testing.T) {
t.Parallel()

checks := []string{"t_begin", "t_first", "t_name", "b_begin", "b_first", "b_name", "tb_begin", "tb_first", "tb_name"}
checks := []string{"t_begin", "t_first", "t_name", "f_begin", "f_first", "f_name", "b_begin", "b_first", "b_name", "tb_begin", "tb_first", "tb_name"}
log.SetFlags(log.LstdFlags | log.Lshortfile)
testdata := analysistest.TestData()

Expand Down
123 changes: 123 additions & 0 deletions pkg/analyzer/testdata/src/f/f.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions pkg/analyzer/testdata/src/f/ft.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package f

import "testing"

func FuzzNothing(f *testing.F) {
f.Add("nothing")
f.Fuzz(func(t *testing.T, s string) {
})
}
Loading

0 comments on commit 4b0c0c9

Please sign in to comment.