From 4b0c0c9f4caab9936e9dc8c4236316cd6866bd5f Mon Sep 17 00:00:00 2001 From: Aleksey Bakin Date: Sat, 2 Apr 2022 22:51:57 +0300 Subject: [PATCH] feat: support Fuzz --- pkg/analyzer/analyzer.go | 79 ++++++++++++++-- pkg/analyzer/analyzer_test.go | 10 +- pkg/analyzer/testdata/src/f/f.go | 123 +++++++++++++++++++++++++ pkg/analyzer/testdata/src/f/ft.go | 9 ++ pkg/analyzer/testdata/src/f_begin/f.go | 123 +++++++++++++++++++++++++ pkg/analyzer/testdata/src/f_first/f.go | 123 +++++++++++++++++++++++++ pkg/analyzer/testdata/src/f_name/f.go | 123 +++++++++++++++++++++++++ 7 files changed, 581 insertions(+), 9 deletions(-) create mode 100644 pkg/analyzer/testdata/src/f/f.go create mode 100644 pkg/analyzer/testdata/src/f/ft.go create mode 100644 pkg/analyzer/testdata/src/f_begin/f.go create mode 100644 pkg/analyzer/testdata/src/f_first/f.go create mode 100644 pkg/analyzer/testdata/src/f_name/f.go diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index 2df72b0..0353023 100644 --- a/pkg/analyzer/analyzer.go +++ b/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,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 } @@ -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 { @@ -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) }) @@ -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 { @@ -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) { @@ -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 { @@ -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 { diff --git a/pkg/analyzer/analyzer_test.go b/pkg/analyzer/analyzer_test.go index b69d3de..b3185c6 100644 --- a/pkg/analyzer/analyzer_test.go +++ b/pkg/analyzer/analyzer_test.go @@ -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 @@ -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) { @@ -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() diff --git a/pkg/analyzer/testdata/src/f/f.go b/pkg/analyzer/testdata/src/f/f.go new file mode 100644 index 0000000..e4e8b0d --- /dev/null +++ b/pkg/analyzer/testdata/src/f/f.go @@ -0,0 +1,123 @@ +// Code generated by generator. DO NOT EDIT. + +package f + +import ( + "context" + "testing" +) + +// ----------------------------- +// Free functions +// ----------------------------- + +func nonTestHelper(f int) {} + +func helperWithoutHelper(f *testing.F) {} // want "test helper function should start from f.Helper()" + +func helperWithHelper(f *testing.F) { + f.Helper() +} + +func helperWithEmptyStringBeforeHelper(f *testing.F) { + + f.Helper() +} + +func helperWithHelperAfterAssignment(f *testing.F) { // want "test helper function should start from f.Helper()" + _ = 0 + f.Helper() +} + +func helperWithHelperAfterOtherCall(f *testing.F) { // want "test helper function should start from f.Helper()" + ff() + f.Helper() +} + +func helperWithHelperAfterOtherSelectionCall(f *testing.F) { // want "test helper function should start from f.Helper()" + f.Fail() + f.Helper() +} + +func helperParamNotFirst(s string, i int, f *testing.F) { // want "parameter \\*testing.F should be the first or after context.Context" + f.Helper() +} + +func helperParamSecondWithoutContext(s string, f *testing.F, i int) { // want "parameter \\*testing.F should be the first or after context.Context" + f.Helper() +} + +func helperParamSecondWithContext(ctx context.Context, f *testing.F) { + f.Helper() +} + +func helperWithIncorrectName(o *testing.F) { // want "parameter \\*testing.F should have name f" + o.Helper() +} + +func helperWithAnonymousHelper(f *testing.F) { + f.Helper() + func(f *testing.F) {}(f) // want "test helper function should start from f.Helper()" +} + +func helperWithNoName(_ *testing.F) { +} + +// ----------------------------- +// Methods of helper type +type helperType struct{} +// ----------------------------- + +func (h helperType) nonTestHelper(f int) {} + +func (h helperType) helperWithoutHelper(f *testing.F) {} // want "test helper function should start from f.Helper()" + +func (h helperType) helperWithHelper(f *testing.F) { + f.Helper() +} + +func (h helperType) helperWithEmptyStringBeforeHelper(f *testing.F) { + + f.Helper() +} + +func (h helperType) helperWithHelperAfterAssignment(f *testing.F) { // want "test helper function should start from f.Helper()" + _ = 0 + f.Helper() +} + +func (h helperType) helperWithHelperAfterOtherCall(f *testing.F) { // want "test helper function should start from f.Helper()" + ff() + f.Helper() +} + +func (h helperType) helperWithHelperAfterOtherSelectionCall(f *testing.F) { // want "test helper function should start from f.Helper()" + f.Fail() + f.Helper() +} + +func (h helperType) helperParamNotFirst(s string, i int, f *testing.F) { // want "parameter \\*testing.F should be the first or after context.Context" + f.Helper() +} + +func (h helperType) helperParamSecondWithoutContext(s string, f *testing.F, i int) { // want "parameter \\*testing.F should be the first or after context.Context" + f.Helper() +} + +func (h helperType) helperParamSecondWithContext(ctx context.Context, f *testing.F) { + f.Helper() +} + +func (h helperType) helperWithIncorrectName(o *testing.F) { // want "parameter \\*testing.F should have name f" + o.Helper() +} + +func (h helperType) helperWithAnonymousHelper(f *testing.F) { + f.Helper() + func(f *testing.F) {}(f) // want "test helper function should start from f.Helper()" +} + +func (h helperType) helperWithNoName(_ *testing.F) { +} + +func ff() {} diff --git a/pkg/analyzer/testdata/src/f/ft.go b/pkg/analyzer/testdata/src/f/ft.go new file mode 100644 index 0000000..65dd955 --- /dev/null +++ b/pkg/analyzer/testdata/src/f/ft.go @@ -0,0 +1,9 @@ +package f + +import "testing" + +func FuzzNothing(f *testing.F) { + f.Add("nothing") + f.Fuzz(func(t *testing.T, s string) { + }) +} diff --git a/pkg/analyzer/testdata/src/f_begin/f.go b/pkg/analyzer/testdata/src/f_begin/f.go new file mode 100644 index 0000000..f079721 --- /dev/null +++ b/pkg/analyzer/testdata/src/f_begin/f.go @@ -0,0 +1,123 @@ +// Code generated by generator. DO NOT EDIT. + +package f + +import ( + "context" + "testing" +) + +// ----------------------------- +// Free functions +// ----------------------------- + +func nonTestHelper(f int) {} + +func helperWithoutHelper(f *testing.F) {} // want "test helper function should start from f.Helper()" + +func helperWithHelper(f *testing.F) { + f.Helper() +} + +func helperWithEmptyStringBeforeHelper(f *testing.F) { + + f.Helper() +} + +func helperWithHelperAfterAssignment(f *testing.F) { // want "test helper function should start from f.Helper()" + _ = 0 + f.Helper() +} + +func helperWithHelperAfterOtherCall(f *testing.F) { // want "test helper function should start from f.Helper()" + ff() + f.Helper() +} + +func helperWithHelperAfterOtherSelectionCall(f *testing.F) { // want "test helper function should start from f.Helper()" + f.Fail() + f.Helper() +} + +func helperParamNotFirst(s string, i int, f *testing.F) { + f.Helper() +} + +func helperParamSecondWithoutContext(s string, f *testing.F, i int) { + f.Helper() +} + +func helperParamSecondWithContext(ctx context.Context, f *testing.F) { + f.Helper() +} + +func helperWithIncorrectName(o *testing.F) { + o.Helper() +} + +func helperWithAnonymousHelper(f *testing.F) { + f.Helper() + func(f *testing.F) {}(f) // want "test helper function should start from f.Helper()" +} + +func helperWithNoName(_ *testing.F) { +} + +// ----------------------------- +// Methods of helper type +type helperType struct{} +// ----------------------------- + +func (h helperType) nonTestHelper(f int) {} + +func (h helperType) helperWithoutHelper(f *testing.F) {} // want "test helper function should start from f.Helper()" + +func (h helperType) helperWithHelper(f *testing.F) { + f.Helper() +} + +func (h helperType) helperWithEmptyStringBeforeHelper(f *testing.F) { + + f.Helper() +} + +func (h helperType) helperWithHelperAfterAssignment(f *testing.F) { // want "test helper function should start from f.Helper()" + _ = 0 + f.Helper() +} + +func (h helperType) helperWithHelperAfterOtherCall(f *testing.F) { // want "test helper function should start from f.Helper()" + ff() + f.Helper() +} + +func (h helperType) helperWithHelperAfterOtherSelectionCall(f *testing.F) { // want "test helper function should start from f.Helper()" + f.Fail() + f.Helper() +} + +func (h helperType) helperParamNotFirst(s string, i int, f *testing.F) { + f.Helper() +} + +func (h helperType) helperParamSecondWithoutContext(s string, f *testing.F, i int) { + f.Helper() +} + +func (h helperType) helperParamSecondWithContext(ctx context.Context, f *testing.F) { + f.Helper() +} + +func (h helperType) helperWithIncorrectName(o *testing.F) { + o.Helper() +} + +func (h helperType) helperWithAnonymousHelper(f *testing.F) { + f.Helper() + func(f *testing.F) {}(f) // want "test helper function should start from f.Helper()" +} + +func (h helperType) helperWithNoName(_ *testing.F) { +} + +func ff() {} diff --git a/pkg/analyzer/testdata/src/f_first/f.go b/pkg/analyzer/testdata/src/f_first/f.go new file mode 100644 index 0000000..8b7ea2d --- /dev/null +++ b/pkg/analyzer/testdata/src/f_first/f.go @@ -0,0 +1,123 @@ +// Code generated by generator. DO NOT EDIT. + +package f + +import ( + "context" + "testing" +) + +// ----------------------------- +// Free functions +// ----------------------------- + +func nonTestHelper(f int) {} + +func helperWithoutHelper(f *testing.F) {} + +func helperWithHelper(f *testing.F) { + f.Helper() +} + +func helperWithEmptyStringBeforeHelper(f *testing.F) { + + f.Helper() +} + +func helperWithHelperAfterAssignment(f *testing.F) { + _ = 0 + f.Helper() +} + +func helperWithHelperAfterOtherCall(f *testing.F) { + ff() + f.Helper() +} + +func helperWithHelperAfterOtherSelectionCall(f *testing.F) { + f.Fail() + f.Helper() +} + +func helperParamNotFirst(s string, i int, f *testing.F) { // want "parameter \\*testing.F should be the first or after context.Context" + f.Helper() +} + +func helperParamSecondWithoutContext(s string, f *testing.F, i int) { // want "parameter \\*testing.F should be the first or after context.Context" + f.Helper() +} + +func helperParamSecondWithContext(ctx context.Context, f *testing.F) { + f.Helper() +} + +func helperWithIncorrectName(o *testing.F) { + o.Helper() +} + +func helperWithAnonymousHelper(f *testing.F) { + f.Helper() + func(f *testing.F) {}(f) +} + +func helperWithNoName(_ *testing.F) { +} + +// ----------------------------- +// Methods of helper type +type helperType struct{} +// ----------------------------- + +func (h helperType) nonTestHelper(f int) {} + +func (h helperType) helperWithoutHelper(f *testing.F) {} + +func (h helperType) helperWithHelper(f *testing.F) { + f.Helper() +} + +func (h helperType) helperWithEmptyStringBeforeHelper(f *testing.F) { + + f.Helper() +} + +func (h helperType) helperWithHelperAfterAssignment(f *testing.F) { + _ = 0 + f.Helper() +} + +func (h helperType) helperWithHelperAfterOtherCall(f *testing.F) { + ff() + f.Helper() +} + +func (h helperType) helperWithHelperAfterOtherSelectionCall(f *testing.F) { + f.Fail() + f.Helper() +} + +func (h helperType) helperParamNotFirst(s string, i int, f *testing.F) { // want "parameter \\*testing.F should be the first or after context.Context" + f.Helper() +} + +func (h helperType) helperParamSecondWithoutContext(s string, f *testing.F, i int) { // want "parameter \\*testing.F should be the first or after context.Context" + f.Helper() +} + +func (h helperType) helperParamSecondWithContext(ctx context.Context, f *testing.F) { + f.Helper() +} + +func (h helperType) helperWithIncorrectName(o *testing.F) { + o.Helper() +} + +func (h helperType) helperWithAnonymousHelper(f *testing.F) { + f.Helper() + func(f *testing.F) {}(f) +} + +func (h helperType) helperWithNoName(_ *testing.F) { +} + +func ff() {} diff --git a/pkg/analyzer/testdata/src/f_name/f.go b/pkg/analyzer/testdata/src/f_name/f.go new file mode 100644 index 0000000..bfa6c05 --- /dev/null +++ b/pkg/analyzer/testdata/src/f_name/f.go @@ -0,0 +1,123 @@ +// Code generated by generator. DO NOT EDIT. + +package f + +import ( + "context" + "testing" +) + +// ----------------------------- +// Free functions +// ----------------------------- + +func nonTestHelper(f int) {} + +func helperWithoutHelper(f *testing.F) {} + +func helperWithHelper(f *testing.F) { + f.Helper() +} + +func helperWithEmptyStringBeforeHelper(f *testing.F) { + + f.Helper() +} + +func helperWithHelperAfterAssignment(f *testing.F) { + _ = 0 + f.Helper() +} + +func helperWithHelperAfterOtherCall(f *testing.F) { + ff() + f.Helper() +} + +func helperWithHelperAfterOtherSelectionCall(f *testing.F) { + f.Fail() + f.Helper() +} + +func helperParamNotFirst(s string, i int, f *testing.F) { + f.Helper() +} + +func helperParamSecondWithoutContext(s string, f *testing.F, i int) { + f.Helper() +} + +func helperParamSecondWithContext(ctx context.Context, f *testing.F) { + f.Helper() +} + +func helperWithIncorrectName(o *testing.F) { // want "parameter \\*testing.F should have name f" + o.Helper() +} + +func helperWithAnonymousHelper(f *testing.F) { + f.Helper() + func(f *testing.F) {}(f) +} + +func helperWithNoName(_ *testing.F) { +} + +// ----------------------------- +// Methods of helper type +type helperType struct{} +// ----------------------------- + +func (h helperType) nonTestHelper(f int) {} + +func (h helperType) helperWithoutHelper(f *testing.F) {} + +func (h helperType) helperWithHelper(f *testing.F) { + f.Helper() +} + +func (h helperType) helperWithEmptyStringBeforeHelper(f *testing.F) { + + f.Helper() +} + +func (h helperType) helperWithHelperAfterAssignment(f *testing.F) { + _ = 0 + f.Helper() +} + +func (h helperType) helperWithHelperAfterOtherCall(f *testing.F) { + ff() + f.Helper() +} + +func (h helperType) helperWithHelperAfterOtherSelectionCall(f *testing.F) { + f.Fail() + f.Helper() +} + +func (h helperType) helperParamNotFirst(s string, i int, f *testing.F) { + f.Helper() +} + +func (h helperType) helperParamSecondWithoutContext(s string, f *testing.F, i int) { + f.Helper() +} + +func (h helperType) helperParamSecondWithContext(ctx context.Context, f *testing.F) { + f.Helper() +} + +func (h helperType) helperWithIncorrectName(o *testing.F) { // want "parameter \\*testing.F should have name f" + o.Helper() +} + +func (h helperType) helperWithAnonymousHelper(f *testing.F) { + f.Helper() + func(f *testing.F) {}(f) +} + +func (h helperType) helperWithNoName(_ *testing.F) { +} + +func ff() {}