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

Return verbose errors why expected method call did not match #97

Merged
merged 4 commits into from
Aug 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 27 additions & 18 deletions gomock/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package gomock
import (
"fmt"
"reflect"
"strconv"
"strings"
)

Expand All @@ -29,6 +30,7 @@ type Call struct {
methodType reflect.Type // the type of the method
args []Matcher // the args
rets []interface{} // the return values (if any)
origin string // file and line number of call setup

preReqs []*Call // prerequisite calls

Expand Down Expand Up @@ -79,8 +81,8 @@ func (c *Call) Do(f interface{}) *Call {
func (c *Call) Return(rets ...interface{}) *Call {
mt := c.methodType
if len(rets) != mt.NumOut() {
c.t.Fatalf("wrong number of arguments to Return for %T.%v: got %d, want %d",
c.receiver, c.method, len(rets), mt.NumOut())
c.t.Fatalf("wrong number of arguments to Return for %T.%v: got %d, want %d [%s]",
c.receiver, c.method, len(rets), mt.NumOut(), c.origin)
}
for i, ret := range rets {
if got, want := reflect.TypeOf(ret), mt.Out(i); got == want {
Expand All @@ -91,8 +93,8 @@ func (c *Call) Return(rets ...interface{}) *Call {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
// ok
default:
c.t.Fatalf("argument %d to Return for %T.%v is nil, but %v is not nillable",
i, c.receiver, c.method, want)
c.t.Fatalf("argument %d to Return for %T.%v is nil, but %v is not nillable [%s]",
i, c.receiver, c.method, want, c.origin)
}
} else if got.AssignableTo(want) {
// Assignable type relation. Make the assignment now so that the generated code
Expand All @@ -101,8 +103,8 @@ func (c *Call) Return(rets ...interface{}) *Call {
v.Set(reflect.ValueOf(ret))
rets[i] = v.Interface()
} else {
c.t.Fatalf("wrong type of argument %d to Return for %T.%v: %v is not assignable to %v",
i, c.receiver, c.method, got, want)
c.t.Fatalf("wrong type of argument %d to Return for %T.%v: %v is not assignable to %v [%s]",
i, c.receiver, c.method, got, want, c.origin)
}
}

Expand All @@ -125,7 +127,8 @@ func (c *Call) SetArg(n int, value interface{}) *Call {
// TODO: This will break on variadic methods.
// We will need to check those at invocation time.
if n < 0 || n >= mt.NumIn() {
c.t.Fatalf("SetArg(%d, ...) called for a method with %d args", n, mt.NumIn())
c.t.Fatalf("SetArg(%d, ...) called for a method with %d args [%s]",
n, mt.NumIn(), c.origin)
}
// Permit setting argument through an interface.
// In the interface case, we don't (nay, can't) check the type here.
Expand All @@ -134,12 +137,14 @@ func (c *Call) SetArg(n int, value interface{}) *Call {
case reflect.Ptr:
dt := at.Elem()
if vt := reflect.TypeOf(value); !vt.AssignableTo(dt) {
c.t.Fatalf("SetArg(%d, ...) argument is a %v, not assignable to %v", n, vt, dt)
c.t.Fatalf("SetArg(%d, ...) argument is a %v, not assignable to %v [%s]",
n, vt, dt, c.origin)
}
case reflect.Interface:
// nothing to do
default:
c.t.Fatalf("SetArg(%d, ...) referring to argument of non-pointer non-interface type %v", n, at)
c.t.Fatalf("SetArg(%d, ...) referring to argument of non-pointer non-interface type %v [%s]",
n, at, c.origin)
}
c.setArgs[n] = reflect.ValueOf(value)
return c
Expand All @@ -158,7 +163,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 c == preReq {
c.t.Fatalf("A call isn't allowed to be it's own prerequisite")
c.t.Fatalf("A call isn't allowed to be its own prerequisite")
}
if preReq.isPreReq(c) {
c.t.Fatalf("Loop in call order: %v is a prerequisite to %v (possibly indirectly).", c, preReq)
Expand All @@ -168,7 +173,7 @@ func (c *Call) After(preReq *Call) *Call {
return c
}

// Returns true iff the minimum number of calls have been made.
// Returns true if the minimum number of calls have been made.
func (c *Call) satisfied() bool {
return c.numCalls >= c.minCalls
}
Expand All @@ -184,31 +189,35 @@ func (c *Call) String() string {
args[i] = arg.String()
}
arguments := strings.Join(args, ", ")
return fmt.Sprintf("%T.%v(%s)", c.receiver, c.method, arguments)
return fmt.Sprintf("%T.%v(%s) %s", c.receiver, c.method, arguments, c.origin)
}

// Tests if the given call matches the expected call.
func (c *Call) matches(args []interface{}) bool {
// If yes, returns nil. If no, returns error with message explaining why it does not match.
func (c *Call) matches(args []interface{}) error {
if len(args) != len(c.args) {
return false
return fmt.Errorf("Expected call at %s has the wrong number of arguments. Got: %s, want: %s",
c.origin, strconv.Itoa(len(args)), strconv.Itoa(len(c.args)))
}
for i, m := range c.args {
if !m.Matches(args[i]) {
return false
return fmt.Errorf("Expected call at %s doesn't match the argument at index %s.\nGot: %v\nWant: %v\n",
c.origin, strconv.Itoa(i), args[i], m)
}
}

// Check that all prerequisite calls have been satisfied.
for _, preReqCall := range c.preReqs {
if !preReqCall.satisfied() {
return false
return fmt.Errorf("Expected call at %s doesn't have a prerequisite call satisfied:\n%v\nshould be called before:\n%v",
c.origin, preReqCall, c)
}
}

return true
return nil
}

// dropPrereqs tells the expected Call to not re-check prerequite calls any
// dropPrereqs tells the expected Call to not re-check prerequisite calls any
// longer, and to return its current set.
func (c *Call) dropPrereqs() (preReqs []*Call) {
preReqs = c.preReqs
Expand Down
24 changes: 17 additions & 7 deletions gomock/callset.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@

package gomock

import (
"errors"
"fmt"
)

// callSet represents a set of expected calls, indexed by receiver and method
// name.
type callSet map[interface{}]map[string][]*Call
Expand Down Expand Up @@ -47,30 +52,35 @@ func (cs callSet) Remove(call *Call) {
}
}

// FindMatch searches for a matching call. Returns nil if no call matched.
func (cs callSet) FindMatch(receiver interface{}, method string, args []interface{}) *Call {
// FindMatch searches for a matching call. Returns error with explanation message if no call matched.
func (cs callSet) FindMatch(receiver interface{}, method string, args []interface{}) (*Call, error) {
methodMap, ok := cs[receiver]
if !ok {
return nil
return nil, errors.New("there are no expected method calls for that receiver")
}
calls, ok := methodMap[method]
if !ok {
return nil
return nil, fmt.Errorf("there are no expected calls of the method: %s for that receiver", method)
}

// Search through the unordered set of calls expected on a method on a
// receiver.
callsErrors := ""
for _, call := range calls {
// A call should not normally still be here if exhausted,
// but it can happen if, for instance, .Times(0) was used.
// Pretend the call doesn't match.
if call.exhausted() {
callsErrors += "\nThe call was exhausted."
continue
}
if call.matches(args) {
return call
err := call.matches(args)
if err != nil {
callsErrors += "\n" + err.Error()
} else {
return call, nil
}
}

return nil
return nil, fmt.Errorf(callsErrors)
}
18 changes: 14 additions & 4 deletions gomock/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ package gomock
import (
"fmt"
"reflect"
"runtime"
"sync"
)

Expand Down Expand Up @@ -114,7 +115,8 @@ func (ctrl *Controller) RecordCallWithMethodType(receiver interface{}, method st
ctrl.mu.Lock()
defer ctrl.mu.Unlock()

call := &Call{t: ctrl.t, receiver: receiver, method: method, methodType: methodType, args: margs, minCalls: 1, maxCalls: 1}
origin := callerInfo(2)
call := &Call{t: ctrl.t, receiver: receiver, method: method, methodType: methodType, args: margs, origin: origin, minCalls: 1, maxCalls: 1}

ctrl.expectedCalls.Add(call)
return call
Expand All @@ -124,9 +126,10 @@ func (ctrl *Controller) Call(receiver interface{}, method string, args ...interf
ctrl.mu.Lock()
defer ctrl.mu.Unlock()

expected := ctrl.expectedCalls.FindMatch(receiver, method, args)
if expected == nil {
ctrl.t.Fatalf("no matching expected call: %T.%v(%v)", receiver, method, args)
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)
}

// Two things happen here:
Expand Down Expand Up @@ -181,3 +184,10 @@ func (ctrl *Controller) Finish() {
ctrl.t.Fatalf("aborting test due to missing call(s)")
}
}

func callerInfo(skip int) string {
if _, file, line, ok := runtime.Caller(skip + 1); ok {
return fmt.Sprintf("%s:%d", file, line)
}
return "unknown file"
}
Loading