suite: add SyncTest
Introduces (*Suite).SyncTest which is to synctest.Run like (*Suite).Run is to (*testing.T).Run.
Its role is to maintain s.T() (so it points to synctest bubble's t).
synctest was introduced in go 1.25, so this is built conditionally.
@brackendawson, can take a look please?
Hey, thanks for adding this!
I created a issue about support of synctest in suite. Even do your change is nice, probably alot of people will use it wrong. You can't reuse channels inside the bubble, if they are created outside the bubble (e.g. if they are part of the suite).
I created a issue about support of synctest in suite. Even do your change is nice, probably alot of people will use it wrong. You can't reuse channels inside the bubble, if they are created outside the bubble (e.g. if they are part of the suite).
It's a fair point that we need to think of an API that makes sense for how people normally use suites. Let me think about it.
As an alternative experiment, I locally forked the entire suite package to make a Go 1.25 syncsuite package, and only modified the end ofsuite.go file's last two lines of func Run(t *testing.T, suite TestingSuite) { and the following function: func runTests(t *testing.T, tests []test) { as follows (signature change intended):
runTests(t, suite, tests)
}
func runTests(t *testing.T, suite TestingSuite, tests []test) {
if len(tests) == 0 {
t.Log("warning: no tests to run")
return
}
oldT := suite.T()
for _, currentTest := range tests {
synctest.Test(t, func(t *testing.T) {
suite.SetT(t)
defer suite.SetT(oldT)
t.Run(currentTest.name, currentTest.run)
})
}
}
As an alternative experiment, I locally forked the entire
suitepackage to make a Go 1.25syncsuitepackage, and only modified the end ofsuite.gofile's last two lines offunc Run(t *testing.T, suite TestingSuite) {and the following function:func runTests(t *testing.T, tests []test) {as follows (signature change intended):runTests(t, suite, tests) } func runTests(t *testing.T, suite TestingSuite, tests []test) { if len(tests) == 0 { t.Log("warning: no tests to run") return } oldT := suite.T() for _, currentTest := range tests { synctest.Test(t, func(t *testing.T) { suite.SetT(t) defer suite.SetT(oldT) t.Run(currentTest.name, currentTest.run) }) } }
Note that a suite only for synctest shall not provide SetupSuite. I don't know if inside a synctest you can use normal subtests.
Update: I also did a fast draft and added direct synctest support into the suite by prefixing synctests with SyncTest. It works in simple cases but breaks in special cases. I added tests where it works and where it fails.
https://github.com/stretchr/testify/compare/master...Nocccer:testify:feature/suite-synctest
Probably the solution is a new suite that runs each test inside a bubble and does not allow to have SetupSuite and subtest functionality.
Forking Suite into SyncSuite without a Run or a SetupSuite makes sense to me.
Suite methods:
- Assert
- Require
- T
- ~Run~
- ~SetS~
- ~SetT~
The first 3 methods can be moved into a common struct.
Forking
SuiteintoSyncSuitewithout aRunor aSetupSuitemakes sense to me.
Suitemethods:
- Assert
- Require
- T
- ~Run~
- ~SetS~
- ~SetT~
The first 3 methods can be moved into a common struct.
I tried to create a new SyncSuite yesterday. The problem is that Suite is supported till go 1.17. We can't extend the global Run method to check what the suite embeeds (Suite or SyncSuite), because for go <1.25 it will not compile, because SyncSuite will be undefined. The only way to add a new suite type without breaking go compatibility, would be to provide another method to run a SyncSuite or create a new package.
Maybe i am dumb and it can work. I don't have experience with build flags so far. I will try out more today.
Update: Here is a branch where i added SyncTest and splitt the existing Suite into reusable parts.
https://github.com/Nocccer/testify/tree/feature/synctest-suite
Did you try two mutually exclusive files, one for go1.25 and one for !go1.25?