Skip to content

Commit eb776aa

Browse files
authored
new checker 'suite-method-signature' (#228)
* new checker 'suite-method-signature' * suite-method-signature: lost files * suite-method-signature: review fixes * docs: add FAQ in contribution.md * review fixes
1 parent 4d3fdcc commit eb776aa

11 files changed

+359
-1
lines changed

.golangci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ issues:
2525
- path: "_test\\.go"
2626
linters: [ "lll" ]
2727

28-
- source: ' // want "'
28+
- source: ' // want '
2929
linters: [ "lll" ]
3030

3131
linters-settings:

CONTRIBUTING.md

+15
Original file line numberDiff line numberDiff line change
@@ -354,3 +354,18 @@ func (s *HandlersSuite) Test_UsecaseSuccess()
354354

355355
Any other figments of your imagination are welcome 🙏<br>
356356
List of `testify` functions [here](https://pkg.go.dev/github.com/stretchr/testify@master/assert#pkg-functions).
357+
358+
# FAQ
359+
360+
### Why do we use `internal/testify` instead of `github.com/stretchr/testify`?
361+
362+
1) [internal/testify](./internal/testify) is not a local copy of `stretchr/testify`. The package contains domain (for
363+
linter context) entities, which absent in `testify` itself.
364+
365+
2) We cannot depend on `stretch/testify`, because it causes dependency bomb for linter's module. We should keep as min
366+
dependencies as possible.
367+
368+
### Why do we import `github.com/stretchr/testify` in tests?
369+
370+
Such imports in `testdata` do not affect linter module's dependencies. Moreover, of course, tests should use the real
371+
`testify`.

README.md

+24
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ https://golangci-lint.run/usage/linters/#testifylint
112112
| [suite-broken-parallel](#suite-broken-parallel) |||
113113
| [suite-dont-use-pkg](#suite-dont-use-pkg) |||
114114
| [suite-extra-assert-call](#suite-extra-assert-call) |||
115+
| [suite-method-signature](#suite-method-signature) |||
115116
| [suite-subtest-run](#suite-subtest-run) |||
116117
| [suite-thelper](#suite-thelper) |||
117118
| [useless-assert](#useless-assert) |||
@@ -963,6 +964,29 @@ You can enable such behavior through `--suite-extra-assert-call.mode=require`.
963964

964965
---
965966

967+
### suite-method-signature
968+
969+
```go
970+
971+
func (s *MySuite) SetupTest(i int) { /* ... */ }
972+
func (s *MySuite) TestAlice(t *testing.T) { s.SetupTest(rand()) /* ... */ }
973+
func (s *MySuite) TestBob() { s.SetupTest(rand()) /* ... */ }
974+
975+
976+
func (s *MySuite) SetupTest() { s.setupTest(rand()) } // Use inversion-of-control's methods from suite interfaces.
977+
func (s *MySuite) TestAlice() { /* ... */ } // Keep test methods clean from args and returning values.
978+
func (s *MySuite) TestBob() { /* ... */ }
979+
func (s *MySuite) setupTest(i int) { /* ... */ }
980+
```
981+
982+
**Autofix**: false. <br>
983+
**Enabled by default**: true. <br>
984+
**Reason**: Protection from panics and bugs.
985+
986+
All suite functionality methods are presented [here](https://github.com/stretchr/testify/blob/master/suite/interfaces.go).
987+
988+
---
989+
966990
### suite-subtest-run
967991

968992
```go

analyzer/checkers_factory_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,15 @@ func Test_newCheckers(t *testing.T) {
6060
checkers.NewGoRequire(),
6161
checkers.NewRequireError(),
6262
checkers.NewSuiteBrokenParallel(),
63+
checkers.NewSuiteMethodSignature(),
6364
checkers.NewSuiteSubtestRun(),
6465
}
6566
allAdvancedCheckers := []checkers.AdvancedChecker{
6667
checkers.NewBlankImport(),
6768
checkers.NewGoRequire(),
6869
checkers.NewRequireError(),
6970
checkers.NewSuiteBrokenParallel(),
71+
checkers.NewSuiteMethodSignature(),
7072
checkers.NewSuiteSubtestRun(),
7173
checkers.NewSuiteTHelper(),
7274
}

analyzer/testdata/src/checkers-default/suite-method-signature/suite_method_signature_test.go

+84
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package debug
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/suite"
7+
)
8+
9+
func TestBrokenSuite(t *testing.T) {
10+
suite.Run(t, new(BrokenSuite))
11+
}
12+
13+
type BrokenSuite struct {
14+
suite.Suite
15+
}
16+
17+
func (s *BrokenSuite) SetupTest() {
18+
var _ suite.TearDownSubTest
19+
}
20+
21+
func (s *BrokenSuite) SetT() { // *BrokenSuite does not implement suite.TestingSuite (wrong type for method SetT)
22+
}
23+
24+
func (s *BrokenSuite) TestTypo(_ *testing.T) { // value.go:424: test panicked: reflect: Call with too few input arguments
25+
s.True(true)
26+
}
27+
28+
/*
29+
1) Flagged by govet (copylocks).
30+
2) Related to https://github.com/go-critic/go-critic/issues/331.
31+
*/
32+
func (s BrokenSuite) TestValueReceiver() {
33+
s.True(true)
34+
}

internal/checkers/checkers_registry.go

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ var registry = checkersRegistry{
3030
{factory: asCheckerFactory(NewGoRequire), enabledByDefault: true},
3131
{factory: asCheckerFactory(NewRequireError), enabledByDefault: true},
3232
{factory: asCheckerFactory(NewSuiteBrokenParallel), enabledByDefault: true},
33+
{factory: asCheckerFactory(NewSuiteMethodSignature), enabledByDefault: true},
3334
{factory: asCheckerFactory(NewSuiteSubtestRun), enabledByDefault: true},
3435
{factory: asCheckerFactory(NewSuiteTHelper), enabledByDefault: false},
3536
}

internal/checkers/checkers_registry_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func TestAll(t *testing.T) {
5757
"go-require",
5858
"require-error",
5959
"suite-broken-parallel",
60+
"suite-method-signature",
6061
"suite-subtest-run",
6162
"suite-thelper",
6263
}
@@ -95,6 +96,7 @@ func TestEnabledByDefault(t *testing.T) {
9596
"go-require",
9697
"require-error",
9798
"suite-broken-parallel",
99+
"suite-method-signature",
98100
"suite-subtest-run",
99101
}
100102
if !slices.Equal(expected, checkerList) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package checkers
2+
3+
import (
4+
"fmt"
5+
"go/ast"
6+
7+
"golang.org/x/tools/go/analysis"
8+
"golang.org/x/tools/go/ast/inspector"
9+
10+
"github.com/Antonboom/testifylint/internal/analysisutil"
11+
"github.com/Antonboom/testifylint/internal/testify"
12+
)
13+
14+
// SuiteMethodSignature warns, when
15+
// - suite's test methods have arguments or returning values;
16+
// - suite's custom (user) methods conflicts with (shadows/overrides) suite functionality methods.
17+
type SuiteMethodSignature struct{}
18+
19+
// NewSuiteMethodSignature constructs SuiteMethodSignature checker.
20+
func NewSuiteMethodSignature() SuiteMethodSignature { return SuiteMethodSignature{} }
21+
func (SuiteMethodSignature) Name() string { return "suite-method-signature" }
22+
23+
func (checker SuiteMethodSignature) Check(pass *analysis.Pass, inspector *inspector.Inspector) (diags []analysis.Diagnostic) {
24+
inspector.Preorder([]ast.Node{(*ast.FuncDecl)(nil)}, func(node ast.Node) {
25+
fd := node.(*ast.FuncDecl)
26+
if !isSuiteMethod(pass, fd) {
27+
return
28+
}
29+
30+
methodName := fd.Name.Name
31+
rcv := fd.Recv.List[0]
32+
33+
if isSuiteTestMethod(methodName) {
34+
if fd.Type.Params.NumFields() > 0 || fd.Type.Results.NumFields() > 0 {
35+
const msg = "test method should not have any arguments or returning values"
36+
diags = append(diags, *newDiagnostic(checker.Name(), fd, msg))
37+
return
38+
}
39+
}
40+
41+
iface, ok := suiteMethodToInterface[methodName]
42+
if !ok {
43+
return
44+
}
45+
ifaceObj := analysisutil.ObjectOf(pass.Pkg, testify.SuitePkgPath, iface)
46+
if ifaceObj == nil {
47+
return
48+
}
49+
if !implements(pass, rcv.Type, ifaceObj) {
50+
msg := fmt.Sprintf("method conflicts with %s.%s interface", testify.SuitePkgName, iface)
51+
diags = append(diags, *newDiagnostic(checker.Name(), fd, msg))
52+
}
53+
})
54+
return diags
55+
}
56+
57+
// https://github.com/stretchr/testify/blob/master/suite/interfaces.go
58+
var suiteMethodToInterface = map[string]string{
59+
// NOTE(a.telyshev): We ignore suite.TestingSuite interface, because
60+
// - suite.Run will not work for suite-types that do not implement it;
61+
// - this interface has several methods, which may cause a false positive for one of the methods in the suite-type.
62+
"SetupSuite": "SetupAllSuite",
63+
"SetupTest": "SetupTestSuite",
64+
"TearDownSuite": "TearDownAllSuite",
65+
"TearDownTest": "TearDownTestSuite",
66+
"BeforeTest": "BeforeTest",
67+
"AfterTest": "AfterTest",
68+
"HandleStats": "WithStats",
69+
"SetupSubTest": "SetupSubTest",
70+
"TearDownSubTest": "TearDownSubTest",
71+
}

0 commit comments

Comments
 (0)