1### CHRE Simulation Test Framework 2 3#### Background 4 5Simulation tests are written for the CHRE linux (i.e. simulation) platform, and 6can be useful in validating higher level CHRE behavior. By "higher level", we 7mean: 8 9* More coverage than a module-level unit test. 10* But smaller in scope compared to a full end-to-end integration test. 11 12You can think of a simulation test as treating the core CHRE framework as a 13black box, and is able to validate its output. 14 15#### Running the tests 16 17You can run simulation tests through `atest`: 18 19``` 20atest --host chre_simulation_tests 21``` 22 23#### How to write a test 24 25The simulation test framework encourages writing self contained tests as follow: 26 27```cpp 28// Use the same unique prefix for all the tests in a single file 29TEST_F(TestBase, <PrefixedTestName>) { 30 // 1. Create tests event to trigger code in the Nanoapp context. 31 CREATE_CHRE_TEST_EVENT(MY_TEST_EVENT, 0); 32 33 // 2. Create a test Nanpoapp by inheriting TestNanoapp. 34 class App : public TestNanoapp { 35 void handleEvent(uint32_t, uint16_t eventType, const void *eventData) override { 36 switch (eventType) { 37 // 3. Handle system events. 38 case CHRE_EVENT_WIFI_ASYNC_RESULT: { 39 // ... 40 // 4. Send event back to the test. 41 TestEventQueueSingleton::get()->pushEvent( 42 CHRE_EVENT_WIFI_ASYNC_RESULT) 43 break; 44 } 45 46 case CHRE_EVENT_TEST_EVENT: { 47 auto event = static_cast<const TestEvent *>(eventData); 48 switch (event->type) { 49 // 5. Handle test events to execute code in the context the Nanoapp. 50 case MY_TEST_EVENT: 51 // ... 52 break; 53 } 54 } 55 } 56 } 57 }; 58 59 // 6. Load the app and add initial expectations. 60 uint64_t appId = loadNanoapp(MakeUnique<App>());; 61 EXPECT_TRUE(...); 62 63 // 7. Send test events to the Nanoapp to execute some actions and add 64 // expectations about the result. 65 sendEventToNanoapp(appId, MY_TEST_EVENT); 66 waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT); 67 EXPECT_TRUE(...); 68 69 // 8. Optionally unload the Nanoapp 70 unloadNanoapp(appId); 71} 72``` 73 74##### Test app (#2, #6, #8) 75 76Inherit from `TestNanoapp` to create a test nanoapp. 77 78If you need to customize any of the nanoapp `name`, `id`, `version`, or `perms`, 79you will need to add a constructor calling the `TestNanoapp` constructor with that info, i.e.: 80 81``` 82class App: public TestNanoapp { 83 public: 84 explicit App(TestNanoappInfo info): TestNanoapp(info) {} 85 86 // ... 87}; 88``` 89 90The nanoapp entry points are implemented as methods of the class: 91 92- `start`, 93- `handleEvent`, 94- `end`. 95 96##### Test events (#1) 97 98The test events are local to a single test and created using the 99`CREATE_CHRE_TEST_EVENT(name, id)` macro. The id must be unique in a single 100test and in the range [0, 0xfff]. 101 102##### System event (#3) 103 104Add code to `handleEvent` to handle the system events you are interested in for 105the test: 106 107```cpp 108void handleEvent(uint32_t, uint16_t eventType, const void *eventData) override { 109 switch (eventType) { 110 case CHRE_EVENT_WIFI_ASYNC_RESULT: { 111 // ... 112 break; 113 } 114 } 115} 116``` 117 118The handler would typically send an event back to the nanoapp, see the next 119section for more details. 120 121##### Send event from the nanoapp (#4) 122 123You can send an event from the nanoapp (typically inside `handleEvent`): 124 125```cpp 126// Sending a system event. 127TestEventQueueSingleton::get()->pushEvent(CHRE_EVENT_WIFI_ASYNC_RESULT); 128 129// Sending a test event. 130TestEventQueueSingleton::get()->pushEvent(MY_TEST_EVENT); 131``` 132 133Use `waitForEvent` to wait for an event in your test code: 134 135```cpp 136// Wait for a system event. 137waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT); 138 139// Wait for a test event. 140waitForEvent(MY_TEST_EVENT); 141``` 142 143Waiting for an event as described above is sufficient to express a boolean 144expectation. For example the status of an event: 145 146```cpp 147void handleEvent(uint32_t, uint16_t eventType, const void *eventData) override { 148 switch (eventType) { 149 case CHRE_EVENT_WIFI_ASYNC_RESULT: { 150 auto *event = static_cast<const chreAsyncResult *>(eventData); 151 if (event->success) { 152 TestEventQueueSingleton::get()->pushEvent( 153 CHRE_EVENT_WIFI_ASYNC_RESULT); 154 } 155 break; 156 } 157 } 158} 159``` 160 161With the above snippet `waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT)` will timeout 162if the nanoapp did not receive a successful status. 163 164Sometimes you want to attach additional data alongside the event. Simply pass 165the data as the second argument to pushEvent: 166 167```cpp 168 void handleEvent(uint32_t, uint16_t eventType, const void *eventData) override { 169 switch (eventType) { 170 case CHRE_EVENT_WIFI_ASYNC_RESULT: { 171 auto *event = static_cast<const chreAsyncResult *>(eventData); 172 if (event->success) { 173 TestEventQueueSingleton::get()->pushEvent( 174 CHRE_EVENT_WIFI_ASYNC_RESULT, 175 *(static_cast<const uint32_t *>(event->cookie))); 176 } 177 break; 178 } 179 } 180 } 181``` 182 183The data must be trivially copyable (a scalar or a struct of scalar are safe). 184 185Use the second argument of `waitForEvent` to retrieve the data in your test 186code: 187 188```cpp 189uint32_t cookie; 190waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT, &cookie); 191EXPECT_EQ(cookie, ...); 192``` 193 194##### Send event to the nanoapp (#5) 195 196To execute the code in the nanoapp context, you will need to create a test 197event and send it to the nanoapp as follow: 198 199```cpp 200CREATE_CHRE_TEST_EVENT(MY_TEST_EVENT, 0); 201 202// ... 203 204sendEventToNanoapp(appId, MY_TEST_EVENT); 205``` 206 207The code to be executed in the context of the nanoapp should be added to its 208`handleEvent` function: 209 210```cpp 211void handleEvent(uint32_t, uint16_t eventType, const void *eventData) override { 212 switch (eventType) { 213 // Test event are received with a CHRE_EVENT_TEST_EVENT type. 214 case CHRE_EVENT_TEST_EVENT: { 215 auto event = static_cast<const TestEvent *>(eventData); 216 switch (event->type) { 217 // Create a case for each of the test events. 218 case MY_TEST_EVENT: 219 // Code running in the context of the nanoapp. 220 break; 221 } 222 } 223 } 224} 225``` 226 227It is possible to send data alongside a test event: 228 229```cpp 230bool enable = true; 231sendEventToNanoapp(appId, MY_TEST_EVENT, &enable); 232``` 233 234The data should be a scalar type or a struct of scalars. Be careful not to send 235a pointer to a memory block that might be released before the data is consumed 236in `handleEvent`. This would result in a use after free error and flaky tests. 237 238The `handleEvent` function receives a copy of the data in the `data` field of 239the `TestEvent`: 240 241```cpp 242void handleEvent(uint32_t, uint16_t eventType, const void *eventData) override { 243 switch (eventType) { 244 // Test event are received with a CHRE_EVENT_TEST_EVENT type. 245 case CHRE_EVENT_TEST_EVENT: { 246 auto event = static_cast<const TestEvent *>(eventData); 247 switch (event->type) { 248 // Create a case for each of the test events. 249 case MY_TEST_EVENT: 250 chreFunctionTakingABool(*(bool*(event->data))); 251 break; 252 } 253 } 254 } 255} 256``` 257