| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 1 | // 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 | |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 15 | package gomock |
| 16 | |
| 17 | import ( |
| 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. |
| 27 | type TestReporter interface { |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 28 | Errorf(format string, args ...any) |
| 29 | Fatalf(format string, args ...any) |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 30 | } |
| 31 | |
| 32 | // TestHelper is a TestReporter that has the Helper method. It is satisfied |
| 33 | // by the standard library's *testing.T. |
| 34 | type TestHelper interface { |
| 35 | TestReporter |
| 36 | Helper() |
| 37 | } |
| 38 | |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 39 | // 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. |
| 44 | type cleanuper interface { |
| 45 | Cleanup(func()) |
| 46 | } |
| 47 | |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 48 | // 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 |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 51 | // goroutines. Each test should create a new Controller. |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 52 | // |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 53 | // func TestFoo(t *testing.T) { |
| 54 | // ctrl := gomock.NewController(t) |
| 55 | // // .. |
| 56 | // } |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 57 | // |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 58 | // 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 | // }) |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 68 | type 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 | |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 80 | // NewController returns a new Controller. It is the preferred way to create a Controller. |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 81 | // |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 82 | // Passing [*testing.T] registers cleanup function to automatically call [Controller.Finish] |
| 83 | // when the test and all its subtests complete. |
| 84 | func NewController(t TestReporter, opts ...ControllerOption) *Controller { |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 85 | h, ok := t.(TestHelper) |
| 86 | if !ok { |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 87 | h = &nopTestHelper{t} |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 88 | } |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 89 | ctrl := &Controller{ |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 90 | T: h, |
| 91 | expectedCalls: newCallSet(), |
| 92 | } |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 93 | for _, opt := range opts { |
| 94 | opt.apply(ctrl) |
| 95 | } |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 96 | 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 |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 104 | } |
| 105 | |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 106 | // ControllerOption configures how a Controller should behave. |
| 107 | type ControllerOption interface { |
| 108 | apply(*Controller) |
| 109 | } |
| 110 | |
| 111 | type overridableExpectationsOption struct{} |
| 112 | |
| 113 | // WithOverridableExpectations allows for overridable call expectations |
| 114 | // i.e., subsequent call expectations override existing call expectations |
| 115 | func WithOverridableExpectations() overridableExpectationsOption { |
| 116 | return overridableExpectationsOption{} |
| 117 | } |
| 118 | |
| 119 | func (o overridableExpectationsOption) apply(ctrl *Controller) { |
| 120 | ctrl.expectedCalls = newOverridableCallSet() |
| 121 | } |
| 122 | |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 123 | type cancelReporter struct { |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 124 | t TestHelper |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 125 | cancel func() |
| 126 | } |
| 127 | |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 128 | func (r *cancelReporter) Errorf(format string, args ...any) { |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 129 | r.t.Errorf(format, args...) |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 130 | } |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 131 | |
| 132 | func (r *cancelReporter) Fatalf(format string, args ...any) { |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 133 | defer r.cancel() |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 134 | r.t.Fatalf(format, args...) |
| 135 | } |
| 136 | |
| 137 | func (r *cancelReporter) Helper() { |
| 138 | r.t.Helper() |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 139 | } |
| 140 | |
| 141 | // WithContext returns a new Controller and a Context, which is cancelled on any |
| 142 | // fatal failure. |
| 143 | func WithContext(ctx context.Context, t TestReporter) (*Controller, context.Context) { |
| 144 | h, ok := t.(TestHelper) |
| 145 | if !ok { |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 146 | h = &nopTestHelper{t: t} |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 147 | } |
| 148 | |
| 149 | ctx, cancel := context.WithCancel(ctx) |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 150 | return NewController(&cancelReporter{t: h, cancel: cancel}), ctx |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 151 | } |
| 152 | |
| 153 | type nopTestHelper struct { |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 154 | t TestReporter |
| 155 | } |
| 156 | |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 157 | func (h *nopTestHelper) Errorf(format string, args ...any) { |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 158 | h.t.Errorf(format, args...) |
| 159 | } |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 160 | |
| 161 | func (h *nopTestHelper) Fatalf(format string, args ...any) { |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 162 | h.t.Fatalf(format, args...) |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 163 | } |
| 164 | |
| 165 | func (h nopTestHelper) Helper() {} |
| 166 | |
| 167 | // RecordCall is called by a mock. It should not be called by user code. |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 168 | func (ctrl *Controller) RecordCall(receiver any, method string, args ...any) *Call { |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 169 | 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. |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 182 | func (ctrl *Controller) RecordCallWithMethodType(receiver any, method string, methodType reflect.Type, args ...any) *Call { |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 183 | 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. |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 195 | func (ctrl *Controller) Call(receiver any, method string, args ...any) []any { |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 196 | ctrl.T.Helper() |
| 197 | |
| 198 | // Nest this code so we can use defer to make sure the lock is released. |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 199 | actions := func() []func([]any) []any { |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 200 | 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 Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 206 | // 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) |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 210 | 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) |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 215 | } |
| 216 | |
| 217 | // Two things happen here: |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 218 | // * the matching call no longer needs to check prerequisite calls, |
| 219 | // * and the prerequisite calls are no longer expected, so remove them. |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 220 | 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 | |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 232 | var rets []any |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 233 | for _, action := range actions { |
| 234 | if r := action(args); r != nil { |
| 235 | rets = r |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | return rets |
| 240 | } |
| 241 | |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 242 | // 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 Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 244 | // |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 245 | // Note: If you pass a *testing.T into [NewController], you no longer |
| 246 | // need to call ctrl.Finish() in your test methods. |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 247 | func (ctrl *Controller) Finish() { |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 248 | // 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 | |
| bseeniva | dd66c36 | 2026-02-12 19:13:26 +0530 | [diff] [blame^] | 254 | // 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. |
| 256 | func (ctrl *Controller) Satisfied() bool { |
| 257 | ctrl.mu.Lock() |
| 258 | defer ctrl.mu.Unlock() |
| 259 | return ctrl.expectedCalls.Satisfied() |
| 260 | } |
| 261 | |
| 262 | func (ctrl *Controller) finish(cleanup bool, panicErr any) { |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 263 | ctrl.T.Helper() |
| 264 | |
| 265 | ctrl.mu.Lock() |
| 266 | defer ctrl.mu.Unlock() |
| 267 | |
| 268 | if ctrl.finished { |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 269 | 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 |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 273 | } |
| 274 | ctrl.finished = true |
| 275 | |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 276 | // Short-circuit, pass through the panic. |
| 277 | if panicErr != nil { |
| 278 | panic(panicErr) |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 279 | } |
| 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 Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 287 | 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)") |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 292 | } |
| 293 | } |
| 294 | |
| Abhay Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 295 | // 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. |
| vinokuma | f7605fc | 2023-06-02 18:08:01 +0530 | [diff] [blame] | 297 | func 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 Kumar | fe505f2 | 2025-11-10 14:16:31 +0000 | [diff] [blame] | 303 | |
| 304 | // isCleanuper checks it if t's base TestReporter has a Cleanup method. |
| 305 | func 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. |
| 312 | func 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 | } |