Skip to content

Commit cb1561b

Browse files
authoredMar 31, 2025··
fix: default panic output (#102)
Signed-off-by: Tronje Krop <[email protected]>
1 parent cc1f154 commit cb1561b

File tree

9 files changed

+186
-69
lines changed

9 files changed

+186
-69
lines changed
 

‎Makefile

+11-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
## Manual: http://github.com/tkrop/go-make/MANUAL.md
12
SHELL := /bin/bash
23

34
# Include custom variables to modify behavior.
@@ -11,18 +12,21 @@ export GO ?= go
1112
export GOPATH ?= $(shell $(GO) env GOPATH)
1213
export GOBIN ?= $(GOPATH)/bin
1314

14-
# Setup go-make version to use desired build and config scripts.
15-
GOMAKE_DEP ?= github.com/tkrop/go-make@v0.0.126
15+
# Setup default temporary directory for go-make.
16+
TMPDIR ?= /tmp
17+
# Setup default go-make installation flags.
1618
INSTALL_FLAGS ?= -mod=readonly -buildvcs=auto
17-
# Request targets from go-make targets target.
19+
# Setup go-make version to use desired build and config scripts.
20+
GOMAKE_DEP ?= github.com/tkrop/go-make@v0.0.132
21+
# Request targets from go-make show-targets target.
1822
TARGETS := $(shell command -v $(GOBIN)/go-make >/dev/null || \
1923
$(GO) install $(INSTALL_FLAGS) $(GOMAKE_DEP) >/dev/stderr && \
20-
MAKEFLAGS="" $(GOBIN)/go-make show-targets 2>/dev/null)
24+
cat "$(abspath $(TMPDIR))/go-make-$(USER)$(realpath $(CURDIR))/targets"; \
25+
MAKEFLAGS="" $(GOBIN)/go-make show-targets >/dev/null 2>&1 &)
2126
# Declare all targets phony to make them available for auto-completion.
2227
.PHONY:: $(TARGETS)
2328

2429
# Delegate all targets to go-make in a single stubbing call.
25-
GOAL := $(firstword $(MAKECMDGOALS) all)
2630
$(eval $(MAKECMDGOALS)::;@:)
27-
$(GOAL):: $(GOBIN)/go-make
28-
@$(GOBIN)/go-make $(MAKECMDGOALS);
31+
$(firstword $(MAKECMDGOALS) all)::
32+
@+$(GOBIN)/go-make $(MAKECMDGOALS);

‎VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.0.24
1+
0.0.25

‎go.mod

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
module github.com/tkrop/go-testing
22

3-
go 1.24.0
3+
go 1.24.1
44

55
require (
66
github.com/golang/mock v1.6.0
77
github.com/h2non/gock v1.2.0
88
github.com/huandu/go-clone v1.6.0
99
github.com/stretchr/testify v1.10.0
1010
golang.org/x/text v0.18.0
11-
golang.org/x/tools v0.30.0
11+
golang.org/x/tools v0.31.0
1212
)
1313

1414
require (
@@ -17,8 +17,8 @@ require (
1717
github.com/kr/pretty v0.3.1 // indirect
1818
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
1919
github.com/rogpeppe/go-internal v1.12.0 // indirect
20-
golang.org/x/mod v0.23.0 // indirect
21-
golang.org/x/sync v0.11.0 // indirect
20+
golang.org/x/mod v0.24.0 // indirect
21+
golang.org/x/sync v0.12.0 // indirect
2222
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
2323
gopkg.in/yaml.v3 v3.0.1 // indirect
2424
)

‎go.sum

+6-6
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,15 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
3939
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
4040
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
4141
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
42-
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
43-
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
42+
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
43+
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
4444
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
4545
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
4646
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
4747
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
4848
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
49-
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
50-
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
49+
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
50+
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
5151
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
5252
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
5353
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -61,8 +61,8 @@ golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
6161
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
6262
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
6363
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
64-
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
65-
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
64+
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
65+
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
6666
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
6767
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
6868
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

‎internal/maps/maps_test.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package maps_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
. "github.com/tkrop/go-testing/internal/maps"
9+
"github.com/tkrop/go-testing/test"
10+
)
11+
12+
type CopyParam struct {
13+
input map[string]int
14+
expect map[string]int
15+
}
16+
17+
var testCopyParams = map[string]CopyParam{
18+
"empty map": {
19+
input: map[string]int{},
20+
expect: map[string]int{},
21+
},
22+
"single key-value pair": {
23+
input: map[string]int{"a": 1},
24+
expect: map[string]int{"a": 1},
25+
},
26+
"multiple key-value pairs": {
27+
input: map[string]int{"a": 1, "b": 2, "c": 3},
28+
expect: map[string]int{"a": 1, "b": 2, "c": 3},
29+
},
30+
}
31+
32+
func TestCopy(t *testing.T) {
33+
test.Map(t, testCopyParams).
34+
Run(func(t test.Test, param CopyParam) {
35+
// When
36+
expect := Copy(param.input)
37+
38+
// Then
39+
assert.Equal(t, param.expect, expect)
40+
})
41+
}
42+
43+
type AddParam struct {
44+
target map[string]int
45+
sources []map[string]int
46+
expect map[string]int
47+
}
48+
49+
var testAddParams = map[string]AddParam{
50+
"no sources": {
51+
target: map[string]int{"a": 1},
52+
sources: []map[string]int{},
53+
expect: map[string]int{"a": 1},
54+
},
55+
"single source": {
56+
target: map[string]int{"a": 1},
57+
sources: []map[string]int{{"b": 2}},
58+
expect: map[string]int{"a": 1, "b": 2},
59+
},
60+
"multiple sources with no conflicts": {
61+
target: map[string]int{"a": 1},
62+
sources: []map[string]int{{"b": 2}, {"c": 3}},
63+
expect: map[string]int{"a": 1, "b": 2, "c": 3},
64+
},
65+
"multiple sources with conflicts": {
66+
target: map[string]int{"a": 1},
67+
sources: []map[string]int{{"a": 2}, {"a": 3, "b": 4}},
68+
expect: map[string]int{"a": 3, "b": 4},
69+
},
70+
}
71+
72+
func TestAdd(t *testing.T) {
73+
test.Map(t, testAddParams).
74+
Run(func(t test.Test, param AddParam) {
75+
// When
76+
result := Add(param.target, param.sources...)
77+
78+
// Then
79+
assert.Equal(t, param.expect, result)
80+
})
81+
}

‎test/caller_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,14 @@ var (
131131
}()
132132
// CallerTestError provides the file with the line number of the `Error`
133133
// call in the test context implementation.
134-
CallerTestError = path.Join(SourceDir, "context.go:333")
134+
CallerTestError = path.Join(SourceDir, "context.go:334")
135135
// CallerReporterErrorf provides the file with the line number of the
136136
// `Errorf` call in the test reporter/validator implementation.
137137
CallerReporterError = path.Join(SourceDir, "reporter.go:83")
138138

139139
// CallerTestErrorf provides the file with the line number of the `Errorf`
140140
// call in the test context implementation.
141-
CallerTestErrorf = path.Join(SourceDir, "context.go:351")
141+
CallerTestErrorf = path.Join(SourceDir, "context.go:352")
142142
// CallerReporterErrorf provides the file with the line number of the
143143
// `Errorf` call in the test reporter/validator implementation.
144144
CallerReporterErrorf = path.Join(SourceDir, "reporter.go:105")

‎test/common_test.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,17 @@ var (
9999
t.FailNow()
100100
}
101101
// TestPanic is a test function that panics.
102-
TestPanic = func(t test.Test) {
102+
TestPanic = func(test.Test) {
103103
// Duplicate terminal failures are ignored.
104-
go func() { t.(*test.Context).Panic("fail") }()
104+
go func() {
105+
// Recover from panic to avoid test abort.
106+
defer func() {
107+
if r := recover(); r != "fail" {
108+
panic(r)
109+
}
110+
}()
111+
panic("fail")
112+
}()
105113
panic("fail")
106114
}
107115
)

‎test/context.go

+19-31
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ func (t *Context) Deadline() (time.Time, bool) {
262262

263263
t.mu.Lock()
264264
defer t.mu.Unlock()
265+
265266
if !t.deadline.IsZero() {
266267
return t.deadline, true
267268
}
@@ -359,12 +360,7 @@ func (t *Context) Errorf(format string, args ...any) {
359360
func (t *Context) Fatal(args ...any) {
360361
t.t.Helper()
361362

362-
if t.failed.Swap(true) {
363-
runtime.Goexit()
364-
}
365-
366-
t.mu.Lock()
367-
defer t.mu.Unlock()
363+
t.lockOrExit()
368364
defer t.unlock()
369365

370366
if t.expect == Success {
@@ -382,12 +378,7 @@ func (t *Context) Fatal(args ...any) {
382378
func (t *Context) Fatalf(format string, args ...any) {
383379
t.t.Helper()
384380

385-
if t.failed.Swap(true) {
386-
runtime.Goexit()
387-
}
388-
389-
t.mu.Lock()
390-
defer t.mu.Unlock()
381+
t.lockOrExit()
391382
defer t.unlock()
392383

393384
if t.expect == Success {
@@ -404,12 +395,7 @@ func (t *Context) Fatalf(format string, args ...any) {
404395
func (t *Context) Fail() {
405396
t.t.Helper()
406397

407-
if t.failed.Swap(true) {
408-
runtime.Goexit()
409-
}
410-
411-
t.mu.Lock()
412-
defer t.mu.Unlock()
398+
t.lockOrExit()
413399
defer t.unlock()
414400

415401
if t.expect == Success {
@@ -427,12 +413,7 @@ func (t *Context) Fail() {
427413
func (t *Context) FailNow() {
428414
t.t.Helper()
429415

430-
if t.failed.Swap(true) {
431-
runtime.Goexit()
432-
}
433-
434-
t.mu.Lock()
435-
defer t.mu.Unlock()
416+
t.lockOrExit()
436417
defer t.unlock()
437418

438419
if t.expect == Success {
@@ -460,17 +441,12 @@ var regexPanic = regexp.MustCompile(`(?m)\nruntime\/debug\.Stack\(\)` +
460441
func (t *Context) Panic(arg any) {
461442
t.t.Helper()
462443

463-
if t.failed.Swap(true) {
464-
runtime.Goexit()
465-
}
466-
467-
t.mu.Lock()
468-
defer t.mu.Unlock()
444+
t.lockOrExit()
469445
defer t.unlock()
470446

471447
if t.expect == Success {
472448
stack := regexPanic.Split(string(debug.Stack()), -1)
473-
t.Fatalf("panic: %v\n%s\n%s", arg, stack[0], stack[1])
449+
t.t.Fatalf("panic: %v\n%s\n%s", arg, stack[0], stack[1])
474450
} else if t.reporter != nil {
475451
t.reporter.Panic(arg)
476452
}
@@ -583,10 +559,22 @@ func (t *Context) finish() {
583559
}
584560
}
585561

562+
// lockOrExit either locks the test mutex or aborts a test in case of a pending
563+
// test failure to ensure that only the first failure is reported.
564+
func (t *Context) lockOrExit() {
565+
t.t.Helper()
566+
567+
if t.expect == Failure && t.failed.Swap(true) {
568+
runtime.Goexit()
569+
}
570+
t.mu.Lock()
571+
}
572+
586573
// unlock unlocks the wait group of the test by consuming the wait group
587574
// counter completely.
588575
func (t *Context) unlock() {
589576
if t.wg != nil {
590577
t.wg.Add(math.MinInt)
591578
}
579+
t.mu.Unlock()
592580
}

‎test/context_test.go

+52-16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66
"time"
77

8+
"github.com/golang/mock/gomock"
89
"github.com/stretchr/testify/assert"
910

1011
"github.com/tkrop/go-testing/mock"
@@ -46,18 +47,52 @@ func TestTempDir(t *testing.T) {
4647
}))
4748
}
4849

49-
// PanicParam is a test parameter type for testing the test context with panic
50-
// cases.
51-
type PanicParam struct {
50+
// ContextParam is a test parameter type for testing the test context.
51+
type ContextParam struct {
52+
setup mock.SetupFunc
53+
test func(test.Test)
54+
}
55+
56+
// testContextParams is a map of test parameters for testing the test context.
57+
var testContextParams = map[string]ContextParam{
58+
"panic": {
59+
setup: mock.Chain(
60+
test.Fatalf("panic: %v\n%s\n%s", "test", gomock.Any(), gomock.Any()),
61+
),
62+
test: func(test.Test) {
63+
panic("test")
64+
},
65+
},
66+
// TODO: add more test cases for the test context.
67+
}
68+
69+
// TestContext is testing the test context with single simple test cases.
70+
func TestContext(t *testing.T) {
71+
for name, param := range testContextParams {
72+
name, param := name, param
73+
t.Run(name, test.Run(test.Success, func(t test.Test) {
74+
// Given
75+
mock.NewMocks(t).Expect(param.setup)
76+
77+
// When
78+
test.New(t, test.Success).
79+
Run(param.test, !test.Parallel)
80+
}))
81+
}
82+
}
83+
84+
// ParallelParam is a test parameter type for testing the test context in
85+
// conflicting parallel cases resulting in panics.
86+
type ParallelParam struct {
87+
setup mock.SetupFunc
5288
parallel bool
5389
before func(test.Test)
5490
during func(test.Test)
55-
expect mock.SetupFunc
5691
}
5792

58-
// testPanicParams is a map of test parameters for testing the test context with
59-
// panic cases.
60-
var testPanicParams = map[string]PanicParam{
93+
// testParallelParams is a map of test parameters for testing the test context
94+
// in conflicting parallel cases resulting in a panics.
95+
var testParallelParams = map[string]ParallelParam{
6196
"setenv in run without parallel": {
6297
during: func(t test.Test) {
6398
t.Setenv("TESTING", "during")
@@ -66,13 +101,13 @@ var testPanicParams = map[string]PanicParam{
66101
},
67102

68103
"setenv in run with parallel": {
104+
setup: test.Panic("testing: test using t.Setenv or t.Chdir" +
105+
" can not use t.Parallel"),
69106
parallel: true,
70107
during: func(t test.Test) {
71108
t.Setenv("TESTING", "during")
72109
assert.Equal(t, "during", os.Getenv("TESTING"))
73110
},
74-
expect: test.Panic("testing: test using t.Setenv or t.Chdir" +
75-
" can not use t.Parallel"),
76111
},
77112

78113
"setenv before run without parallel": {
@@ -87,13 +122,13 @@ var testPanicParams = map[string]PanicParam{
87122
},
88123

89124
"setenv before run with parallel": {
125+
setup: test.Panic("testing: test using t.Setenv or t.Chdir" +
126+
" can not use t.Parallel"),
90127
parallel: true,
91128
before: func(t test.Test) {
92129
t.Setenv("TESTING", "before")
93130
assert.Equal(t, "before", os.Getenv("TESTING"))
94131
},
95-
expect: test.Panic("testing: test using t.Setenv or t.Chdir" +
96-
" can not use t.Parallel"),
97132
},
98133

99134
"swallow multiple parallel calls": {
@@ -104,20 +139,21 @@ var testPanicParams = map[string]PanicParam{
104139
},
105140
}
106141

107-
// TestContextPanic is testing the test context with panic cases.
108-
func TestContextPanic(t *testing.T) {
109-
for name, param := range testPanicParams {
142+
// TestContextParallel is testing the test context in conflicting parallel
143+
// cases creating panics.
144+
func TestContextParallel(t *testing.T) {
145+
for name, param := range testParallelParams {
110146
name, param := name, param
111147
t.Run(name, test.RunSeq(test.Success, func(t test.Test) {
112148
// Given
113149
if param.before != nil {
114-
mock.NewMocks(t).Expect(param.expect)
150+
mock.NewMocks(t).Expect(param.setup)
115151
param.before(t)
116152
}
117153

118154
// When
119155
test.New(t, test.Success).Run(func(t test.Test) {
120-
mock.NewMocks(t).Expect(param.expect)
156+
mock.NewMocks(t).Expect(param.setup)
121157
param.during(t)
122158
}, param.parallel)
123159
}))

0 commit comments

Comments
 (0)
Please sign in to comment.