Support assert* in (anonymous) callbacks
squix78 opened this issue · 3 comments
First of all, thank you so much for this great library!!!
I have the following test code:
test(parseEvent) {
String eventText = String("BEGIN:VEVENT\nEND:VEVENT");
parser.parseEvent(eventText, [](ICalParser::Event event) {
assertEqual(123, event.startDate);
});
}
Since the assertEqual is called within the callback function I'm getting the following compile error:
In file included from .piolibdeps/AUnit_ID2778/src/AUnit.h:53:0,
from src/main.cpp:2:
src/main.cpp: In lambda function:
.piolibdeps/AUnit_ID2778/src/aunit/AssertMacros.h:79:59: error: 'this' was not captured for this lambda function
if (!assertion(__FILE__,__LINE__,(arg1),opName,op,(arg2)))\
^
.piolibdeps/AUnit_ID2778/src/aunit/AssertMacros.h:41:5: note: in expansion of macro 'assertOpInternal'
assertOpInternal(arg1,aunit::internal::compareEqual,"==",arg2)
^
src/main.cpp:56:5: note: in expansion of macro 'assertEqual'
assertEqual(123, 123);
^
*** [.pioenvs/nodemcuv2/src/main.cpp.o] Error 1
Is it possible that I can do an assertion in a callback?
Platform: Espressif/ ESP8266, Platformio
Thank you so much!
Hi Daniel,
Thank you for using AUnit. Yes, you can use assertXxx() macros in lambda expressions, but there are some subtle points that I'll try to explain.
Option 1: The quickest way to solve your compiler error is to capture the this
pointer, like this:
test(parseEvent) {
String eventText = String("BEGIN:VEVENT\nEND:VEVENT");
parser.parseEvent(eventText, [this](ICalParser::Event event) {
assertEqual(123, event.startDate);
});
}
Option 2: Another way is to return the assertion value from the lambda expression, out of band through a return variable captured by reference, then perform the assertion from the calling code, like this:
test(parseEvent) {
String eventText = String("BEGIN:VEVENT\nEND:VEVENT");
int startDate;
parser.parseEvent(eventText, [&startDate](ICalParser::Event event) {
startDate = event.startDate;
});
assertEqual(123, startDate);
}
Your decision of whether to use Option 1 or Option 2 depends on the structure of your test code in real life, and how it is affected by the following feature of AUnit: Recall that the assertion macros in AUnit (just like ArduinoUnit and GoogleTest) do not use exceptions (https://github.com/bxparks/AUnit#early-return-and-delayed-assertions). If the assertion succeeds, the code execution continues normally. But if the assertion fails, the macro simply sets some internal state on the Test
object, then returns immediately from the current stack frame. If the assertion is inside the lambda expression, then the execution returns from the lambda expression, but continues through the parser.parseEvent() method, then continues to execute the rest of the test code (until it encounters the next assertXxx() macro, which performs an early return upon a failure state, see explanation in the link above).
In other words, if you used Option 1, and it contained additional code following the call to parser.parse()
(in the example below, the the Serial.println()
), then that code will be executed even if the assertion failed:
test(parseEvent) {
String eventText = String("BEGIN:VEVENT\nEND:VEVENT");
parser.parseEvent(eventText, [this](ICalParser::Event event) {
assertEqual(123, event.startDate);
});
// execution continues to here even if the above assertEqual() fails
Serial.println(eventText); // <--------- this will get executed
}
If you used Option 2, where the assertEqual()
is outside the lambda, and just before the Serial.println
, then the Serial.println()
would not get executed, because the assertion would cause the test to return immediately.
Depending on what your actual test code looks like, you may be fine with the Option 1 where the assertion macro is inside the lambda expression.
Does this help?
Hi, Wondering if you had any additional questions. Otherwise, I want to close this for housecleaning.
Closing due to no activity for 4 months. Let me know if you have further questions.