petergtz/pegomock

Issue with Matcher I do not understand (AssignableTo returns unexpected false)

Closed this issue · 3 comments

I am building a REST endpoint using gorilla/mux. I am passing an object into my http.Handlerfunc
which will get enriched with the request's context when the endpoint is called.

Now the code sample at the bottom reproduces the issue. I also added a Println to matcher.go:

func (matcher *AnyMatcher) Matches(param Param) bool {
        matcher.actual = reflect.TypeOf(param)
        if matcher.actual == nil {
                switch matcher.Type.Kind() {
                case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map,
                        reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
                        return true
                default:
                        return false
                }
        }
        fmt.Printf("Matcher Type: %v - Param Type: %v -> matches %v\n", matcher.Type, matcher.actual, matcher.actual.AssignableTo(matcher.Type) )
        return matcher.actual.AssignableTo(matcher.Type)
}

The output, when I run the sample code below, is as follows:

Matcher Type: *context.valueCtx - Param Type: *context.valueCtx -> matches false
Shouldn't be nil <nil>

What I do not understand is: The types are the same, but AssignableTo returns false. I guess I am making a wrong assumption about reflect.TypeOf because my understanding is: If I reflect.TypeOf and object, the type should be completely independent from the content of the object. So why would the code fail? I am clueless.

The sample code:

package main
import(
	"github.com/petergtz/pegomock"
	"golang.org/x/net/context"
	"net/http"
	"fmt"
	"reflect"
	"net/http/httptest"
	"github.com/gorilla/mux"
)

func main() {
        //Create mock
	mockClient := NewMockSomeClient()
        //Return mock when Withontext is called with any context (`mux` creates a *context.valueCtx so does the Matcher)
	pegomock.When(mockClient.WithContext(AnyContext())).ThenReturn(mockClient)
	responseRecorder := httptest.NewRecorder()
	request, _ := http.NewRequest("GET", "/path/to/success", nil)

	r := mux.NewRouter()
	r.HandleFunc("/path/to/success", NewEndpoint(mockClient))
	r.ServeHTTP(responseRecorder, request)

}
func AnyContext() context.Context {
	ctx := context.WithValue(context.Background(), "", "")
	pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf(ctx)))
	return ctx
}

func NewEndpoint(client SomeClient) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request){
		clientWithContext := client.WithContext(r.Context())
		fmt.Printf("Shouldn't be nil %v", clientWithContext)
	}
}
//Interface to mock
type SomeClient interface {
	WithContext(context.Context) SomeClient
}

//MOCK - Generated but package adjusted, so it can be in the same file
type MockSomeClient struct {
	fail func(message string, callerSkip ...int)
}

func NewMockSomeClient() *MockSomeClient {
	return &MockSomeClient{fail: pegomock.GlobalFailHandler}
}

func (mock *MockSomeClient) WithContext(_param0 context.Context) SomeClient {
	params := []pegomock.Param{_param0}
	result := pegomock.GetGenericMockFrom(mock).Invoke("WithContext", params, []reflect.Type{reflect.TypeOf((*SomeClient)(nil)).Elem()})
	var ret0 SomeClient
	if len(result) != 0 {
		if result[0] != nil {
			ret0 = result[0].(SomeClient)
		}
	}
	return ret0
}

func (mock *MockSomeClient) VerifyWasCalledOnce() *VerifierSomeClient {
	return &VerifierSomeClient{mock, pegomock.Times(1), nil}
}

func (mock *MockSomeClient) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierSomeClient {
	return &VerifierSomeClient{mock, invocationCountMatcher, nil}
}

func (mock *MockSomeClient) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierSomeClient {
	return &VerifierSomeClient{mock, invocationCountMatcher, inOrderContext}
}

type VerifierSomeClient struct {
	mock                   *MockSomeClient
	invocationCountMatcher pegomock.Matcher
	inOrderContext         *pegomock.InOrderContext
}

func (verifier *VerifierSomeClient) WithContext(_param0 context.Context) *SomeClient_WithContext_OngoingVerification {
	params := []pegomock.Param{_param0}
	methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "WithContext", params)
	return &SomeClient_WithContext_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}

type SomeClient_WithContext_OngoingVerification struct {
	mock              *MockSomeClient
	methodInvocations []pegomock.MethodInvocation
}

func (c *SomeClient_WithContext_OngoingVerification) GetCapturedArguments() context.Context {
	_param0 := c.GetAllCapturedArguments()
	return _param0[len(_param0)-1]
}

func (c *SomeClient_WithContext_OngoingVerification) GetAllCapturedArguments() (_param0 []context.Context) {
	params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations)
	if len(params) > 0 {
		_param0 = make([]context.Context, len(params[0]))
		for u, param := range params[0] {
			_param0[u] = param.(context.Context)
		}
	}
	return
}

Interesting. I just tried to reproduce this and couldn't. I copied your code verbatim (btw thanks, this made it trivial to reproduce!) and got this:

$ go run main.go 
Matcher Type: *context.valueCtx - Param Type: *context.valueCtx -> matches true
Shouldn't be nil &{<nil>}

To make that last line more readable, I changed it to use https://github.com/davecgh/go-spew:

func NewEndpoint(client SomeClient) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		clientWithContext := client.WithContext(r.Context())
		spew.Dump(clientWithContext)
	}
}

That gives me:

$ go run main.go 
Matcher Type: *context.valueCtx - Param Type: *context.valueCtx -> matches true
(*main.MockSomeClient)(0xc42009c040)({
 fail: (func(string, ...int)) <nil>
})

I'm using

$ go version
go version go1.7.3 linux/amd64

Is it possible that you're using a different go version?

Hi Peter
Thanks for answering so quickly.
I am puzzled. I ran the original tests on OS/X so, now I tried it on a linux VM but I am getting the same failure result.

-bash-4.2$ go run main.go
O: *context.valueCtx, P: *context.valueCtx - match false
(interface {}) <nil>
-bash-4.2$ go version
go version go1.7.3 linux/amd64
-bash-4.2$

I'm asking my coworkers to run it on their machines.

Okay so my coworkers ran it and get the same result as you. I set up go on my Windows machine and I also get the expected, correct result.

So something really odd seems to be going on with my Mac...anyway, closing.

Thanks for your help