Skip to content

Commit 29a4b97

Browse files
authored
Better output when using with go XUnit-style tests, fixes #255 (#297)
* Improve stack traces in go test tests * improve XUnit output for versions of Go that do not implement T.Helper * [CHANGELOG.md] - put changes at the top
1 parent ae19f1b commit 29a4b97

File tree

9 files changed

+203
-79
lines changed

9 files changed

+203
-79
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## HEAD
2+
3+
Improvements:
4+
5+
- XUnit style golang tests have much more useful stack trace in Golang 1.9 thanks to t.Helper.
6+
17
## 1.4.1
28

39
### Fixes:

gomega_dsl.go

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Alternatively, you may have forgotten to register a fail handler with RegisterFa
3232
Depending on your vendoring solution you may be inadvertently importing gomega and subpackages (e.g. ghhtp, gexec,...) from different locations.
3333
`
3434

35-
var globalFailHandler types.GomegaFailHandler
35+
var globalFailWrapper *types.GomegaFailWrapper
3636

3737
var defaultEventuallyTimeout = time.Second
3838
var defaultEventuallyPollingInterval = 10 * time.Millisecond
@@ -42,7 +42,14 @@ var defaultConsistentlyPollingInterval = 10 * time.Millisecond
4242
//RegisterFailHandler connects Ginkgo to Gomega. When a matcher fails
4343
//the fail handler passed into RegisterFailHandler is called.
4444
func RegisterFailHandler(handler types.GomegaFailHandler) {
45-
globalFailHandler = handler
45+
if handler == nil {
46+
globalFailWrapper = nil
47+
return
48+
}
49+
globalFailWrapper = &types.GomegaFailWrapper{
50+
Fail: handler,
51+
TWithHelper: testingtsupport.EmptyTWithHelper{},
52+
}
4653
}
4754

4855
//RegisterTestingT connects Gomega to Golang's XUnit style
@@ -67,7 +74,7 @@ func RegisterFailHandler(handler types.GomegaFailHandler) {
6774
//
6875
// (As an aside: Ginkgo gets around this limitation by running parallel tests in different *processes*).
6976
func RegisterTestingT(t types.GomegaTestingT) {
70-
RegisterFailHandler(testingtsupport.BuildTestingTGomegaFailHandler(t))
77+
RegisterFailHandler(testingtsupport.BuildTestingTGomegaFailWrapper(t).Fail)
7178
}
7279

7380
//InterceptGomegaHandlers runs a given callback and returns an array of
@@ -80,7 +87,7 @@ func RegisterTestingT(t types.GomegaTestingT) {
8087
//This is most useful when testing custom matchers, but can also be used to check
8188
//on a value using a Gomega assertion without causing a test failure.
8289
func InterceptGomegaFailures(f func()) []string {
83-
originalHandler := globalFailHandler
90+
originalHandler := globalFailWrapper.Fail
8491
failures := []string{}
8592
RegisterFailHandler(func(message string, callerSkip ...int) {
8693
failures = append(failures, message)
@@ -142,10 +149,10 @@ func Expect(actual interface{}, extra ...interface{}) GomegaAssertion {
142149
//error message to refer to the calling line in the test (as opposed to the line in the helper function)
143150
//set the first argument of `ExpectWithOffset` appropriately.
144151
func ExpectWithOffset(offset int, actual interface{}, extra ...interface{}) GomegaAssertion {
145-
if globalFailHandler == nil {
152+
if globalFailWrapper == nil {
146153
panic(nilFailHandlerPanic)
147154
}
148-
return assertion.New(actual, globalFailHandler, offset, extra...)
155+
return assertion.New(actual, globalFailWrapper, offset, extra...)
149156
}
150157

151158
//Eventually wraps an actual value allowing assertions to be made on it.
@@ -192,7 +199,7 @@ func Eventually(actual interface{}, intervals ...interface{}) GomegaAsyncAsserti
192199
//initial argument to indicate an offset in the call stack. This is useful when building helper
193200
//functions that contain matchers. To learn more, read about `ExpectWithOffset`.
194201
func EventuallyWithOffset(offset int, actual interface{}, intervals ...interface{}) GomegaAsyncAssertion {
195-
if globalFailHandler == nil {
202+
if globalFailWrapper == nil {
196203
panic(nilFailHandlerPanic)
197204
}
198205
timeoutInterval := defaultEventuallyTimeout
@@ -203,7 +210,7 @@ func EventuallyWithOffset(offset int, actual interface{}, intervals ...interface
203210
if len(intervals) > 1 {
204211
pollingInterval = toDuration(intervals[1])
205212
}
206-
return asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, actual, globalFailHandler, timeoutInterval, pollingInterval, offset)
213+
return asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, actual, globalFailWrapper, timeoutInterval, pollingInterval, offset)
207214
}
208215

209216
//Consistently wraps an actual value allowing assertions to be made on it.
@@ -237,7 +244,7 @@ func Consistently(actual interface{}, intervals ...interface{}) GomegaAsyncAsser
237244
//initial argument to indicate an offset in the call stack. This is useful when building helper
238245
//functions that contain matchers. To learn more, read about `ExpectWithOffset`.
239246
func ConsistentlyWithOffset(offset int, actual interface{}, intervals ...interface{}) GomegaAsyncAssertion {
240-
if globalFailHandler == nil {
247+
if globalFailWrapper == nil {
241248
panic(nilFailHandlerPanic)
242249
}
243250
timeoutInterval := defaultConsistentlyDuration
@@ -248,7 +255,7 @@ func ConsistentlyWithOffset(offset int, actual interface{}, intervals ...interfa
248255
if len(intervals) > 1 {
249256
pollingInterval = toDuration(intervals[1])
250257
}
251-
return asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, actual, globalFailHandler, timeoutInterval, pollingInterval, offset)
258+
return asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, actual, globalFailWrapper, timeoutInterval, pollingInterval, offset)
252259
}
253260

254261
//Set the default timeout duration for Eventually. Eventually will repeatedly poll your condition until it succeeds, or until this timeout elapses.
@@ -340,7 +347,7 @@ func NewGomegaWithT(t types.GomegaTestingT) *GomegaWithT {
340347

341348
//See documentation for Expect
342349
func (g *GomegaWithT) Expect(actual interface{}, extra ...interface{}) GomegaAssertion {
343-
return assertion.New(actual, testingtsupport.BuildTestingTGomegaFailHandler(g.t), 0, extra...)
350+
return assertion.New(actual, testingtsupport.BuildTestingTGomegaFailWrapper(g.t), 0, extra...)
344351
}
345352

346353
//See documentation for Eventually
@@ -353,7 +360,7 @@ func (g *GomegaWithT) Eventually(actual interface{}, intervals ...interface{}) G
353360
if len(intervals) > 1 {
354361
pollingInterval = toDuration(intervals[1])
355362
}
356-
return asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, actual, testingtsupport.BuildTestingTGomegaFailHandler(g.t), timeoutInterval, pollingInterval, 0)
363+
return asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, actual, testingtsupport.BuildTestingTGomegaFailWrapper(g.t), timeoutInterval, pollingInterval, 0)
357364
}
358365

359366
//See documentation for Consistently
@@ -366,7 +373,7 @@ func (g *GomegaWithT) Consistently(actual interface{}, intervals ...interface{})
366373
if len(intervals) > 1 {
367374
pollingInterval = toDuration(intervals[1])
368375
}
369-
return asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, actual, testingtsupport.BuildTestingTGomegaFailHandler(g.t), timeoutInterval, pollingInterval, 0)
376+
return asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, actual, testingtsupport.BuildTestingTGomegaFailWrapper(g.t), timeoutInterval, pollingInterval, 0)
370377
}
371378

372379
func toDuration(input interface{}) time.Duration {

internal/assertion/assertion.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,42 @@ import (
99

1010
type Assertion struct {
1111
actualInput interface{}
12-
fail types.GomegaFailHandler
12+
failWrapper *types.GomegaFailWrapper
1313
offset int
1414
extra []interface{}
1515
}
1616

17-
func New(actualInput interface{}, fail types.GomegaFailHandler, offset int, extra ...interface{}) *Assertion {
17+
func New(actualInput interface{}, failWrapper *types.GomegaFailWrapper, offset int, extra ...interface{}) *Assertion {
1818
return &Assertion{
1919
actualInput: actualInput,
20-
fail: fail,
20+
failWrapper: failWrapper,
2121
offset: offset,
2222
extra: extra,
2323
}
2424
}
2525

2626
func (assertion *Assertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
27+
assertion.failWrapper.TWithHelper.Helper()
2728
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
2829
}
2930

3031
func (assertion *Assertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
32+
assertion.failWrapper.TWithHelper.Helper()
3133
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
3234
}
3335

3436
func (assertion *Assertion) To(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
37+
assertion.failWrapper.TWithHelper.Helper()
3538
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
3639
}
3740

3841
func (assertion *Assertion) ToNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
42+
assertion.failWrapper.TWithHelper.Helper()
3943
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
4044
}
4145

4246
func (assertion *Assertion) NotTo(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
47+
assertion.failWrapper.TWithHelper.Helper()
4348
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
4449
}
4550

@@ -55,8 +60,9 @@ func (assertion *Assertion) buildDescription(optionalDescription ...interface{})
5560
func (assertion *Assertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
5661
matches, err := matcher.Match(assertion.actualInput)
5762
description := assertion.buildDescription(optionalDescription...)
63+
assertion.failWrapper.TWithHelper.Helper()
5864
if err != nil {
59-
assertion.fail(description+err.Error(), 2+assertion.offset)
65+
assertion.failWrapper.Fail(description+err.Error(), 2+assertion.offset)
6066
return false
6167
}
6268
if matches != desiredMatch {
@@ -66,7 +72,7 @@ func (assertion *Assertion) match(matcher types.GomegaMatcher, desiredMatch bool
6672
} else {
6773
message = matcher.NegatedFailureMessage(assertion.actualInput)
6874
}
69-
assertion.fail(description+message, 2+assertion.offset)
75+
assertion.failWrapper.Fail(description+message, 2+assertion.offset)
7076
return false
7177
}
7278

@@ -80,7 +86,8 @@ func (assertion *Assertion) vetExtras(optionalDescription ...interface{}) bool {
8086
}
8187

8288
description := assertion.buildDescription(optionalDescription...)
83-
assertion.fail(description+message, 2+assertion.offset)
89+
assertion.failWrapper.TWithHelper.Helper()
90+
assertion.failWrapper.Fail(description+message, 2+assertion.offset)
8491
return false
8592
}
8693

internal/assertion/assertion_test.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ package assertion_test
33
import (
44
"errors"
55

6+
"github.com/onsi/gomega/internal/testingtsupport"
7+
68
. "github.com/onsi/ginkgo"
79
. "github.com/onsi/gomega"
810
. "github.com/onsi/gomega/internal/assertion"
911
"github.com/onsi/gomega/internal/fakematcher"
12+
"github.com/onsi/gomega/types"
1013
)
1114

1215
var _ = Describe("Assertion", func() {
@@ -19,18 +22,21 @@ var _ = Describe("Assertion", func() {
1922

2023
input := "The thing I'm testing"
2124

22-
var fakeFailHandler = func(message string, callerSkip ...int) {
23-
failureMessage = message
24-
if len(callerSkip) == 1 {
25-
failureCallerSkip = callerSkip[0]
26-
}
25+
var fakeFailWrapper = &types.GomegaFailWrapper{
26+
Fail: func(message string, callerSkip ...int) {
27+
failureMessage = message
28+
if len(callerSkip) == 1 {
29+
failureCallerSkip = callerSkip[0]
30+
}
31+
},
32+
TWithHelper: testingtsupport.EmptyTWithHelper{},
2733
}
2834

2935
BeforeEach(func() {
3036
matcher = &fakematcher.FakeMatcher{}
3137
failureMessage = ""
3238
failureCallerSkip = 0
33-
a = New(input, fakeFailHandler, 1)
39+
a = New(input, fakeFailWrapper, 1)
3440
})
3541

3642
Context("when called", func() {
@@ -186,7 +192,7 @@ var _ = Describe("Assertion", func() {
186192
matcher.ErrToReturn = nil
187193

188194
var typedNil []string
189-
a = New(input, fakeFailHandler, 1, 0, nil, typedNil)
195+
a = New(input, fakeFailWrapper, 1, 0, nil, typedNil)
190196

191197
result := a.Should(matcher)
192198
Expect(result).Should(BeTrue())
@@ -201,32 +207,32 @@ var _ = Describe("Assertion", func() {
201207
matcher.MatchesToReturn = false
202208
matcher.ErrToReturn = nil
203209

204-
a = New(input, fakeFailHandler, 1, errors.New("foo"))
210+
a = New(input, fakeFailWrapper, 1, errors.New("foo"))
205211
result := a.Should(matcher)
206212
Expect(result).Should(BeFalse())
207213
Expect(matcher.ReceivedActual).Should(BeZero(), "The matcher doesn't even get called")
208214
Expect(failureMessage).Should(ContainSubstring("foo"))
209215
failureMessage = ""
210216

211-
a = New(input, fakeFailHandler, 1, nil, 1)
217+
a = New(input, fakeFailWrapper, 1, nil, 1)
212218
result = a.ShouldNot(matcher)
213219
Expect(result).Should(BeFalse())
214220
Expect(failureMessage).Should(ContainSubstring("1"))
215221
failureMessage = ""
216222

217-
a = New(input, fakeFailHandler, 1, nil, 0, []string{"foo"})
223+
a = New(input, fakeFailWrapper, 1, nil, 0, []string{"foo"})
218224
result = a.To(matcher)
219225
Expect(result).Should(BeFalse())
220226
Expect(failureMessage).Should(ContainSubstring("foo"))
221227
failureMessage = ""
222228

223-
a = New(input, fakeFailHandler, 1, nil, 0, []string{"foo"})
229+
a = New(input, fakeFailWrapper, 1, nil, 0, []string{"foo"})
224230
result = a.ToNot(matcher)
225231
Expect(result).Should(BeFalse())
226232
Expect(failureMessage).Should(ContainSubstring("foo"))
227233
failureMessage = ""
228234

229-
a = New(input, fakeFailHandler, 1, nil, 0, []string{"foo"})
235+
a = New(input, fakeFailWrapper, 1, nil, 0, []string{"foo"})
230236
result = a.NotTo(matcher)
231237
Expect(result).Should(BeFalse())
232238
Expect(failureMessage).Should(ContainSubstring("foo"))

internal/asyncassertion/async_assertion.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ type AsyncAssertion struct {
2222
actualInput interface{}
2323
timeoutInterval time.Duration
2424
pollingInterval time.Duration
25-
fail types.GomegaFailHandler
25+
failWrapper *types.GomegaFailWrapper
2626
offset int
2727
}
2828

29-
func New(asyncType AsyncAssertionType, actualInput interface{}, fail types.GomegaFailHandler, timeoutInterval time.Duration, pollingInterval time.Duration, offset int) *AsyncAssertion {
29+
func New(asyncType AsyncAssertionType, actualInput interface{}, failWrapper *types.GomegaFailWrapper, timeoutInterval time.Duration, pollingInterval time.Duration, offset int) *AsyncAssertion {
3030
actualType := reflect.TypeOf(actualInput)
3131
if actualType.Kind() == reflect.Func {
3232
if actualType.NumIn() != 0 || actualType.NumOut() == 0 {
@@ -37,18 +37,20 @@ func New(asyncType AsyncAssertionType, actualInput interface{}, fail types.Gomeg
3737
return &AsyncAssertion{
3838
asyncType: asyncType,
3939
actualInput: actualInput,
40-
fail: fail,
40+
failWrapper: failWrapper,
4141
timeoutInterval: timeoutInterval,
4242
pollingInterval: pollingInterval,
4343
offset: offset,
4444
}
4545
}
4646

4747
func (assertion *AsyncAssertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
48+
assertion.failWrapper.TWithHelper.Helper()
4849
return assertion.match(matcher, true, optionalDescription...)
4950
}
5051

5152
func (assertion *AsyncAssertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
53+
assertion.failWrapper.TWithHelper.Helper()
5254
return assertion.match(matcher, false, optionalDescription...)
5355
}
5456

@@ -110,6 +112,8 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch
110112
matches, err = matcher.Match(value)
111113
}
112114

115+
assertion.failWrapper.TWithHelper.Helper()
116+
113117
fail := func(preamble string) {
114118
errMsg := ""
115119
message := ""
@@ -122,7 +126,8 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch
122126
message = matcher.NegatedFailureMessage(value)
123127
}
124128
}
125-
assertion.fail(fmt.Sprintf("%s after %.3fs.\n%s%s%s", preamble, time.Since(timer).Seconds(), description, message, errMsg), 3+assertion.offset)
129+
assertion.failWrapper.TWithHelper.Helper()
130+
assertion.failWrapper.Fail(fmt.Sprintf("%s after %.3fs.\n%s%s%s", preamble, time.Since(timer).Seconds(), description, message, errMsg), 3+assertion.offset)
126131
}
127132

128133
if assertion.asyncType == AsyncAssertionTypeEventually {

0 commit comments

Comments
 (0)