diff --git a/components/mqtt/test/test_mqtt.c b/components/mqtt/test/test_mqtt.c index 5cc0e47..ede3b31 100644 --- a/components/mqtt/test/test_mqtt.c +++ b/components/mqtt/test/test_mqtt.c @@ -7,7 +7,8 @@ #include "nvs_flash.h" #include "esp_ota_ops.h" #include "sdkconfig.h" -#include "soc/soc_caps.h" +#include "test_mqtt_client_broker.h" +#include "test_mqtt_connection.h" static void test_leak_setup(const char * file, long line) { @@ -74,47 +75,19 @@ TEST_CASE("mqtt enqueue and destroy outbox", "[mqtt][leaks=0]") } #if SOC_EMAC_SUPPORTED - -void eth_test_fixture_connect(void); -void eth_test_fixture_deinit(void); - -static const int CONNECT_BIT = BIT0; -static const int DISCONNECT_BIT = BIT1; - -static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) +/** + * This test cases uses ethernet kit, so build and use it only if EMAC supported + */ +TEST_CASE("mqtt broker tests", "[mqtt][test_env=UT_T2_Ethernet]") { - EventGroupHandle_t *event_group = handler_args; - switch ((esp_mqtt_event_id_t)event_id) { - case MQTT_EVENT_CONNECTED: - xEventGroupSetBits(*event_group, CONNECT_BIT); - break; - case MQTT_EVENT_DISCONNECTED: - xEventGroupSetBits(*event_group, DISCONNECT_BIT); - break; - default: - break; - } -} - -TEST_CASE("connect disconnect", "[mqtt][test_env=UT_T2_Ethernet]") -{ - test_leak_setup(__FILE__, __LINE__); test_case_uses_tcpip(); - eth_test_fixture_connect(); - const int TEST_CONNECT_TIMEOUT = 10000; - const esp_mqtt_client_config_t mqtt_cfg = { - // no connection takes place, but the uri has to be valid for init() to succeed - .uri = CONFIG_MQTT_TEST_BROKER_URI, - }; - EventGroupHandle_t event_group = xEventGroupCreate(); - esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg); - TEST_ASSERT_NOT_EQUAL(NULL, client ); - esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, &event_group); - TEST_ASSERT_EQUAL(ESP_OK, esp_mqtt_client_start(client)); - TEST_ASSERT_TRUE(xEventGroupWaitBits(event_group, CONNECT_BIT, pdTRUE, pdTRUE, pdMS_TO_TICKS(TEST_CONNECT_TIMEOUT)) & CONNECT_BIT); - esp_mqtt_client_disconnect(client); - TEST_ASSERT_TRUE(xEventGroupWaitBits(event_group, DISCONNECT_BIT, pdTRUE, pdTRUE, pdMS_TO_TICKS(TEST_CONNECT_TIMEOUT)) & DISCONNECT_BIT); - esp_mqtt_client_destroy(client); - eth_test_fixture_deinit(); + connect_test_fixture_setup(); + + RUN_MQTT_BROKER_TEST(mqtt_connect_disconnect); + RUN_MQTT_BROKER_TEST(mqtt_subscribe_publish); + RUN_MQTT_BROKER_TEST(mqtt_lwt_clean_disconnect); + RUN_MQTT_BROKER_TEST(mqtt_subscribe_payload); + + connect_test_fixture_teardown(); } #endif // SOC_EMAC_SUPPORTED diff --git a/components/mqtt/test/test_mqtt_client_broker.c b/components/mqtt/test/test_mqtt_client_broker.c new file mode 100644 index 0000000..167a007 --- /dev/null +++ b/components/mqtt/test/test_mqtt_client_broker.c @@ -0,0 +1,226 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "mqtt_client.h" +#include "esp_log.h" + +#define WAIT_FOR_EVENT(event) \ + TEST_ASSERT_TRUE(xEventGroupWaitBits(s_event_group, event, pdTRUE, pdTRUE, pdMS_TO_TICKS(COMMON_OPERATION_TIMEOUT)) & event); + +#define TEST_ASSERT_TRUE(condition) TEST_ASSERT_TRUE_LINE(condition, __LINE__) +#define TEST_ASSERT_TRUE_LINE(condition, line) \ + do { \ + if (!(condition)) { \ + ESP_LOGE("test_mqtt_client_broker.c", \ + "Assertion failed in line %d", line); \ + return false; \ + } \ + } while(0) + + +static const int COMMON_OPERATION_TIMEOUT = 10000; +static const int CONNECT_BIT = BIT0; +static const int DISCONNECT_BIT = BIT1; +static const int DATA_BIT = BIT2; + +static EventGroupHandle_t s_event_group; + +static char* append_mac(const char* string) +{ + uint8_t mac[6]; + char *id_string = NULL; + esp_read_mac(mac, ESP_MAC_WIFI_STA); + asprintf(&id_string, "%s_%02x%02X%02X", string, mac[3], mac[4], mac[5]); + return id_string; +} + +static void mqtt_data_handler_qos(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) +{ + if (event_id == MQTT_EVENT_DATA) { + esp_mqtt_event_handle_t event = event_data; + int * qos = handler_args; + *qos = event->qos; + xEventGroupSetBits(s_event_group, DATA_BIT); + } +} + +static void mqtt_data_handler_lwt(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) +{ + if (event_id == MQTT_EVENT_DATA) { + esp_mqtt_event_handle_t event = event_data; + ESP_LOGI("mqtt-lwt", "MQTT_EVENT_DATA"); + ESP_LOGI("mqtt-lwt", "TOPIC=%.*s", event->topic_len, event->topic); + ESP_LOGI("mqtt-lwt", "DATA=%.*s", event->data_len, event->data); + if (strncmp(event->data, "no-lwt", event->data_len) == 0) { + // no lwt, just to indicate the test has finished + xEventGroupSetBits(s_event_group, DATA_BIT); + } else { + // count up any potential lwt message + int * count = handler_args; + *count = *count + 1; + ESP_LOGE("mqtt-lwt", "count=%d", *count); + } + } +} + +static void mqtt_data_handler_subscribe(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) +{ + if (event_id == MQTT_EVENT_SUBSCRIBED) { + esp_mqtt_event_handle_t event = event_data; + ESP_LOGI("mqtt-subscribe", "MQTT_EVENT_SUBSCRIBED, data size=%d", event->data_len); + int * sub_payload = handler_args; + if (event->data_len == 1) { + ESP_LOGI("mqtt-subscribe", "DATA=%d", *(uint8_t*)event->data); + *sub_payload = *(uint8_t*)event->data; + } + xEventGroupSetBits(s_event_group, DATA_BIT); + } +} + + +static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) +{ + switch ((esp_mqtt_event_id_t)event_id) { + case MQTT_EVENT_CONNECTED: + xEventGroupSetBits(s_event_group, CONNECT_BIT); + break; + + case MQTT_EVENT_DISCONNECTED: + xEventGroupSetBits(s_event_group, DISCONNECT_BIT); + break; + default: + break; + } +} + +bool mqtt_connect_disconnect(void) +{ + const esp_mqtt_client_config_t mqtt_cfg = { + .uri = CONFIG_MQTT_TEST_BROKER_URI, + .disable_auto_reconnect = true, + }; + s_event_group = xEventGroupCreate(); + esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg); + TEST_ASSERT_TRUE(NULL != client ); + esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); + TEST_ASSERT_TRUE(ESP_OK == esp_mqtt_client_start(client)); + WAIT_FOR_EVENT(CONNECT_BIT); + esp_mqtt_client_disconnect(client); + WAIT_FOR_EVENT(DISCONNECT_BIT); + esp_mqtt_client_reconnect(client); + WAIT_FOR_EVENT(CONNECT_BIT); + esp_mqtt_client_destroy(client); + vEventGroupDelete(s_event_group); + return true; +} + +bool mqtt_subscribe_publish(void) +{ + const esp_mqtt_client_config_t mqtt_cfg = { + .uri = CONFIG_MQTT_TEST_BROKER_URI, + }; + char* topic = append_mac("topic"); + TEST_ASSERT_TRUE(NULL != topic); + s_event_group = xEventGroupCreate(); + esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg); + TEST_ASSERT_TRUE(NULL != client ); + esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); + TEST_ASSERT_TRUE(ESP_OK == esp_mqtt_client_start(client)); + WAIT_FOR_EVENT(CONNECT_BIT); + int qos = -1; + esp_mqtt_client_register_event(client, MQTT_EVENT_DATA, mqtt_data_handler_qos, &qos); + TEST_ASSERT_TRUE(esp_mqtt_client_subscribe(client, topic, 2) != -1); + TEST_ASSERT_TRUE(esp_mqtt_client_publish(client, topic, "message", 0, 2, 0) != -1); + WAIT_FOR_EVENT(DATA_BIT); + TEST_ASSERT_TRUE(qos == 2); + TEST_ASSERT_TRUE(esp_mqtt_client_publish(client, topic, "message", 0, 1, 0) != -1); + WAIT_FOR_EVENT(DATA_BIT); + TEST_ASSERT_TRUE(qos == 1); + esp_mqtt_client_destroy(client); + vEventGroupDelete(s_event_group); + free(topic); + return true; +} + +bool mqtt_lwt_clean_disconnect(void) +{ + char* lwt = append_mac("lwt"); + TEST_ASSERT_TRUE(lwt); + const esp_mqtt_client_config_t mqtt_cfg1 = { + .uri = CONFIG_MQTT_TEST_BROKER_URI, + .set_null_client_id = true, + .lwt_topic = lwt, + .lwt_msg = "lwt_msg" + }; + const esp_mqtt_client_config_t mqtt_cfg2 = { + .uri = CONFIG_MQTT_TEST_BROKER_URI, + .set_null_client_id = true, + .lwt_topic = lwt, + .lwt_msg = "lwt_msg" + }; + s_event_group = xEventGroupCreate(); + + esp_mqtt_client_handle_t client1 = esp_mqtt_client_init(&mqtt_cfg1); + esp_mqtt_client_handle_t client2 = esp_mqtt_client_init(&mqtt_cfg2); + TEST_ASSERT_TRUE(NULL != client1 && NULL != client2 ); + esp_mqtt_client_register_event(client1, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); + esp_mqtt_client_register_event(client2, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); + TEST_ASSERT_TRUE(esp_mqtt_client_start(client1) == ESP_OK); + WAIT_FOR_EVENT(CONNECT_BIT); + TEST_ASSERT_TRUE(esp_mqtt_client_start(client2) == ESP_OK); + WAIT_FOR_EVENT(CONNECT_BIT); + int counter = 0; + esp_mqtt_client_register_event(client1, MQTT_EVENT_DATA, mqtt_data_handler_lwt, &counter); + esp_mqtt_client_register_event(client2, MQTT_EVENT_DATA, mqtt_data_handler_lwt, &counter); + TEST_ASSERT_TRUE(esp_mqtt_client_subscribe(client1, lwt, 0) != -1); + TEST_ASSERT_TRUE(esp_mqtt_client_subscribe(client2, lwt, 0) != -1); + esp_mqtt_client_disconnect(client1); + WAIT_FOR_EVENT(DISCONNECT_BIT); + esp_mqtt_client_reconnect(client1); + WAIT_FOR_EVENT(CONNECT_BIT); + TEST_ASSERT_TRUE(esp_mqtt_client_subscribe(client1, lwt, 0) != -1); + esp_mqtt_client_stop(client2); + esp_mqtt_client_start(client2); + WAIT_FOR_EVENT(CONNECT_BIT); + TEST_ASSERT_TRUE(esp_mqtt_client_subscribe(client2, lwt, 0) != -1); + TEST_ASSERT_TRUE(esp_mqtt_client_publish(client1, lwt, "no-lwt", 0, 0, 0) != -1); + WAIT_FOR_EVENT(DATA_BIT); + TEST_ASSERT_TRUE(counter == 0); + esp_mqtt_client_destroy(client1); + esp_mqtt_client_destroy(client2); + vEventGroupDelete(s_event_group); + free(lwt); + return true; +} + +bool mqtt_subscribe_payload(void) +{ + const esp_mqtt_client_config_t mqtt_cfg = { + .uri = CONFIG_MQTT_TEST_BROKER_URI, + .disable_auto_reconnect = true, + }; + char* topic = append_mac("topic"); + TEST_ASSERT_TRUE(NULL != topic); + s_event_group = xEventGroupCreate(); + esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg); + TEST_ASSERT_TRUE(NULL != client ); + esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); + TEST_ASSERT_TRUE(ESP_OK == esp_mqtt_client_start(client)); + WAIT_FOR_EVENT(CONNECT_BIT); + int qos_payload = -1; + esp_mqtt_client_register_event(client, MQTT_EVENT_SUBSCRIBED, mqtt_data_handler_subscribe, &qos_payload); + TEST_ASSERT_TRUE(esp_mqtt_client_subscribe(client, topic, 2) != -1); + WAIT_FOR_EVENT(DATA_BIT); + TEST_ASSERT_TRUE(qos_payload == 2); + TEST_ASSERT_TRUE(esp_mqtt_client_subscribe(client, topic, 0) != -1); + WAIT_FOR_EVENT(DATA_BIT); + TEST_ASSERT_TRUE(qos_payload == 0); + esp_mqtt_client_destroy(client); + vEventGroupDelete(s_event_group); + free(topic); + return true; +} diff --git a/components/mqtt/test/test_mqtt_client_broker.h b/components/mqtt/test/test_mqtt_client_broker.h new file mode 100644 index 0000000..6bfd5d8 --- /dev/null +++ b/components/mqtt/test/test_mqtt_client_broker.h @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once +#include "esp_log.h" + +/** + * @brief MQTT client-broker tests are not implemented as separate test cases + * due to time consuming connection setup/teardown. + * This utility macro is used to run functional cases as MQTT tests + * and evaluate as separate assertions in one "mqtt broker tests" test case. + */ +#define RUN_MQTT_BROKER_TEST(test_name) \ + do { \ + ESP_LOGI("mqtt_test", "Running test:" #test_name "()"); \ + TEST_ASSERT_TRUE_MESSAGE(test_name(), "Mqtt test failed: " #test_name "() "); \ + ESP_LOGI("mqtt_test", "Test:" #test_name "() passed "); \ + } while(0) + + +/** + * @brief This module contains mqtt test cases interacting the client with a (real) broker + */ + +/** + * @brief The client subscribes and publishes on the same topic + * and verifies the received published qos in the event + */ +bool mqtt_subscribe_publish(void); + +/** + * @brief The client connects, disconnects and reconnects. + * Tests basic client state transitions + */ +bool mqtt_connect_disconnect(void); + +/** + * @brief Two clients with defined lwt connect and subscribe to lwt topic. + * This test verifies that no lwt is send when each of the client disconnects. + * (we expect a clean disconnection, so no last-will being sent) + */ +bool mqtt_lwt_clean_disconnect(void); + +/** + * @brief The client subscribes to a topic with certain qos + * and verifies the qos in SUBACK message from the broker. + */ +bool mqtt_subscribe_payload(void); diff --git a/components/mqtt/test/connection.c b/components/mqtt/test/test_mqtt_connection.c similarity index 70% rename from components/mqtt/test/connection.c rename to components/mqtt/test/test_mqtt_connection.c index 213d6b3..bf3f09a 100644 --- a/components/mqtt/test/connection.c +++ b/components/mqtt/test/test_mqtt_connection.c @@ -10,6 +10,7 @@ #include "esp_eth.h" #include "esp_log.h" + #if SOC_EMAC_SUPPORTED #define ETH_START_BIT BIT(0) #define ETH_STOP_BIT BIT(1) @@ -18,7 +19,15 @@ #define ETH_STOP_TIMEOUT_MS (10000) #define ETH_GET_IP_TIMEOUT_MS (60000) + static const char *TAG = "esp32_eth_test_fixture"; +static EventGroupHandle_t s_eth_event_group = NULL; +static esp_netif_t *s_eth_netif = NULL; +static esp_eth_mac_t *s_mac = NULL; +static esp_eth_phy_t *s_phy = NULL; +static esp_eth_handle_t s_eth_handle = NULL; +static esp_eth_netif_glue_handle_t s_eth_glue = NULL; + /** Event handler for Ethernet events */ static void eth_event_handler(void *arg, esp_event_base_t event_base, @@ -78,61 +87,56 @@ static esp_err_t test_uninstall_driver(esp_eth_handle_t eth_hdl, uint32_t ms_to_ return ESP_FAIL; } } -static EventGroupHandle_t eth_event_group; -static esp_netif_t *eth_netif; -static esp_eth_mac_t *mac; -static esp_eth_phy_t *phy; -static esp_eth_handle_t eth_handle = NULL; -static esp_eth_netif_glue_handle_t glue; -void eth_test_fixture_connect(void) + +void connect_test_fixture_setup(void) { EventBits_t bits; - eth_event_group = xEventGroupCreate(); - TEST_ASSERT(eth_event_group != NULL); + s_eth_event_group = xEventGroupCreate(); + TEST_ASSERT(s_eth_event_group != NULL); TEST_ESP_OK(esp_event_loop_create_default()); // create TCP/IP netif esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH(); - eth_netif = esp_netif_new(&netif_cfg); + s_eth_netif = esp_netif_new(&netif_cfg); eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); - mac = esp_eth_mac_new_esp32(&mac_config); + s_mac = esp_eth_mac_new_esp32(&mac_config); eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); - phy = esp_eth_phy_new_ip101(&phy_config); - esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy); + s_phy = esp_eth_phy_new_ip101(&phy_config); + esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(s_mac, s_phy); // install Ethernet driver - TEST_ESP_OK(esp_eth_driver_install(ð_config, ð_handle)); + TEST_ESP_OK(esp_eth_driver_install(ð_config, &s_eth_handle)); // combine driver with netif - glue = esp_eth_new_netif_glue(eth_handle); - TEST_ESP_OK(esp_netif_attach(eth_netif, glue)); + s_eth_glue = esp_eth_new_netif_glue(s_eth_handle); + TEST_ESP_OK(esp_netif_attach(s_eth_netif, s_eth_glue)); // register user defined event handers - TEST_ESP_OK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, eth_event_group)); - TEST_ESP_OK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, eth_event_group)); + TEST_ESP_OK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, s_eth_event_group)); + TEST_ESP_OK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, s_eth_event_group)); // start Ethernet driver - TEST_ESP_OK(esp_eth_start(eth_handle)); + TEST_ESP_OK(esp_eth_start(s_eth_handle)); /* wait for IP lease */ - bits = xEventGroupWaitBits(eth_event_group, ETH_GOT_IP_BIT, true, true, pdMS_TO_TICKS(ETH_GET_IP_TIMEOUT_MS)); + bits = xEventGroupWaitBits(s_eth_event_group, ETH_GOT_IP_BIT, true, true, pdMS_TO_TICKS(ETH_GET_IP_TIMEOUT_MS)); TEST_ASSERT((bits & ETH_GOT_IP_BIT) == ETH_GOT_IP_BIT); } -void eth_test_fixture_deinit(void) +void connect_test_fixture_teardown(void) { EventBits_t bits; // stop Ethernet driver - TEST_ESP_OK(esp_eth_stop(eth_handle)); + TEST_ESP_OK(esp_eth_stop(s_eth_handle)); /* wait for connection stop */ - bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(ETH_STOP_TIMEOUT_MS)); + bits = xEventGroupWaitBits(s_eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(ETH_STOP_TIMEOUT_MS)); TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT); - TEST_ESP_OK(esp_eth_del_netif_glue(glue)); + TEST_ESP_OK(esp_eth_del_netif_glue(s_eth_glue)); /* driver should be uninstalled within 2 seconds */ - TEST_ESP_OK(test_uninstall_driver(eth_handle, 2000)); - TEST_ESP_OK(phy->del(phy)); - TEST_ESP_OK(mac->del(mac)); + TEST_ESP_OK(test_uninstall_driver(s_eth_handle, 2000)); + TEST_ESP_OK(s_phy->del(s_phy)); + TEST_ESP_OK(s_mac->del(s_mac)); TEST_ESP_OK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, got_ip_event_handler)); TEST_ESP_OK(esp_event_handler_unregister(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler)); - esp_netif_destroy(eth_netif); + esp_netif_destroy(s_eth_netif); TEST_ESP_OK(esp_event_loop_delete_default()); - vEventGroupDelete(eth_event_group); + vEventGroupDelete(s_eth_event_group); } #endif // SOC_EMAC_SUPPORTED diff --git a/components/mqtt/test/test_mqtt_connection.h b/components/mqtt/test/test_mqtt_connection.h new file mode 100644 index 0000000..5322ddf --- /dev/null +++ b/components/mqtt/test/test_mqtt_connection.h @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once +#include "soc/soc_caps.h" + +/** + * Connection test fixture setup, so we expect the broker is available + * on network + */ +void connect_test_fixture_setup(void); + +/** + * Cleans up the connection + */ +void connect_test_fixture_teardown(void);