Snaipe / Mimick

A KISS, cross-platform C mocking library
MIT License
152 stars 43 forks source link

mmk_mock/mmk_when question #5

Open kugel- opened 7 years ago

kugel- commented 7 years ago

Hello,

I know this is very experimental, but we're playing around with mimick in the need for a decent mocking framework.

I'm mainly asking for clarification. I found that the mock is installed by calling mmk_mock(). It doesn't seem to matter when mmk_when() is called, so long it's called after mmk_mock().

Is this intended like this? I think it makes the API a bit awkward, if you want to install the mock in the middle of a test. It would seem more natural to have the mmk_when() call at the beginning of a scope, and making it effective with a later mmk_mock() call.

Also, maybe I'm missing something, but as of now mmk_mock() could also be folded into mmk_when(), and then calling mmk_when() when the mock should be installed.

Don't think of this as a bug report, I just want to understand mimick better, because it seems very useful.

kugel- commented 7 years ago

I have two more questions:

1) Have you considered libffi for installing trampolines in a more portable manner (libffi supprts a vast number of architectures)? 2) Can you install real functions for mocks instead of mmk_when()-sugar? The ability to return expressions and to modify arbitrary global variables seems limited inside mmk_when().

Snaipe commented 7 years ago

The thing is that mmk_when is supposed to be called multiple times -- it just setups an input-output binding for an existing mock. Think of it as it inserting an if (actual_inputs == when_inputs) return when_output in the mock's code.

It might be a bit awkward when you have a really simple behaviour to describe, but when you want to make a mock that does something a bit more complicated depending on inputs, putting that complex handling in one big mmk_when makes things hard, when you could split that into multiple mmk_when.

As for your questions:

  1. I'll take a more in-depth look at libffi. The problem with mimick is that it has to both do this low-level plumbing while not being able to trust anyone (which is why mimick is redefining a few libc functions itself because it cannot assume that they aren't going to be mocked themselves). If libffi is using internally some functions that are probably going to be mocked, this is not going to work out.

  2. You could either just install a stub with mmk_stub_create using your function, or use the .then_call parameter in mmk_when. I'd recommend the first one if you don't want to specify additional behaviour with mmk_when, or don't care about verifying relationships with mmk_verify.

kugel- commented 7 years ago

ffi is really low-level too. I'm not aware that it calls libc functions besides mmap() and mprotect() to get writable, then executable sections. It goes great lengths for many architectures to install trampoline functions with any number of parameters and even variadic functions (the trampoline functions can call back into user code which can then introspect the parameters). It can also be linked statically.

Just saying, it would be help to get rid of assembler code and support more architectures.

Snaipe commented 7 years ago

I'll send an email to their mailing list to get some more info. libffi seems to heavily rely on the assumption that callers know exactly the types & calling conventions of their functions are, which is sadly not the case for mimick -- we just forward a call to another function, preserving registers along the way as they are. I hope there's a way for me to use libffi for just that, but I don't know enough of the library yet.

Snaipe commented 7 years ago

So I investigated some more on how I could "convince" libffi (or dyncall, for that matter), but I don't think this is going anywhere.

Fundamentally, the problem is very delicate for Mimick:

All in all, libffi & dyncall both address the problem of calling foreign functions, but that's not the problem we are trying to solve with trampolines in Mimick. It's a shame because that means that I'll have to maintain & test a bunch of different architectures, but I don't know any other project that does this.