ovechkin-dm / mockio

Mockito for golang
MIT License
73 stars 2 forks source link

Interface casting not working with mocks #61

Closed Zurvarian closed 4 months ago

Zurvarian commented 5 months ago

I know this is a rather obscure case, but, if I try to do a casting of an interface X into another interface Y that shares the same method, if the instance behind interface X is a mock, it throws a runtime exception stating that the DynamicStruct lacks of the "shared" method (In a sense that both have the same method)

Test Example:

package mockio

import (
    "testing"

    "github.com/ovechkin-dm/mockio/mock"
)

type OriginalInterface interface {
    methodX() error
}

type ShadowingInterface interface {
    methodX() error
}

func TestInterfaceShadowingNotWorking(t *testing.T) {
    shadowingMock := mock.Mock[ShadowingInterface]()

    var original OriginalInterface
    original = (OriginalInterface)(shadowingMock)

    original.methodX()
}
Zurvarian commented 5 months ago

I'm using version v0.5.6

ovechkin-dm commented 5 months ago

Unfortunately there is no way currently to fix that behaviour, because of how interfaces and type casts are working in golang. I was thinking about hacking into type matching table cache, but failed to do so. So the only way currently to do this is use the proxy.UnsafeCast method from go-dyno library, which already should be inside your project's dependency.

Maybe I can expose this as an API method for mockio if you find it useful

ovechkin-dm commented 5 months ago

After some thinking, I decided to add Cast[T] method directly to mockio API. It will check for whether the cast is safe, and cast correctly to another interface.

Zurvarian commented 5 months ago

Hi @ovechkin-dm,

Thanks for the quick response. I was expecting an answer like that... unfortunately the casting is happening inside the source code under test, not in the test method, so I cannot make use of any mocking function there.

The solution I've found, while a bit "workaroundish", was to wrap the call to the interface Y under an ad-hoc struct that implements the interface X. TL;DR: I solved the problem using Delegation pattern https://en.wikipedia.org/wiki/Delegation_pattern

I hoped there could be a simpler solution... Perhaps in future versions of Golang.

ovechkin-dm commented 5 months ago

Good that you have found a workaround. I think this is the only case where library fails to do it's job well. Other issues should be easily fixable.

ovechkin-dm commented 5 months ago

@Zurvarian Actually I have an idea how to fix this. The idea is to use reflect.StructOf with delegated anonymous interface, as you mentioned. I already have a working POC. It still requires some rework though, because StructOf does not allow invoking creating/methods. I think I will fix it within a month.

Zurvarian commented 5 months ago

Amazing, I'll test it once it is released :)

ovechkin-dm commented 4 months ago

I think I managed to do it. Can you please check? v0.6.0

Zurvarian commented 4 months ago

Hi,

Tested, it throws an error though.

When testing with --race flag:

=== RUN   TestInterfaceShadowingNotWorking
fatal error: checkptr: converted pointer straddles multiple allocations

goroutine 6 [running]:
runtime.throw({0x8a17fe?, 0xc00023e220?})
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/panic.go:1077 +0x5c fp=0xc0002258b0 sp=0xc000225880 pc=0x47313c
runtime.checkptrAlignment(0xc00023e210?, 0x10?, 0x2?)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/checkptr.go:26 +0x5b fp=0xc0002258d0 sp=0xc0002258b0 pc=0x44197b
github.com/ovechkin-dm/go-dyno/proxy.Create[...](0x927900)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/packages/pkg/mod/github.com/ovechkin-dm/go-dyno@v0.1.2/proxy/proxy.go:34 +0x8b6 fp=0xc000225c18 sp=0xc0002258d0 pc=0x815016
github.com/ovechkin-dm/go-dyno/pkg/dyno.Dynamic[...]({0x922800, 0xc0000e1110?})
        /home/jmontesinos/.asdf/installs/golang/1.21.9/packages/pkg/mod/github.com/ovechkin-dm/go-dyno@v0.1.2/pkg/dyno/api.go:20 +0xf7 fp=0xc000225c60 sp=0xc000225c18 pc=0x8146f7
github.com/ovechkin-dm/mockio/mock.Mock[...].func1()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/packages/pkg/mod/github.com/ovechkin-dm/mockio@v0.6.0/registry/registry.go:62 +0xb4 fp=0xc000225d18 sp=0xc000225c60 pc=0x812374
github.com/ovechkin-dm/mockio/registry.withCheck[...](0xc0000b7e38?)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/packages/pkg/mod/github.com/ovechkin-dm/mockio@v0.6.0/registry/registry.go:155 +0x26c fp=0xc000225e10 sp=0xc000225d18 pc=0x815ccc
github.com/ovechkin-dm/mockio/registry.Mock[...](...)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/packages/pkg/mod/github.com/ovechkin-dm/mockio@v0.6.0/registry/registry.go:60
github.com/ovechkin-dm/mockio/mock.Mock[...]()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/packages/pkg/mod/github.com/ovechkin-dm/mockio@v0.6.0/mock/api.go:65 +0x88 fp=0xc000225e58 sp=0xc000225e10 pc=0x8145c8
jmontesinos/golang-mocking/test/mockio.TestInterfaceShadowingNotWorking(0x0?)
        /home/jmontesinos/Projects/Training/Go/golang-mocking/test/mockio/interface_shadowing_test.go:20 +0x45 fp=0xc000225ea0 sp=0xc000225e58 pc=0x811285
testing.tRunner(0xc00023a4e0, 0x8bf6d8)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/testing/testing.go:1595 +0x262 fp=0xc000225fb0 sp=0xc000225ea0 pc=0x5b6002
testing.(*T).Run.func1()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/testing/testing.go:1648 +0x45 fp=0xc000225fe0 sp=0xc000225fb0 pc=0x5b7ca5
runtime.goexit()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/asm_amd64.s:1650 +0x1 fp=0xc000225fe8 sp=0xc000225fe0 pc=0x4aad21
created by testing.(*T).Run in goroutine 1
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/testing/testing.go:1648 +0x846

goroutine 1 [chan receive]:
runtime.gopark(0x0?, 0x0?, 0x50?, 0x21?, 0x18?)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/proc.go:398 +0xce fp=0xc0002296a0 sp=0xc000229680 pc=0x47602e
runtime.chanrecv(0xc0000184d0, 0xc000229787, 0x1)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/chan.go:583 +0x385 fp=0xc000229718 sp=0xc0002296a0 pc=0x441185
runtime.chanrecv1(0x88aec0?, 0x834520?)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/chan.go:442 +0x12 fp=0xc000229740 sp=0xc000229718 pc=0x440dd2
testing.(*T).Run(0xc00023a340, {0x899db0, 0x20}, 0x8bf6d8)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/testing/testing.go:1649 +0x871 fp=0xc000229860 sp=0xc000229740 pc=0x5b7a11
testing.runTests.func1(0x0?)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/testing/testing.go:2054 +0x85 fp=0xc0002298b8 sp=0xc000229860 pc=0x5bb9a5
testing.tRunner(0xc00023a340, 0xc000229af8)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/testing/testing.go:1595 +0x262 fp=0xc0002299c8 sp=0xc0002298b8 pc=0x5b6002
testing.runTests(0xc0000e4be0?, {0xb3b8c0, 0x2, 0x2}, {0xd0?, 0x1e?, 0xb45800?})
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/testing/testing.go:2052 +0x8ae fp=0xc000229b28 sp=0xc0002299c8 pc=0x5bb80e
testing.(*M).Run(0xc0000e4be0)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/testing/testing.go:1925 +0xcd8 fp=0xc000229eb8 sp=0xc000229b28 pc=0x5b8d98
main.main()
        _testmain.go:49 +0x2be fp=0xc000229f40 sp=0xc000229eb8 pc=0x81613e
runtime.main()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/proc.go:267 +0x2bb fp=0xc000229fe0 sp=0xc000229f40 pc=0x475bbb
runtime.goexit()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/asm_amd64.s:1650 +0x1 fp=0xc000229fe8 sp=0xc000229fe0 pc=0x4aad21

goroutine 2 [force gc (idle)]:
runtime.gopark(0xb04d50?, 0xb460e0?, 0x0?, 0x0?, 0x0?)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/proc.go:398 +0xce fp=0xc0000a27a8 sp=0xc0000a2788 pc=0x47602e
runtime.goparkunlock(...)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/proc.go:404
runtime.forcegchelper()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/proc.go:322 +0xb3 fp=0xc0000a27e0 sp=0xc0000a27a8 pc=0x475e93
runtime.goexit()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/asm_amd64.s:1650 +0x1 fp=0xc0000a27e8 sp=0xc0000a27e0 pc=0x4aad21
created by runtime.init.6 in goroutine 1
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/proc.go:310 +0x1a

goroutine 3 [GC sweep wait]:
runtime.gopark(0x0?, 0x0?, 0x0?, 0x0?, 0x0?)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/proc.go:398 +0xce fp=0xc0000b2f78 sp=0xc0000b2f58 pc=0x47602e
runtime.goparkunlock(...)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/proc.go:404
runtime.bgsweep(0x0?)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/mgcsweep.go:280 +0x94 fp=0xc0000b2fc8 sp=0xc0000b2f78 pc=0x460134
runtime.gcenable.func1()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/mgc.go:200 +0x25 fp=0xc0000b2fe0 sp=0xc0000b2fc8 pc=0x455305
runtime.goexit()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/asm_amd64.s:1650 +0x1 fp=0xc0000b2fe8 sp=0xc0000b2fe0 pc=0x4aad21
created by runtime.gcenable in goroutine 1
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/mgc.go:200 +0x66

goroutine 4 [GC scavenge wait]:
runtime.gopark(0xc000018070?, 0x91f990?, 0x1?, 0x0?, 0xc0000071e0?)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/proc.go:398 +0xce fp=0xc0000b8f70 sp=0xc0000b8f50 pc=0x47602e
runtime.goparkunlock(...)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/proc.go:404
runtime.(*scavengerState).park(0xb45880)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/mgcscavenge.go:425 +0x49 fp=0xc0000b8fa0 sp=0xc0000b8f70 pc=0x45da29
runtime.bgscavenge(0x0?)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/mgcscavenge.go:653 +0x3c fp=0xc0000b8fc8 sp=0xc0000b8fa0 pc=0x45df9c
runtime.gcenable.func2()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/mgc.go:201 +0x25 fp=0xc0000b8fe0 sp=0xc0000b8fc8 pc=0x4552a5
runtime.goexit()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/asm_amd64.s:1650 +0x1 fp=0xc0000b8fe8 sp=0xc0000b8fe0 pc=0x4aad21
created by runtime.gcenable in goroutine 1
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/mgc.go:201 +0xa5

goroutine 5 [finalizer wait]:
runtime.gopark(0x0?, 0x0?, 0x0?, 0x0?, 0x0?)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/proc.go:398 +0xce fp=0xc000184e20 sp=0xc000184e00 pc=0x47602e
runtime.runfinq()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/mfinal.go:193 +0x13b fp=0xc000184fe0 sp=0xc000184e20 pc=0x45431b
runtime.goexit()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/asm_amd64.s:1650 +0x1 fp=0xc000184fe8 sp=0xc000184fe0 pc=0x4aad21
created by runtime.createfing in goroutine 1
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/mfinal.go:163 +0x3d
FAIL    jmontesinos/golang-mocking/test/mockio  0.010s

When testing without --race flag:

--- FAIL: TestInterfaceShadowingNotWorking (0.00s)
panic: interface conversion: struct { Delegate mockio.ShadowingInterface; DynamicStruct *proxy.DynamicStruct } is not mockio.ShadowingInterface: missing method methodX [recovered]
        panic: interface conversion: struct { Delegate mockio.ShadowingInterface; DynamicStruct *proxy.DynamicStruct } is not mockio.ShadowingInterface: missing method methodX

goroutine 35 [running]:
testing.tRunner.func1.2({0x6b56a0, 0xc0001954d0})
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/testing/testing.go:1545 +0x238
testing.tRunner.func1()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/testing/testing.go:1548 +0x397
panic({0x6b56a0?, 0xc0001954d0?})
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/runtime/panic.go:914 +0x21f
github.com/ovechkin-dm/go-dyno/proxy.Create[...](0x0)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/packages/pkg/mod/github.com/ovechkin-dm/go-dyno@v0.1.2/proxy/proxy.go:48 +0x786
github.com/ovechkin-dm/go-dyno/pkg/dyno.Dynamic[...]({0x7775a0, 0xc000195080?})
        /home/jmontesinos/.asdf/installs/golang/1.21.9/packages/pkg/mod/github.com/ovechkin-dm/go-dyno@v0.1.2/pkg/dyno/api.go:20 +0x86
github.com/ovechkin-dm/mockio/mock.Mock[...].func1()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/packages/pkg/mod/github.com/ovechkin-dm/mockio@v0.6.0/registry/registry.go:62 +0x5c
github.com/ovechkin-dm/mockio/registry.withCheck[...](0xc000210710?)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/packages/pkg/mod/github.com/ovechkin-dm/mockio@v0.6.0/registry/registry.go:155 +0x156
github.com/ovechkin-dm/mockio/registry.Mock[...](...)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/packages/pkg/mod/github.com/ovechkin-dm/mockio@v0.6.0/registry/registry.go:60
github.com/ovechkin-dm/mockio/mock.Mock[...]()
        /home/jmontesinos/.asdf/installs/golang/1.21.9/packages/pkg/mod/github.com/ovechkin-dm/mockio@v0.6.0/mock/api.go:65 +0x35
jmontesinos/golang-mocking/test/mockio.TestInterfaceShadowingNotWorking(0x0?)
        /home/jmontesinos/Projects/Training/Go/golang-mocking/test/mockio/interface_shadowing_test.go:20 +0x31
testing.tRunner(0xc000185040, 0x727f70)
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/testing/testing.go:1595 +0xff
created by testing.(*T).Run in goroutine 1
        /home/jmontesinos/.asdf/installs/golang/1.21.9/go/src/testing/testing.go:1648 +0x3ad
exit status 2
FAIL    jmontesinos/golang-mocking/test/mockio  0.005s
ovechkin-dm commented 4 months ago

The issue is with unexported method in the interface. Will try to fix it.

Zurvarian commented 4 months ago

Tested with an exported method and it, indeed works.

Though the issue with --race flag persists.

ovechkin-dm commented 4 months ago

Will fix both issues, probably tomorrow

ovechkin-dm commented 4 months ago

Should work fine now v0.6.1 Can you please check?

Zurvarian commented 4 months ago

It works like a charm now :)

Though I have another obscure issue when using -race and working with channels.

runtime: pointer 0xc0001c1228 to unused region of span span.base()=0xc0004f2000 span.limit=0xc0004f8000 span.state=1
runtime: found in object at *(0xc000395500+0x88)
object=0xc000395500 s.base()=0xc000394000 s.limit=0xc000395f80 s.spanclass=62 s.elemsize=896 s.state=mSpanInUse

It very well could be the test itself that is doing something off, and it works when the -race flag is not supplied, so I think this could be treated as a different issue.

ovechkin-dm commented 4 months ago

Thanks for checking. I will close this issue then. Feel free to provide minimal reproducible example for that race error.