diff --git a/gomock/call.go b/gomock/call.go index a3fa1ae4..3d54d9f5 100644 --- a/gomock/call.go +++ b/gomock/call.go @@ -23,7 +23,7 @@ import ( // Call represents an expected call to a mock. type Call struct { - t TestReporter // for triggering test failures on invalid call setup + t TestHelper // for triggering test failures on invalid call setup receiver interface{} // the receiver of the method call method string // the name of the method @@ -46,10 +46,8 @@ type Call struct { // newCall creates a *Call. It requires the method type in order to support // unexported methods. -func newCall(t TestReporter, receiver interface{}, method string, methodType reflect.Type, args ...interface{}) *Call { - if h, ok := t.(testHelper); ok { - h.Helper() - } +func newCall(t TestHelper, receiver interface{}, method string, methodType reflect.Type, args ...interface{}) *Call { + t.Helper() // TODO: check arity, types. margs := make([]Matcher, len(args)) @@ -159,9 +157,7 @@ func (c *Call) Do(f interface{}) *Call { // Return declares the values to be returned by the mocked function call. func (c *Call) Return(rets ...interface{}) *Call { - if h, ok := c.t.(testHelper); ok { - h.Helper() - } + c.t.Helper() mt := c.methodType if len(rets) != mt.NumOut() { @@ -209,9 +205,7 @@ func (c *Call) Times(n int) *Call { // indirected through a pointer. Or, in the case of a slice, SetArg // will copy value's elements into the nth argument. func (c *Call) SetArg(n int, value interface{}) *Call { - if h, ok := c.t.(testHelper); ok { - h.Helper() - } + c.t.Helper() mt := c.methodType // TODO: This will break on variadic methods. @@ -264,9 +258,7 @@ func (c *Call) isPreReq(other *Call) bool { // After declares that the call may only match after preReq has been exhausted. func (c *Call) After(preReq *Call) *Call { - if h, ok := c.t.(testHelper); ok { - h.Helper() - } + c.t.Helper() if c == preReq { c.t.Fatalf("A call isn't allowed to be its own prerequisite") diff --git a/gomock/controller.go b/gomock/controller.go index e3cc0804..6fde25f5 100644 --- a/gomock/controller.go +++ b/gomock/controller.go @@ -70,45 +70,73 @@ type TestReporter interface { Fatalf(format string, args ...interface{}) } +// TestHelper is a TestReporter that has the Helper method. It is satisfied +// by the standard library's *testing.T. +type TestHelper interface { + TestReporter + Helper() +} + // A Controller represents the top-level control of a mock ecosystem. // It defines the scope and lifetime of mock objects, as well as their expectations. // It is safe to call Controller's methods from multiple goroutines. type Controller struct { + // T should only be called within a generated mock. It is not intended to + // be used in user code and may be changed in future versions. T is the + // TestReporter passed in when creating the Controller via NewController. + // If the TestReporter does not implment a TestHelper it will be wrapped + // with a nopTestHelper. + T TestHelper mu sync.Mutex - t TestReporter expectedCalls *callSet finished bool } func NewController(t TestReporter) *Controller { + h, ok := t.(TestHelper) + if !ok { + h = nopTestHelper{t} + } + return &Controller{ - t: t, + T: h, expectedCalls: newCallSet(), } } type cancelReporter struct { - t TestReporter + TestHelper cancel func() } -func (r *cancelReporter) Errorf(format string, args ...interface{}) { r.t.Errorf(format, args...) } +func (r *cancelReporter) Errorf(format string, args ...interface{}) { + r.TestHelper.Errorf(format, args...) +} func (r *cancelReporter) Fatalf(format string, args ...interface{}) { defer r.cancel() - r.t.Fatalf(format, args...) + r.TestHelper.Fatalf(format, args...) } // WithContext returns a new Controller and a Context, which is cancelled on any // fatal failure. func WithContext(ctx context.Context, t TestReporter) (*Controller, context.Context) { + h, ok := t.(TestHelper) + if !ok { + h = nopTestHelper{t} + } + ctx, cancel := context.WithCancel(ctx) - return NewController(&cancelReporter{t, cancel}), ctx + return NewController(&cancelReporter{h, cancel}), ctx } +type nopTestHelper struct { + TestReporter +} + +func (h nopTestHelper) Helper() {} + func (ctrl *Controller) RecordCall(receiver interface{}, method string, args ...interface{}) *Call { - if h, ok := ctrl.t.(testHelper); ok { - h.Helper() - } + ctrl.T.Helper() recv := reflect.ValueOf(receiver) for i := 0; i < recv.Type().NumMethod(); i++ { @@ -116,16 +144,14 @@ func (ctrl *Controller) RecordCall(receiver interface{}, method string, args ... return ctrl.RecordCallWithMethodType(receiver, method, recv.Method(i).Type(), args...) } } - ctrl.t.Fatalf("gomock: failed finding method %s on %T", method, receiver) + ctrl.T.Fatalf("gomock: failed finding method %s on %T", method, receiver) panic("unreachable") } func (ctrl *Controller) RecordCallWithMethodType(receiver interface{}, method string, methodType reflect.Type, args ...interface{}) *Call { - if h, ok := ctrl.t.(testHelper); ok { - h.Helper() - } + ctrl.T.Helper() - call := newCall(ctrl.t, receiver, method, methodType, args...) + call := newCall(ctrl.T, receiver, method, methodType, args...) ctrl.mu.Lock() defer ctrl.mu.Unlock() @@ -135,19 +161,18 @@ func (ctrl *Controller) RecordCallWithMethodType(receiver interface{}, method st } func (ctrl *Controller) Call(receiver interface{}, method string, args ...interface{}) []interface{} { - if h, ok := ctrl.t.(testHelper); ok { - h.Helper() - } + ctrl.T.Helper() // Nest this code so we can use defer to make sure the lock is released. actions := func() []func([]interface{}) []interface{} { + ctrl.T.Helper() ctrl.mu.Lock() defer ctrl.mu.Unlock() expected, err := ctrl.expectedCalls.FindMatch(receiver, method, args) if err != nil { origin := callerInfo(2) - ctrl.t.Fatalf("Unexpected call to %T.%v(%v) at %s because: %s", receiver, method, args, origin, err) + ctrl.T.Fatalf("Unexpected call to %T.%v(%v) at %s because: %s", receiver, method, args, origin, err) } // Two things happen here: @@ -176,15 +201,13 @@ func (ctrl *Controller) Call(receiver interface{}, method string, args ...interf } func (ctrl *Controller) Finish() { - if h, ok := ctrl.t.(testHelper); ok { - h.Helper() - } + ctrl.T.Helper() ctrl.mu.Lock() defer ctrl.mu.Unlock() if ctrl.finished { - ctrl.t.Fatalf("Controller.Finish was called more than once. It has to be called exactly once.") + ctrl.T.Fatalf("Controller.Finish was called more than once. It has to be called exactly once.") } ctrl.finished = true @@ -197,10 +220,10 @@ func (ctrl *Controller) Finish() { // Check that all remaining expected calls are satisfied. failures := ctrl.expectedCalls.Failures() for _, call := range failures { - ctrl.t.Errorf("missing call(s) to %v", call) + ctrl.T.Errorf("missing call(s) to %v", call) } if len(failures) != 0 { - ctrl.t.Fatalf("aborting test due to missing call(s)") + ctrl.T.Fatalf("aborting test due to missing call(s)") } } @@ -210,8 +233,3 @@ func callerInfo(skip int) string { } return "unknown file" } - -type testHelper interface { - TestReporter - Helper() -} diff --git a/gomock/controller_test.go b/gomock/controller_test.go index b3bb4de2..70c9e3ac 100644 --- a/gomock/controller_test.go +++ b/gomock/controller_test.go @@ -122,6 +122,15 @@ func (e *ErrorReporter) Fatalf(format string, args ...interface{}) { panic(&e.fatalToken) } +type HelperReporter struct { + gomock.TestReporter + helper int +} + +func (h *HelperReporter) Helper() { + h.helper++ +} + // A type purely for use as a receiver in testing the Controller. type Subject struct{} @@ -710,3 +719,21 @@ func TestDuplicateFinishCallFails(t *testing.T) { rep.assertFatal(ctrl.Finish, "Controller.Finish was called more than once. It has to be called exactly once.") } + +func TestTestNoHelper(t *testing.T) { + ctrlNoHelper := gomock.NewController(NewErrorReporter(t)) + + // doesn't panic + ctrlNoHelper.T.Helper() +} + +func TestTestWithHelper(t *testing.T) { + withHelper := &HelperReporter{TestReporter: NewErrorReporter(t)} + ctrlWithHelper := gomock.NewController(withHelper) + + ctrlWithHelper.T.Helper() + + if withHelper.helper == 0 { + t.Fatal("expected Helper to be invoked") + } +}