diff --git a/mqtt_client.c b/mqtt_client.c index 505400a..46a356b 100644 --- a/mqtt_client.c +++ b/mqtt_client.c @@ -34,6 +34,7 @@ ESP_EVENT_DEFINE_BASE(MQTT_EVENTS); static const int STOPPED_BIT = (1 << 0); static const int RECONNECT_BIT = (1 << 1); static const int DISCONNECT_BIT = (1 << 2); +static const int STOP_REQ_BIT = (1 << 3); static esp_err_t esp_mqtt_dispatch_event(esp_mqtt_client_handle_t client); static esp_err_t esp_mqtt_dispatch_event_with_msgid(esp_mqtt_client_handle_t client); @@ -2013,7 +2014,7 @@ static void esp_mqtt_task(void *pv) break; case MQTT_STATE_INIT: - xEventGroupClearBits(client->status_bits, RECONNECT_BIT | DISCONNECT_BIT); + xEventGroupClearBits(client->status_bits, RECONNECT_BIT | DISCONNECT_BIT | STOP_REQ_BIT); client->transport = client->config->transport; if (!client->transport) { @@ -2185,7 +2186,7 @@ static void esp_mqtt_task(void *pv) } MQTT_API_UNLOCK(client); - xEventGroupWaitBits(client->status_bits, RECONNECT_BIT, false, true, + xEventGroupWaitBits(client->status_bits, RECONNECT_BIT | STOP_REQ_BIT, false, false, max_poll_timeout(client, client->wait_timeout_ms / 2 / portTICK_PERIOD_MS)); // continue the while loop instead of break, as the mutex is unlocked continue; @@ -2352,6 +2353,7 @@ esp_err_t esp_mqtt_client_stop(esp_mqtt_client_handle_t client) client->run = false; client->state = MQTT_STATE_DISCONNECTED; + xEventGroupSetBits(client->status_bits, STOP_REQ_BIT); MQTT_API_UNLOCK(client); xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY); return ESP_OK; diff --git a/test/host/main/CMakeLists.txt b/test/host/main/CMakeLists.txt index d588967..675aae2 100644 --- a/test/host/main/CMakeLists.txt +++ b/test/host/main/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(SRCS "test_mqtt_client.cpp" "test_mqtt5_client.cpp" "mqtt5_client_test_adapter.c" "test_log_intercept.cpp" "test_log_matchers.cpp" "test_log_parser.cpp" +idf_component_register(SRCS "test_mqtt_client.cpp" "test_mqtt5_client.cpp" "mqtt_client_test_adapter.c" "mqtt5_client_test_adapter.c" "test_log_intercept.cpp" "test_log_matchers.cpp" "test_log_parser.cpp" REQUIRES cmock mqtt esp_timer esp_hw_support http_parser log WHOLE_ARCHIVE) diff --git a/test/host/main/mqtt_client_test_adapter.c b/test/host/main/mqtt_client_test_adapter.c new file mode 100644 index 0000000..da8397f --- /dev/null +++ b/test/host/main/mqtt_client_test_adapter.c @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "mqtt_client_priv.h" + +void test_mqtt_client_enter_reconnect_wait(esp_mqtt_client_handle_t client) +{ + static int mqtt_task; + client->run = true; + client->state = MQTT_STATE_WAIT_RECONNECT; + client->task_handle = (TaskHandle_t)&mqtt_task; +} diff --git a/test/host/main/test_mqtt_client.cpp b/test/host/main/test_mqtt_client.cpp index b30edcb..4b8333d 100644 --- a/test/host/main/test_mqtt_client.cpp +++ b/test/host/main/test_mqtt_client.cpp @@ -34,6 +34,7 @@ extern "C" { #include "Mockidf_additions.h" #endif #include "Mockesp_timer.h" + void test_mqtt_client_enter_reconnect_wait(esp_mqtt_client_handle_t client); /* * The following functions are not directly called but the generation of them * from cmock is broken, so we need to define them here. @@ -102,6 +103,45 @@ static BaseType_t fail_to_create_fake_task(TaskFunction_t, const char *const, co return pdFALSE; } +struct stop_wake_trace_t { + EventGroupHandle_t event_group; + int phase; + bool invalid_call; +}; + +static stop_wake_trace_t stop_wake_trace; + +static EventBits_t track_event_group_set_bits(EventGroupHandle_t event_group, + EventBits_t bits, int) +{ + static constexpr EventBits_t stop_req_bit = 1U << 3; + + if (event_group != stop_wake_trace.event_group || bits != stop_req_bit || + stop_wake_trace.phase != 0) { + stop_wake_trace.invalid_call = true; + } + + stop_wake_trace.phase = 1; + return bits; +} + +static EventBits_t track_event_group_wait_bits(EventGroupHandle_t event_group, + EventBits_t bits, BaseType_t clear_on_exit, + BaseType_t wait_for_all, TickType_t ticks_to_wait, + int) +{ + static constexpr EventBits_t stopped_bit = 1U << 0; + + if (event_group != stop_wake_trace.event_group || bits != stopped_bit || + clear_on_exit || !wait_for_all || ticks_to_wait != portMAX_DELAY || + stop_wake_trace.phase != 1) { + stop_wake_trace.invalid_call = true; + } + + stop_wake_trace.phase = 2; + return stopped_bit; +} + SCENARIO("MQTT Client Operation") { // Set expectations for the mocked calls. @@ -335,6 +375,21 @@ SCENARIO("MQTT Client Operation") // Only need to start the client, destroy is called automatically at the // end of scope } + SECTION("Stop wakes a client waiting to reconnect") { + /* The host build does not run esp_mqtt_task. This checks the signal + * stop() publishes, which is the bit that wait waits on, and that + * the signal is not the reconnect request. */ + test_mqtt_client_enter_reconnect_wait(client.get()); + stop_wake_trace = { + reinterpret_cast(&event_group), 0, false + }; + xTaskGetCurrentTaskHandle_IgnoreAndReturn(nullptr); + xEventGroupSetBits_Stub(track_event_group_set_bits); + xEventGroupWaitBits_Stub(track_event_group_wait_bits); + REQUIRE(esp_mqtt_client_stop(client.get()) == ESP_OK); + REQUIRE(stop_wake_trace.phase == 2); + REQUIRE_FALSE(stop_wake_trace.invalid_call); + } SECTION("get_state reports client lifecycle correctly") { SECTION("returns NOT_INITIALIZED for null handle") { REQUIRE(esp_mqtt_client_get_state(nullptr) ==