Skip to content

Commit 7f270c3

Browse files
committed
feat: add tdsynctest helper, a wrapper around testing/synctest
Signed-off-by: Maxime Soulé <btik-git@scoubidou.com>
1 parent 67eae66 commit 7f270c3

File tree

6 files changed

+223
-7
lines changed

6 files changed

+223
-7
lines changed

README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Currently supports go 1.16 → 1.24.
2424
- [Helpers](#helpers)
2525
- [`tdhttp` or HTTP API testing helper](https://pkg.go.dev/github.com/maxatome/go-testdeep/helpers/tdhttp)
2626
- [`tdsuite` or testing suite helper](https://pkg.go.dev/github.com/maxatome/go-testdeep/helpers/tdsuite)
27+
- [`tdsynctest` or `testing/synctest` helper](https://pkg.go.dev/github.com/maxatome/go-testdeep/helpers/tdsynctest)
2728
- [`tdutil` aka the helper of helpers](https://pkg.go.dev/github.com/maxatome/go-testdeep/helpers/tdutil)
2829
- [See also](#see-also)
2930
- [License](#license)
@@ -231,19 +232,25 @@ suite feature to go-testdeep in a non-intrusive way, but easily and powerfully.
231232
A tests suite is a set of tests run sequentially that share some data.
232233

233234
Some hooks can be set to be automatically called before the suite is run,
234-
before, after and/or between each test, and at the end of the suite.
235+
before, after and/or between each test, and at the end of the suite.
235236

236237
See [`tdsuite`] documentation for details.
237238

239+
### `tdsynctest` or `testing/synctest` helper
240+
241+
The package `github.com/maxatome/go-testdeep/helpers/tdsynctest`
242+
allows to use [`testing/synctest`](https://pkg.go.dev/testing/synctest)
243+
on steroids thanks to go-testdeep.
244+
245+
See [`tdsynctest`] documentation for details.
246+
238247
### `tdutil` aka the helper of helpers
239248

240249
The package `github.com/maxatome/go-testdeep/helpers/tdutil` allows to
241250
write unit tests for go-testdeep helpers and so provides some helpful
242251
functions.
243252

244-
See
245-
[`tdutil`](https://pkg.go.dev/github.com/maxatome/go-testdeep/helpers/tdutil)
246-
for details.
253+
See [`tdutil`] documentation for details.
247254

248255

249256
## See also
@@ -284,6 +291,7 @@ See [FAQ](https://go-testdeep.zetta.rocks/faq/).
284291

285292
[`tdhttp`]: https://pkg.go.dev/github.com/maxatome/go-testdeep/helpers/tdhttp
286293
[`tdsuite`]: https://pkg.go.dev/github.com/maxatome/go-testdeep/helpers/tdsuite
294+
[`tdsynctest`]: https://pkg.go.dev/github.com/maxatome/go-testdeep/helpers/tdsynctest
287295
[`tdutil`]: https://pkg.go.dev/github.com/maxatome/go-testdeep/helpers/tdutil
288296

289297
[`BeLax` config flag]: https://pkg.go.dev/github.com/maxatome/go-testdeep/td#ContextConfig.BeLax

helpers/tdsynctest/test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2025, Maxime Soulé
2+
// All rights reserved.
3+
//
4+
// This source code is licensed under the BSD-style license found in the
5+
// LICENSE file in the root directory of this source tree.
6+
7+
//go:build go1.25
8+
// +build go1.25
9+
10+
package tdsynctest
11+
12+
import (
13+
"testing"
14+
"testing/synctest"
15+
16+
"github.com/maxatome/go-testdeep/td"
17+
)
18+
19+
// Test is a wrapper around [synctest.Test].
20+
//
21+
// The t param of f inherits the configuration of t.
22+
//
23+
// t.TB (set by [td.NewT], [td.Assert], [td.Require], …) must be a
24+
// [*testing.T] instance.
25+
func Test(t *td.T, f func(t *td.T)) {
26+
tt, ok := t.TB.(*testing.T)
27+
if !ok {
28+
t.Helper()
29+
t.Fatalf("tdsynctest.Test only works if underlying T.TB field is a *testing.T, so not a %T", t.TB)
30+
}
31+
conf := t.Config
32+
synctest.Test(tt, func(t *testing.T) {
33+
f(td.NewT(t, conf))
34+
})
35+
}
36+
37+
// TestAssertRequire is a wrapper around [synctest.Test].
38+
//
39+
// The assert and require params of f inherit the configuration of t,
40+
// except that a failure is never fatal using assert and always fatal
41+
// using require.
42+
//
43+
// t.TB (set by [td.NewT], [td.Assert], [td.Require], …) must be a
44+
// [*testing.T] instance.
45+
func TestAssertRequire(t *td.T, f func(assert, require *td.T)) {
46+
tt, ok := t.TB.(*testing.T)
47+
if !ok {
48+
t.Helper()
49+
t.Fatalf("tdsynctest.TestAssertRequire only works if underlying T.TB field is a *testing.T, so not a %T", t.TB)
50+
}
51+
conf := t.Config
52+
synctest.Test(tt, func(t *testing.T) {
53+
f(td.AssertRequire(t, conf))
54+
})
55+
}
56+
57+
// Wait simply calls [synctest.Wait]. It only exists to avoid to
58+
// import [testing/synctest].
59+
func Wait() {
60+
synctest.Wait()
61+
}

helpers/tdsynctest/test_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright (c) 2025, Maxime Soulé
2+
// All rights reserved.
3+
//
4+
// This source code is licensed under the BSD-style license found in the
5+
// LICENSE file in the root directory of this source tree.
6+
7+
//go:build go1.25
8+
// +build go1.25
9+
10+
// Until go 1.23 in go.mod
11+
//go:debug asynctimerchan=0
12+
13+
package tdsynctest_test
14+
15+
import (
16+
"testing"
17+
18+
"github.com/maxatome/go-testdeep/helpers/tdsynctest"
19+
"github.com/maxatome/go-testdeep/helpers/tdutil"
20+
"github.com/maxatome/go-testdeep/internal/test"
21+
"github.com/maxatome/go-testdeep/td"
22+
)
23+
24+
func TestTest(t *testing.T) {
25+
t.Run("testing.T required", func(t *testing.T) {
26+
tt := tdutil.NewT(t.Name())
27+
28+
run := false
29+
failed := tt.CatchFailNow(func() {
30+
assert := td.Assert(tt)
31+
tdsynctest.Test(assert, func(assert *td.T) {
32+
run = true
33+
})
34+
})
35+
test.IsFalse(t, run)
36+
test.IsTrue(t, failed)
37+
test.MatchStr(t, tt.LogBuf(),
38+
`^\s+test_test\.go:\d+: tdsynctest\.Test only works if underlying T\.TB field is a \*testing\.T, so not a \*tdutil\.T\n\z`)
39+
})
40+
41+
t.Run("OK1", func(t *testing.T) {
42+
run, belax, req := false, false, true
43+
assert := td.Assert(t).BeLax()
44+
tdsynctest.Test(assert, func(assert *td.T) {
45+
go func() {
46+
run = true
47+
belax = assert.Config.BeLax
48+
req = assert.Config.FailureIsFatal
49+
}()
50+
tdsynctest.Wait()
51+
})
52+
test.IsTrue(t, run)
53+
test.IsTrue(t, belax)
54+
test.IsFalse(t, req)
55+
})
56+
57+
t.Run("OK2", func(t *testing.T) {
58+
run, belax, req := false, true, false
59+
require := td.Require(t)
60+
tdsynctest.Test(require, func(require *td.T) {
61+
go func() {
62+
run = true
63+
belax = require.Config.BeLax
64+
req = require.Config.FailureIsFatal
65+
}()
66+
tdsynctest.Wait()
67+
})
68+
test.IsTrue(t, run)
69+
test.IsFalse(t, belax)
70+
test.IsTrue(t, req)
71+
})
72+
}
73+
74+
func TestTestAssertRequire(t *testing.T) {
75+
t.Run("testing.T required", func(t *testing.T) {
76+
tt := tdutil.NewT(t.Name())
77+
78+
run := false
79+
failed := tt.CatchFailNow(func() {
80+
assert := td.Assert(tt)
81+
tdsynctest.TestAssertRequire(assert, func(assert, require *td.T) {
82+
run = true
83+
})
84+
})
85+
test.IsFalse(t, run)
86+
test.IsTrue(t, failed)
87+
test.MatchStr(t, tt.LogBuf(),
88+
`^\s+test_test\.go:\d+: tdsynctest\.TestAssertRequire only works if underlying T\.TB field is a \*testing\.T, so not a \*tdutil\.T\n\z`)
89+
})
90+
91+
t.Run("OK1", func(t *testing.T) {
92+
var run, belaxA, belaxR, ass, req bool
93+
assert := td.Assert(t).BeLax()
94+
tdsynctest.TestAssertRequire(assert, func(assert, require *td.T) {
95+
go func() {
96+
run = true
97+
belaxA = assert.Config.BeLax
98+
ass = !assert.Config.FailureIsFatal
99+
belaxR = require.Config.BeLax
100+
req = require.Config.FailureIsFatal
101+
}()
102+
tdsynctest.Wait()
103+
})
104+
test.IsTrue(t, run)
105+
test.IsTrue(t, belaxA)
106+
test.IsTrue(t, ass)
107+
test.IsTrue(t, belaxR)
108+
test.IsTrue(t, req)
109+
})
110+
111+
t.Run("OK2", func(t *testing.T) {
112+
run, belaxA, belaxR, ass, req := false, true, true, false, false
113+
assert := td.Assert(t)
114+
tdsynctest.TestAssertRequire(assert, func(assert, require *td.T) {
115+
go func() {
116+
run = true
117+
belaxA = assert.Config.BeLax
118+
ass = !assert.Config.FailureIsFatal
119+
belaxR = require.Config.BeLax
120+
req = require.Config.FailureIsFatal
121+
}()
122+
tdsynctest.Wait()
123+
})
124+
test.IsTrue(t, run)
125+
test.IsFalse(t, belaxA)
126+
test.IsTrue(t, ass)
127+
test.IsFalse(t, belaxR)
128+
test.IsTrue(t, req)
129+
})
130+
}

internal/test/check.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package test
88

99
import (
10+
"regexp"
1011
"strings"
1112
"testing"
1213
"unicode"
@@ -57,6 +58,18 @@ func EqualStr(t *testing.T, got, expected string, args ...any) bool {
5758
return false
5859
}
5960

61+
// MatchStr checks that got matches expected regexp.
62+
func MatchStr(t *testing.T, got, expectedRe string, args ...any) bool {
63+
re := regexp.MustCompile(expectedRe)
64+
if re.MatchString(got) {
65+
return true
66+
}
67+
68+
t.Helper()
69+
EqualErrorMessage(t, spewIfNeeded(got), spewIfNeeded(expectedRe), args...)
70+
return false
71+
}
72+
6073
// EqualInt checks that got equals expected.
6174
func EqualInt(t *testing.T, got, expected int, args ...any) bool {
6275
if got == expected {

td/t_struct.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,9 @@ func (t *T) getRunFunc() (runtFuncs, bool) {
761761
//
762762
// The t param of f inherits the configuration of the self-reference.
763763
//
764-
// See also [T.RunAssertRequire].
764+
// See also [T.RunAssertRequire],
765+
// [github.com/maxatome/go-testdeep/helpers/tdsynctest.Run] and
766+
// [github.com/maxatome/go-testdeep/helpers/tdsynctest.RunAssertRequire].
765767
func (t *T) Run(name string, f func(t *T)) bool {
766768
t.Helper()
767769

@@ -815,7 +817,9 @@ func (t *T) Run(name string, f func(t *T)) bool {
815817
// of the self-reference, except that a failure is never fatal using
816818
// assert and always fatal using require.
817819
//
818-
// See also [T.Run].
820+
// See also [T.Run],
821+
// [github.com/maxatome/go-testdeep/helpers/tdsynctest.RunAssertRequire]
822+
// and [github.com/maxatome/go-testdeep/helpers/tdsynctest.Run].
819823
func (t *T) RunAssertRequire(name string, f func(assert, require *T)) bool {
820824
t.Helper()
821825

tools/gen_funcs.pl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ sub extract_params
629629
. "\n\n"
630630
# Helpers
631631
. join("\n", map "[`$_`]: $URL_GODOC/helpers/$_",
632-
qw(tdhttp tdsuite tdutil))
632+
qw(tdhttp tdsuite tdsynctest tdutil))
633633
. "\n\n"
634634
# Specific links
635635
. "[`BeLax` config flag]: $td_url#ContextConfig.BeLax\n"

0 commit comments

Comments
 (0)