Skip to content

Commit 515001f

Browse files
committed
Adopt cmp.Diff for showing unmatched arguments.
1 parent e649d89 commit 515001f

File tree

7 files changed

+173
-24
lines changed

7 files changed

+173
-24
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module go.uber.org/mock
33
go 1.20
44

55
require (
6+
github.com/google/go-cmp v0.6.0
67
golang.org/x/mod v0.11.0
78
golang.org/x/tools v0.2.0
89
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
2+
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
3+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
4+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
15
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
26
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
37
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=

gomock/call.go

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import (
1919
"reflect"
2020
"strconv"
2121
"strings"
22+
23+
"github.com/google/go-cmp/cmp"
2224
)
2325

2426
// Call represents an expected call to a mock.
@@ -42,11 +44,13 @@ type Call struct {
4244
// can set the return values by returning a non-nil slice. Actions run in the
4345
// order they are created.
4446
actions []func([]any) []any
47+
48+
cmpOpts cmp.Options // comparison options
4549
}
4650

4751
// newCall creates a *Call. It requires the method type in order to support
4852
// unexported methods.
49-
func newCall(t TestHelper, receiver any, method string, methodType reflect.Type, args ...any) *Call {
53+
func newCall(t TestHelper, receiver any, method string, methodType reflect.Type, cmpOpts cmp.Options, args ...any) *Call {
5054
t.Helper()
5155

5256
// TODO: check arity, types.
@@ -76,7 +80,8 @@ func newCall(t TestHelper, receiver any, method string, methodType reflect.Type,
7680
return rets
7781
}}
7882
return &Call{t: t, receiver: receiver, method: method, methodType: methodType,
79-
args: mArgs, origin: origin, minCalls: 1, maxCalls: 1, actions: actions}
83+
args: mArgs, origin: origin, minCalls: 1, maxCalls: 1, actions: actions,
84+
cmpOpts: cmpOpts}
8085
}
8186

8287
// AnyTimes allows the expectation to be called 0 or more times
@@ -321,6 +326,30 @@ func (c *Call) String() string {
321326
return fmt.Sprintf("%T.%v(%s) %s", c.receiver, c.method, arguments, c.origin)
322327
}
323328

329+
func (c *Call) matchError(m Matcher, arg any) error {
330+
if g, ok := m.(GotFormatter); ok {
331+
return fmt.Errorf(
332+
"\nGot: %v\nWant: %v",
333+
g.Got(arg), m,
334+
)
335+
}
336+
if d, ok := m.(Differ); ok {
337+
diff := d.Diff(arg, c.cmpOpts...)
338+
// Recover if the diff is empty, implying the match failed on ignored fields.
339+
if diff == "" {
340+
return nil
341+
}
342+
return fmt.Errorf(
343+
"\nDiff (-want +got): %s",
344+
diff,
345+
)
346+
}
347+
return fmt.Errorf(
348+
"\nGot: %v\nWant: %v",
349+
formatGottenArg(m, arg), m,
350+
)
351+
}
352+
324353
// Tests if the given call matches the expected call.
325354
// If yes, returns nil. If no, returns error with message explaining why it does not match.
326355
func (c *Call) matches(args []any) error {
@@ -331,11 +360,9 @@ func (c *Call) matches(args []any) error {
331360
}
332361

333362
for i, m := range c.args {
334-
if !m.Matches(args[i]) {
335-
return fmt.Errorf(
336-
"expected call at %s doesn't match the argument at index %d.\nGot: %v\nWant: %v",
337-
c.origin, i, formatGottenArg(m, args[i]), m,
338-
)
363+
arg := args[i]
364+
if !m.Matches(arg) {
365+
return fmt.Errorf("expected call at %s doesn't match the argument at index %d: %w", c.origin, i, c.matchError(m, arg))
339366
}
340367
}
341368
} else {
@@ -353,11 +380,12 @@ func (c *Call) matches(args []any) error {
353380
}
354381

355382
for i, m := range c.args {
383+
arg := args[i]
356384
if i < c.methodType.NumIn()-1 {
357385
// Non-variadic args
358-
if !m.Matches(args[i]) {
359-
return fmt.Errorf("expected call at %s doesn't match the argument at index %s.\nGot: %v\nWant: %v",
360-
c.origin, strconv.Itoa(i), formatGottenArg(m, args[i]), m)
386+
if !m.Matches(arg) {
387+
return fmt.Errorf("expected call at %s doesn't match the argument at index %d: %w",
388+
c.origin, i, c.matchError(m, args[i]))
361389
}
362390
continue
363391
}

gomock/callset_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func TestCallSetAdd(t *testing.T) {
3030

3131
numCalls := 10
3232
for i := 0; i < numCalls; i++ {
33-
cs.Add(newCall(t, receiver, method, reflect.TypeOf(receiverType{}.Func)))
33+
cs.Add(newCall(t, receiver, method, reflect.TypeOf(receiverType{}.Func), nil))
3434
}
3535

3636
call, err := cs.FindMatch(receiver, method, []any{})
@@ -47,13 +47,13 @@ func TestCallSetAdd_WhenOverridable_ClearsPreviousExpectedAndExhausted(t *testin
4747
var receiver any = "TestReceiver"
4848
cs := newOverridableCallSet()
4949

50-
cs.Add(newCall(t, receiver, method, reflect.TypeOf(receiverType{}.Func)))
50+
cs.Add(newCall(t, receiver, method, reflect.TypeOf(receiverType{}.Func), nil))
5151
numExpectedCalls := len(cs.expected[callSetKey{receiver, method}])
5252
if numExpectedCalls != 1 {
5353
t.Fatalf("Expected 1 expected call in callset, got %d", numExpectedCalls)
5454
}
5555

56-
cs.Add(newCall(t, receiver, method, reflect.TypeOf(receiverType{}.Func)))
56+
cs.Add(newCall(t, receiver, method, reflect.TypeOf(receiverType{}.Func), nil))
5757
newNumExpectedCalls := len(cs.expected[callSetKey{receiver, method}])
5858
if newNumExpectedCalls != 1 {
5959
t.Fatalf("Expected 1 expected call in callset, got %d", newNumExpectedCalls)
@@ -100,7 +100,7 @@ func TestCallSetFindMatch(t *testing.T) {
100100
method := "TestMethod"
101101
args := []any{}
102102

103-
c1 := newCall(t, receiver, method, reflect.TypeOf(receiverType{}.Func))
103+
c1 := newCall(t, receiver, method, reflect.TypeOf(receiverType{}.Func), nil)
104104
cs.exhausted = map[callSetKey][]*Call{
105105
{receiver: receiver, fname: method}: {c1},
106106
}

gomock/controller.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"reflect"
2121
"runtime"
2222
"sync"
23+
24+
"github.com/google/go-cmp/cmp"
2325
)
2426

2527
// A TestReporter is something that can be used to report test failures. It
@@ -76,6 +78,7 @@ type Controller struct {
7678
mu sync.Mutex
7779
expectedCalls *callSet
7880
finished bool
81+
cmpOpts cmp.Options
7982
}
8083

8184
// NewController returns a new Controller. It is the preferred way to create a Controller.
@@ -121,6 +124,20 @@ func (o overridableExpectationsOption) apply(ctrl *Controller) {
121124
ctrl.expectedCalls = newOverridableCallSet()
122125
}
123126

127+
type cmpOptions struct {
128+
opts []cmp.Option
129+
}
130+
131+
func (o cmpOptions) apply(ctrl *Controller) {
132+
ctrl.cmpOpts = o.opts
133+
}
134+
135+
// WithCmpOpts is a ControllerOption that configures the options to pass to
136+
// cmp.Diff.
137+
func WithCmpOpts(opts ...cmp.Option) cmpOptions {
138+
return cmpOptions{opts: opts}
139+
}
140+
124141
type cancelReporter struct {
125142
t TestHelper
126143
cancel func()
@@ -181,7 +198,7 @@ func (ctrl *Controller) RecordCall(receiver any, method string, args ...any) *Ca
181198
func (ctrl *Controller) RecordCallWithMethodType(receiver any, method string, methodType reflect.Type, args ...any) *Call {
182199
ctrl.T.Helper()
183200

184-
call := newCall(ctrl.T, receiver, method, methodType, args...)
201+
call := newCall(ctrl.T, receiver, method, methodType, ctrl.cmpOpts, args...)
185202

186203
ctrl.mu.Lock()
187204
defer ctrl.mu.Unlock()

gomock/controller_test.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"strings"
2121
"testing"
2222

23+
"github.com/google/go-cmp/cmp/cmpopts"
24+
2325
"go.uber.org/mock/gomock"
2426
)
2527

@@ -74,8 +76,11 @@ func (e *ErrorReporter) assertFatal(fn func(), expectedErrMsgs ...string) {
7476
// check the last actualErrMsg, because the previous messages come from previous errors
7577
actualErrMsg := e.log[len(e.log)-1]
7678
for _, expectedErrMsg := range expectedErrMsgs {
77-
if !strings.Contains(actualErrMsg, expectedErrMsg) {
79+
i := strings.Index(actualErrMsg, expectedErrMsg)
80+
if i == -1 {
7881
e.t.Errorf("Error message:\ngot: %q\nwant to contain: %q\n", actualErrMsg, expectedErrMsg)
82+
} else {
83+
actualErrMsg = actualErrMsg[i+len(expectedErrMsg):]
7984
}
8085
}
8186
}
@@ -149,8 +154,9 @@ func (s *Subject) VariadicMethod(arg int, vararg ...string) {}
149154

150155
// A type purely for ActOnTestStructMethod
151156
type TestStruct struct {
152-
Number int
153-
Message string
157+
Number int
158+
Message string
159+
secretMessage string
154160
}
155161

156162
func (s *Subject) ActOnTestStructMethod(arg TestStruct, arg1 int) int {
@@ -171,7 +177,9 @@ func createFixtures(t *testing.T) (reporter *ErrorReporter, ctrl *gomock.Control
171177
// Controller. We use it to test that the mock considered tests
172178
// successful or failed.
173179
reporter = NewErrorReporter(t)
174-
ctrl = gomock.NewController(reporter)
180+
ctrl = gomock.NewController(
181+
reporter, gomock.WithCmpOpts(cmpopts.IgnoreUnexported(TestStruct{})),
182+
)
175183
return
176184
}
177185

@@ -298,13 +306,13 @@ func TestUnexpectedArgValue_FirstArg(t *testing.T) {
298306
// the method argument (of TestStruct type) has 1 unexpected value (for the Message field)
299307
ctrl.Call(subject, "ActOnTestStructMethod", TestStruct{Number: 123, Message: "no message"}, 15)
300308
}, "Unexpected call to", "doesn't match the argument at index 0",
301-
"Got: {123 no message} (gomock_test.TestStruct)\nWant: is equal to {123 hello %s} (gomock_test.TestStruct)")
309+
"Diff (-want +got):", "gomock_test.TestStruct{", "Number: 123", "-", "Message: \"hello %s\",", "+", "Message: \"no message\",", "}")
302310

303311
reporter.assertFatal(func() {
304312
// the method argument (of TestStruct type) has 2 unexpected values (for both fields)
305313
ctrl.Call(subject, "ActOnTestStructMethod", TestStruct{Number: 11, Message: "no message"}, 15)
306314
}, "Unexpected call to", "doesn't match the argument at index 0",
307-
"Got: {11 no message} (gomock_test.TestStruct)\nWant: is equal to {123 hello %s} (gomock_test.TestStruct)")
315+
"Diff (-want +got):", "gomock_test.TestStruct{", "-", "Number: 123,", "+", "Number: 11,", "-", "Message: \"hello %s\",", "+", "Message: \"no message\",", "}")
308316

309317
reporter.assertFatal(func() {
310318
// The expected call wasn't made.
@@ -323,7 +331,7 @@ func TestUnexpectedArgValue_SecondArg(t *testing.T) {
323331
reporter.assertFatal(func() {
324332
ctrl.Call(subject, "ActOnTestStructMethod", TestStruct{Number: 123, Message: "hello"}, 3)
325333
}, "Unexpected call to", "doesn't match the argument at index 1",
326-
"Got: 3 (int)\nWant: is equal to 15 (int)")
334+
"Diff (-want +got):", "int(", "-", "15,", "+", "3,", ")")
327335

328336
reporter.assertFatal(func() {
329337
// The expected call wasn't made.
@@ -742,8 +750,8 @@ func TestVariadicNoMatch(t *testing.T) {
742750
ctrl.RecordCall(s, "VariadicMethod", 0)
743751
rep.assertFatal(func() {
744752
ctrl.Call(s, "VariadicMethod", 1)
745-
}, "expected call at", "doesn't match the argument at index 0",
746-
"Got: 1 (int)\nWant: is equal to 0 (int)")
753+
}, "expected call at", "doesn't match the argument at index 0:",
754+
"Diff (-want +got):", "int(", "-", "0,", "+", "1,", ")")
747755
ctrl.Call(s, "VariadicMethod", 0)
748756
}
749757

0 commit comments

Comments
 (0)