Simulation tests are written for the CHRE linux (i.e. simulation) platform, and can be useful in validating higher level CHRE behavior. By "higher level", we mean:
You can think of a simulation test as treating the core CHRE framework as a black box, and is able to validate its output.
You can run simulation tests through atest
:
atest --host chre_simulation_tests
The simulation test framework encourages writing self contained tests as follow:
// Use the same unique prefix for all the tests in a single file
TEST_F(TestBase, <PrefixedTestName>) {
// 1. Create tests event to trigger code in the Nanoapp context.
CREATE_CHRE_TEST_EVENT(MY_TEST_EVENT, 0);
// 2. Create a test Nanpoapp by inheriting TestNanoapp.
struct App : public TestNanoapp {
decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
const void *eventData) {
switch (eventType) {
// 3. Handle system events.
case CHRE_EVENT_WIFI_ASYNC_RESULT: {
// ...
// 4. Send event back to the test.
TestEventQueueSingleton::get()->pushEvent(
CHRE_EVENT_WIFI_ASYNC_RESULT)
break;
}
case CHRE_EVENT_TEST_EVENT: {
auto event = static_cast<const TestEvent *>(eventData);
switch (event->type) {
// 5. Handle test events to execute code in the context the Nanoapp.
case MY_TEST_EVENT:
// ...
break;
}
}
}
};
};
// 6. Load the app and add initial expectations.
auto app = loadNanoapp<App>();
EXPECT_TRUE(...);
// 7. Send test events to the Nanoapp to execute some actions and add
// expectations about the result.
sendEventToNanoapp(app, MY_TEST_EVENT);
waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT);
EXPECT_TRUE(...);
// 8. Optionally unload the Nanoapp
unloadNanoapp(app);
}
Inherit from TestNanoapp
to create a test nanoapp. The following
properties oif a nanoapp can be overridden name
, id
, version
, perms
,
start
, handleEvent
, and end
.
Typical tests only override of few of the above properties:
perms
to set the permissions required for the test,start
to put the system in a known state before each test,handleEvent
is probably the most important function where system and test
events are handled. See the sections below for more details.The test events are local to a single test and created using the
CREATE_CHRE_TEST_EVENT(name, id)
macro. The id must be unique in a single
test and in the range [0, 0xfff].
Add code to handleEvent
to handle the system events you are interested in for
the test:
decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
const void *eventData) {
switch (eventType) {
case CHRE_EVENT_WIFI_ASYNC_RESULT: {
// ...
break;
}
}
};
The handler would typically send an event back to the nanoapp, see the next section for more details.
You can send an event from the nanoapp (typically inside handleEvent
):
// Sending a system event.
TestEventQueueSingleton::get()->pushEvent(CHRE_EVENT_WIFI_ASYNC_RESULT);
// Sending a test event.
TestEventQueueSingleton::get()->pushEvent(MY_TEST_EVENT);
Use waitForEvent
to wait for an event in your test code:
// Wait for a system event.
waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT);
// Wait for a test event.
waitForEvent(MY_TEST_EVENT);
Waiting for an event as described above is sufficient to express a boolean expectation. For example the status of an event:
decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
const void *eventData) {
switch (eventType) {
case CHRE_EVENT_WIFI_ASYNC_RESULT: {
auto *event = static_cast<const chreAsyncResult *>(eventData);
if (event->success) {
TestEventQueueSingleton::get()->pushEvent(
CHRE_EVENT_WIFI_ASYNC_RESULT);
}
break;
}
}
};
};
With the above snippet waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT)
will timeout
if the nanoapp did not receive a successful status.
Sometimes you want to attach additional data alongside the event. Simply pass the data as the second argument to pushEvent:
decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
const void *eventData) {
switch (eventType) {
case CHRE_EVENT_WIFI_ASYNC_RESULT: {
auto *event = static_cast<const chreAsyncResult *>(eventData);
if (event->success) {
TestEventQueueSingleton::get()->pushEvent(
CHRE_EVENT_WIFI_ASYNC_RESULT,
*(static_cast<const uint32_t *>(event->cookie)));
}
break;
}
}
};
The data must be trivially copyable (a scalar or a struct of scalar are safe).
Use the second argument of waitForEvent
to retrieve the data in your test
code:
uint32_t cookie;
waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT, &cookie);
EXPECT_EQ(cookie, ...);
To execute the code in the nanoapp context, you will need to create a test event and send it to the nanoapp as follow:
CREATE_CHRE_TEST_EVENT(MY_TEST_EVENT, 0);
// ...
sendEventToNanoapp(app, MY_TEST_EVENT);
The code to be executed in the context of the nanoapp should be added to its
handleEvent
function:
decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
const void *eventData) {
switch (eventType) {
// Test event are received with a CHRE_EVENT_TEST_EVENT type.
case CHRE_EVENT_TEST_EVENT: {
auto event = static_cast<const TestEvent *>(eventData);
switch (event->type) {
// Create a case for each of the test events.
case MY_TEST_EVENT:
// Code running in the context of the nanoapp.
break;
}
}
}
};
It is possible to send data alongside a test event:
bool enable = true;
sendEventToNanoapp(app, MY_TEST_EVENT, &enable);
The data should be a scalar type or a struct of scalars. Be careful not to send
a pointer to a memory block that might be released before the data is consumed
in handleEvent
. This would result in a use after free error and flaky tests.
The handleEvent
function receives a copy of the data in the data
field of
the TestEvent
:
decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
const void *eventData) {
switch (eventType) {
// Test event are received with a CHRE_EVENT_TEST_EVENT type.
case CHRE_EVENT_TEST_EVENT: {
auto event = static_cast<const TestEvent *>(eventData);
switch (event->type) {
// Create a case for each of the test events.
case MY_TEST_EVENT:
chreFunctionTakingABool(*(bool*(event->data)));
break;
}
}
}
};