testify icon indicating copy to clipboard operation
testify copied to clipboard

suite: add SyncTest

Open ikonst opened this issue 1 month ago • 9 comments

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.

ikonst avatar Nov 27 '25 19:11 ikonst

@brackendawson, can take a look please?

ikonst avatar Dec 02 '25 09:12 ikonst

Hey, thanks for adding this!

StevenACoffman avatar Dec 04 '25 14:12 StevenACoffman

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).

Nocccer avatar Dec 04 '25 20:12 Nocccer

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.

ikonst avatar Dec 05 '25 13:12 ikonst

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)
		})
	}
}

StevenACoffman avatar Dec 05 '25 14:12 StevenACoffman

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)
		})
	}
}

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.

Nocccer avatar Dec 05 '25 17:12 Nocccer

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.

ikonst avatar Dec 07 '25 10:12 ikonst

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.

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

Nocccer avatar Dec 07 '25 10:12 Nocccer

Did you try two mutually exclusive files, one for go1.25 and one for !go1.25?

ikonst avatar Dec 08 '25 09:12 ikonst