blob: 674c3298c40ed69deb2054fa2396b3ff4ad26dba [file] [log] [blame]
vinokumaf7605fc2023-06-02 18:08:01 +05301// Copyright 2010 Google Inc.
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
vinokumaf7605fc2023-06-02 18:08:01 +053015package gomock
16
17import (
18 "context"
19 "fmt"
20 "reflect"
21 "runtime"
22 "sync"
23)
24
25// A TestReporter is something that can be used to report test failures. It
26// is satisfied by the standard library's *testing.T.
27type TestReporter interface {
bseenivadd66c362026-02-12 19:13:26 +053028 Errorf(format string, args ...any)
29 Fatalf(format string, args ...any)
vinokumaf7605fc2023-06-02 18:08:01 +053030}
31
32// TestHelper is a TestReporter that has the Helper method. It is satisfied
33// by the standard library's *testing.T.
34type TestHelper interface {
35 TestReporter
36 Helper()
37}
38
Abhay Kumarfe505f22025-11-10 14:16:31 +000039// cleanuper is used to check if TestHelper also has the `Cleanup` method. A
40// common pattern is to pass in a `*testing.T` to
41// `NewController(t TestReporter)`. In Go 1.14+, `*testing.T` has a cleanup
42// method. This can be utilized to call `Finish()` so the caller of this library
43// does not have to.
44type cleanuper interface {
45 Cleanup(func())
46}
47
vinokumaf7605fc2023-06-02 18:08:01 +053048// A Controller represents the top-level control of a mock ecosystem. It
49// defines the scope and lifetime of mock objects, as well as their
50// expectations. It is safe to call Controller's methods from multiple
bseenivadd66c362026-02-12 19:13:26 +053051// goroutines. Each test should create a new Controller.
vinokumaf7605fc2023-06-02 18:08:01 +053052//
bseenivadd66c362026-02-12 19:13:26 +053053// func TestFoo(t *testing.T) {
54// ctrl := gomock.NewController(t)
55// // ..
56// }
vinokumaf7605fc2023-06-02 18:08:01 +053057//
bseenivadd66c362026-02-12 19:13:26 +053058// func TestBar(t *testing.T) {
59// t.Run("Sub-Test-1", st) {
60// ctrl := gomock.NewController(st)
61// // ..
62// })
63// t.Run("Sub-Test-2", st) {
64// ctrl := gomock.NewController(st)
65// // ..
66// })
67// })
vinokumaf7605fc2023-06-02 18:08:01 +053068type Controller struct {
69 // T should only be called within a generated mock. It is not intended to
70 // be used in user code and may be changed in future versions. T is the
71 // TestReporter passed in when creating the Controller via NewController.
72 // If the TestReporter does not implement a TestHelper it will be wrapped
73 // with a nopTestHelper.
74 T TestHelper
75 mu sync.Mutex
76 expectedCalls *callSet
77 finished bool
78}
79
bseenivadd66c362026-02-12 19:13:26 +053080// NewController returns a new Controller. It is the preferred way to create a Controller.
Abhay Kumarfe505f22025-11-10 14:16:31 +000081//
bseenivadd66c362026-02-12 19:13:26 +053082// Passing [*testing.T] registers cleanup function to automatically call [Controller.Finish]
83// when the test and all its subtests complete.
84func NewController(t TestReporter, opts ...ControllerOption) *Controller {
vinokumaf7605fc2023-06-02 18:08:01 +053085 h, ok := t.(TestHelper)
86 if !ok {
Abhay Kumarfe505f22025-11-10 14:16:31 +000087 h = &nopTestHelper{t}
vinokumaf7605fc2023-06-02 18:08:01 +053088 }
Abhay Kumarfe505f22025-11-10 14:16:31 +000089 ctrl := &Controller{
vinokumaf7605fc2023-06-02 18:08:01 +053090 T: h,
91 expectedCalls: newCallSet(),
92 }
bseenivadd66c362026-02-12 19:13:26 +053093 for _, opt := range opts {
94 opt.apply(ctrl)
95 }
Abhay Kumarfe505f22025-11-10 14:16:31 +000096 if c, ok := isCleanuper(ctrl.T); ok {
97 c.Cleanup(func() {
98 ctrl.T.Helper()
99 ctrl.finish(true, nil)
100 })
101 }
102
103 return ctrl
vinokumaf7605fc2023-06-02 18:08:01 +0530104}
105
bseenivadd66c362026-02-12 19:13:26 +0530106// ControllerOption configures how a Controller should behave.
107type ControllerOption interface {
108 apply(*Controller)
109}
110
111type overridableExpectationsOption struct{}
112
113// WithOverridableExpectations allows for overridable call expectations
114// i.e., subsequent call expectations override existing call expectations
115func WithOverridableExpectations() overridableExpectationsOption {
116 return overridableExpectationsOption{}
117}
118
119func (o overridableExpectationsOption) apply(ctrl *Controller) {
120 ctrl.expectedCalls = newOverridableCallSet()
121}
122
vinokumaf7605fc2023-06-02 18:08:01 +0530123type cancelReporter struct {
Abhay Kumarfe505f22025-11-10 14:16:31 +0000124 t TestHelper
vinokumaf7605fc2023-06-02 18:08:01 +0530125 cancel func()
126}
127
bseenivadd66c362026-02-12 19:13:26 +0530128func (r *cancelReporter) Errorf(format string, args ...any) {
Abhay Kumarfe505f22025-11-10 14:16:31 +0000129 r.t.Errorf(format, args...)
vinokumaf7605fc2023-06-02 18:08:01 +0530130}
bseenivadd66c362026-02-12 19:13:26 +0530131
132func (r *cancelReporter) Fatalf(format string, args ...any) {
vinokumaf7605fc2023-06-02 18:08:01 +0530133 defer r.cancel()
Abhay Kumarfe505f22025-11-10 14:16:31 +0000134 r.t.Fatalf(format, args...)
135}
136
137func (r *cancelReporter) Helper() {
138 r.t.Helper()
vinokumaf7605fc2023-06-02 18:08:01 +0530139}
140
141// WithContext returns a new Controller and a Context, which is cancelled on any
142// fatal failure.
143func WithContext(ctx context.Context, t TestReporter) (*Controller, context.Context) {
144 h, ok := t.(TestHelper)
145 if !ok {
Abhay Kumarfe505f22025-11-10 14:16:31 +0000146 h = &nopTestHelper{t: t}
vinokumaf7605fc2023-06-02 18:08:01 +0530147 }
148
149 ctx, cancel := context.WithCancel(ctx)
Abhay Kumarfe505f22025-11-10 14:16:31 +0000150 return NewController(&cancelReporter{t: h, cancel: cancel}), ctx
vinokumaf7605fc2023-06-02 18:08:01 +0530151}
152
153type nopTestHelper struct {
Abhay Kumarfe505f22025-11-10 14:16:31 +0000154 t TestReporter
155}
156
bseenivadd66c362026-02-12 19:13:26 +0530157func (h *nopTestHelper) Errorf(format string, args ...any) {
Abhay Kumarfe505f22025-11-10 14:16:31 +0000158 h.t.Errorf(format, args...)
159}
bseenivadd66c362026-02-12 19:13:26 +0530160
161func (h *nopTestHelper) Fatalf(format string, args ...any) {
Abhay Kumarfe505f22025-11-10 14:16:31 +0000162 h.t.Fatalf(format, args...)
vinokumaf7605fc2023-06-02 18:08:01 +0530163}
164
165func (h nopTestHelper) Helper() {}
166
167// RecordCall is called by a mock. It should not be called by user code.
bseenivadd66c362026-02-12 19:13:26 +0530168func (ctrl *Controller) RecordCall(receiver any, method string, args ...any) *Call {
vinokumaf7605fc2023-06-02 18:08:01 +0530169 ctrl.T.Helper()
170
171 recv := reflect.ValueOf(receiver)
172 for i := 0; i < recv.Type().NumMethod(); i++ {
173 if recv.Type().Method(i).Name == method {
174 return ctrl.RecordCallWithMethodType(receiver, method, recv.Method(i).Type(), args...)
175 }
176 }
177 ctrl.T.Fatalf("gomock: failed finding method %s on %T", method, receiver)
178 panic("unreachable")
179}
180
181// RecordCallWithMethodType is called by a mock. It should not be called by user code.
bseenivadd66c362026-02-12 19:13:26 +0530182func (ctrl *Controller) RecordCallWithMethodType(receiver any, method string, methodType reflect.Type, args ...any) *Call {
vinokumaf7605fc2023-06-02 18:08:01 +0530183 ctrl.T.Helper()
184
185 call := newCall(ctrl.T, receiver, method, methodType, args...)
186
187 ctrl.mu.Lock()
188 defer ctrl.mu.Unlock()
189 ctrl.expectedCalls.Add(call)
190
191 return call
192}
193
194// Call is called by a mock. It should not be called by user code.
bseenivadd66c362026-02-12 19:13:26 +0530195func (ctrl *Controller) Call(receiver any, method string, args ...any) []any {
vinokumaf7605fc2023-06-02 18:08:01 +0530196 ctrl.T.Helper()
197
198 // Nest this code so we can use defer to make sure the lock is released.
bseenivadd66c362026-02-12 19:13:26 +0530199 actions := func() []func([]any) []any {
vinokumaf7605fc2023-06-02 18:08:01 +0530200 ctrl.T.Helper()
201 ctrl.mu.Lock()
202 defer ctrl.mu.Unlock()
203
204 expected, err := ctrl.expectedCalls.FindMatch(receiver, method, args)
205 if err != nil {
Abhay Kumarfe505f22025-11-10 14:16:31 +0000206 // callerInfo's skip should be updated if the number of calls between the user's test
207 // and this line changes, i.e. this code is wrapped in another anonymous function.
208 // 0 is us, 1 is controller.Call(), 2 is the generated mock, and 3 is the user's test.
209 origin := callerInfo(3)
bseenivadd66c362026-02-12 19:13:26 +0530210 stringArgs := make([]string, len(args))
211 for i, arg := range args {
212 stringArgs[i] = getString(arg)
213 }
214 ctrl.T.Fatalf("Unexpected call to %T.%v(%v) at %s because: %s", receiver, method, stringArgs, origin, err)
vinokumaf7605fc2023-06-02 18:08:01 +0530215 }
216
217 // Two things happen here:
bseenivadd66c362026-02-12 19:13:26 +0530218 // * the matching call no longer needs to check prerequisite calls,
219 // * and the prerequisite calls are no longer expected, so remove them.
vinokumaf7605fc2023-06-02 18:08:01 +0530220 preReqCalls := expected.dropPrereqs()
221 for _, preReqCall := range preReqCalls {
222 ctrl.expectedCalls.Remove(preReqCall)
223 }
224
225 actions := expected.call()
226 if expected.exhausted() {
227 ctrl.expectedCalls.Remove(expected)
228 }
229 return actions
230 }()
231
bseenivadd66c362026-02-12 19:13:26 +0530232 var rets []any
vinokumaf7605fc2023-06-02 18:08:01 +0530233 for _, action := range actions {
234 if r := action(args); r != nil {
235 rets = r
236 }
237 }
238
239 return rets
240}
241
bseenivadd66c362026-02-12 19:13:26 +0530242// Finish checks to see if all the methods that were expected to be called were called.
243// It is not idempotent and therefore can only be invoked once.
Abhay Kumarfe505f22025-11-10 14:16:31 +0000244//
bseenivadd66c362026-02-12 19:13:26 +0530245// Note: If you pass a *testing.T into [NewController], you no longer
246// need to call ctrl.Finish() in your test methods.
vinokumaf7605fc2023-06-02 18:08:01 +0530247func (ctrl *Controller) Finish() {
Abhay Kumarfe505f22025-11-10 14:16:31 +0000248 // If we're currently panicking, probably because this is a deferred call.
249 // This must be recovered in the deferred function.
250 err := recover()
251 ctrl.finish(false, err)
252}
253
bseenivadd66c362026-02-12 19:13:26 +0530254// Satisfied returns whether all expected calls bound to this Controller have been satisfied.
255// Calling Finish is then guaranteed to not fail due to missing calls.
256func (ctrl *Controller) Satisfied() bool {
257 ctrl.mu.Lock()
258 defer ctrl.mu.Unlock()
259 return ctrl.expectedCalls.Satisfied()
260}
261
262func (ctrl *Controller) finish(cleanup bool, panicErr any) {
vinokumaf7605fc2023-06-02 18:08:01 +0530263 ctrl.T.Helper()
264
265 ctrl.mu.Lock()
266 defer ctrl.mu.Unlock()
267
268 if ctrl.finished {
Abhay Kumarfe505f22025-11-10 14:16:31 +0000269 if _, ok := isCleanuper(ctrl.T); !ok {
270 ctrl.T.Fatalf("Controller.Finish was called more than once. It has to be called exactly once.")
271 }
272 return
vinokumaf7605fc2023-06-02 18:08:01 +0530273 }
274 ctrl.finished = true
275
Abhay Kumarfe505f22025-11-10 14:16:31 +0000276 // Short-circuit, pass through the panic.
277 if panicErr != nil {
278 panic(panicErr)
vinokumaf7605fc2023-06-02 18:08:01 +0530279 }
280
281 // Check that all remaining expected calls are satisfied.
282 failures := ctrl.expectedCalls.Failures()
283 for _, call := range failures {
284 ctrl.T.Errorf("missing call(s) to %v", call)
285 }
286 if len(failures) != 0 {
Abhay Kumarfe505f22025-11-10 14:16:31 +0000287 if !cleanup {
288 ctrl.T.Fatalf("aborting test due to missing call(s)")
289 return
290 }
291 ctrl.T.Errorf("aborting test due to missing call(s)")
vinokumaf7605fc2023-06-02 18:08:01 +0530292 }
293}
294
Abhay Kumarfe505f22025-11-10 14:16:31 +0000295// callerInfo returns the file:line of the call site. skip is the number
296// of stack frames to skip when reporting. 0 is callerInfo's call site.
vinokumaf7605fc2023-06-02 18:08:01 +0530297func callerInfo(skip int) string {
298 if _, file, line, ok := runtime.Caller(skip + 1); ok {
299 return fmt.Sprintf("%s:%d", file, line)
300 }
301 return "unknown file"
302}
Abhay Kumarfe505f22025-11-10 14:16:31 +0000303
304// isCleanuper checks it if t's base TestReporter has a Cleanup method.
305func isCleanuper(t TestReporter) (cleanuper, bool) {
306 tr := unwrapTestReporter(t)
307 c, ok := tr.(cleanuper)
308 return c, ok
309}
310
311// unwrapTestReporter unwraps TestReporter to the base implementation.
312func unwrapTestReporter(t TestReporter) TestReporter {
313 tr := t
314 switch nt := t.(type) {
315 case *cancelReporter:
316 tr = nt.t
317 if h, check := tr.(*nopTestHelper); check {
318 tr = h.t
319 }
320 case *nopTestHelper:
321 tr = nt.t
322 default:
323 // not wrapped
324 }
325 return tr
326}