From 76f75d8f9786d8a43801f725ab6f4b065cdc2a15 Mon Sep 17 00:00:00 2001 From: Wesley Merkel Date: Sun, 7 Feb 2016 23:13:09 -0700 Subject: [PATCH] Show file and line number of call during errors This commit makes erroneous calls show their origin. Unexpected calls show where they happened. Missing calls and improper expectations show where they were set up in the tests. This information is printed after each error message in square brackets. --- gomock/call.go | 24 ++++++++++++++---------- gomock/controller.go | 19 ++++++++++++++++--- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/gomock/call.go b/gomock/call.go index c5601970..5688184b 100644 --- a/gomock/call.go +++ b/gomock/call.go @@ -28,6 +28,7 @@ type Call struct { method string // the name 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 @@ -78,8 +79,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 { @@ -90,8 +91,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 @@ -100,8 +101,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) } } @@ -124,7 +125,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. @@ -133,12 +135,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 @@ -184,7 +188,7 @@ 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. diff --git a/gomock/controller.go b/gomock/controller.go index 6ca95280..ed3c3476 100644 --- a/gomock/controller.go +++ b/gomock/controller.go @@ -55,7 +55,11 @@ // - Handle different argument/return types (e.g. ..., chan, map, interface). package gomock -import "sync" +import ( + "fmt" + "runtime" + "sync" +) // A TestReporter is something that can be used to report test failures. // It is satisfied by the standard library's *testing.T. @@ -98,7 +102,8 @@ func (ctrl *Controller) RecordCall(receiver interface{}, method string, args ... ctrl.mu.Lock() defer ctrl.mu.Unlock() - call := &Call{t: ctrl.t, receiver: receiver, method: method, args: margs, minCalls: 1, maxCalls: 1} + origin := callerInfo(2) + call := &Call{t: ctrl.t, receiver: receiver, method: method, args: margs, origin: origin, minCalls: 1, maxCalls: 1} ctrl.expectedCalls.Add(call) return call @@ -110,7 +115,8 @@ func (ctrl *Controller) Call(receiver interface{}, method string, args ...interf expected := ctrl.expectedCalls.FindMatch(receiver, method, args) if expected == nil { - ctrl.t.Fatalf("no matching expected call: %T.%v(%v)", receiver, method, args) + origin := callerInfo(2) + ctrl.t.Fatalf("no matching expected call: %T.%v(%v) [%s]", receiver, method, args, origin) } // Two things happen here: @@ -165,3 +171,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" +}