Map member function to free function at compile time

Viewed 89

I am attempting to stub out a C++ library for testing an application which uses the library in a unit test framework primarily written for C (the story behind why I need to use this unit test framework is long and complicated).

The unit test framework operates by using C-style function pointers that get cast to integers to hash into a table for various needs. Obviously, this doesn't work for member functions of a C++ class very well (invalid cast issues), so I thought that I could work around it by creating free functions which are used for the unit test framework, and then calling those free functions from my stubbed class implementation of the library.

For example, say I have the following in the stub C++ file (which I've written a script to auto-generate from the header files):

#include <iostream>

double Apple_getV_stub(int v)
{
  std::cout << "inside stub";
  return (double)v;
}

void Apple_doThings_stub(double k)
{
  std::cout << "inside stub " << k;
}

class Apple
{
public:
  double getV(int v) {
    std::cout << "in class A";
    return Apple_getV_stub(v);
  }

  void doThings(double k) {
    std::cout << "in class A " << k;
    Apple_doThings_stub(k);
  }
};

Now, essentially what I want to do is define some easy way for the user, without knowing the stub names explicitly, to be able to get a function pointer to the corresponding stub function when providing the class method that they want to test.

So, something along the lines of STUB_FUNCTION(Apple::doThings) which would result in &Apple_doThings_stub or similar.

I've looked into using the preprocessor, but unfortunately it doesn't seem to support string modification (which is what I did in the script to auto generate the stub file from the header file, namely replacing :: with _ in the fully qualified method name).

I've also looked into using a templated struct with a static std::map attribute, but couldn't figure out how to get that to work the way I wanted either.

I think the biggest issue is that the methods (and consequently stubs) do not all have the same argument or return types, otherwise things would probably be relatively simple.

Ultimately, this isn't necessary for the project I'm working on, I could just require that the user knows the stub functions are written by concatenating all elements of the fully qualified method name with _ instead of :: (ie, {namespace}_{class}_{method}_stub), but figured it would be worth asking on here if there is anyway to make this work in a "user-friendly way".

For reference, the closest I got with the template map was this:

#include <map>

template<auto f, typename G>
struct FtypeToMap {
  static std::map<decltype(f), G> f_map;
};

template<>
std::map<decltype(&Apple::doThings), decltype(&Apple_doThings_stub)> FtypeToMap<&Apple::doThings, decltype(&Apple_doThings_stub)>::f_map = {{&Apple::doThings, &Apple_doThings_stub}};

But this still requires the user to know the type of the stub function when trying to use it, which in some ways is not really "user-friendly", from my perspective at least.

Just to clarify, I don't necessarily mind having to write complicated logic in the stub file (as long as it can be generated in an automated fashion), since presumably the user won't necessarily be looking at the stub file all that often.

Clarification (edit) In the comments there was some question if this is really necessary so I wanted to clarify the use case. Essentially, we have the "Apple" library which defines the Apple class (and other classes/functions) and is fully unit tested on its own using google test (that is, we are confident Apple works as expected).

Now we have another app (lets call it my_app) which uses this Apple library by linking with it and uses it to do some heavy computations. We are currently attempting to do unit/coverage testing on my_app but don't want to have to use the full Apple library due to it being computationally expensive (and also want to ability to do error injections and other testing processes to ensure 100% coverage in this app). Therefore we are stubbing out the Apple library instead of using the full library. For programmatic reasons, my_app requires unit testing be done through the c focused unit testing architecture already mentioned.

The way the unit testing architecture being used for my_app works requires that functions you are trying to stub out be convertible to c style function pointers, and ultimately for that pointer to be convertible to an integer (well technically a cpuaddr) so that you can do things like counting the number of times a stub is called, specifying the outputs for the stubs/etc. Trying to do this directly with a pointer to a c++ class method doesn't work (invalid cast problem), so I figured, instead, I could create a fake implementation of the Apple class for the stub, and have the methods of the fake Apple implementation call the free functions which can be stubbed using the unit test framework we're using. Therefore, my_app itself is not changed, it still uses the Apple class (and other classes from the Apple library) as is, but instead of linking against the full implementation of those classes, it links against the stubbed version, hence the "mass scale code generation" because the stub functions are all pretty much the same, since they don't really need a true implementation because that is handled by the unit test framework. In this question, I'm just looking for an easy way for the user (currently myself, but possibly others) to "redirect" from the visible Apple methods (which are what are actually used in my_app) to the stubbed implementation free functions, because this is what is required for the unit test framework. By redirect, I don't mean at run time, I simply mean that I want to translate from the method itself to the free function. As an example, the way the unit test framework work is it has Macros/functions along the lines of EXPECT_CALL_COUNT(FUNCTION_KEY(my_function), 5) and I need a way to make something along the lines of EXPECT_CALL_COUNT(CPP_METHOD_KEY(Apple::getV), 5) to result in the compiler seeing EXPECT_CALL_COUNT(FUNCTION_KEY(Apple_getV_stub), 5).

As lorro pointed out in the comments, the simplest way to do this is with a variadic macro, which takes in the fully qualified name of the method (for instance (namespace1, namespace2, ..., class1, class2, ..., method)) and replaces it with the stub name (which is known because it is autogenerated). I think this is an acceptable solution, but will leave the question active for a little while longer to see if there are any other ways to go about this (and also to give lorro a chance to write out his solution as an answer if he so chooses).

0 Answers
Related