0

I'm writing a library with a C (not C++) interface that contains an event loop, call it processEvents. This should be called in a loop, and invokes user-defined callbacks when something has happened. The "something" in this case is triggered by an RPC response that is received in a different thread, and added to an event queue which is consumed by processEvents on the main thread.

So from the point of view of the user of my library, the usage looks like this:

function myCallback(void *userData) {
  // ...
}

int main() {
  setCallback(&myCallback, NULL);
  requestCallback();
  while (true) {
    processEvents(); /* Eventually calls myCallback, but not immediately. */
    doSomeOtherStuff();
  }
}

Now I want to test, using Google Test and Google Mock, that the callback is indeed called.

I've used MockFunction<void()> to intercept the actual callback; this is called by a C-style static function that casts the void *userData to a MockFunction<void()> * and calls it. This works fine.

The trouble is: the callback isn't necessarily happen on the first call of processEvents; all I know is that it happens eventually if we keep calling processEvents in a loop.

So I guess I need something like this:

while (!testing::Mock::AllExpectationsSatisfied() && !timedOut()) {
  processEvents();
}

But this fictional AllExpectationsSatisfied doesn't seem to exist. The closest I can find is VerifyAndClearExpectations, but it makes the test fail immediately if the expectations aren't met on the first try (and clears them, to boot).

Of course I make this loop run for a full second or so, which would make the test green, but also make it needlessly slow.

Does anyone know a better solution?

Thomas
  • 174,939
  • 50
  • 355
  • 478

2 Answers2

0

If you are looking for efficient synchronization between threads, check out std::condition_variable. Until a next event comes in, your implementation with a while loop will keep on spinning – using up CPU resources doing nothing useful.

Instead, it would make better sense to suspend the execution of your code, freeing up processing time for other threads, until an event comes in, and then signal to the suspended thread to resume its work. Condition variables do just that. For more information, check out the docs.

Furthermore, you might be interested in looking into std::future and std::promise, which basically encapsulate the pattern of waiting for something to come asynchronously. Find more details here.

Petr Mánek
  • 1,046
  • 1
  • 9
  • 24
  • A spinning wait in a unit test is fine with me – the RPC is local so the response will just be a few context switches away (and I could put a brief sleep into the spin loop if CPU usage is a concern). As to production code: this lib is meant to be integrated into a game loop (which typically runs 60 times per second), so `processEvents` is designed to be called frequently. A blocking "wait for events" API is not useful for that case, because it would block the rest of the event loop as well, and generally does not play nice with other user-controlled event loops. – Thomas Sep 23 '18 at 08:41
  • That said, you did put me on the right track, thanks! – Thomas Sep 23 '18 at 09:08
-1

After posting the question, I thought of using a counter that is decremented by each mock function invocation. But @PetrMánek's answer gave me a better idea. I ended up doing something like this:

MockFunction<void()> myMockFunction;

// Machinery to wire callback to invoke myMockFunction...

Semaphore semaphore; // Implementation from https://stackoverflow.com/a/4793662/14637
EXPECT_CALL(myMockFunction, Call())
    .WillRepeatedly(Invoke(&semaphore, &Semaphore::notify));
do {
  processEvents();
} while (semaphore.try_wait());

(I'm using a semaphore rather than std::condition_variable because (1) spurious wakeups and (2) it can be used in case I expect multiple callback invocations.)

Of course this still needs an overall timeout so a failing test won't hang forever. An optional timeout could also be added to try_wait() to make this more CPU-efficient. These improvements are left as an exercise to the reader ;)

Thomas
  • 174,939
  • 50
  • 355
  • 478