Skip to content

Commit 78fc7cd

Browse files
authored
new checker 'suite-subtest-run' (#104)
* new checker 'suite-subtest-run' * suite-subtest-run: self-review #1 * suite-subtest-run: self-review #2
1 parent 654e0ae commit 78fc7cd

20 files changed

+397
-67
lines changed

CONTRIBUTING.md

-27
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ Describe a new checker in [checkers section](./README.md#checkers).
135135
- [http-const](#http-const)
136136
- [http-sugar](#http-sugar)
137137
- [require-len](#require-len)
138-
- [suite-run](#suite-run)
139138
- [suite-test-name](#suite-test-name)
140139
- [useless-assert](#useless-assert)
141140

@@ -320,32 +319,6 @@ then before that there must be a length constraint through `require`.
320319

321320
Or maybe do something similar for maps? And come up with better name for the checker.
322321

323-
### suite-run
324-
325-
```go
326-
func (s *Suite) TestSomething() {
327-
328-
s.T().Run("subtest1", func(t *testing.T) {
329-
// ...
330-
assert.Equal(t, "gopher", result)
331-
})
332-
333-
334-
s.Run("subtest1", func() {
335-
// ...
336-
s.Equal("gopher", result)
337-
})
338-
}
339-
```
340-
341-
**Autofix**: true. <br>
342-
**Enabled by default**: probably yes. <br>
343-
**Reason**: Code simplification and consistency. <br>
344-
**Related issues**: [#35](https://github.com/Antonboom/testifylint/issues/35)
345-
346-
But need to investigate the technical difference and the reasons for the appearance of `s.Run`.
347-
Also, maybe this case is already covered by [suite-dont-use-pkg](README.md#suite-dont-use-pkg)?
348-
349322
---
350323

351324
### suite-test-name

README.md

+50-18
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ https://golangci-lint.run/usage/linters/#testifylint
105105
| [require-error](#require-error) |||
106106
| [suite-dont-use-pkg](#suite-dont-use-pkg) |||
107107
| [suite-extra-assert-call](#suite-extra-assert-call) |||
108+
| [suite-subtest-run](#suite-subtest-run) |||
108109
| [suite-thelper](#suite-thelper) |||
109110
| [useless-assert](#useless-assert) |||
110111

@@ -443,8 +444,8 @@ but did not take into account its package, thereby reacting to everything that i
443444
```go
444445
// isPrint records the unformatted-print functions.
445446
var isPrint = map[string]bool{
446-
"error": true,
447-
"fatal": true,
447+
"error": true,
448+
"fatal": true,
448449
// ...
449450
}
450451
```
@@ -456,8 +457,8 @@ in Go 1.10 after a related [issue](https://github.com/golang/go/issues/22936):
456457
```go
457458
// isPrint records the print functions.
458459
var isPrint = map[string]bool{
459-
"fmt.Errorf": true,
460-
"fmt.Fprint": true,
460+
"fmt.Errorf": true,
461+
"fmt.Fprint": true,
461462
// ...
462463
}
463464
```
@@ -672,8 +673,8 @@ Also, to minimize false positives, `require-error` ignores:
672673
import "github.com/stretchr/testify/assert"
673674

674675
func (s *MySuite) TestSomething() {
675-
❌ assert.Equal(s.T(), 42, value)
676-
✅ s.Equal(42, value)
676+
❌ assert.Equal(s.T(), 42, value)
677+
✅ s.Equal(42, value)
677678
}
678679
```
679680

@@ -689,24 +690,24 @@ By default, the checker wants you to remove unnecessary `Assert()` calls:
689690

690691
```go
691692
func (s *MySuite) TestSomething() {
692-
❌ s.Assert().Equal(42, value)
693-
✅ s.Equal(42, value)
693+
❌ s.Assert().Equal(42, value)
694+
✅ s.Equal(42, value)
694695
}
695696
```
696697

697698
But sometimes, on the contrary, people want consistency with `s.Assert()` and `s.Require()`:
698699

699700
```go
700701
func (s *MySuite) TestSomething() {
701-
// ...
702+
// ...
702703

703-
704-
s.Require().NoError(err)
705-
s.Equal(42, value)
704+
705+
s.Require().NoError(err)
706+
s.Equal(42, value)
706707

707-
708-
s.Require().NoError(err)
709-
s.Assert().Equal(42, value)
708+
709+
s.Require().NoError(err)
710+
s.Assert().Equal(42, value)
710711
}
711712
```
712713

@@ -718,18 +719,49 @@ You can enable such behavior through `--suite-extra-assert-call.mode=require`.
718719

719720
---
720721

722+
### suite-subtest-run
723+
724+
```go
725+
func (s *MySuite) TestSomething() {
726+
727+
s.T().Run("subtest", func(t *testing.T) {
728+
assert.Equal(t, 42, result)
729+
})
730+
731+
732+
s.Run("subtest", func() {
733+
s.Equal(42, result)
734+
})
735+
}
736+
```
737+
738+
**Autofix**: false. <br>
739+
**Enabled by default**: true. <br>
740+
**Reason**: Protection from undefined behavior.
741+
742+
According to `testify` [documentation](https://pkg.go.dev/github.com/stretchr/testify/suite#Suite.Run), `s.Run` should
743+
be used for running subtests. This call (among other things) initializes the suite with a fresh instance of `t` and
744+
protects tests from undefined behavior (such as data races).
745+
746+
Autofix is disabled because in the most cases it requires rewriting the assertions in the subtest and can leads to dead
747+
code.
748+
749+
The checker is especially useful in combination with [suite-dont-use-pkg](#suite-dont-use-pkg).
750+
751+
---
752+
721753
### suite-thelper
722754

723755
```go
724756
725757
func (s *RoomSuite) assertRoomRound(roundID RoundID) {
726-
s.Equal(roundID, s.getRoom().CurrentRound.ID)
758+
s.Equal(roundID, s.getRoom().CurrentRound.ID)
727759
}
728760

729761
730762
func (s *RoomSuite) assertRoomRound(roundID RoundID) {
731-
s.T().Helper()
732-
s.Equal(roundID, s.getRoom().CurrentRound.ID)
763+
s.T().Helper()
764+
s.Equal(roundID, s.getRoom().CurrentRound.ID)
733765
}
734766
```
735767

analyzer/checkers_factory_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ func Test_newCheckers(t *testing.T) {
5151
checkers.NewBlankImport(),
5252
checkers.NewGoRequire(),
5353
checkers.NewRequireError(),
54+
checkers.NewSuiteSubtestRun(),
5455
}
5556
allAdvancedCheckers := []checkers.AdvancedChecker{
5657
checkers.NewBlankImport(),
5758
checkers.NewGoRequire(),
5859
checkers.NewRequireError(),
60+
checkers.NewSuiteSubtestRun(),
5961
checkers.NewSuiteTHelper(),
6062
}
6163

analyzer/testdata/src/checkers-default/suite-dont-use-pkg/suite_dont_use_pkg_test.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

analyzer/testdata/src/checkers-default/suite-dont-use-pkg/suite_dont_use_pkg_test.go.golden

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func (s *SuiteDontUsePkgCheckerSuite) TestAll() {
4646
s.Require().Equal(42, result) // want "suite-dont-use-pkg: use s\\.Require\\(\\)\\.Equal"
4747
s.Require().Equalf(42, result, "msg with args %d %s", 42, "42") // want "suite-dont-use-pkg: use s\\.Require\\(\\)\\.Equalf"
4848

49-
s.T().Run("not detected", func(t *testing.T) {
49+
s.T().Run("not detected in order to avoid conflict with suite-subtest-run", func(t *testing.T) {
5050
var result any
5151
assObj, reqObj := assert.New(t), require.New(t)
5252

analyzer/testdata/src/checkers-default/suite-subtest-run/suite_subtest_run_test.go

+76
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,59 @@
1+
package debug
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/suite"
7+
)
8+
9+
type SomeSuite struct {
10+
suite.Suite
11+
}
12+
13+
func TestSomeSuite(t *testing.T) {
14+
suite.Run(t, new(SomeSuite))
15+
}
16+
17+
func (s *SomeSuite) BeforeTest(suiteName, testName string) {
18+
s.Equal(1, 2)
19+
20+
s.T().Run("init 1", func(t *testing.T) {
21+
s.Require().Equal(1, 2)
22+
})
23+
24+
s.Run("init 2", func() {
25+
s.Require().Equal(2, 1)
26+
})
27+
}
28+
29+
func (s *SomeSuite) TestSomething() {
30+
s.T().Parallel()
31+
32+
s.Run("sum", func() {
33+
dummy := 3 + 1
34+
s.Equal(4, dummy)
35+
})
36+
37+
s.Run("mul", func() {
38+
dummy := 3 * 1
39+
s.Equal(3, dummy)
40+
})
41+
}
42+
43+
func (s *SomeSuite) TestSomething_ThroughT() {
44+
s.T().Parallel()
45+
46+
s.T().Run("sum", func(t *testing.T) {
47+
t.Parallel()
48+
49+
dummy := 3 + 1
50+
s.Equal(4, dummy)
51+
})
52+
53+
s.T().Run("mul", func(t *testing.T) {
54+
t.Parallel()
55+
56+
dummy := 3 * 1
57+
s.Equal(3, dummy)
58+
})
59+
}

internal/checkers/checkers_registry.go

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var registry = checkersRegistry{
2525
{factory: asCheckerFactory(NewBlankImport), enabledByDefault: true},
2626
{factory: asCheckerFactory(NewGoRequire), enabledByDefault: true},
2727
{factory: asCheckerFactory(NewRequireError), enabledByDefault: true},
28+
{factory: asCheckerFactory(NewSuiteSubtestRun), enabledByDefault: true},
2829
{factory: asCheckerFactory(NewSuiteTHelper), enabledByDefault: false},
2930
}
3031

internal/checkers/checkers_registry_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func TestAll(t *testing.T) {
5252
"blank-import",
5353
"go-require",
5454
"require-error",
55+
"suite-subtest-run",
5556
"suite-thelper",
5657
}
5758
if !slices.Equal(expected, checkerList) {
@@ -84,6 +85,7 @@ func TestEnabledByDefault(t *testing.T) {
8485
"blank-import",
8586
"go-require",
8687
"require-error",
88+
"suite-subtest-run",
8789
}
8890
if !slices.Equal(expected, checkerList) {
8991
t.Fatalf("unexpected list: %#v", checkerList)

internal/checkers/expected_actual.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
var DefaultExpectedVarPattern = regexp.MustCompile(
1515
`(^(exp(ected)?|want(ed)?)([A-Z]\w*)?$)|(^(\w*[a-z])?(Exp(ected)?|Want(ed)?)$)`)
1616

17-
// ExpectedActual detects situation like
17+
// ExpectedActual detects situations like
1818
//
1919
// assert.Equal(t, result, expected)
2020
// assert.EqualExportedValues(t, resultObj, User{Name: "Anton"})
@@ -130,9 +130,9 @@ func (checker ExpectedActual) isExpectedValueCandidate(pass *analysis.Pass, expr
130130
return isBasicLit(expr) ||
131131
isUntypedConst(pass, expr) ||
132132
isTypedConst(pass, expr) ||
133-
isIdentNamedAsExpected(checker.expVarPattern, expr) ||
134-
isStructVarNamedAsExpected(checker.expVarPattern, expr) ||
135-
isStructFieldNamedAsExpected(checker.expVarPattern, expr)
133+
isIdentNamedAfterPattern(checker.expVarPattern, expr) ||
134+
isStructVarNamedAfterPattern(checker.expVarPattern, expr) ||
135+
isStructFieldNamedAfterPattern(checker.expVarPattern, expr)
136136
}
137137

138138
func isParenExpr(ce *ast.CallExpr) bool {
@@ -158,7 +158,7 @@ func isCastedBasicLitOrExpectedValue(ce *ast.CallExpr, pattern *regexp.Regexp) b
158158
"int", "int8", "int16", "int32", "int64",
159159
"float32", "float64",
160160
"rune", "string":
161-
return isBasicLit(ce.Args[0]) || isIdentNamedAsExpected(pattern, ce.Args[0])
161+
return isBasicLit(ce.Args[0]) || isIdentNamedAfterPattern(pattern, ce.Args[0])
162162
}
163163
return false
164164
}

0 commit comments

Comments
 (0)