Kaynağa Gözat

refactor(esp_event): improved esp_event unit tests

* Decomposed tests into atomic unit tests
* Made tests less time-dependent, hence more robust
  on different platforms (ESP32, QEMU, Linux)
* Ported most of the tests to linux
* Removed some redundant tests
* Fixed bug the tests discovered
* Simplified parts of the tests to be more clear
* Partially used C++ to simplify setup/teardown
* Unified setup/teardown in general
Jakob Hasse 2 yıl önce
ebeveyn
işleme
8df4625c84

+ 1 - 1
components/esp_event/.build-test-rules.yml

@@ -2,5 +2,5 @@
 
 components/esp_event/test_apps:
   enable:
-    - if: IDF_TARGET in ["esp32", "esp32s2", "esp32c3"]
+    - if: IDF_TARGET in ["esp32", "esp32s2", "esp32c3", "linux"]
       reason: covers all major arch types, xtensa vs riscv, single vs dual-core

+ 1 - 1
components/esp_event/esp_event.c

@@ -778,7 +778,7 @@ esp_err_t esp_event_handler_unregister_with_internal(esp_event_loop_handle_t eve
 
     if (event_base == ESP_EVENT_ANY_BASE && event_id != ESP_EVENT_ANY_ID) {
         ESP_LOGE(TAG, "unregistering to any event base with specific id unsupported");
-        return ESP_FAIL;
+        return ESP_ERR_INVALID_ARG;
     }
 
     if (event_base == ESP_EVENT_ANY_BASE) {

+ 4 - 2
components/esp_event/test_apps/README.md

@@ -1,2 +1,4 @@
-| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 |
-| ----------------- | ----- | -------- | -------- |
+| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | Linux |
+| ----------------- | ----- | -------- | -------- | ----- |
+
+For testing on linux, please deactivate the task stack overflow watchpoint

+ 15 - 4
components/esp_event/test_apps/main/CMakeLists.txt

@@ -1,8 +1,19 @@
-set(srcs "test_event_main.c"
-         "test_default_loop.c"
-         "test_event.c")
+idf_build_get_property(target IDF_TARGET)
+
+set(srcs "test_event_main.c" "test_event_common.cpp")
+set(priv_requires "esp_event unity")
+
+if(NOT ${target} STREQUAL "linux")
+    list(APPEND srcs
+        "test_event_target.c")
+
+    list(APPEND priv_requires
+        "driver"
+        "esp_timer"
+        "test_utils")
+endif()
 
 idf_component_register(SRCS ${srcs}
                        PRIV_INCLUDE_DIRS . ../../private_include
-                       PRIV_REQUIRES esp_event driver esp_timer unity test_utils
+                       PRIV_REQUIRES ${priv_requires}
                        WHOLE_ARCHIVE)

+ 0 - 158
components/esp_event/test_apps/main/test_default_loop.c

@@ -1,158 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
- *
- * SPDX-License-Identifier: Unlicense OR CC0-1.0
- */
-
-#include <stdbool.h>
-#include <string.h>
-
-#include "esp_event.h"
-#include "sdkconfig.h"
-
-#include "freertos/FreeRTOS.h"
-#include "freertos/task.h"
-#include "esp_log.h"
-
-#include "esp_event.h"
-#include "esp_event_private.h"
-#include "esp_event_internal.h"
-
-#include "esp_heap_caps.h"
-
-#include "sdkconfig.h"
-#include "unity.h"
-
-typedef struct {
-    void* data;
-    SemaphoreHandle_t mutex;
-} simple_arg_t;
-
-static const char* TAG = "test_event";
-
-ESP_EVENT_DECLARE_BASE(s_default_test_base1);
-ESP_EVENT_DECLARE_BASE(s_default_test_base2);
-
-ESP_EVENT_DEFINE_BASE(s_default_test_base1);
-ESP_EVENT_DEFINE_BASE(s_default_test_base2);
-
-enum {
-    TEST_EVENT_BASE1_EV1,
-    TEST_EVENT_BASE1_EV2,
-    TEST_EVENT_BASE1_MAX
-};
-
-enum {
-    TEST_EVENT_BASE2_EV1,
-    TEST_EVENT_BASE2_EV2,
-    TEST_EVENT_BASE2_MAX
-};
-
-// The initial logging "initializing test" is to ensure mutex allocation is not counted against memory not being freed
-// during teardown.
-#define TEST_SETUP() \
-        ESP_LOGI(TAG, "initializing test");
-
-static void test_event_simple_handler(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
-{
-    if (!event_handler_arg) {
-        return;
-    }
-    simple_arg_t* arg = (simple_arg_t*) event_handler_arg;
-    xSemaphoreTake(arg->mutex, portMAX_DELAY);
-
-    int* count = (int*) arg->data;
-
-    if (event_data == NULL) {
-        (*count)++;
-    } else {
-        (*count) += *((int*) event_data);
-    }
-
-    xSemaphoreGive(arg->mutex);
-}
-
-TEST_CASE("default loop: can create and delete loop", "[event]")
-{
-    TEST_SETUP();
-
-    TEST_ESP_OK(esp_event_loop_create_default());
-
-    TEST_ESP_OK(esp_event_loop_delete_default());
-}
-
-TEST_CASE("default loop: registering fails on uninitialized default loop", "[event]")
-{
-    TEST_SETUP();
-
-    esp_event_handler_instance_t instance;
-    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_event_handler_instance_register(s_default_test_base1,
-            TEST_EVENT_BASE1_EV1,
-            test_event_simple_handler,
-            NULL,
-            &instance));
-}
-
-TEST_CASE("default loop: registering/unregistering event works", "[event]")
-{
-    TEST_SETUP();
-
-    int count = 0;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    TEST_ESP_OK(esp_event_loop_create_default());
-
-    esp_event_handler_instance_t instance;
-    TEST_ESP_OK(esp_event_handler_instance_register(s_default_test_base1,
-            TEST_EVENT_BASE1_EV1,
-            test_event_simple_handler,
-            &arg,
-            &instance));
-    TEST_ASSERT(instance);
-    TEST_ESP_OK(esp_event_post(s_default_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-
-    vTaskDelay(10);
-
-    TEST_ASSERT_EQUAL(1, count);
-
-    TEST_ESP_OK(esp_event_handler_instance_unregister(s_default_test_base1,
-            TEST_EVENT_BASE1_EV1,
-            &instance));
-
-    vTaskDelay(10);
-
-    TEST_ASSERT_EQUAL(1, count);
-
-    TEST_ESP_OK(esp_event_loop_delete_default());
-
-    vSemaphoreDelete(arg.mutex);
-}
-
-TEST_CASE("default event loop: registering event handler instance without instance context works", "[event]") {
-    TEST_SETUP();
-
-    int count_1 = 0;
-
-    simple_arg_t arg_1 = {
-        .data = &count_1,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    TEST_ESP_OK(esp_event_loop_create_default());
-
-    TEST_ESP_OK(esp_event_handler_instance_register(ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, test_event_simple_handler, &arg_1, NULL));
-
-    TEST_ESP_OK(esp_event_post(s_default_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-
-    vTaskDelay(10);
-
-    TEST_ASSERT_EQUAL(1, count_1);
-
-    TEST_ESP_OK(esp_event_loop_delete_default());
-
-    vSemaphoreDelete(arg_1.mutex);
-}

+ 0 - 2097
components/esp_event/test_apps/main/test_event.c

@@ -1,2097 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
- *
- * SPDX-License-Identifier: Unlicense OR CC0-1.0
- */
-#include <stdbool.h>
-#include <string.h>
-
-#include "esp_event.h"
-#include "sdkconfig.h"
-
-#include "freertos/FreeRTOS.h"
-#include "freertos/task.h"
-#include "esp_log.h"
-#include "driver/gptimer.h"
-
-#include "esp_event.h"
-#include "esp_event_private.h"
-#include "esp_event_internal.h"
-
-#include "esp_heap_caps.h"
-#include "esp_timer.h"
-
-#include "sdkconfig.h"
-#include "unity.h"
-
-#include "test_utils.h"
-
-
-static const char* TAG = "test_event";
-
-#define TEST_CONFIG_ITEMS_TO_REGISTER        5
-#define TEST_CONFIG_TASKS_TO_SPAWN           2
-_Static_assert(TEST_CONFIG_TASKS_TO_SPAWN >= 2); // some tests test simultaneous posting of events, etc.
-
-/* General wait time for tests, e.g. waiting for background task to finish,
-   expected timeout, etc. */
-#define TEST_CONFIG_WAIT_TIME               30
-
-/* Time used in tearDown function to wait for cleaning up memory in background tasks */
-#define TEST_CONFIG_TEARDOWN_WAIT           30
-
-// The initial logging "initializing test" is to ensure mutex allocation is not counted against memory not being freed
-// during teardown.
-#define TEST_SETUP() \
-        ESP_LOGI(TAG, "initializing test"); \
-        size_t free_mem_before = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); \
-        s_test_core_id = xPortGetCoreID(); \
-        s_test_priority = uxTaskPriorityGet(NULL);
-
-#define TEST_TEARDOWN() \
-        vTaskDelay(pdMS_TO_TICKS(TEST_CONFIG_TEARDOWN_WAIT)); \
-        TEST_ASSERT_EQUAL(free_mem_before, heap_caps_get_free_size(MALLOC_CAP_DEFAULT));
-
-typedef struct {
-    void* data;
-    SemaphoreHandle_t start;
-    SemaphoreHandle_t done;
-} task_arg_t;
-
-typedef struct {
-    esp_event_base_t base;
-    int32_t id;
-    esp_event_handler_t* handles;
-    int32_t num;
-    esp_event_loop_handle_t loop;
-    bool is_registration;
-} handler_registration_data_t;
-
-typedef struct {
-    esp_event_base_t base;
-    int32_t id;
-    esp_event_loop_handle_t loop;
-    int32_t num;
-} post_event_data_t;
-
-typedef struct {
-    int performed;
-    int expected;
-    SemaphoreHandle_t done;
-} performance_data_t;
-
-typedef struct {
-    void* data;
-    SemaphoreHandle_t mutex;
-} simple_arg_t;
-
-typedef struct {
-    esp_event_handler_instance_t *context;
-    void* data;
-} instance_unregister_data_t;
-
-typedef struct {
-    int *arr;
-    int index;
-} ordered_data_t;
-
-static BaseType_t s_test_core_id;
-static UBaseType_t s_test_priority;
-
-ESP_EVENT_DECLARE_BASE(s_test_base1);
-ESP_EVENT_DECLARE_BASE(s_test_base2);
-ESP_EVENT_DECLARE_BASE(s_semphr_base);
-
-ESP_EVENT_DEFINE_BASE(s_test_base1);
-ESP_EVENT_DEFINE_BASE(s_test_base2);
-ESP_EVENT_DEFINE_BASE(s_semphr_base);
-
-enum {
-    TEST_EVENT_BASE1_EV1,
-    TEST_EVENT_BASE1_EV2,
-    TEST_EVENT_BASE1_EV3,
-    TEST_EVENT_BASE1_MAX
-};
-
-enum {
-    TEST_EVENT_BASE2_EV1,
-    TEST_EVENT_BASE2_EV2,
-    TEST_EVENT_BASE2_MAX
-};
-
-extern void set_leak_threshold(int threshold);
-
-static BaseType_t test_event_get_core(void)
-{
-    static int calls = 0;
-
-    if (portNUM_PROCESSORS > 1) {
-        return (s_test_core_id + calls++) % portNUM_PROCESSORS;
-    } else {
-        return s_test_core_id;
-    }
-}
-
-static esp_event_loop_args_t test_event_get_default_loop_args(void)
-{
-    esp_event_loop_args_t loop_config = {
-        .queue_size = CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE,
-        .task_name = "loop",
-        .task_priority = s_test_priority,
-        .task_stack_size = 2048,
-        .task_core_id = test_event_get_core()
-    };
-
-    return loop_config;
-}
-
-static void pass_4byte_event(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
-{
-    uint32_t* target = (uint32_t*) event_handler_arg;
-    uint32_t data = *((uint32_t*) event_data);
-    *target = data;
-}
-
-static void test_event_simple_handler(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
-{
-    if (!event_handler_arg) {
-        return;
-    }
-    simple_arg_t* arg = (simple_arg_t*) event_handler_arg;
-    xSemaphoreTake(arg->mutex, portMAX_DELAY);
-
-    int* count = (int*) arg->data;
-
-    if (event_data == NULL) {
-        (*count)++;
-    } else {
-        (*count) += *((int*) event_data);
-    }
-
-    xSemaphoreGive(arg->mutex);
-}
-
-static void post_sem(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
-{
-    SemaphoreHandle_t *sem = (SemaphoreHandle_t*) event_handler_arg;
-    xSemaphoreGive(*sem);
-}
-
-static void test_event_ordered_dispatch(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
-{
-    int *arg = (int*) event_handler_arg;
-    ordered_data_t *data = *((ordered_data_t**) (event_data));
-
-    data->arr[data->index++] = *arg;
-}
-
-static void test_event_performance_handler(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
-{
-    performance_data_t* data = (performance_data_t*) event_handler_arg;
-
-    data->performed++;
-
-    if (data->performed >= data->expected) {
-        xSemaphoreGive(data->done);
-    }
-}
-
-static void test_event_post_task(void* args)
-{
-    task_arg_t* arg = (task_arg_t*) args;
-    post_event_data_t* data = arg->data;
-
-    xSemaphoreTake(arg->start, portMAX_DELAY);
-
-    for (int i = 0; i < data->num; i++) {
-        TEST_ESP_OK(esp_event_post_to(data->loop, data->base, data->id, NULL, 0, portMAX_DELAY));
-        vTaskDelay(1);
-    }
-
-    xSemaphoreGive(arg->done);
-
-    vTaskDelete(NULL);
-}
-
-static void test_event_simple_handler_registration_task(void* args)
-{
-    task_arg_t* arg = (task_arg_t*) args;
-    handler_registration_data_t* data = (handler_registration_data_t*) arg->data;
-
-    xSemaphoreTake(arg->start, portMAX_DELAY);
-
-    for(int i = 0; i < data->num; i++) {
-        if (data->is_registration) {
-            TEST_ESP_OK(esp_event_handler_register_with(data->loop, data->base, data->id, data->handles[i], NULL));
-        } else {
-            TEST_ESP_OK(esp_event_handler_unregister_with(data->loop, data->base, data->id, data->handles[i]));
-        }
-        vTaskDelay(1);
-    }
-
-    xSemaphoreGive(arg->done);
-
-    vTaskDelete(NULL);
-}
-
-static void test_handler_post_w_task(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
-{
-    simple_arg_t* arg = (simple_arg_t*) event_handler_arg;
-
-    esp_event_loop_handle_t* loop = (esp_event_loop_handle_t*) event_data;
-    int* count = (int*) arg->data;
-
-    (*count)++;
-
-    if (*count <= 2) {
-        if (event_base == s_test_base1 && event_id == TEST_EVENT_BASE1_EV1) {
-            TEST_ESP_OK(esp_event_post_to(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-        } else{
-            xSemaphoreGive((SemaphoreHandle_t) arg->mutex);
-        }
-    } else {
-        // Test that once the queue is full and the handler attempts to post to the same loop,
-        // posting does not block indefinitely.
-        if (event_base == s_test_base1 && event_id == TEST_EVENT_BASE1_EV1) {
-            xSemaphoreTake((SemaphoreHandle_t) arg->mutex, portMAX_DELAY);
-            TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-        }
-    }
-}
-
-static void test_handler_post_wo_task(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
-{
-    simple_arg_t* arg = (simple_arg_t*) event_handler_arg;
-
-    esp_event_loop_handle_t* loop = (esp_event_loop_handle_t*) event_data;
-    int* count = (int*) arg->data;
-
-    (*count)++;
-
-    if (*count <= 2) {
-        if (event_base == s_test_base1 && event_id == TEST_EVENT_BASE1_EV1) {
-            TEST_ESP_OK(esp_event_post_to(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-        } else{
-            xSemaphoreGive((SemaphoreHandle_t) arg->mutex);
-        }
-    } else {
-        // Test that once the queue is full and the handler attempts to post to the same loop,
-        // posting does not block indefinitely.
-        if (event_base == s_test_base1 && event_id == TEST_EVENT_BASE1_EV1) {
-            xSemaphoreTake((SemaphoreHandle_t) arg->mutex, portMAX_DELAY);
-            TEST_ESP_OK(esp_event_post_to(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-            TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-        }
-    }
-}
-
-static void test_handler_unregister_itself(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
-{
-    esp_event_loop_handle_t* loop = (esp_event_loop_handle_t*) event_data;
-    int* unregistered = (int*) event_handler_arg;
-
-    (*unregistered) += (event_base == s_test_base1 ? 0 : 10) + event_id + 1;
-
-    // Unregister this handler for this event
-    TEST_ESP_OK(esp_event_handler_unregister_with(*loop, event_base, event_id, test_handler_unregister_itself));
-}
-
-static void test_handler_instance_unregister_itself(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
-{
-    esp_event_loop_handle_t* loop = (esp_event_loop_handle_t*) event_data;
-    instance_unregister_data_t *unregister_data = (instance_unregister_data_t*) event_handler_arg;
-    esp_event_handler_instance_t *context = (esp_event_handler_instance_t*) unregister_data->context;
-    int *count = (int*) unregister_data->data;
-
-    (*count)++;
-
-    // Unregister this handler for this event
-    TEST_ESP_OK(esp_event_handler_instance_unregister_with(*loop, event_base, event_id, *context));
-}
-
-static void test_post_from_handler_loop_task(void* args)
-{
-    esp_event_loop_handle_t event_loop = (esp_event_loop_handle_t) args;
-
-    while(1) {
-        TEST_ESP_OK(esp_event_loop_run(event_loop, portMAX_DELAY));
-    }
-}
-
-TEST_CASE("can create and delete event loops", "[event]")
-{
-    /* this test aims to verify that:
-    *  - creating loops with and without a task succeeds
-    *  - event queue can accomodate the set queue size, and drops the post when exceeded
-    *  - deleting loops with unconsumed posts and unregistered handlers (when unregistration is enabled) does not leak memory */
-
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop1; // with dedicated task
-    esp_event_loop_handle_t loop2; // without dedicated task
-    esp_event_loop_handle_t loop3; // with leftover post and handlers
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop1));
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop2));
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop3));
-
-    TEST_ESP_OK(esp_event_handler_register_with(loop3, s_test_base1, TEST_EVENT_BASE1_EV1, (void*) 0x00000001, NULL));
-    TEST_ESP_OK(esp_event_handler_register_with(loop3, s_test_base1, TEST_EVENT_BASE1_EV2, (void*) 0x00000002, NULL));
-    TEST_ESP_OK(esp_event_handler_register_with(loop3, s_test_base2, TEST_EVENT_BASE1_EV1, (void*) 0x00000003, NULL));
-
-    for (int i = 0; i < loop_args.queue_size; i++) {
-        int mod = i % 4;
-
-        switch(mod) {
-            case 0:
-                TEST_ESP_OK(esp_event_post_to(loop3, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-            break;
-            case 1:
-                TEST_ESP_OK(esp_event_post_to(loop3, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-            break;
-            case 2:
-                TEST_ESP_OK(esp_event_post_to(loop3, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-            break;
-            case 3:
-                TEST_ESP_OK(esp_event_post_to(loop3, s_test_base2, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-            break;
-            default:
-            break;
-        }
-    }
-
-    TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(loop3, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, pdMS_TO_TICKS(10)));
-
-    TEST_ESP_OK(esp_event_loop_delete(loop1));
-    TEST_ESP_OK(esp_event_loop_delete(loop2));
-    TEST_ESP_OK(esp_event_loop_delete(loop3));
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("registering event handler instance without instance context works", "[event]") {
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    int count_1 = 0;
-
-    simple_arg_t arg_1 = {
-        .data = &count_1,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    TEST_ESP_OK(esp_event_handler_instance_register_with(loop, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, test_event_simple_handler, &arg_1, NULL));
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ASSERT_EQUAL(1, count_1);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg_1.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("registering event twice with same handler yields updated handler arg", "[event]") {
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    int count = 0;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    /* Register the handler twice to the same base and id but with a different argument (expects to return ESP_OK and log a warning)
-     * This aims to verify: 1) Handler's argument to be updated
-     *                      2) Registration not to leak memory
-     */
-    TEST_ESP_OK(esp_event_handler_register_with(loop, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, test_event_simple_handler, NULL));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    // exec loop, no handler data: count stays 0
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(0, count);
-
-    // overriding the former registration of the same event
-    TEST_ESP_OK(esp_event_handler_register_with(loop, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, test_event_simple_handler, &arg));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    // exec loop, registered handler data exists: count increases
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(1, count);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("registering event handler instance twice works", "[event]") {
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    int count_1 = 0;
-    int count_2 = 0;
-
-    simple_arg_t arg_1 = {
-        .data = &count_1,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    simple_arg_t arg_2 = {
-        .data = &count_2,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_handler_instance_t ctx_1;
-    esp_event_handler_instance_t ctx_2;
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    TEST_ESP_OK(esp_event_handler_instance_register_with(loop, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, test_event_simple_handler, &arg_1, &ctx_1));
-    TEST_ESP_OK(esp_event_handler_instance_register_with(loop, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, test_event_simple_handler, &arg_2, &ctx_2));
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ASSERT_EQUAL(1, count_1);
-    TEST_ASSERT_EQUAL(1, count_2);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg_1.mutex);
-    vSemaphoreDelete(arg_2.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("registering with ANY_BASE but specific ID fails", "[event]") {
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    int count = 0;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_handler_register_with(loop, ESP_EVENT_ANY_BASE, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ASSERT_EQUAL(0, count);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("registering instance with ANY_BASE but specific ID fails", "[event]") {
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    int count = 0;
-    esp_event_handler_instance_t ctx;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_handler_instance_register_with(loop, ESP_EVENT_ANY_BASE, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg, &ctx));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ASSERT_EQUAL(0, count);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("Check registering ANY_ID", "[event]") {
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    int count = 0;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, ESP_EVENT_ANY_ID, test_event_simple_handler, &arg));
-
-    // handler shouldn't be triggered with different base
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(0, count);
-
-    // for all events with correct base, it should be triggered
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(2, count);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("Check registering instance with ANY_ID", "[event]") {
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    int count = 0;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_handler_instance_t ctx;
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    TEST_ESP_OK(esp_event_handler_instance_register_with(loop, s_test_base1, ESP_EVENT_ANY_ID, test_event_simple_handler, &arg, &ctx));
-
-    // handler shouldn't be triggered with different base
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(0, count);
-
-    // for all events with correct base, it should be triggered
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(2, count);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("Check registering specific event", "[event]") {
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    int count = 0;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg));
-
-    // handler should not be triggered with different base
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(0, count);
-
-    // for incorrect id, it should not be triggered
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(0, count);
-
-    // for correct event and base, it should be triggered
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(1, count);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("Check registering instance with specific event", "[event]") {
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    int count = 0;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_handler_instance_t ctx;
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    TEST_ESP_OK(esp_event_handler_instance_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg, &ctx));
-
-    // handler should not be triggered with different base
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(0, count);
-
-    // for incorrect id, it should not be triggered
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(0, count);
-
-    // for correct event and base, it should be triggered
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(1, count);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("Specific event is not called when no correct events are posted", "[event]") {
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    int count = 0;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg));
-
-    // handler should not be triggered by any of these postings
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(0, count);
-
-    // Post unknown events.
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_MAX, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE2_MAX, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(0, count);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("Specific event instance is not called when no correct events are posted", "[event]") {
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    int count = 0;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_handler_instance_t ctx;
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    TEST_ESP_OK(esp_event_handler_instance_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg, &ctx));
-
-    // handler should not be triggered by any of these postings
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(0, count);
-
-    // Post unknown events.
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_MAX, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE2_MAX, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(0, count);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("can register/unregister handlers for all events/all events for a specific base", "[event]")
-{
-    /* this test aims to verify that handlers can be registered to be called on all events
-     * or for all events with specific bases */
-
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    int count = 0;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    SemaphoreHandle_t sem = xSemaphoreCreateBinary();
-    TEST_ASSERT(sem);
-
-    /* Register the handler twice to the same base and id but with a different argument (expects to return ESP_OK and log a warning)
-     * This aims to verify: 1) Handler's argument to be updated
-     *                      2) Registration not to leak memory
-     */
-    TEST_ESP_OK(esp_event_handler_register_with(loop, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, test_event_simple_handler, NULL));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, test_event_simple_handler, &arg));
-
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, ESP_EVENT_ANY_ID, test_event_simple_handler, &arg));
-    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_handler_register_with(loop, ESP_EVENT_ANY_BASE, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler, &arg));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base2, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg));
-
-    // Will only be called after all the main tests have finished
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_semphr_base, TEST_EVENT_BASE1_EV3, post_sem, &sem));
-
-    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_post_to(loop, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, NULL, 0, portMAX_DELAY));
-    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_post_to(loop, s_test_base1, ESP_EVENT_ANY_ID, NULL, 0, portMAX_DELAY));
-    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_post_to(loop, ESP_EVENT_ANY_BASE, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY)); // exec loop, base and id level (+3)
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY)); // exec loop, base and id level (+3)
-
-    // Post unknown events. Respective loop level and base level handlers should still execute.
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_MAX, NULL, 0, portMAX_DELAY)); // exec loop and base level (+2)
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE2_MAX, NULL, 0, portMAX_DELAY)); // exec loop level (+1)
-    TEST_ESP_OK(esp_event_post_to(loop, s_semphr_base, TEST_EVENT_BASE1_EV3, NULL, 0, portMAX_DELAY));
-
-    while (xSemaphoreTake(sem, 0) != pdTRUE) {
-        TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    }
-
-    TEST_ASSERT_EQUAL(10, count); // 3 + 3 + 2 + 1 + 1
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg.mutex);
-    vSemaphoreDelete(sem);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("can unregister handler", "[event]")
-{
-    /* this test aims to verify that unregistered handlers no longer execute when events are raised */
-
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    SemaphoreHandle_t sem = xSemaphoreCreateBinary();
-    TEST_ASSERT(sem);
-
-    uint32_t handler_0_record;
-    uint32_t handler_1_record;
-
-    uint32_t a = 0xA;
-    uint32_t b = 0xB;
-    uint32_t c = 0xC;
-    uint32_t d = 0xD;
-
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, pass_4byte_event, &handler_0_record));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base2, TEST_EVENT_BASE1_EV1, pass_4byte_event, &handler_1_record));
-
-    // Will only be called after all the main tests have finished
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_semphr_base, TEST_EVENT_BASE1_EV3, post_sem, &sem));
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, &a, sizeof(uint32_t), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV1, &b, sizeof(uint32_t), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_semphr_base, TEST_EVENT_BASE1_EV3, &b, sizeof(uint32_t), portMAX_DELAY));
-
-    while (xSemaphoreTake(sem, 0) != pdTRUE) {
-        TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    }
-
-    TEST_ASSERT_EQUAL(0xA, handler_0_record);
-    TEST_ASSERT_EQUAL(0xB, handler_1_record);
-
-    TEST_ESP_OK(esp_event_handler_unregister_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, pass_4byte_event));
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, &c, sizeof(uint32_t), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV1, &d, sizeof(uint32_t), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_semphr_base, TEST_EVENT_BASE1_EV3, &b, sizeof(uint32_t), portMAX_DELAY));
-
-    while (xSemaphoreTake(sem, 0) != pdTRUE) {
-        TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    }
-
-
-    TEST_ASSERT_EQUAL(0xA, handler_0_record);
-    TEST_ASSERT_EQUAL(0xD, handler_1_record);
-
-    vSemaphoreDelete(sem);
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("can unregister handler instance", "[event]")
-{
-    /* this test aims to verify that unregistered handlers no longer execute when events are raised */
-
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    int count = 0;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_handler_instance_t ctx;
-
-    TEST_ESP_OK(esp_event_handler_instance_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg, &ctx));
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ASSERT_EQUAL(1, count);
-
-    TEST_ESP_OK(esp_event_handler_instance_unregister_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, ctx));
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ASSERT_EQUAL(1, count);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("handler can unregister itself", "[event]")
-{
-    /* this test aims to verify that handlers can unregister themselves */
-
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    int unregistered = 0;
-
-    /*
-     * s_test_base1, ev1 = 1
-     * s_test_base1, ev2 = 2
-     * s_test_base2, ev1 = 11
-     * s_test_base2, ev2 = 12
-     */
-    int expected_unregistered = 0;
-
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_handler_unregister_itself, &unregistered));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_handler_unregister_itself, &unregistered));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base2, TEST_EVENT_BASE2_EV1, test_handler_unregister_itself, &unregistered));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base2, TEST_EVENT_BASE2_EV2, test_handler_unregister_itself, &unregistered));
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, &loop, sizeof(loop), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    expected_unregistered =  2;  // base1, ev2
-    TEST_ASSERT_EQUAL(expected_unregistered, unregistered);
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, &loop, sizeof(loop), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE2_EV1, &loop, sizeof(loop), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    expected_unregistered +=  1 + 11; // base1, ev1 +  base2, ev1
-    TEST_ASSERT_EQUAL(expected_unregistered, unregistered);
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE2_EV2, &loop, sizeof(loop), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    expected_unregistered += 12; //  base2, ev2
-    TEST_ASSERT_EQUAL(expected_unregistered, unregistered);
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, &loop, sizeof(loop), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, &loop, sizeof(loop), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE2_EV1, &loop, sizeof(loop), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE2_EV2, &loop, sizeof(loop), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(expected_unregistered, unregistered); // all handlers unregistered
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("handler instance can unregister itself", "[event]")
-{
-    /* this test aims to verify that handlers can unregister themselves */
-
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    esp_event_handler_instance_t ctx;
-
-    int count = 0;
-
-    instance_unregister_data_t instance_data = {
-        .context = &ctx,
-        .data = &count
-    };
-
-    TEST_ESP_OK(esp_event_handler_instance_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_handler_instance_unregister_itself, &instance_data, &ctx));
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, &loop, sizeof(loop), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(1, count);
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, &loop, sizeof(loop), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    TEST_ASSERT_EQUAL(1, count);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    TEST_TEARDOWN();
-}
-
-// Ignore this test on QEMU for now since it relies on esp_timer which is based on the host run time on ESP32-QEMU
-TEST_CASE("can exit running loop at approximately the set amount of time", "[event][qemu-ignore]")
-{
-    /* this test aims to verify that running loop does not block indefinitely in cases where
-     * events are posted frequently */
-
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    performance_data_t handler_data = {
-        .performed = 0,
-        .expected = INT32_MAX,
-        .done = xSemaphoreCreateBinary()
-    };
-
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_performance_handler, &handler_data));
-
-    post_event_data_t post_event_data = {
-        .base = s_test_base1,
-        .id = TEST_EVENT_BASE1_EV1,
-        .loop = loop,
-        .num  = INT32_MAX
-    };
-
-    task_arg_t post_event_arg = {
-        .data = &post_event_data,
-        .done = xSemaphoreCreateBinary(),
-        .start = xSemaphoreCreateBinary()
-    };
-
-    TaskHandle_t post_task;
-
-    xTaskCreatePinnedToCore(test_event_post_task, "post", 2048, &post_event_arg, s_test_priority, &post_task, test_event_get_core());
-
-    int runtime_ms = 10;
-    int runtime_us = runtime_ms * 1000;
-
-    int64_t start, diff;
-    start = esp_timer_get_time();
-
-    xSemaphoreGive(post_event_arg.start);
-
-    // Run the loop for the runtime_ms set amount of time, regardless of whether events
-    // are still being posted to the loop.
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(runtime_ms)));
-
-    diff = (esp_timer_get_time() - start);
-
-    // Threshold is 25 percent.
-    TEST_ASSERT_LESS_THAN_INT(runtime_us * 1.25f, diff);
-
-    // Verify that the post task still continues
-    TEST_ASSERT_NOT_EQUAL(pdTRUE, xSemaphoreTake(post_event_arg.done, pdMS_TO_TICKS(10)));
-
-    vSemaphoreDelete(post_event_arg.done);
-    vSemaphoreDelete(post_event_arg.start);
-    vSemaphoreDelete(handler_data.done);
-    vTaskDelete(post_task);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("can register/unregister handlers simultaneously", "[event]")
-{
-    /* this test aims to verify that the event handlers list remains consistent despite
-     * simultaneous access by differenct tasks */
-
-    TEST_SETUP();
-
-    const char* base = "base";
-    int32_t id = 0;
-
-    esp_event_loop_handle_t loop;
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    ESP_LOGI(TAG, "registering handlers");
-
-    handler_registration_data_t* registration_data = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*registration_data));
-    task_arg_t* registration_arg = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*registration_arg));
-
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        registration_data[i].base = base;
-        registration_data[i].id = id;
-        registration_data[i].loop = loop;
-        registration_data[i].handles = calloc(TEST_CONFIG_ITEMS_TO_REGISTER, sizeof(esp_event_handler_t));
-        registration_data[i].num = TEST_CONFIG_ITEMS_TO_REGISTER;
-        registration_data[i].is_registration = true;
-
-        for (int j = 0; j < TEST_CONFIG_ITEMS_TO_REGISTER; j++) {
-            registration_data[i].handles[j] = (void*) (i * TEST_CONFIG_ITEMS_TO_REGISTER) + (j + TEST_CONFIG_ITEMS_TO_REGISTER);
-        }
-
-        registration_arg[i].start = xSemaphoreCreateBinary();
-        registration_arg[i].done = xSemaphoreCreateBinary();
-        registration_arg[i].data = &registration_data[i];
-
-        xTaskCreatePinnedToCore(test_event_simple_handler_registration_task, "register", 2048, &registration_arg[i], s_test_priority, NULL, test_event_get_core());
-    }
-
-    // Give the semaphores to the spawned registration task
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        xSemaphoreGive(registration_arg[i].start);
-    }
-
-    // Take the same semaphores in order to proceed
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        xSemaphoreTake(registration_arg[i].done, portMAX_DELAY);
-    }
-
-    ESP_LOGI(TAG, "checking consistency of handlers list");
-
-    // Check consistency of events list
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        for (int j = 0; j < TEST_CONFIG_ITEMS_TO_REGISTER; j++) {
-            TEST_ASSERT_TRUE(esp_event_is_handler_registered(loop, base, id, registration_data[i].handles[j]));
-        }
-    }
-
-    ESP_LOGI(TAG, "unregistering handlers");
-
-    /* Test if tasks can unregister simultaneously */
-
-    // Unregister registered events
-    handler_registration_data_t* unregistration_data = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*unregistration_data));
-    task_arg_t* unregistration_arg = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*unregistration_arg));
-
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        unregistration_data[i].base = base;
-        unregistration_data[i].id = id;
-        unregistration_data[i].loop = loop;
-        unregistration_data[i].handles = calloc(TEST_CONFIG_ITEMS_TO_REGISTER, sizeof(esp_event_handler_t));
-        unregistration_data[i].num = TEST_CONFIG_ITEMS_TO_REGISTER;
-        unregistration_data[i].is_registration = false;
-
-        memcpy(unregistration_data[i].handles, registration_data[i].handles, TEST_CONFIG_ITEMS_TO_REGISTER * sizeof(esp_event_handler_t));
-
-        unregistration_arg[i].data = &unregistration_data[i];
-        unregistration_arg[i].start = xSemaphoreCreateBinary();
-        unregistration_arg[i].done = xSemaphoreCreateBinary();
-
-        xTaskCreatePinnedToCore(test_event_simple_handler_registration_task, "unregister", 2048, &unregistration_arg[i], s_test_priority, NULL, test_event_get_core());
-    }
-
-    // Give the semaphores to the spawned unregistration task
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        xSemaphoreGive(unregistration_arg[i].start);
-    }
-
-    // Take the same semaphores in order to proceed
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        xSemaphoreTake(unregistration_arg[i].done, portMAX_DELAY);
-    }
-
-    ESP_LOGI(TAG, "checking consistency of handlers list");
-
-    // Check consistency of events list
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        for (int j = 0; j < TEST_CONFIG_ITEMS_TO_REGISTER; j++) {
-            TEST_ASSERT_FALSE(esp_event_is_handler_registered(loop, base, id, registration_data[i].handles[j]));
-        }
-    }
-
-    // Do cleanup
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        free(registration_data[i].handles);
-        vSemaphoreDelete(registration_arg[i].start);
-        vSemaphoreDelete(registration_arg[i].done);
-
-        free(unregistration_data[i].handles);
-        vSemaphoreDelete(unregistration_arg[i].start);
-        vSemaphoreDelete(unregistration_arg[i].done);
-    }
-
-    free(registration_data);
-    free(unregistration_data);
-    free(registration_arg);
-    free(unregistration_arg);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("posting ANY_EVENT or ANY_ID fails", "[event]") {
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    int count = 0;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_post_to(loop, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, NULL, 0, portMAX_DELAY));
-    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_post_to(loop, s_test_base1, ESP_EVENT_ANY_ID, NULL, 0, portMAX_DELAY));
-    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_post_to(loop, ESP_EVENT_ANY_BASE, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ASSERT_EQUAL(0, count);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("can post and run events", "[event]")
-{
-    /* this test aims to verify that:
-     *  - multiple tasks can post to the queue simultaneously
-     *  - handlers recieve the appropriate handler arg and associated event data */
-
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    loop_args.queue_size = TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    int count = 0;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg));
-
-    post_event_data_t* post_event_data = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*post_event_data));
-    task_arg_t* post_event_arg = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*post_event_arg));
-
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++)
-    {
-        post_event_data[i].base = s_test_base1;
-        post_event_data[i].id = TEST_EVENT_BASE1_EV1;
-        post_event_data[i].loop = loop;
-        post_event_data[i].num = TEST_CONFIG_ITEMS_TO_REGISTER;
-
-        post_event_arg[i].data = &post_event_data[i];
-        post_event_arg[i].start = xSemaphoreCreateBinary();
-        post_event_arg[i].done = xSemaphoreCreateBinary();
-
-        xTaskCreatePinnedToCore(test_event_post_task, "post", 2048, &post_event_arg[i], s_test_priority, NULL, test_event_get_core());
-    }
-
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        xSemaphoreGive(post_event_arg[i].start);
-    }
-
-    // Execute some events as they are posted
-    for (int i = 0; i < (TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER) / 2; i++) {
-        TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    }
-
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        xSemaphoreTake(post_event_arg[i].done, portMAX_DELAY);
-    }
-
-    // Execute the rest
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ASSERT_EQUAL(TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER, count);
-
-    // Cleanup
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        vSemaphoreDelete(post_event_arg[i].start);
-        vSemaphoreDelete(post_event_arg[i].done);
-    }
-
-    free(post_event_data);
-    free(post_event_arg);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("can post and run events with instances", "[event]")
-{
-    /* this test aims to verify that:
-     *  - multiple tasks can post to the queue simultaneously
-     *  - handlers recieve the appropriate handler arg and associated event data */
-
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    loop_args.queue_size = TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    int count = 0;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateMutex()
-    };
-
-    esp_event_handler_instance_t ctx;
-
-    TEST_ESP_OK(esp_event_handler_instance_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg, &ctx));
-
-    post_event_data_t* post_event_data = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*post_event_data));
-    task_arg_t* post_event_arg = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*post_event_arg));
-
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++)
-    {
-        post_event_data[i].base = s_test_base1;
-        post_event_data[i].id = TEST_EVENT_BASE1_EV1;
-        post_event_data[i].loop = loop;
-        post_event_data[i].num = TEST_CONFIG_ITEMS_TO_REGISTER;
-
-        post_event_arg[i].data = &post_event_data[i];
-        post_event_arg[i].start = xSemaphoreCreateBinary();
-        post_event_arg[i].done = xSemaphoreCreateBinary();
-
-        xTaskCreatePinnedToCore(test_event_post_task, "post", 2048, &post_event_arg[i], s_test_priority, NULL, test_event_get_core());
-    }
-
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        xSemaphoreGive(post_event_arg[i].start);
-    }
-
-    // Execute some events as they are posted
-    for (int i = 0; i < (TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER) / 2; i++) {
-        TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    }
-
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        xSemaphoreTake(post_event_arg[i].done, portMAX_DELAY);
-    }
-
-    // Execute the rest
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ASSERT_EQUAL(TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER, count);
-
-    // Cleanup
-    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
-        vSemaphoreDelete(post_event_arg[i].start);
-        vSemaphoreDelete(post_event_arg[i].done);
-    }
-
-    free(post_event_data);
-    free(post_event_arg);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-static void loop_run_task(void* args)
-{
-    esp_event_loop_handle_t event_loop = (esp_event_loop_handle_t) args;
-
-    while(1) {
-        esp_event_loop_run(event_loop, portMAX_DELAY);
-    }
-}
-
-static void performance_test(bool dedicated_task)
-{
-    // rand() seems to do a one-time allocation. Call it here so that the memory it allocates
-    // is not counted as a leak.
-    unsigned int _rand __attribute__((unused)) = rand();
-
-    TEST_SETUP();
-
-    const char test_base[] = "qwertyuiopasdfghjklzxvbnmmnbvcxz";
-
-    #define TEST_CONFIG_BASES  (sizeof(test_base) - 1)
-    #define TEST_CONFIG_IDS    (TEST_CONFIG_BASES / 2)
-
-    // Create loop
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-    esp_event_loop_handle_t loop;
-
-    if (!dedicated_task) {
-        loop_args.task_name = NULL;
-    }
-
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    performance_data_t data;
-
-    // Register the handlers
-    for (int base = 0; base < TEST_CONFIG_BASES; base++) {
-        for (int id = 0; id < TEST_CONFIG_IDS; id++) {
-            TEST_ESP_OK(esp_event_handler_register_with(loop, test_base + base, id, test_event_performance_handler, &data));
-        }
-    }
-
-    TaskHandle_t mtask = NULL;
-
-    if (!dedicated_task) {
-        xTaskCreate(loop_run_task, "loop_run", loop_args.task_stack_size, (void*) loop, loop_args.task_priority, &mtask);
-    }
-
-    // Perform performance test
-    float running_sum = 0;
-    float running_count = 0;
-
-    for (int bases = 1; bases <= TEST_CONFIG_BASES; bases *= 2) {
-        for (int ids = 1; ids <= TEST_CONFIG_IDS; ids *= 2) {
-
-            data.performed = 0;
-            data.expected = bases * ids;
-            data.done = xSemaphoreCreateBinary();
-
-            // Generate randomized list of posts
-            int post_bases[TEST_CONFIG_BASES];
-            int post_ids[TEST_CONFIG_IDS];
-
-            for (int i = 0; i < bases; i++) {
-                post_bases[i] = i;
-            }
-
-            for (int i = 0; i < ids; i++) {
-                post_ids[i] = i;
-            }
-
-            for (int i = 0; i < bases; i++) {
-                int rand_a  = rand() % bases;
-                int rand_b  = rand() % bases;
-
-                int temp = post_bases[rand_a];
-                post_bases[rand_a]= post_bases[rand_b];
-                post_bases[rand_b] = temp;
-            }
-
-            for (int i = 0; i < ids; i++) {
-                int rand_a  = rand() % ids;
-                int rand_b  = rand() % ids;
-
-                int temp = post_ids[rand_a];
-                post_ids[rand_a]= post_ids[rand_b];
-                post_ids[rand_b] = temp;
-            }
-
-            // Post the events
-            int64_t start = esp_timer_get_time();
-            for (int base = 0; base < bases; base++) {
-                for (int id = 0; id < ids; id++) {
-                    TEST_ESP_OK(esp_event_post_to(loop, test_base + post_bases[base], post_ids[id], NULL, 0, portMAX_DELAY));
-                }
-            }
-
-            xSemaphoreTake(data.done, portMAX_DELAY);
-            int64_t elapsed = esp_timer_get_time() - start;
-
-            // Record data
-            TEST_ASSERT_EQUAL(data.expected, data.performed);
-
-            running_count++;
-            running_sum += data.performed / (elapsed / (1000000.0));
-
-            vSemaphoreDelete(data.done);
-        }
-    }
-
-    int average = (int) (running_sum / (running_count));
-
-    if (!dedicated_task) {
-        ((esp_event_loop_instance_t*) loop)->task = mtask;
-    }
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    TEST_TEARDOWN();
-
-#ifdef CONFIG_ESP_EVENT_LOOP_PROFILING
-    ESP_LOGI(TAG, "events dispatched/second with profiling enabled: %d", average);
-    // Enabling profiling will slow down event dispatch, so the set threshold
-    // is not valid when it is enabled.
-#else
-#ifndef CONFIG_SPIRAM
-    TEST_PERFORMANCE_GREATER_THAN(EVENT_DISPATCH, "%d", average);
-#else
-    TEST_PERFORMANCE_GREATER_THAN(EVENT_DISPATCH_PSRAM, "%d", average);
-#endif // CONFIG_SPIRAM
-#endif // CONFIG_ESP_EVENT_LOOP_PROFILING
-}
-
-TEST_CASE("performance test - dedicated task", "[event][qemu-ignore]")
-{
-    performance_test(true);
-}
-
-TEST_CASE("performance test - no dedicated task", "[event][qemu-ignore]")
-{
-    performance_test(false);
-}
-
-static SemaphoreHandle_t pending_sem = NULL;
-
-static void test_handler_pending(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
-{
-    xSemaphoreGive(pending_sem);
-}
-
-TEST_CASE("can post to loop from handler - dedicated task", "[event][long]")
-{
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop_w_task;
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    int count;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateBinary()
-    };
-
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop_w_task));
-
-    pending_sem = xSemaphoreCreateCounting(loop_args.queue_size, 0);
-    count = 0;
-
-    TEST_ESP_OK(esp_event_handler_register_with(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV1, test_handler_post_w_task, &arg));
-    TEST_ESP_OK(esp_event_handler_register_with(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV2, test_handler_post_w_task, &arg));
-    TEST_ESP_OK(esp_event_handler_register_with(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV3, test_handler_pending, &arg));
-
-    // Test that a handler can post to a different loop while there is still slots on the queue
-    TEST_ESP_OK(esp_event_post_to(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV1, &loop_w_task, sizeof(&loop_w_task), portMAX_DELAY));
-
-    xSemaphoreTake(arg.mutex, portMAX_DELAY);
-
-    TEST_ASSERT_EQUAL(2, count);
-
-    // Test that other tasks can still post while  there is still slots in the queue, while handler is executing
-    count = 100;
-
-    TEST_ESP_OK(esp_event_post_to(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV1, &loop_w_task, sizeof(&loop_w_task), portMAX_DELAY));
-
-    for (int i = 0; i < loop_args.queue_size; i++) {
-        TEST_ESP_OK(esp_event_post_to(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV3, NULL, 0, portMAX_DELAY));
-    }
-
-    TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0,
-                         pdMS_TO_TICKS(TEST_CONFIG_WAIT_TIME)));
-
-    xSemaphoreGive(arg.mutex);
-
-    for (int i = 0; i < loop_args.queue_size; i++) {
-        TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(pending_sem, pdMS_TO_TICKS(TEST_CONFIG_WAIT_TIME)));
-    }
-
-    TEST_ESP_OK(esp_event_loop_delete(loop_w_task));
-
-    vSemaphoreDelete(pending_sem);
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("can post to loop from handler instance - dedicated task", "[event][long]")
-{
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop_w_task;
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    pending_sem = xSemaphoreCreateCounting(loop_args.queue_size, 0);
-    int count;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateBinary()
-    };
-
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop_w_task));
-
-    count = 0;
-
-    esp_event_handler_instance_t ctx_1;
-    esp_event_handler_instance_t ctx_2;
-    esp_event_handler_instance_t ctx_3;
-
-    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV1, test_handler_post_w_task, &arg, &ctx_1));
-    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV2, test_handler_post_w_task, &arg, &ctx_2));
-    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV3, test_handler_pending, &arg, &ctx_3));
-
-    // Test that a handler can post to a different loop while there is still slots on the queue
-    TEST_ESP_OK(esp_event_post_to(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV1, &loop_w_task, sizeof(&loop_w_task), portMAX_DELAY));
-
-    xSemaphoreTake(arg.mutex, portMAX_DELAY);
-
-    TEST_ASSERT_EQUAL(2, count);
-
-    // Test that other tasks can still post while  there is still slots in the queue, while handler is executing
-    count = 100;
-
-    TEST_ESP_OK(esp_event_post_to(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV1, &loop_w_task, sizeof(&loop_w_task), portMAX_DELAY));
-
-    for (int i = 0; i < loop_args.queue_size; i++) {
-        TEST_ESP_OK(esp_event_post_to(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV3, NULL, 0, portMAX_DELAY));
-    }
-
-    TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(loop_w_task, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0,
-                         pdMS_TO_TICKS(TEST_CONFIG_WAIT_TIME)));
-
-    xSemaphoreGive(arg.mutex);
-
-    for (int i = 0; i < loop_args.queue_size; i++) {
-        TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(pending_sem, pdMS_TO_TICKS(TEST_CONFIG_WAIT_TIME)));
-    }
-
-    TEST_ESP_OK(esp_event_loop_delete(loop_w_task));
-
-    vSemaphoreDelete(pending_sem);
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("can post to loop from handler - no dedicated task", "[event][long]")
-{
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop_wo_task;
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    int count;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateBinary()
-    };
-
-    count = 0;
-
-    loop_args.queue_size = 1;
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop_wo_task));
-
-    TaskHandle_t mtask;
-
-    xTaskCreate(test_post_from_handler_loop_task, "task", 2584, (void*) loop_wo_task, s_test_priority, &mtask);
-
-    TEST_ESP_OK(esp_event_handler_register_with(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV1, test_handler_post_wo_task, &arg));
-    TEST_ESP_OK(esp_event_handler_register_with(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV2, test_handler_post_wo_task, &arg));
-
-    // Test that a handler can post to a different loop while there is still slots on the queue
-    TEST_ESP_OK(esp_event_post_to(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV1, &loop_wo_task, sizeof(&loop_wo_task), portMAX_DELAY));
-
-    xSemaphoreTake(arg.mutex, portMAX_DELAY);
-
-    TEST_ASSERT_EQUAL(2, count);
-
-    count = 100;
-
-    TEST_ESP_OK(esp_event_post_to(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV1, &loop_wo_task, sizeof(&loop_wo_task), portMAX_DELAY));
-
-    vTaskDelay(pdMS_TO_TICKS(TEST_CONFIG_WAIT_TIME));
-
-    // For loop without tasks, posting is more restrictive. Posting should wait until execution of handler finishes
-    TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0,
-                         pdMS_TO_TICKS(TEST_CONFIG_WAIT_TIME)));
-
-    xSemaphoreGive(arg.mutex);
-
-    vTaskDelay(pdMS_TO_TICKS(TEST_CONFIG_WAIT_TIME));
-
-    vTaskDelete(mtask);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop_wo_task));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("can post to loop from handler instance - no dedicated task", "[event][long]")
-{
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop_wo_task;
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    int count;
-
-    simple_arg_t arg = {
-        .data = &count,
-        .mutex = xSemaphoreCreateBinary()
-    };
-
-    count = 0;
-
-    esp_event_handler_instance_t ctx_1;
-    esp_event_handler_instance_t ctx_2;
-
-    loop_args.queue_size = 1;
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop_wo_task));
-
-    TaskHandle_t mtask;
-
-    xTaskCreate(test_post_from_handler_loop_task, "task", 2584, (void*) loop_wo_task, s_test_priority, &mtask);
-
-    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV1, test_handler_post_wo_task, &arg, &ctx_1));
-    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV2, test_handler_post_wo_task, &arg, &ctx_2));
-
-    // Test that a handler can post to a different loop while there is still slots on the queue
-    TEST_ESP_OK(esp_event_post_to(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV1, &loop_wo_task, sizeof(&loop_wo_task), portMAX_DELAY));
-
-    xSemaphoreTake(arg.mutex, portMAX_DELAY);
-
-    TEST_ASSERT_EQUAL(2, count);
-
-    count = 100;
-
-    TEST_ESP_OK(esp_event_post_to(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV1, &loop_wo_task, sizeof(&loop_wo_task), portMAX_DELAY));
-
-    vTaskDelay(pdMS_TO_TICKS(TEST_CONFIG_WAIT_TIME));
-
-    // For loop without tasks, posting is more restrictive. Posting should wait until execution of handler finishes
-    TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(loop_wo_task, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0,
-                         pdMS_TO_TICKS(TEST_CONFIG_WAIT_TIME)));
-
-    xSemaphoreGive(arg.mutex);
-
-    vTaskDelay(pdMS_TO_TICKS(TEST_CONFIG_WAIT_TIME));
-
-    vTaskDelete(mtask);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop_wo_task));
-
-    vSemaphoreDelete(arg.mutex);
-
-    TEST_TEARDOWN();
-}
-
-static void test_event_simple_handler_template(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg)
-{
-    int* count = (int*) handler_arg;
-    (*count)++;
-}
-
-static void test_event_simple_handler_1(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg)
-{
-    test_event_simple_handler_template(handler_arg, base, id, event_arg);
-}
-
-static void test_event_simple_handler_3(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg)
-{
-    test_event_simple_handler_template(handler_arg, base, id, event_arg);
-}
-
-static void test_event_simple_handler_2(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg)
-{
-    test_event_simple_handler_template(handler_arg, base, id, event_arg);
-}
-
-static void test_registration_from_handler_hdlr(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg)
-{
-    esp_event_loop_handle_t* loop = (esp_event_loop_handle_t*) event_arg;
-    TEST_ESP_OK(esp_event_handler_register_with(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler_1, handler_arg));
-    TEST_ESP_OK(esp_event_handler_register_with(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler_2, handler_arg));
-    TEST_ESP_OK(esp_event_handler_register_with(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler_3, handler_arg));
-}
-
-static void test_unregistration_from_handler_hdlr(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg)
-{
-    esp_event_loop_handle_t* loop = (esp_event_loop_handle_t*) event_arg;
-    TEST_ESP_OK(esp_event_handler_unregister_with(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler_1));
-    TEST_ESP_OK(esp_event_handler_unregister_with(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler_2));
-    TEST_ESP_OK(esp_event_handler_unregister_with(*loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_simple_handler_3));
-}
-
-TEST_CASE("can register from handler", "[event]")
-{
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    int count = 0;
-
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_registration_from_handler_hdlr, &count));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base2, TEST_EVENT_BASE2_EV1, test_unregistration_from_handler_hdlr, &count));
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, &loop, sizeof(&loop), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ASSERT_EQUAL(3, count);
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE2_EV1, &loop, sizeof(&loop), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ASSERT_EQUAL(3, count);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    TEST_TEARDOWN();
-}
-
-static void test_create_loop_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data)
-{
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    if (id == TEST_EVENT_BASE1_EV1) {
-        TEST_ESP_OK(esp_event_loop_create(&loop_args, (esp_event_loop_handle_t*) handler_args));
-    } else {
-        TEST_ESP_OK(esp_event_loop_delete(*((esp_event_loop_handle_t*) handler_args)));
-    }
-}
-
-TEST_CASE("can create and delete loop from handler", "[event]")
-{
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-    esp_event_loop_handle_t test_loop;
-
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_create_loop_handler, &test_loop));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_create_loop_handler, &test_loop));
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
-    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    TEST_TEARDOWN();
-}
-
-TEST_CASE("events are dispatched in the order they are registered", "[event]")
-{
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    int id_arr[7];
-
-    for (int i = 0; i < 7; i++) {
-        id_arr[i] = i;
-    }
-
-    int data_arr[12] = {0};
-
-    SemaphoreHandle_t sem = xSemaphoreCreateBinary();
-    TEST_ASSERT(sem);
-
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base2, TEST_EVENT_BASE2_EV1, test_event_ordered_dispatch, id_arr + 0));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, test_event_ordered_dispatch, id_arr + 1));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, ESP_EVENT_ANY_ID, test_event_ordered_dispatch, id_arr + 2));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base2, TEST_EVENT_BASE2_EV2, test_event_ordered_dispatch, id_arr + 3));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_ordered_dispatch, id_arr + 4));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base2, ESP_EVENT_ANY_ID, test_event_ordered_dispatch, id_arr + 5));
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV2, test_event_ordered_dispatch, id_arr + 6));
-
-    // Will only be called after all the main tests have finished
-    TEST_ESP_OK(esp_event_handler_register_with(loop, s_semphr_base, TEST_EVENT_BASE1_EV3, post_sem, &sem));
-
-    esp_event_dump(stdout);
-
-    ordered_data_t data = {
-        .arr = data_arr,
-        .index = 0
-    };
-
-    ordered_data_t* dptr = &data;
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV2, &dptr, sizeof(dptr), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, &dptr, sizeof(dptr), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV2, &dptr, sizeof(dptr), portMAX_DELAY));
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base2, TEST_EVENT_BASE1_EV1, &dptr, sizeof(dptr), portMAX_DELAY));
-
-    // This is just to signal that all test handlers have run
-    TEST_ESP_OK(esp_event_post_to(loop, s_semphr_base, TEST_EVENT_BASE1_EV3, &dptr, sizeof(dptr), portMAX_DELAY));
-
-    while (xSemaphoreTake(sem, 0) != pdTRUE) {
-        TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
-    }
-
-    // Expected data executing the posts above
-    int ref_arr[13] = {1, 3, 5, 1, 2, 4, 1, 2, 6, 0, 1, 5, 0}; // the last element is not checked anymore because
-                                                               // we send one event to post the semaphore
-
-    TEST_ASSERT_EQUAL_INT_ARRAY(ref_arr, data_arr, 12);
-
-    vSemaphoreDelete(sem);
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    TEST_TEARDOWN();
-}
-
-#if CONFIG_ESP_EVENT_POST_FROM_ISR
-TEST_CASE("can properly prepare event data posted to loop", "[event]")
-{
-    TEST_SETUP();
-
-    esp_event_loop_handle_t loop;
-    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
-
-    loop_args.task_name = NULL;
-    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
-
-    esp_event_post_instance_t post;
-    esp_event_loop_instance_t* loop_def = (esp_event_loop_instance_t*) loop;
-
-    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
-    TEST_ASSERT_EQUAL(pdTRUE, xQueueReceive(loop_def->queue, &post, portMAX_DELAY));
-    TEST_ASSERT_EQUAL(false, post.data_set);
-    TEST_ASSERT_EQUAL(false, post.data_allocated);
-    TEST_ASSERT_EQUAL(NULL, post.data.ptr);
-
-    int sample = 0;
-    TEST_ESP_OK(esp_event_isr_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, &sample, sizeof(sample), NULL));
-    TEST_ASSERT_EQUAL(pdTRUE, xQueueReceive(loop_def->queue, &post, portMAX_DELAY));
-    TEST_ASSERT_EQUAL(true, post.data_set);
-    TEST_ASSERT_EQUAL(false, post.data_allocated);
-    TEST_ASSERT_EQUAL(false, post.data.val);
-
-    TEST_ESP_OK(esp_event_loop_delete(loop));
-
-    TEST_TEARDOWN();
-}
-
-static void test_handler_post_from_isr(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
-{
-    SemaphoreHandle_t *sem = (SemaphoreHandle_t*) event_handler_arg;
-    // Event data is just the address value (maybe have been truncated due to casting).
-    int *data = (int*) event_data;
-    TEST_ASSERT_EQUAL(*data, (int) (*sem));
-    xSemaphoreGive(*sem);
-}
-
-bool test_event_on_timer_alarm(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
-{
-    int data = (int)user_ctx;
-    gptimer_stop(timer);
-    // Posting events with data more than 4 bytes should fail.
-    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_isr_post(s_test_base1, TEST_EVENT_BASE1_EV1, &data, 5, NULL));
-    // This should succeedd, as data is int-sized. The handler for the event checks that the passed event data
-    // is correct.
-    BaseType_t task_unblocked;
-    TEST_ESP_OK(esp_event_isr_post(s_test_base1, TEST_EVENT_BASE1_EV1, &data, sizeof(data), &task_unblocked));
-    return task_unblocked == pdTRUE;
-}
-
-TEST_CASE("can post events from interrupt handler", "[event]")
-{
-    TEST_ESP_OK(esp_event_loop_create_default());
-
-    /* Lazy allocated resources in gptimer/intr_alloc */
-    set_leak_threshold(-150);
-
-    SemaphoreHandle_t sem = xSemaphoreCreateBinary();
-    gptimer_handle_t gptimer = NULL;
-    /* Select and initialize basic parameters of the timer */
-    gptimer_config_t config = {
-        .clk_src = GPTIMER_CLK_SRC_DEFAULT,
-        .direction = GPTIMER_COUNT_UP,
-        .resolution_hz = 1000000, // 1MHz, 1 tick = 1us
-    };
-    TEST_ESP_OK(gptimer_new_timer(&config, &gptimer));
-
-    gptimer_alarm_config_t alarm_config = {
-        .reload_count = 0,
-        .alarm_count = 500000,
-    };
-    gptimer_event_callbacks_t cbs = {
-        .on_alarm = test_event_on_timer_alarm
-    };
-    TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, sem));
-    TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config));
-    TEST_ESP_OK(gptimer_enable(gptimer));
-    TEST_ESP_OK(gptimer_start(gptimer));
-
-    TEST_ESP_OK(esp_event_handler_register(s_test_base1, TEST_EVENT_BASE1_EV1,
-                                           test_handler_post_from_isr, &sem));
-
-    xSemaphoreTake(sem, portMAX_DELAY);
-
-    vTaskDelay(pdMS_TO_TICKS(TEST_CONFIG_TEARDOWN_WAIT));
-
-    vSemaphoreDelete(sem);
-    TEST_ESP_OK(gptimer_disable(gptimer));
-    TEST_ESP_OK(gptimer_del_timer(gptimer));
-
-    TEST_ESP_OK(esp_event_loop_delete_default());
-}
-
-#endif // CONFIG_ESP_EVENT_POST_FROM_ISR

+ 1573 - 0
components/esp_event/test_apps/main/test_event_common.cpp

@@ -0,0 +1,1573 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Unlicense OR CC0-1.0
+ */
+
+/* This file contains all tests runnable on targets as well as on the host */
+
+#include <stdio.h>
+#include <string.h>
+#include "sdkconfig.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_event.h"
+#include "unity.h"
+
+ESP_EVENT_DECLARE_BASE(s_test_base1);
+ESP_EVENT_DECLARE_BASE(s_test_base2);
+
+ESP_EVENT_DEFINE_BASE(s_test_base1);
+ESP_EVENT_DEFINE_BASE(s_test_base2);
+
+enum {
+    TEST_EVENT_BASE1_EV1,
+    TEST_EVENT_BASE1_EV2,
+    TEST_EVENT_BASE1_MAX
+};
+
+enum {
+    TEST_EVENT_BASE2_EV1,
+    TEST_EVENT_BASE2_MAX
+};
+
+static const TickType_t ZERO_DELAY = 0;
+
+static esp_event_loop_args_t test_event_get_default_loop_args(void)
+{
+    esp_event_loop_args_t loop_config = {
+        .queue_size = CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE,
+        .task_name = "loop",
+        .task_priority = uxTaskPriorityGet(NULL),
+        .task_stack_size = 2048,
+        .task_core_id = 0 // Linux simulator is single core only
+    };
+
+    return loop_config;
+}
+
+struct EV_LoopFix {
+public:
+    EV_LoopFix(size_t queue_size = CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE, const char *task_name = NULL) {
+        esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
+        loop_args.task_name = task_name;
+        loop_args.queue_size = queue_size;
+        TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
+    }
+
+    ~EV_LoopFix() {
+        esp_event_loop_delete(loop);
+    }
+
+    esp_event_loop_handle_t loop;
+};
+
+TEST_CASE("can create and delete event loop without task", "[event][linux]")
+{
+    esp_event_loop_handle_t loop;
+    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
+    loop_args.task_name = NULL;
+
+    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
+    TEST_ESP_OK(esp_event_loop_delete(loop));
+}
+
+TEST_CASE("can create and delete event loop with task", "[event][linux]")
+{
+    esp_event_loop_handle_t loop;
+    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
+
+    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
+    TEST_ESP_OK(esp_event_loop_delete(loop));
+}
+
+TEST_CASE("can post events up to loop's max queue size", "[event][linux]")
+{
+    EV_LoopFix loop_fix(2);
+
+    TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            NULL,
+            0,
+            pdMS_TO_TICKS(10)));
+    TEST_ASSERT_EQUAL(ESP_OK, esp_event_post_to(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            NULL,
+            0,
+            pdMS_TO_TICKS(10)));
+}
+
+TEST_CASE("posting to full event loop times out", "[event][linux]")
+{
+    EV_LoopFix loop_fix(1);
+
+    TEST_ASSERT_EQUAL(ESP_OK,
+            esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, pdMS_TO_TICKS(0)));
+    TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT,
+            esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, pdMS_TO_TICKS(0)));
+    TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT,
+            esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, pdMS_TO_TICKS(10)));
+}
+
+TEST_CASE("can post to loop and run loop without registration", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+
+    TEST_ASSERT_EQUAL(ESP_OK,
+            esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, pdMS_TO_TICKS(10)));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+}
+
+static void test_handler_inc(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
+{
+    int *target = (int*) event_handler_arg;
+    (*target)++;
+}
+
+TEST_CASE("registering event handler instance without instance context works", "[event][linux]") {
+    EV_LoopFix loop_fix;
+    int count = 0;
+
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &count,
+            NULL));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(1, count);
+}
+
+/* Register the handler twice to the same base and id but with a different argument
+ * (expects to return ESP_OK and log a warning)
+ * This aims to verify: 1) Handler's argument to be updated
+ *                      2) Registration not to leak memory
+ */
+TEST_CASE("registering event twice with same handler yields updated handler arg", "[event][linux]") {
+    EV_LoopFix loop_fix;
+
+    int count_first = 0;
+    int count_second = 0;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count_first));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(count_first, 1);
+
+    // overriding the former registration of the same event
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count_second));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(1, count_first);
+    TEST_ASSERT_EQUAL(1, count_second);
+}
+
+TEST_CASE("registering event handler instance twice works", "[event][linux]") {
+    EV_LoopFix loop_fix;
+    int count_1 = 0;
+    int count_2 = 0;
+    esp_event_handler_instance_t ctx_1;
+    esp_event_handler_instance_t ctx_2;
+
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count_1,
+            &ctx_1));
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count_2,
+            &ctx_2));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, pdMS_TO_TICKS(10)));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(1, count_1);
+    TEST_ASSERT_EQUAL(1, count_2);
+}
+
+TEST_CASE("registering with ANY_BASE but specific ID fails", "[event][linux]") {
+    EV_LoopFix loop_fix;
+    int count = 0;
+    int count_instance = 0;
+    esp_event_handler_instance_t ctx;
+
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_handler_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &count));
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_handler_instance_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &count_instance,
+            &ctx));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(0, count);
+    TEST_ASSERT_EQUAL(0, count_instance);
+}
+
+TEST_CASE("registration of ANY_BASE and ANY_ID always called", "[event][linux]") {
+    EV_LoopFix loop_fix;
+    int count = 0;
+    int count_instance = 0;
+    esp_event_handler_instance_t ctx;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count));
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count_instance,
+            &ctx));
+
+    // handlers should always be triggered
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base2, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
+    for (size_t i = 0; i < 4; i++) {
+        TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+    }
+
+    TEST_ASSERT_EQUAL(4, count);
+    TEST_ASSERT_EQUAL(4, count_instance);
+}
+
+TEST_CASE("registration of ANY_ID not called from wrong base", "[event][linux]") {
+    EV_LoopFix loop_fix;
+    int count = 0;
+    int count_instance = 0;
+    esp_event_handler_instance_t ctx;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count));
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count_instance,
+            &ctx));
+
+    // handlers shouldn't be triggered with different base
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base2, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(0, count);
+    TEST_ASSERT_EQUAL(0, count_instance);
+}
+
+TEST_CASE("registration of ANY_ID called from correct base", "[event][linux]") {
+    EV_LoopFix loop_fix;
+    int count = 0;
+    int count_instance = 0;
+    esp_event_handler_instance_t ctx;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count));
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count_instance,
+            &ctx));
+
+    // for all events with correct base, it should be triggered
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(2, count);
+    TEST_ASSERT_EQUAL(2, count_instance);
+}
+
+TEST_CASE("registering specific event posting different base and different event ID", "[event][linux]") {
+    EV_LoopFix loop_fix;
+    int count = 0;
+    int count_instance = 0;
+    esp_event_handler_instance_t ctx;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &count));
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &count_instance,
+            &ctx));
+
+    // handlers should not be triggered with different base and different ID
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base2, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(0, count);
+    TEST_ASSERT_EQUAL(0, count_instance);
+}
+
+TEST_CASE("registering specific event posting different base", "[event][linux]") {
+    EV_LoopFix loop_fix;
+    int count = 0;
+    int count_instance = 0;
+    esp_event_handler_instance_t ctx;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &count));
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &count_instance,
+            &ctx));
+
+    // handlers should not be triggered with different base
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base2, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(0, count);
+    TEST_ASSERT_EQUAL(0, count_instance);
+}
+
+TEST_CASE("registering specific event posting incorrect event ID", "[event][linux]") {
+    EV_LoopFix loop_fix;
+    int count = 0;
+    int count_instance = 0;
+    esp_event_handler_instance_t ctx;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &count));
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &count_instance,
+            &ctx));
+
+    // for incorrect id, it should not be triggered with different ID
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(0, count);
+    TEST_ASSERT_EQUAL(0, count_instance);
+}
+
+TEST_CASE("registering specific event posting correct event and base", "[event][linux]") {
+    EV_LoopFix loop_fix;
+    int count = 0;
+    int count_instance = 0;
+    esp_event_handler_instance_t ctx;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &count));
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &count_instance,
+            &ctx));
+
+    // for correct event and base, it should be triggered
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(1, count);
+    TEST_ASSERT_EQUAL(1, count_instance);
+}
+
+TEST_CASE("posting ANY_EVENT or ANY_ID fails", "[event][linux]") {
+    EV_LoopFix loop_fix;
+    int count = 0;
+    int count_instance = 0;
+    esp_event_handler_instance_t ctx;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &count));
+
+    // normal and "instance" registration are decently close to each other, don't exercise all cases here
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count_instance,
+            &ctx));
+
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
+            esp_event_post_to(loop_fix.loop, ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, NULL, 0, portMAX_DELAY));
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
+            esp_event_post_to(loop_fix.loop, s_test_base1, ESP_EVENT_ANY_ID, NULL, 0, portMAX_DELAY));
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
+            esp_event_post_to(loop_fix.loop, ESP_EVENT_ANY_BASE, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(0, count);
+    TEST_ASSERT_EQUAL(0, count_instance);
+}
+
+TEST_CASE("posting to loop with multiple registrations at every level", "[event][linux]") {
+    EV_LoopFix loop_fix;
+    int count_loop = 0;
+    int count_loop_inst = 0;
+    int count_base = 0;
+    int count_base_inst = 0;
+    int count_id = 0; // without instance, no more than one registration for a specific event ID
+    int count_id_inst_1 = 0;
+    int count_id_inst_2 = 0;
+    esp_event_handler_instance_t loop_ctx;
+    esp_event_handler_instance_t base_ctx;
+    esp_event_handler_instance_t id_ctx_1;
+    esp_event_handler_instance_t id_ctx_2;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count_loop));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count_base));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &count_id));
+
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count_loop_inst,
+            &loop_ctx));
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count_base_inst,
+            &base_ctx));
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count_id_inst_1,
+            &id_ctx_1));
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &count_id_inst_2,
+            &id_ctx_2));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(count_loop, 1);
+    TEST_ASSERT_EQUAL(count_loop_inst, 1);
+    TEST_ASSERT_EQUAL(count_base, 1);
+    TEST_ASSERT_EQUAL(count_base_inst, 1);
+    TEST_ASSERT_EQUAL(count_id, 1);
+    TEST_ASSERT_EQUAL(count_id_inst_1, 1);
+    TEST_ASSERT_EQUAL(count_id_inst_2, 1);
+}
+
+TEST_CASE("unregistering ANY_BASE and specific ID fails", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    esp_event_handler_instance_t handler_inst = (esp_event_handler_instance_t) 1; // avoid triggering NULL check
+
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_handler_unregister_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc));
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_handler_instance_unregister_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            TEST_EVENT_BASE1_EV1,
+            handler_inst));
+}
+
+TEST_CASE("unregistering NULL instance fails", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    esp_event_handler_instance_t handler_inst = NULL;
+
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_handler_instance_unregister_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            handler_inst));
+}
+
+TEST_CASE("unregistered handler is not executed", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    int loop_count = 0;
+    int base_count = 0;
+    int id_count = 0;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &loop_count));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &base_count));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &id_count));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(loop_count, 1);
+    TEST_ASSERT_EQUAL(base_count, 1);
+    TEST_ASSERT_EQUAL(id_count, 1);
+
+    TEST_ESP_OK(esp_event_handler_unregister_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc));
+    TEST_ESP_OK(esp_event_handler_unregister_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc));
+    TEST_ESP_OK(esp_event_handler_unregister_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc));
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(loop_count, 1);
+    TEST_ASSERT_EQUAL(base_count, 1);
+    TEST_ASSERT_EQUAL(id_count, 1);
+}
+
+TEST_CASE("unregistered handler instance is not executed", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    int loop_count = 0;
+    int base_count = 0;
+    int id_count = 0;
+    esp_event_handler_instance_t loop_handler_inst;
+    esp_event_handler_instance_t base_handler_inst;
+    esp_event_handler_instance_t id_handler_inst;
+
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &loop_count,
+            &loop_handler_inst));
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &base_count,
+            &base_handler_inst));
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &id_count,
+            &id_handler_inst));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(loop_count, 1);
+    TEST_ASSERT_EQUAL(base_count, 1);
+    TEST_ASSERT_EQUAL(id_count, 1);
+
+    TEST_ESP_OK(esp_event_handler_instance_unregister_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            loop_handler_inst));
+    TEST_ESP_OK(esp_event_handler_instance_unregister_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            base_handler_inst));
+    TEST_ESP_OK(esp_event_handler_instance_unregister_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            id_handler_inst));
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(loop_count, 1);
+    TEST_ASSERT_EQUAL(base_count, 1);
+    TEST_ASSERT_EQUAL(id_count, 1);
+}
+
+TEST_CASE("unregistering handler does not influence other handlers", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    int unregister_count = 0;
+    int different_id_count = 0;
+    int different_base_count = 0;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &unregister_count));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base2,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &different_id_count));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV2,
+            test_handler_inc,
+            &different_base_count));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
+    for (size_t i = 0; i < 3; i++) {
+        TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+    }
+
+    TEST_ASSERT_EQUAL(unregister_count, 1);
+    TEST_ASSERT_EQUAL(different_id_count, 1);
+    TEST_ASSERT_EQUAL(different_base_count, 1);
+
+    TEST_ESP_OK(esp_event_handler_unregister_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc));
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
+    for (size_t i = 0; i < 3; i++) {
+        TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+    }
+
+    TEST_ASSERT_EQUAL(unregister_count, 1);
+    TEST_ASSERT_EQUAL(different_id_count, 2);
+    TEST_ASSERT_EQUAL(different_base_count, 2);
+}
+
+TEST_CASE("unregistering ESP_EVENT_ANY_ID does not affect other handlers with same base", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    int any_id_count = 0;
+    int specific_id_count = 0;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &any_id_count));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_inc,
+            &specific_id_count));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(any_id_count, 1);
+    TEST_ASSERT_EQUAL(specific_id_count, 1);
+
+    TEST_ESP_OK(esp_event_handler_unregister_with(loop_fix.loop, s_test_base1, ESP_EVENT_ANY_ID, test_handler_inc));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(any_id_count, 1);
+    TEST_ASSERT_EQUAL(specific_id_count, 2);
+}
+
+TEST_CASE("unregistering ESP_EVENT_ANY_BASE does not affect handlers with specific base", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    int any_base_count = 0;
+    int any_id_count = 0;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &any_base_count));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc,
+            &any_id_count));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(any_base_count, 1);
+    TEST_ASSERT_EQUAL(any_id_count, 1);
+
+    TEST_ESP_OK(esp_event_handler_unregister_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_handler_inc));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(any_base_count, 1);
+    TEST_ASSERT_EQUAL(any_id_count, 2);
+}
+
+typedef struct {
+    esp_event_handler_instance_t context;
+    esp_event_loop_handle_t loop;
+    int count;
+} unregister_test_data_t;
+
+static void test_handler_unregister_itself(void* event_handler_arg,
+            esp_event_base_t event_base,
+            int32_t event_id,
+            void* event_data)
+{
+    unregister_test_data_t *test_data = (unregister_test_data_t*) event_handler_arg;
+
+    (test_data->count)++;
+
+    // Unregister this handler for this event
+    TEST_ESP_OK(esp_event_handler_unregister_with(test_data->loop,
+            event_base,
+            event_id,
+            test_handler_unregister_itself));
+}
+
+TEST_CASE("handler can unregister itself", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+
+    unregister_test_data_t test_data = {
+        .context = NULL,
+        .loop = loop_fix.loop,
+        .count = 0,
+    };
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_unregister_itself, &test_data));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(1, test_data.count);
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            &loop_fix.loop,
+            sizeof(&loop_fix.loop),
+            portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, pdMS_TO_TICKS(10)));
+
+    TEST_ASSERT_EQUAL(1, test_data.count);
+}
+
+static void test_handler_instance_unregister_itself(void* event_handler_arg,
+            esp_event_base_t event_base,
+            int32_t event_id,
+            void* event_data)
+{
+    unregister_test_data_t *test_data = (unregister_test_data_t*) event_handler_arg;
+
+    (test_data->count)++;
+
+    // Unregister this handler for this event
+    TEST_ESP_OK(esp_event_handler_instance_unregister_with(test_data->loop, event_base, event_id, test_data->context));
+}
+
+TEST_CASE("handler instance can unregister itself", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+
+    unregister_test_data_t test_data = {
+        .context = NULL,
+        .loop = loop_fix.loop,
+        .count = 0,
+    };
+
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_instance_unregister_itself,
+            &test_data,
+            &(test_data.context)));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(1, test_data.count);
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            &loop_fix.loop,
+            sizeof(&loop_fix.loop),
+            portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(1, test_data.count);
+}
+
+typedef struct {
+    size_t counter;
+    size_t test_data[4];
+} ordered_dispatch_test_data_t;
+
+static void test_event_ordered_dispatch(void* event_handler_arg,
+        esp_event_base_t event_base,
+        int32_t event_id,
+        void* event_data,
+        size_t handler_index)
+{
+    ordered_dispatch_test_data_t *test_data = (ordered_dispatch_test_data_t*) event_handler_arg;
+    test_data->test_data[handler_index] = test_data->counter;
+    (test_data->counter)++;
+}
+
+static void test_event_ordered_dispatch_0(void* event_handler_arg,
+        esp_event_base_t event_base,
+        int32_t event_id,
+        void* event_data)
+{
+    test_event_ordered_dispatch(event_handler_arg, event_base, event_id, event_data, 0);
+}
+
+static void test_event_ordered_dispatch_1(void* event_handler_arg,
+        esp_event_base_t event_base,
+        int32_t event_id,
+        void* event_data)
+{
+    test_event_ordered_dispatch(event_handler_arg, event_base, event_id, event_data, 1);
+}
+
+static void test_event_ordered_dispatch_2(void* event_handler_arg,
+        esp_event_base_t event_base,
+        int32_t event_id,
+        void* event_data)
+{
+    test_event_ordered_dispatch(event_handler_arg, event_base, event_id, event_data, 2);
+}
+
+static void test_event_ordered_dispatch_3(void* event_handler_arg,
+        esp_event_base_t event_base,
+        int32_t event_id,
+        void* event_data)
+{
+    test_event_ordered_dispatch(event_handler_arg, event_base, event_id, event_data, 3);
+}
+
+TEST_CASE("events handlers for specific ID are dispatched in the order they are registered", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+
+    ordered_dispatch_test_data_t test_data = {
+        .counter = 0,
+        .test_data = {},
+    };
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_event_ordered_dispatch_0,
+            &test_data));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_event_ordered_dispatch_1,
+            &test_data));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_event_ordered_dispatch_2,
+            &test_data));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_event_ordered_dispatch_3,
+            &test_data));
+
+    esp_event_dump(stdout);
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    // Expected data executing the posts above
+    size_t ref_arr[4];
+    for (size_t i = 0; i < 4; i++) {
+        ref_arr[i] = i;
+    }
+
+    TEST_ASSERT_EQUAL_INT_ARRAY(ref_arr, test_data.test_data, 4);
+}
+
+TEST_CASE("events handlers for specific base are dispatched in the order they are registered", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+
+    ordered_dispatch_test_data_t test_data = {
+        .counter = 0,
+        .test_data = {},
+    };
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_event_ordered_dispatch_0,
+            &test_data));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_event_ordered_dispatch_1,
+            &test_data));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_event_ordered_dispatch_2,
+            &test_data));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            ESP_EVENT_ANY_ID,
+            test_event_ordered_dispatch_3,
+            &test_data));
+
+    esp_event_dump(stdout);
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    // Expected data executing the posts above
+    size_t ref_arr[4];
+    for (size_t i = 0; i < 4; i++) {
+        ref_arr[i] = i;
+    }
+
+    TEST_ASSERT_EQUAL_INT_ARRAY(ref_arr, test_data.test_data, 4);
+}
+
+TEST_CASE("events handlers for any base are dispatched in the order they are registered", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+
+    ordered_dispatch_test_data_t test_data = {
+        .counter = 0,
+        .test_data = {},
+    };
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_event_ordered_dispatch_0,
+            &test_data));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_event_ordered_dispatch_1,
+            &test_data));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_event_ordered_dispatch_2,
+            &test_data));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            ESP_EVENT_ANY_BASE,
+            ESP_EVENT_ANY_ID,
+            test_event_ordered_dispatch_3,
+            &test_data));
+
+    esp_event_dump(stdout);
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    // Expected data executing the posts above
+    size_t ref_arr[4];
+    for (size_t i = 0; i < 4; i++) {
+        ref_arr[i] = i;
+    }
+
+    TEST_ASSERT_EQUAL_INT_ARRAY(ref_arr, test_data.test_data, 4);
+}
+
+static void test_create_loop_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data)
+{
+    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
+
+    if (id == TEST_EVENT_BASE1_EV1) {
+        TEST_ESP_OK(esp_event_loop_create(&loop_args, (esp_event_loop_handle_t*) handler_args));
+    } else {
+        TEST_ESP_OK(esp_event_loop_delete(*((esp_event_loop_handle_t*) handler_args)));
+    }
+}
+
+TEST_CASE("can create and delete loop from handler", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    esp_event_loop_handle_t test_loop;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_create_loop_handler,
+            &test_loop));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV2,
+            test_create_loop_handler,
+            &test_loop));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+}
+
+typedef struct {
+    esp_event_loop_handle_t loop;
+    int count;
+} posting_handler_data_t;
+
+static void test_handler_post(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg)
+{
+    posting_handler_data_t* data = (posting_handler_data_t*) handler_arg;
+    TEST_ESP_OK(esp_event_post_to(data->loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
+    data->count++;
+}
+
+TEST_CASE("can post to loop from handler", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+
+    posting_handler_data_t arg = {
+        .loop = loop_fix.loop,
+        .count = 0,
+    };
+    int secondary_handler_count = 0;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_post,
+            &arg));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV2,
+            test_handler_inc,
+            &secondary_handler_count));
+
+    // Test that a handler can post to a different loop while there is still slots on the queue
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(1, arg.count);
+    TEST_ASSERT_EQUAL(1, secondary_handler_count);
+}
+
+TEST_CASE("can post to loop from handler instance", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    esp_event_handler_instance_t ctx;
+
+    posting_handler_data_t arg = {
+        .loop = loop_fix.loop,
+        .count = 0,
+    };
+    int secondary_handler_count = 0;
+
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_post,
+            &arg,
+            &ctx));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV2,
+            test_handler_inc,
+            &secondary_handler_count));
+
+    // Test that a handler can post to a different loop while there is still slots on the queue
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(1, arg.count);
+    TEST_ASSERT_EQUAL(1, secondary_handler_count);
+}
+
+static void test_handler_post_timeout(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg)
+{
+    posting_handler_data_t* data = (posting_handler_data_t*) handler_arg;
+    TEST_ESP_OK(esp_event_post_to(data->loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
+    TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(data->loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV2,
+            NULL,
+            0,
+            1));
+    data->count++;
+}
+
+TEST_CASE("posting to loop from handler times out", "[event][linux]")
+{
+    EV_LoopFix loop_fix(1);
+
+    posting_handler_data_t arg = {
+        .loop = loop_fix.loop,
+        .count = 0,
+    };
+    int secondary_handler_count = 0;
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_post_timeout,
+            &arg));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV2,
+            test_handler_inc,
+            &secondary_handler_count));
+
+    // Test that a handler can post to a different loop while there is still slots on the queue
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(1, arg.count);
+    TEST_ASSERT_EQUAL(1, secondary_handler_count);
+}
+
+static void test_handler_take_sem(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg)
+{
+    SemaphoreHandle_t sem = (SemaphoreHandle_t) handler_arg;
+    TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(sem, portMAX_DELAY));
+}
+
+static void test_handler_give_sem(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg)
+{
+    SemaphoreHandle_t sem = (SemaphoreHandle_t) handler_arg;
+    TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreGive(sem));
+}
+
+TEST_CASE("can post while handler is executing - dedicated task", "[event][linux]")
+{
+    EV_LoopFix loop_fix(1, "loop_task");
+
+    SemaphoreHandle_t ev1_sem = xSemaphoreCreateBinary();
+    SemaphoreHandle_t ev2_sem = xSemaphoreCreateBinary();
+    TEST_ASSERT(ev1_sem);
+    TEST_ASSERT(ev2_sem);
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_take_sem,
+            ev1_sem));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV2,
+            test_handler_give_sem,
+            ev2_sem));
+
+    // Trigger waiting by sending first event
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+
+    // Check that event can be posted while handler is running (waiting on the semaphore)
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV2, NULL, 0, portMAX_DELAY));
+
+    // Now the event queue has to be full, expect timeout
+    TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV2,
+            NULL,
+            0,
+            1));
+
+    // Run all events
+    xSemaphoreGive(ev1_sem);
+
+    // verify that test_handler_give_sem() has run, too
+    TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(ev2_sem, portMAX_DELAY));
+
+    vSemaphoreDelete(ev1_sem);
+    vSemaphoreDelete(ev2_sem);
+}
+
+static void test_post_from_handler_loop_task(void* args)
+{
+    esp_event_loop_handle_t event_loop = (esp_event_loop_handle_t) args;
+
+    TEST_ESP_OK(esp_event_loop_run(event_loop, portMAX_DELAY));
+}
+
+static void never_run(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg)
+{
+    TEST_ASSERT(false);
+}
+
+TEST_CASE("can not post while handler is executing - no dedicated task", "[event][linux]")
+{
+    EV_LoopFix loop_fix(1);
+
+    SemaphoreHandle_t sem = xSemaphoreCreateBinary();
+    SemaphoreHandle_t wait_done_sem = xSemaphoreCreateBinary();
+    TEST_ASSERT(sem);
+
+    TaskHandle_t mtask;
+    xTaskCreate(test_post_from_handler_loop_task,
+            "task",
+            2584,
+            (void*) loop_fix.loop,
+            uxTaskPriorityGet(NULL) + 1,
+            &mtask);
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_take_sem,
+            sem));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV2,
+            never_run,
+            NULL));
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base2,
+            TEST_EVENT_BASE1_EV1,
+            test_handler_give_sem,
+            wait_done_sem));
+
+    // Trigger waiting by sending first event
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+
+    // Wait until semaphore is actually taken, which means handler is running and blocked
+    while (xSemaphoreTake(sem, 1) == pdTRUE) {
+        xSemaphoreGive(sem);
+        vTaskDelay(2);
+    }
+
+    // For loop without tasks, posting is more restrictive. Posting should wait until execution of handler finishes
+    TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, esp_event_post_to(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV2,
+            NULL,
+            0,
+            1));
+
+    // Being running all events
+    xSemaphoreGive(sem);
+
+    // wait until all events have run
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base2, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    xSemaphoreTake(wait_done_sem, portMAX_DELAY);
+
+    // Clean up
+    vTaskDelete(mtask);
+    vSemaphoreDelete(wait_done_sem);
+    vSemaphoreDelete(sem);
+}
+
+struct EventData {
+    EventData(size_t expected_event_data_size) : expected_size(expected_event_data_size) { }
+    constexpr static size_t MAX_SIZE = 16;
+    void *event_arg; // only test for nullptr!
+    size_t expected_size;
+    uint8_t event_data[MAX_SIZE];
+};
+
+static void save_ev_data(void* handler_arg, esp_event_base_t base, int32_t id, void* event_arg)
+{
+    EventData *ev_data = (EventData *) handler_arg;
+    ev_data->event_arg = event_arg;
+    if (ev_data->expected_size > 0) {
+        memcpy(ev_data->event_data, event_arg, ev_data->expected_size);
+    }
+}
+
+TEST_CASE("event data null", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    EventData saved_ev_data(0);
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            save_ev_data,
+            &saved_ev_data));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_EQUAL(NULL, saved_ev_data.event_arg);
+}
+
+TEST_CASE("event data one nonzero byte", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    uint8_t ev_data = 47;
+    EventData saved_ev_data(1);
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            save_ev_data,
+            &saved_ev_data));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            &ev_data,
+            sizeof(ev_data),
+            portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_NOT_EQUAL(NULL, saved_ev_data.event_arg);
+    TEST_ASSERT_EQUAL(47, saved_ev_data.event_data[0]);
+}
+
+TEST_CASE("event data one zero byte", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    uint8_t ev_data = 0;
+    EventData saved_ev_data(1);
+    saved_ev_data.event_data[0] = 47;
+
+    esp_event_handler_instance_t ctx;
+
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            s_test_base2,
+            TEST_EVENT_BASE1_EV1,
+            save_ev_data,
+            &saved_ev_data,
+            &ctx));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop,
+            s_test_base2,
+            TEST_EVENT_BASE1_EV1,
+            &ev_data,
+            sizeof(ev_data),
+            portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_NOT_EQUAL(NULL, saved_ev_data.event_arg);
+    TEST_ASSERT_EQUAL(0, saved_ev_data.event_data[0]);
+}
+
+TEST_CASE("event data many bytes", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    uint8_t ev_data[EventData::MAX_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+    EventData saved_ev_data(16);
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            save_ev_data,
+            &saved_ev_data));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            &ev_data,
+            sizeof(ev_data),
+            portMAX_DELAY));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_NOT_EQUAL(NULL, saved_ev_data.event_arg);
+    TEST_ASSERT_EQUAL_HEX8_ARRAY(ev_data, saved_ev_data.event_data, EventData::MAX_SIZE);
+}
+
+TEST_CASE("event data one byte is copied on post", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    uint8_t ev_data = 47;
+    EventData saved_ev_data(1);
+
+    esp_event_handler_instance_t ctx;
+
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop_fix.loop,
+            s_test_base2,
+            TEST_EVENT_BASE1_EV1,
+            save_ev_data,
+            &saved_ev_data,
+            &ctx));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop,
+            s_test_base2,
+            TEST_EVENT_BASE1_EV1,
+            &ev_data,
+            sizeof(ev_data),
+            portMAX_DELAY));
+    ev_data = 42;
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_NOT_EQUAL(NULL, saved_ev_data.event_arg);
+    TEST_ASSERT_EQUAL(47, saved_ev_data.event_data[0]);
+}
+
+TEST_CASE("event data many bytes are copied on post", "[event][linux]")
+{
+    EV_LoopFix loop_fix;
+    uint8_t ev_data[EventData::MAX_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+    uint8_t ev_data_expected[EventData::MAX_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+    EventData saved_ev_data(16);
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            save_ev_data,
+            &saved_ev_data));
+
+    TEST_ESP_OK(esp_event_post_to(loop_fix.loop,
+            s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            &ev_data,
+            sizeof(ev_data),
+            portMAX_DELAY));
+    memset(ev_data, 0, sizeof(ev_data));
+    TEST_ESP_OK(esp_event_loop_run(loop_fix.loop, ZERO_DELAY));
+
+    TEST_ASSERT_NOT_EQUAL(NULL, saved_ev_data.event_arg);
+    TEST_ASSERT_EQUAL_HEX8_ARRAY(ev_data_expected, saved_ev_data.event_data, EventData::MAX_SIZE);
+}
+
+TEST_CASE("default loop: registering fails on uninitialized default loop", "[event][default][linux]")
+{
+    esp_event_handler_instance_t instance;
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_event_handler_instance_register(s_test_base1,
+            TEST_EVENT_BASE1_EV1,
+            never_run,
+            NULL,
+            &instance));
+}
+
+TEST_CASE("default loop: can create and delete loop", "[event][default][linux]")
+{
+    TEST_ESP_OK(esp_event_loop_create_default());
+
+    TEST_ESP_OK(esp_event_loop_delete_default());
+}
+
+TEST_CASE("default loop: registering/unregistering event works", "[event][default][linux]")
+{
+    TEST_ESP_OK(esp_event_loop_create_default());
+
+    if (TEST_PROTECT()) {
+        SemaphoreHandle_t waiter = xSemaphoreCreateBinary();
+
+        esp_event_handler_instance_t instance = NULL;
+        TEST_ESP_OK(esp_event_handler_instance_register(s_test_base1,
+                TEST_EVENT_BASE1_EV1,
+                test_handler_give_sem,
+                waiter,
+                &instance));
+        TEST_ASSERT(instance);
+        TEST_ESP_OK(esp_event_post(s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+
+        TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(waiter, pdMS_TO_TICKS(50)));
+
+        TEST_ESP_OK(esp_event_handler_instance_unregister(s_test_base1,
+                TEST_EVENT_BASE1_EV1,
+                instance));
+        TEST_ESP_OK(esp_event_post(s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+
+        TEST_ASSERT_EQUAL(pdFALSE, xSemaphoreTake(waiter, pdMS_TO_TICKS(50)));
+
+        vSemaphoreDelete(waiter);
+    }
+
+    TEST_ESP_OK(esp_event_loop_delete_default());
+}
+
+TEST_CASE("default event loop: registering event handler instance without instance context works", "[event][default][linux]") {
+    TEST_ESP_OK(esp_event_loop_create_default());
+
+    if (TEST_PROTECT()) {
+        SemaphoreHandle_t waiter = xSemaphoreCreateBinary();
+
+        TEST_ESP_OK(esp_event_handler_instance_register(ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, test_handler_give_sem, waiter, NULL));
+        TEST_ESP_OK(esp_event_post(s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+
+        TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(waiter, pdMS_TO_TICKS(50)));
+
+        vSemaphoreDelete(waiter);
+    }
+
+    TEST_ESP_OK(esp_event_loop_delete_default());
+}

+ 4 - 27
components/esp_event/test_apps/main/test_event_main.c

@@ -6,32 +6,15 @@
 
 #include "unity.h"
 #include "unity_test_runner.h"
-#include "esp_heap_caps.h"
+#include "unity_test_utils_memory.h"
 #include "esp_log.h"
 #ifdef CONFIG_HEAP_TRACING
 #include "memory_checks.h"
 #include "esp_heap_trace.h"
 #endif
 
-#define TEST_MEMORY_LEAK_THRESHOLD_DEFAULT 0
-static int leak_threshold = TEST_MEMORY_LEAK_THRESHOLD_DEFAULT;
-
-void set_leak_threshold(int threshold)
-{
-    leak_threshold = threshold;
-}
-
-static size_t before_free_8bit;
-static size_t before_free_32bit;
 static const char* TAG = "event_test_app";
 
-static void check_leak(size_t before_free, size_t after_free, const char *type)
-{
-    ssize_t delta = after_free - before_free;
-    printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
-    TEST_ASSERT_MESSAGE(delta >= leak_threshold, "memory leak");
-}
-
 void setUp(void)
 {
     // If heap tracing is enabled in kconfig, leak trace the test
@@ -40,10 +23,8 @@ void setUp(void)
     heap_trace_start(HEAP_TRACE_LEAKS);
 #endif
 
-    leak_threshold = TEST_MEMORY_LEAK_THRESHOLD_DEFAULT;
-
-    before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
-    before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
+    unity_utils_set_leak_level(0);
+    unity_utils_record_free_mem();
 }
 
 void tearDown(void)
@@ -53,11 +34,7 @@ void tearDown(void)
     heap_trace_dump();
 #endif
 
-    size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
-    size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
-    check_leak(before_free_8bit, after_free_8bit, "8BIT");
-    check_leak(before_free_32bit, after_free_32bit, "32BIT");
-
+    unity_utils_evaluate_leaks();
 }
 
 void app_main(void)

+ 765 - 0
components/esp_event/test_apps/main/test_event_target.c

@@ -0,0 +1,765 @@
+/*
+ * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Unlicense OR CC0-1.0
+ */
+
+/* This file contains tests only runnable on the chip targets */
+
+#include <stdbool.h>
+#include <string.h>
+
+#include "esp_event.h"
+#include "sdkconfig.h"
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_log.h"
+#include "driver/gptimer.h"
+
+#include "esp_event.h"
+#include "esp_event_private.h"
+#include "esp_event_internal.h"
+
+#include "esp_heap_caps.h"
+#include "esp_timer.h"
+
+#include "sdkconfig.h"
+#include "unity.h"
+#include "unity_test_utils_memory.h"
+
+#include "test_utils.h"
+
+
+static const char* TAG = "test_event";
+
+static const TickType_t ZERO_DELAY = 0;
+
+#define TEST_CONFIG_ITEMS_TO_REGISTER        5
+#define TEST_CONFIG_TASKS_TO_SPAWN           2
+_Static_assert(TEST_CONFIG_TASKS_TO_SPAWN >= 2); // some tests test simultaneous posting of events, etc.
+
+/* Time used in tearDown function to wait for cleaning up memory in background tasks */
+#define TEST_CONFIG_TEARDOWN_WAIT           30
+
+typedef struct {
+    void* data;
+    SemaphoreHandle_t start;
+    SemaphoreHandle_t done;
+} task_arg_t;
+
+typedef struct {
+    esp_event_base_t base;
+    int32_t id;
+    esp_event_handler_t* handles;
+    int32_t num;
+    esp_event_loop_handle_t loop;
+    bool is_registration;
+} handler_registration_data_t;
+
+typedef struct {
+    esp_event_base_t base;
+    int32_t id;
+    esp_event_loop_handle_t loop;
+    int32_t num;
+} post_event_data_t;
+
+typedef struct {
+    int performed;
+    int expected;
+    SemaphoreHandle_t done;
+} performance_data_t;
+
+typedef struct {
+    void* data;
+    SemaphoreHandle_t mutex;
+} simple_arg_t;
+
+ESP_EVENT_DECLARE_BASE(s_test_base1);
+
+enum {
+    TEST_EVENT_BASE1_EV1,
+    TEST_EVENT_BASE1_MAX
+};
+
+static BaseType_t get_other_core(void)
+{
+    return (xPortGetCoreID() + 1) % portNUM_PROCESSORS;
+}
+
+static esp_event_loop_args_t test_event_get_default_loop_args(void)
+{
+    esp_event_loop_args_t loop_config = {
+        .queue_size = CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE,
+        .task_name = "loop",
+        .task_priority = uxTaskPriorityGet(NULL),
+        .task_stack_size = 2048,
+        .task_core_id = get_other_core()
+    };
+
+    return loop_config;
+}
+
+static void test_event_simple_handler(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
+{
+    if (!event_handler_arg) {
+        return;
+    }
+    simple_arg_t* arg = (simple_arg_t*) event_handler_arg;
+    xSemaphoreTake(arg->mutex, portMAX_DELAY);
+
+    int* count = (int*) arg->data;
+
+    if (event_data == NULL) {
+        (*count)++;
+    } else {
+        (*count) += *((int*) event_data);
+    }
+
+    xSemaphoreGive(arg->mutex);
+}
+
+static void test_event_performance_handler(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
+{
+    performance_data_t* data = (performance_data_t*) event_handler_arg;
+
+    data->performed++;
+
+    if (data->performed >= data->expected) {
+        xSemaphoreGive(data->done);
+    }
+}
+
+static void test_event_post_task(void* args)
+{
+    task_arg_t* arg = (task_arg_t*) args;
+    post_event_data_t* data = arg->data;
+
+    xSemaphoreTake(arg->start, portMAX_DELAY);
+
+    for (int i = 0; i < data->num; i++) {
+        TEST_ESP_OK(esp_event_post_to(data->loop, data->base, data->id, NULL, 0, portMAX_DELAY));
+        vTaskDelay(1);
+    }
+
+    xSemaphoreGive(arg->done);
+
+    vTaskDelete(NULL);
+}
+
+static void test_event_simple_handler_registration_task(void* args)
+{
+    task_arg_t* arg = (task_arg_t*) args;
+    handler_registration_data_t* data = (handler_registration_data_t*) arg->data;
+
+    xSemaphoreTake(arg->start, portMAX_DELAY);
+
+    for(int i = 0; i < data->num; i++) {
+        if (data->is_registration) {
+            TEST_ESP_OK(esp_event_handler_register_with(data->loop, data->base, data->id, data->handles[i], NULL));
+        } else {
+            TEST_ESP_OK(esp_event_handler_unregister_with(data->loop, data->base, data->id, data->handles[i]));
+        }
+        vTaskDelay(1);
+    }
+
+    xSemaphoreGive(arg->done);
+
+    vTaskDelete(NULL);
+}
+
+// Ignore this test on QEMU for now since it relies on esp_timer which is based on the host run time on ESP32-QEMU
+TEST_CASE("can exit running loop at approximately the set amount of time", "[event][qemu-ignore]")
+{
+    /* this test aims to verify that running loop does not block indefinitely in cases where
+     * events are posted frequently */
+
+    esp_event_loop_handle_t loop;
+    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
+
+    loop_args.task_name = NULL;
+    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
+
+    performance_data_t handler_data = {
+        .performed = 0,
+        .expected = INT32_MAX,
+        .done = xSemaphoreCreateBinary()
+    };
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_performance_handler, &handler_data));
+
+    post_event_data_t post_event_data = {
+        .base = s_test_base1,
+        .id = TEST_EVENT_BASE1_EV1,
+        .loop = loop,
+        .num  = INT32_MAX
+    };
+
+    task_arg_t post_event_arg = {
+        .data = &post_event_data,
+        .done = xSemaphoreCreateBinary(),
+        .start = xSemaphoreCreateBinary()
+    };
+
+    TaskHandle_t post_task;
+    xTaskCreatePinnedToCore(test_event_post_task, "post", 2048, &post_event_arg, uxTaskPriorityGet(NULL), &post_task, get_other_core());
+
+    int runtime_ms = 10;
+    int runtime_us = runtime_ms * 1000;
+
+    int64_t start, diff;
+    start = esp_timer_get_time();
+
+    xSemaphoreGive(post_event_arg.start);
+
+    // Run the loop for the runtime_ms set amount of time, regardless of whether events
+    // are still being posted to the loop.
+    TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(runtime_ms)));
+
+    diff = (esp_timer_get_time() - start);
+
+    // Threshold is 25 percent.
+    TEST_ASSERT_LESS_THAN_INT(runtime_us * 1.25f, diff);
+
+    // Verify that the post task still continues
+    TEST_ASSERT_NOT_EQUAL(pdTRUE, xSemaphoreTake(post_event_arg.done, pdMS_TO_TICKS(10)));
+
+    vSemaphoreDelete(post_event_arg.done);
+    vSemaphoreDelete(post_event_arg.start);
+    vSemaphoreDelete(handler_data.done);
+    vTaskDelete(post_task);
+
+    TEST_ESP_OK(esp_event_loop_delete(loop));
+
+    vTaskDelay(pdMS_TO_TICKS(TEST_CONFIG_TEARDOWN_WAIT));
+}
+
+TEST_CASE("can register/unregister handlers simultaneously", "[event]")
+{
+    /* this test aims to verify that the event handlers list remains consistent despite
+     * simultaneous access by differenct tasks */
+
+    const char* base = "base";
+    int32_t id = 0;
+
+    esp_event_loop_handle_t loop;
+    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
+
+    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
+
+    ESP_LOGI(TAG, "registering handlers");
+
+    handler_registration_data_t* registration_data = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*registration_data));
+    task_arg_t* registration_arg = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*registration_arg));
+
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        registration_data[i].base = base;
+        registration_data[i].id = id;
+        registration_data[i].loop = loop;
+        registration_data[i].handles = calloc(TEST_CONFIG_ITEMS_TO_REGISTER, sizeof(esp_event_handler_t));
+        registration_data[i].num = TEST_CONFIG_ITEMS_TO_REGISTER;
+        registration_data[i].is_registration = true;
+
+        for (int j = 0; j < TEST_CONFIG_ITEMS_TO_REGISTER; j++) {
+            registration_data[i].handles[j] = (void*) (i * TEST_CONFIG_ITEMS_TO_REGISTER) + (j + TEST_CONFIG_ITEMS_TO_REGISTER);
+        }
+
+        registration_arg[i].start = xSemaphoreCreateBinary();
+        registration_arg[i].done = xSemaphoreCreateBinary();
+        registration_arg[i].data = &registration_data[i];
+
+        xTaskCreatePinnedToCore(test_event_simple_handler_registration_task, "register", 2048, &registration_arg[i], uxTaskPriorityGet(NULL), NULL, i % portNUM_PROCESSORS);
+    }
+
+    // Give the semaphores to the spawned registration task
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        xSemaphoreGive(registration_arg[i].start);
+    }
+
+    // Take the same semaphores in order to proceed
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        xSemaphoreTake(registration_arg[i].done, portMAX_DELAY);
+    }
+
+    ESP_LOGI(TAG, "checking consistency of handlers list");
+
+    // Check consistency of events list
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        for (int j = 0; j < TEST_CONFIG_ITEMS_TO_REGISTER; j++) {
+            TEST_ASSERT_TRUE(esp_event_is_handler_registered(loop, base, id, registration_data[i].handles[j]));
+        }
+    }
+
+    ESP_LOGI(TAG, "unregistering handlers");
+
+    /* Test if tasks can unregister simultaneously */
+
+    // Unregister registered events
+    handler_registration_data_t* unregistration_data = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*unregistration_data));
+    task_arg_t* unregistration_arg = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*unregistration_arg));
+
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        unregistration_data[i].base = base;
+        unregistration_data[i].id = id;
+        unregistration_data[i].loop = loop;
+        unregistration_data[i].handles = calloc(TEST_CONFIG_ITEMS_TO_REGISTER, sizeof(esp_event_handler_t));
+        unregistration_data[i].num = TEST_CONFIG_ITEMS_TO_REGISTER;
+        unregistration_data[i].is_registration = false;
+
+        memcpy(unregistration_data[i].handles, registration_data[i].handles, TEST_CONFIG_ITEMS_TO_REGISTER * sizeof(esp_event_handler_t));
+
+        unregistration_arg[i].data = &unregistration_data[i];
+        unregistration_arg[i].start = xSemaphoreCreateBinary();
+        unregistration_arg[i].done = xSemaphoreCreateBinary();
+
+        xTaskCreatePinnedToCore(test_event_simple_handler_registration_task, "unregister", 2048, &unregistration_arg[i], uxTaskPriorityGet(NULL), NULL, i % portNUM_PROCESSORS);
+    }
+
+    // Give the semaphores to the spawned unregistration task
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        xSemaphoreGive(unregistration_arg[i].start);
+    }
+
+    // Take the same semaphores in order to proceed
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        xSemaphoreTake(unregistration_arg[i].done, portMAX_DELAY);
+    }
+
+    ESP_LOGI(TAG, "checking consistency of handlers list");
+
+    // Check consistency of events list
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        for (int j = 0; j < TEST_CONFIG_ITEMS_TO_REGISTER; j++) {
+            TEST_ASSERT_FALSE(esp_event_is_handler_registered(loop, base, id, registration_data[i].handles[j]));
+        }
+    }
+
+    // Do cleanup
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        free(registration_data[i].handles);
+        vSemaphoreDelete(registration_arg[i].start);
+        vSemaphoreDelete(registration_arg[i].done);
+
+        free(unregistration_data[i].handles);
+        vSemaphoreDelete(unregistration_arg[i].start);
+        vSemaphoreDelete(unregistration_arg[i].done);
+    }
+
+    free(registration_data);
+    free(unregistration_data);
+    free(registration_arg);
+    free(unregistration_arg);
+
+    TEST_ESP_OK(esp_event_loop_delete(loop));
+
+    vTaskDelay(pdMS_TO_TICKS(TEST_CONFIG_TEARDOWN_WAIT));
+}
+
+TEST_CASE("can post and run events simultaneously", "[event]")
+{
+    /* this test aims to verify that:
+     *  - multiple tasks can post to the queue simultaneously
+     *  - handlers recieve the appropriate handler arg and associated event data */
+
+    esp_event_loop_handle_t loop;
+
+    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
+
+    loop_args.task_name = NULL;
+    loop_args.queue_size = TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER;
+    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
+
+    int count = 0;
+
+    simple_arg_t arg = {
+        .data = &count,
+        .mutex = xSemaphoreCreateMutex()
+    };
+
+    TEST_ESP_OK(esp_event_handler_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg));
+
+    post_event_data_t* post_event_data = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*post_event_data));
+    task_arg_t* post_event_arg = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*post_event_arg));
+
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++)
+    {
+        post_event_data[i].base = s_test_base1;
+        post_event_data[i].id = TEST_EVENT_BASE1_EV1;
+        post_event_data[i].loop = loop;
+        post_event_data[i].num = TEST_CONFIG_ITEMS_TO_REGISTER;
+
+        post_event_arg[i].data = &post_event_data[i];
+        post_event_arg[i].start = xSemaphoreCreateBinary();
+        post_event_arg[i].done = xSemaphoreCreateBinary();
+
+        xTaskCreatePinnedToCore(test_event_post_task, "post", 2048, &post_event_arg[i], uxTaskPriorityGet(NULL), NULL, i % portNUM_PROCESSORS);
+    }
+
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        xSemaphoreGive(post_event_arg[i].start);
+    }
+
+    // Execute some events as they are posted
+    for (int i = 0; i < (TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER) / 2; i++) {
+        TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
+    }
+
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        xSemaphoreTake(post_event_arg[i].done, portMAX_DELAY);
+    }
+
+    // Execute the rest
+    for (size_t i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER; i++) {
+        TEST_ESP_OK(esp_event_loop_run(loop, ZERO_DELAY));
+    }
+
+    TEST_ASSERT_EQUAL(TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER, count);
+
+    // Cleanup
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        vSemaphoreDelete(post_event_arg[i].start);
+        vSemaphoreDelete(post_event_arg[i].done);
+    }
+
+    free(post_event_data);
+    free(post_event_arg);
+
+    TEST_ESP_OK(esp_event_loop_delete(loop));
+
+    vSemaphoreDelete(arg.mutex);
+
+    vTaskDelay(pdMS_TO_TICKS(TEST_CONFIG_TEARDOWN_WAIT));
+}
+
+TEST_CASE("can post and run events simultaneously with instances", "[event]")
+{
+    /* this test aims to verify that:
+     *  - multiple tasks can post to the queue simultaneously
+     *  - handlers recieve the appropriate handler arg and associated event data */
+
+    esp_event_loop_handle_t loop;
+
+    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
+
+    loop_args.task_name = NULL;
+    loop_args.queue_size = TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER;
+    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
+
+    int count = 0;
+
+    simple_arg_t arg = {
+        .data = &count,
+        .mutex = xSemaphoreCreateMutex()
+    };
+
+    esp_event_handler_instance_t ctx;
+
+    TEST_ESP_OK(esp_event_handler_instance_register_with(loop, s_test_base1, TEST_EVENT_BASE1_EV1, test_event_simple_handler, &arg, &ctx));
+
+    post_event_data_t* post_event_data = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*post_event_data));
+    task_arg_t* post_event_arg = calloc(TEST_CONFIG_TASKS_TO_SPAWN, sizeof(*post_event_arg));
+
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++)
+    {
+        post_event_data[i].base = s_test_base1;
+        post_event_data[i].id = TEST_EVENT_BASE1_EV1;
+        post_event_data[i].loop = loop;
+        post_event_data[i].num = TEST_CONFIG_ITEMS_TO_REGISTER;
+
+        post_event_arg[i].data = &post_event_data[i];
+        post_event_arg[i].start = xSemaphoreCreateBinary();
+        post_event_arg[i].done = xSemaphoreCreateBinary();
+
+        xTaskCreatePinnedToCore(test_event_post_task, "post", 2048, &post_event_arg[i], uxTaskPriorityGet(NULL), NULL, i % portNUM_PROCESSORS);
+    }
+
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        xSemaphoreGive(post_event_arg[i].start);
+    }
+
+    // Execute some events as they are posted
+    for (int i = 0; i < (TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER) / 2; i++) {
+        TEST_ESP_OK(esp_event_loop_run(loop, pdMS_TO_TICKS(10)));
+    }
+
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        xSemaphoreTake(post_event_arg[i].done, portMAX_DELAY);
+    }
+
+    // Execute the rest, we use the maximum number of events because we don't know
+    // if any events have been dispatched before
+    for (size_t i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER; i++) {
+        TEST_ESP_OK(esp_event_loop_run(loop, ZERO_DELAY));
+    }
+
+    TEST_ASSERT_EQUAL(TEST_CONFIG_TASKS_TO_SPAWN * TEST_CONFIG_ITEMS_TO_REGISTER, count);
+
+    // Cleanup
+    for (int i = 0; i < TEST_CONFIG_TASKS_TO_SPAWN; i++) {
+        vSemaphoreDelete(post_event_arg[i].start);
+        vSemaphoreDelete(post_event_arg[i].done);
+    }
+
+    free(post_event_data);
+    free(post_event_arg);
+
+    TEST_ESP_OK(esp_event_loop_delete(loop));
+
+    vSemaphoreDelete(arg.mutex);
+
+    vTaskDelay(pdMS_TO_TICKS(TEST_CONFIG_TEARDOWN_WAIT));
+}
+
+static void loop_run_task(void* args)
+{
+    esp_event_loop_handle_t event_loop = (esp_event_loop_handle_t) args;
+
+    while(1) {
+        esp_event_loop_run(event_loop, portMAX_DELAY);
+    }
+}
+
+static void performance_test(bool dedicated_task)
+{
+    // rand() seems to do a one-time allocation. Call it here so that the memory it allocates
+    // is not counted as a leak.
+    unsigned int _rand __attribute__((unused)) = rand();
+
+    const char test_base[] = "qwertyuiopasdfghjklzxvbnmmnbvcxz";
+
+    #define TEST_CONFIG_BASES  (sizeof(test_base) - 1)
+    #define TEST_CONFIG_IDS    (TEST_CONFIG_BASES / 2)
+
+    // Create loop
+    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
+    esp_event_loop_handle_t loop;
+
+    if (!dedicated_task) {
+        loop_args.task_name = NULL;
+    }
+
+    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
+
+    performance_data_t data;
+
+    // Register the handlers
+    for (int base = 0; base < TEST_CONFIG_BASES; base++) {
+        for (int id = 0; id < TEST_CONFIG_IDS; id++) {
+            TEST_ESP_OK(esp_event_handler_register_with(loop, test_base + base, id, test_event_performance_handler, &data));
+        }
+    }
+
+    TaskHandle_t mtask = NULL;
+
+    if (!dedicated_task) {
+        xTaskCreate(loop_run_task, "loop_run", loop_args.task_stack_size, (void*) loop, loop_args.task_priority, &mtask);
+    }
+
+    // Perform performance test
+    float running_sum = 0;
+    float running_count = 0;
+
+    for (int bases = 1; bases <= TEST_CONFIG_BASES; bases *= 2) {
+        for (int ids = 1; ids <= TEST_CONFIG_IDS; ids *= 2) {
+
+            data.performed = 0;
+            data.expected = bases * ids;
+            data.done = xSemaphoreCreateBinary();
+
+            // Generate randomized list of posts
+            int post_bases[TEST_CONFIG_BASES];
+            int post_ids[TEST_CONFIG_IDS];
+
+            for (int i = 0; i < bases; i++) {
+                post_bases[i] = i;
+            }
+
+            for (int i = 0; i < ids; i++) {
+                post_ids[i] = i;
+            }
+
+            for (int i = 0; i < bases; i++) {
+                int rand_a  = rand() % bases;
+                int rand_b  = rand() % bases;
+
+                int temp = post_bases[rand_a];
+                post_bases[rand_a]= post_bases[rand_b];
+                post_bases[rand_b] = temp;
+            }
+
+            for (int i = 0; i < ids; i++) {
+                int rand_a  = rand() % ids;
+                int rand_b  = rand() % ids;
+
+                int temp = post_ids[rand_a];
+                post_ids[rand_a]= post_ids[rand_b];
+                post_ids[rand_b] = temp;
+            }
+
+            // Post the events
+            int64_t start = esp_timer_get_time();
+            for (int base = 0; base < bases; base++) {
+                for (int id = 0; id < ids; id++) {
+                    TEST_ESP_OK(esp_event_post_to(loop, test_base + post_bases[base], post_ids[id], NULL, 0, portMAX_DELAY));
+                }
+            }
+
+            xSemaphoreTake(data.done, portMAX_DELAY);
+            int64_t elapsed = esp_timer_get_time() - start;
+
+            // Record data
+            TEST_ASSERT_EQUAL(data.expected, data.performed);
+
+            running_count++;
+            running_sum += data.performed / (elapsed / (1000000.0));
+
+            vSemaphoreDelete(data.done);
+        }
+    }
+
+    int average = (int) (running_sum / (running_count));
+
+    if (!dedicated_task) {
+        ((esp_event_loop_instance_t*) loop)->task = mtask;
+    }
+
+    TEST_ESP_OK(esp_event_loop_delete(loop));
+
+    vTaskDelay(pdMS_TO_TICKS(TEST_CONFIG_TEARDOWN_WAIT));
+
+#ifdef CONFIG_ESP_EVENT_LOOP_PROFILING
+    ESP_LOGI(TAG, "events dispatched/second with profiling enabled: %d", average);
+    // Enabling profiling will slow down event dispatch, so the set threshold
+    // is not valid when it is enabled.
+#else
+#ifndef CONFIG_SPIRAM
+    TEST_PERFORMANCE_GREATER_THAN(EVENT_DISPATCH, "%d", average);
+#else
+    TEST_PERFORMANCE_GREATER_THAN(EVENT_DISPATCH_PSRAM, "%d", average);
+#endif // CONFIG_SPIRAM
+#endif // CONFIG_ESP_EVENT_LOOP_PROFILING
+}
+
+TEST_CASE("performance test - dedicated task", "[event][qemu-ignore]")
+{
+    performance_test(true);
+}
+
+TEST_CASE("performance test - no dedicated task", "[event][qemu-ignore]")
+{
+    performance_test(false);
+}
+
+#if CONFIG_ESP_EVENT_POST_FROM_ISR
+TEST_CASE("data posted normally is correctly set internally", "[event][intr]")
+{
+    esp_event_loop_handle_t loop;
+    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
+
+    loop_args.task_name = NULL;
+    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
+
+    esp_event_post_instance_t post;
+    esp_event_loop_instance_t* loop_def = (esp_event_loop_instance_t*) loop;
+
+    TEST_ESP_OK(esp_event_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, portMAX_DELAY));
+    TEST_ASSERT_EQUAL(pdTRUE, xQueueReceive(loop_def->queue, &post, portMAX_DELAY));
+    TEST_ASSERT_EQUAL(false, post.data_set);
+    TEST_ASSERT_EQUAL(false, post.data_allocated);
+    TEST_ASSERT_EQUAL(NULL, post.data.ptr);
+
+    TEST_ESP_OK(esp_event_loop_delete(loop));
+
+    vTaskDelay(pdMS_TO_TICKS(TEST_CONFIG_TEARDOWN_WAIT));
+}
+
+TEST_CASE("data posted from ISR is correctly set internally", "[event][intr]")
+{
+    esp_event_loop_handle_t loop;
+    esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
+
+    loop_args.task_name = NULL;
+    TEST_ESP_OK(esp_event_loop_create(&loop_args, &loop));
+
+    esp_event_post_instance_t post;
+    esp_event_loop_instance_t* loop_def = (esp_event_loop_instance_t*) loop;
+    int sample = 0;
+    TEST_ESP_OK(esp_event_isr_post_to(loop, s_test_base1, TEST_EVENT_BASE1_EV1, &sample, sizeof(sample), NULL));
+    TEST_ASSERT_EQUAL(pdTRUE, xQueueReceive(loop_def->queue, &post, portMAX_DELAY));
+    TEST_ASSERT_EQUAL(true, post.data_set);
+    TEST_ASSERT_EQUAL(false, post.data_allocated);
+    TEST_ASSERT_EQUAL(false, post.data.val);
+
+    TEST_ESP_OK(esp_event_loop_delete(loop));
+
+    vTaskDelay(pdMS_TO_TICKS(TEST_CONFIG_TEARDOWN_WAIT));
+}
+
+static void test_handler_post_from_isr(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
+{
+    SemaphoreHandle_t *sem = (SemaphoreHandle_t*) event_handler_arg;
+    // Event data is just the address value (maybe have been truncated due to casting).
+    int *data = (int*) event_data;
+    TEST_ASSERT_EQUAL(*data, (int) (*sem));
+    xSemaphoreGive(*sem);
+}
+
+bool test_event_on_timer_alarm(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
+{
+    int data = (int)user_ctx;
+    gptimer_stop(timer);
+    // Posting events with data more than 4 bytes should fail.
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_event_isr_post(s_test_base1, TEST_EVENT_BASE1_EV1, &data, 5, NULL));
+    // This should succeedd, as data is int-sized. The handler for the event checks that the passed event data
+    // is correct.
+    BaseType_t task_unblocked;
+    TEST_ESP_OK(esp_event_isr_post(s_test_base1, TEST_EVENT_BASE1_EV1, &data, sizeof(data), &task_unblocked));
+    return task_unblocked == pdTRUE;
+}
+
+TEST_CASE("can post events from interrupt handler", "[event][intr]")
+{
+    /* Lazy allocated resources in gptimer/intr_alloc */
+    unity_utils_set_leak_level(150);
+
+    TEST_ESP_OK(esp_event_loop_create_default());
+
+    SemaphoreHandle_t sem = xSemaphoreCreateBinary();
+    gptimer_handle_t gptimer = NULL;
+    /* Select and initialize basic parameters of the timer */
+    gptimer_config_t config = {
+        .clk_src = GPTIMER_CLK_SRC_DEFAULT,
+        .direction = GPTIMER_COUNT_UP,
+        .resolution_hz = 1000000, // 1MHz, 1 tick = 1us
+    };
+    TEST_ESP_OK(gptimer_new_timer(&config, &gptimer));
+
+    gptimer_alarm_config_t alarm_config = {
+        .reload_count = 0,
+        .alarm_count = 500000,
+    };
+    gptimer_event_callbacks_t cbs = {
+        .on_alarm = test_event_on_timer_alarm
+    };
+    TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, sem));
+    TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config));
+    TEST_ESP_OK(gptimer_enable(gptimer));
+    TEST_ESP_OK(gptimer_start(gptimer));
+
+    TEST_ESP_OK(esp_event_handler_register(s_test_base1, TEST_EVENT_BASE1_EV1,
+                                           test_handler_post_from_isr, &sem));
+
+    xSemaphoreTake(sem, portMAX_DELAY);
+
+    vTaskDelay(pdMS_TO_TICKS(TEST_CONFIG_TEARDOWN_WAIT));
+
+    vSemaphoreDelete(sem);
+    TEST_ESP_OK(gptimer_disable(gptimer));
+    TEST_ESP_OK(gptimer_del_timer(gptimer));
+
+    TEST_ESP_OK(esp_event_loop_delete_default());
+
+    vTaskDelay(2);
+}
+
+#endif // CONFIG_ESP_EVENT_POST_FROM_ISR

+ 8 - 0
components/esp_event/test_apps/pytest_esp_event.py

@@ -35,3 +35,11 @@ def test_esp_event_qemu_esp32c3(dut: Dut) -> None:
     for case in dut.test_menu:
         if 'qemu-ignore' not in case.groups and not case.is_ignored and case.type == 'normal':
             dut._run_normal_case(case)
+
+
+@pytest.mark.linux
+@pytest.mark.host_test
+def test_esp_event_posix_simulator(dut: Dut) -> None:
+    dut.expect_exact('Press ENTER to see the list of tests.')
+    dut.write('*')
+    dut.expect(r'\d{2} Tests 0 Failures 0 Ignored', timeout=120)

+ 0 - 1
components/esp_event/test_apps/sdkconfig.defaults

@@ -1 +0,0 @@
-CONFIG_ESP_TASK_WDT_EN=n

+ 1 - 0
components/esp_event/test_apps/sdkconfig.defaults.linux

@@ -0,0 +1 @@
+CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=n