Skip to content
This repository was archived by the owner on Jun 27, 2023. It is now read-only.

Commit 6d816de

Browse files
authored
add default calling of ctrl.Finish() in go1.14+ (#422)
In go1.14 the testing package added a new cleanup method that is called at the end of test runs. By taping into this gomock can remove the need for users to call ctrl.Finish() if they pass a *testing.T into gomock.NewController(...). Fixes #407
1 parent 8a3d595 commit 6d816de

File tree

6 files changed

+251
-76
lines changed

6 files changed

+251
-76
lines changed

Diff for: .travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ language: go
22

33
go:
44
- 1.11.x
5-
- 1.12.x
65
- 1.13.x
6+
- 1.14.x
77

88
env:
99
- GO111MODULE=on

Diff for: README.md

+3
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ func TestFoo(t *testing.T) {
141141
}
142142
```
143143

144+
In Go versions 1.14+, if you pass a *testing.T into `gomock.NewController(t)`
145+
you no longer need to call `ctrl.Finish()`.
146+
144147
Building Stubs
145148
--------------
146149

Diff for: gomock/controller.go

+72-13
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,6 @@
5050
// mockObj.EXPECT().SomeMethod(2, "second"),
5151
// mockObj.EXPECT().SomeMethod(3, "third"),
5252
// )
53-
//
54-
// TODO:
55-
// - Handle different argument/return types (e.g. ..., chan, map, interface).
5653
package gomock
5754

5855
import (
@@ -77,6 +74,15 @@ type TestHelper interface {
7774
Helper()
7875
}
7976

77+
// cleanuper is used to check if TestHelper also has the `Cleanup` method. A
78+
// common pattern is to pass in a `*testing.T` to
79+
// `NewController(t TestReporter)`. In Go 1.14+, `*testing.T` has a cleanup
80+
// method. This can be utilized to call `Finish()` so the caller of this library
81+
// does not have to.
82+
type cleanuper interface {
83+
Cleanup(func())
84+
}
85+
8086
// A Controller represents the top-level control of a mock ecosystem. It
8187
// defines the scope and lifetime of mock objects, as well as their
8288
// expectations. It is safe to call Controller's methods from multiple
@@ -115,45 +121,66 @@ type Controller struct {
115121

116122
// NewController returns a new Controller. It is the preferred way to create a
117123
// Controller.
124+
//
125+
// New in go1.14+, if you are passing a *testing.T into this function you no
126+
// longer need to call ctrl.Finish() in your test methods
118127
func NewController(t TestReporter) *Controller {
119128
h, ok := t.(TestHelper)
120129
if !ok {
121-
h = nopTestHelper{t}
130+
h = &nopTestHelper{t}
122131
}
123-
124-
return &Controller{
132+
ctrl := &Controller{
125133
T: h,
126134
expectedCalls: newCallSet(),
127135
}
136+
if c, ok := isCleanuper(ctrl.T); ok {
137+
c.Cleanup(func() {
138+
ctrl.T.Helper()
139+
ctrl.Finish()
140+
})
141+
}
142+
143+
return ctrl
128144
}
129145

130146
type cancelReporter struct {
131-
TestHelper
147+
t TestHelper
132148
cancel func()
133149
}
134150

135151
func (r *cancelReporter) Errorf(format string, args ...interface{}) {
136-
r.TestHelper.Errorf(format, args...)
152+
r.t.Errorf(format, args...)
137153
}
138154
func (r *cancelReporter) Fatalf(format string, args ...interface{}) {
139155
defer r.cancel()
140-
r.TestHelper.Fatalf(format, args...)
156+
r.t.Fatalf(format, args...)
157+
}
158+
159+
func (r *cancelReporter) Helper() {
160+
r.t.Helper()
141161
}
142162

143163
// WithContext returns a new Controller and a Context, which is cancelled on any
144164
// fatal failure.
145165
func WithContext(ctx context.Context, t TestReporter) (*Controller, context.Context) {
146166
h, ok := t.(TestHelper)
147167
if !ok {
148-
h = nopTestHelper{t}
168+
h = &nopTestHelper{t: t}
149169
}
150170

151171
ctx, cancel := context.WithCancel(ctx)
152-
return NewController(&cancelReporter{h, cancel}), ctx
172+
return NewController(&cancelReporter{t: h, cancel: cancel}), ctx
153173
}
154174

155175
type nopTestHelper struct {
156-
TestReporter
176+
t TestReporter
177+
}
178+
179+
func (h *nopTestHelper) Errorf(format string, args ...interface{}) {
180+
h.t.Errorf(format, args...)
181+
}
182+
func (h *nopTestHelper) Fatalf(format string, args ...interface{}) {
183+
h.t.Fatalf(format, args...)
157184
}
158185

159186
func (h nopTestHelper) Helper() {}
@@ -236,7 +263,15 @@ func (ctrl *Controller) Finish() {
236263
defer ctrl.mu.Unlock()
237264

238265
if ctrl.finished {
239-
ctrl.T.Fatalf("Controller.Finish was called more than once. It has to be called exactly once.")
266+
if _, ok := isCleanuper(ctrl.T); !ok {
267+
ctrl.T.Fatalf("Controller.Finish was called more than once. It has to be called exactly once.")
268+
}
269+
// provide a log message to guide users to remove `defer ctrl.Finish()` in Go 1.14+
270+
tr := unwrapTestReporter(ctrl.T)
271+
if l, ok := tr.(interface{ Log(args ...interface{}) }); ok {
272+
l.Log("In Go 1.14+ you no longer need to `ctrl.Finish()` if a *testing.T is passed to `NewController(...)`")
273+
}
274+
return
240275
}
241276
ctrl.finished = true
242277

@@ -262,3 +297,27 @@ func callerInfo(skip int) string {
262297
}
263298
return "unknown file"
264299
}
300+
301+
// isCleanuper checks it if t's base TestReporter has a Cleanup method.
302+
func isCleanuper(t TestReporter) (cleanuper, bool) {
303+
tr := unwrapTestReporter(t)
304+
c, ok := tr.(cleanuper)
305+
return c, ok
306+
}
307+
308+
// unwrapTestReporter unwraps TestReporter to the base implementation.
309+
func unwrapTestReporter(t TestReporter) TestReporter {
310+
tr := t
311+
switch nt := t.(type) {
312+
case *cancelReporter:
313+
tr = nt.t
314+
if h, check := tr.(*nopTestHelper); check {
315+
tr = h.t
316+
}
317+
case *nopTestHelper:
318+
tr = nt.t
319+
default:
320+
// not wrapped
321+
}
322+
return tr
323+
}

Diff for: gomock/controller_113_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// +build !go1.14
16+
17+
package gomock_test
18+
19+
import "testing"
20+
21+
func TestDuplicateFinishCallFails(t *testing.T) {
22+
rep, ctrl := createFixtures(t)
23+
24+
ctrl.Finish()
25+
rep.assertPass("the first Finish call should succeed")
26+
27+
rep.assertFatal(ctrl.Finish, "Controller.Finish was called more than once. It has to be called exactly once.")
28+
}

Diff for: gomock/controller_114_test.go

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// +build go1.14
16+
17+
package gomock_test
18+
19+
import (
20+
"testing"
21+
22+
"github.com/golang/mock/gomock"
23+
)
24+
25+
func (e *ErrorReporter) Cleanup(f func()) {
26+
e.t.Helper()
27+
e.t.Cleanup(f)
28+
}
29+
30+
func TestMultipleDefers(t *testing.T) {
31+
reporter := NewErrorReporter(t)
32+
reporter.Cleanup(func() {
33+
reporter.assertLogf("In Go 1.14+ you no longer need to `ctrl.Finish()` if a *testing.T is passed to `NewController(...)`")
34+
})
35+
ctrl := gomock.NewController(reporter)
36+
ctrl.Finish()
37+
}
38+
39+
// Equivalent to the TestNoRecordedCallsForAReceiver, but without explicitly
40+
// calling Finish.
41+
func TestDeferNotNeededFail(t *testing.T) {
42+
reporter := NewErrorReporter(t)
43+
subject := new(Subject)
44+
var ctrl *gomock.Controller
45+
reporter.Cleanup(func() {
46+
reporter.assertFatal(func() {
47+
ctrl.Call(subject, "NotRecordedMethod", "argument")
48+
}, "Unexpected call to", "there are no expected calls of the method \"NotRecordedMethod\" for that receiver")
49+
})
50+
ctrl = gomock.NewController(reporter)
51+
}
52+
53+
func TestDeferNotNeededPass(t *testing.T) {
54+
reporter := NewErrorReporter(t)
55+
subject := new(Subject)
56+
var ctrl *gomock.Controller
57+
reporter.Cleanup(func() {
58+
reporter.assertPass("Expected method call made.")
59+
})
60+
ctrl = gomock.NewController(reporter)
61+
ctrl.RecordCall(subject, "FooMethod", "argument")
62+
ctrl.Call(subject, "FooMethod", "argument")
63+
}
64+
65+
func TestOrderedCallsInCorrect(t *testing.T) {
66+
reporter := NewErrorReporter(t)
67+
subjectOne := new(Subject)
68+
subjectTwo := new(Subject)
69+
var ctrl *gomock.Controller
70+
reporter.Cleanup(func() {
71+
reporter.assertFatal(func() {
72+
gomock.InOrder(
73+
ctrl.RecordCall(subjectOne, "FooMethod", "1").AnyTimes(),
74+
ctrl.RecordCall(subjectTwo, "FooMethod", "2"),
75+
ctrl.RecordCall(subjectTwo, "BarMethod", "3"),
76+
)
77+
ctrl.Call(subjectOne, "FooMethod", "1")
78+
// FooMethod(2) should be called before BarMethod(3)
79+
ctrl.Call(subjectTwo, "BarMethod", "3")
80+
}, "Unexpected call to", "Subject.BarMethod([3])", "doesn't have a prerequisite call satisfied")
81+
})
82+
ctrl = gomock.NewController(reporter)
83+
}
84+
85+
// Test that calls that are prerequisites to other calls but have maxCalls >
86+
// minCalls are removed from the expected call set.
87+
func TestOrderedCallsWithPreReqMaxUnbounded(t *testing.T) {
88+
reporter := NewErrorReporter(t)
89+
subjectOne := new(Subject)
90+
subjectTwo := new(Subject)
91+
var ctrl *gomock.Controller
92+
reporter.Cleanup(func() {
93+
reporter.assertFatal(func() {
94+
// Initially we should be able to call FooMethod("1") as many times as we
95+
// want.
96+
ctrl.Call(subjectOne, "FooMethod", "1")
97+
ctrl.Call(subjectOne, "FooMethod", "1")
98+
99+
// But calling something that has it as a prerequite should remove it from
100+
// the expected call set. This allows tests to ensure that FooMethod("1") is
101+
// *not* called after FooMethod("2").
102+
ctrl.Call(subjectTwo, "FooMethod", "2")
103+
104+
ctrl.Call(subjectOne, "FooMethod", "1")
105+
})
106+
})
107+
ctrl = gomock.NewController(reporter)
108+
}
109+
110+
func TestCallAfterLoopPanic(t *testing.T) {
111+
reporter := NewErrorReporter(t)
112+
subject := new(Subject)
113+
var ctrl *gomock.Controller
114+
reporter.Cleanup(func() {
115+
firstCall := ctrl.RecordCall(subject, "FooMethod", "1")
116+
secondCall := ctrl.RecordCall(subject, "FooMethod", "2")
117+
thirdCall := ctrl.RecordCall(subject, "FooMethod", "3")
118+
119+
gomock.InOrder(firstCall, secondCall, thirdCall)
120+
121+
defer func() {
122+
err := recover()
123+
if err == nil {
124+
t.Error("Call.After creation of dependency loop did not panic.")
125+
}
126+
}()
127+
128+
// This should panic due to dependency loop.
129+
firstCall.After(thirdCall)
130+
})
131+
ctrl = gomock.NewController(reporter)
132+
}

0 commit comments

Comments
 (0)