cgreen-devs / cgreen

A modern, portable, cross-language unit testing and mocking framework for C and C++
ISC License
176 stars 47 forks source link

with_side_effects() should provide ability to inspect arguments passed to a mocked function #295

Open Kamil2000 opened 2 years ago

Kamil2000 commented 2 years ago

The with_side_effects() should create the constrain that allows user to browse the arguments of a function. This could be relatively easily achieved by just passing a second argument to a user callback.

Expected feature: void user_callback(void data, Arguments args) { // here it is possible to inspect/interact with args }

//using cb expect(mocked_function, with_side_effect(&user_callback, &unimportant));

Such change should be backward-compatible, because same thing was used by GTK+ extensively: // old code, most important thing is that gtk_main_quit // declares no parameters, receives two // and despite all of this works like a charm. g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);

thoni56 commented 2 years ago

This is a great idea!

I'm not familiar enough with GTK to see how they get away with this. Just calling a function/macro with more arguments than it has declared will surely fire error messages from most C compilers.

Or am I misunderstanding you? I would appreciate a more detailed sketch of how this would look.

Kamil2000 commented 2 years ago

Ok, I will try to explain this whole C function pointers magic above:

The most important thing is to handle two function signatures with a one macro. This can be done with little bit of casting. Take a look at this trivial program:

include

typedef void (Func2)(int, int); typedef void (Func1)(int); void func(void) { printf("LaLa\n"); } int main() { Func2 func2 = (Func2)&func; Func1 func1 = (Func1)&func; func2(4,5); func1(5); return 0; }

It will compile and run on any platform. However, this doesn't mean this is a C-compilant program, because standard doesn't define such calls at any point.

For more information please visit: https://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type As one reply suggest, this is technically undefined behavior., but the second one suggests that whole GNOME framework depends upon it, because of ABI compatibility.

After some reconsideration I figured out we might use some other, more standard C tricks to handle two types with a single macro. In C11 there is a _Generic macro, which can be useful:

include

typedef void (FuncA)(void); typedef void (FuncB)(int);

void funcA(void) { printf("LaLa\n"); } void funcB(int unused) { (void)unused; printf("TaTa\n"); } void callA(FuncA a) { a(); } void callB(FuncB b) { b(0); }

define CALL(x) _Generic(x, FuncA: callA, default: callB)(x)

int main() { CALL(funcA); CALL(funcB); return 0; }

I personally like the second approach more sue to standard compliance and cleaner error messages. If that approach is to be chosen sth like with_parametrized_side_effects needs to be provided for for older C versions.

Hope all is clear by now ;)