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

Commit

Permalink
gomock: export TestHelper and Controller.T
Browse files Browse the repository at this point in the history
Controller takes a TestReporter but will convert it to a TestHelper. If
the given TestReproter does not implement a TestHelper it will wrap it
in a nopTestHelper.
  • Loading branch information
poy committed Dec 8, 2018
1 parent 5f3a40b commit b2f8551
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 43 deletions.
20 changes: 6 additions & 14 deletions gomock/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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")
Expand Down
76 changes: 47 additions & 29 deletions gomock/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,62 +70,88 @@ 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++ {
if recv.Type().Method(i).Name == method {
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()
Expand All @@ -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:
Expand Down Expand Up @@ -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

Expand All @@ -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)")
}
}

Expand All @@ -210,8 +233,3 @@ func callerInfo(skip int) string {
}
return "unknown file"
}

type testHelper interface {
TestReporter
Helper()
}
27 changes: 27 additions & 0 deletions gomock/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}

Expand Down Expand Up @@ -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")
}
}

0 comments on commit b2f8551

Please sign in to comment.