diff --git a/.github/workflows/modem__build-host-tests.yml b/.github/workflows/modem__build-host-tests.yml index a1c52011994..3bf5c4dcb1a 100644 --- a/.github/workflows/modem__build-host-tests.yml +++ b/.github/workflows/modem__build-host-tests.yml @@ -109,7 +109,6 @@ jobs: - name: Build and Test shell: bash run: | - apt-get update apt-get update && apt-get install -y gcc-8 g++-8 python3-pip apt-get install -y rsync update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 800 --slave /usr/bin/g++ g++ /usr/bin/g++-8 @@ -129,9 +128,9 @@ jobs: cd $GITHUB_WORKSPACE/${{ env.COMP_DIR }} gcov-8 `find . -name "esp_modem*gcda" -printf '%h\n' | head -n 1`/* gcovr --gcov-ignore-parse-errors -g -k -r . --html index.html -x esp_modem_coverage.xml - mkdir docs_gcovr - cp $GITHUB_WORKSPACE/${{ env.COMP_DIR }}/index.html docs_gcovr - cp -rf docs_gcovr $GITHUB_WORKSPACE + mkdir docs_gcovr_modem + cp $GITHUB_WORKSPACE/${{ env.COMP_DIR }}/index.html docs_gcovr_modem + cp -rf docs_gcovr_modem $GITHUB_WORKSPACE - name: Code Coverage Summary Report uses: irongut/CodeCoverageSummary@v1.3.0 with: @@ -151,13 +150,13 @@ jobs: uses: actions/upload-artifact@v3 if: always() with: - name: docs_gcovr + name: docs_gcovr_modem path: | - ${{ env.COMP_DIR }}/docs_gcovr + ${{ env.COMP_DIR }}/docs_gcovr_modem if-no-files-found: error - name: Deploy code coverage results if: github.ref == 'refs/heads/master' uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs_gcovr + publish_dir: ./docs_gcovr_modem diff --git a/.github/workflows/websocket__build-host-tests.yml b/.github/workflows/websocket__build-host-tests.yml new file mode 100644 index 00000000000..16fc7fc7ea0 --- /dev/null +++ b/.github/workflows/websocket__build-host-tests.yml @@ -0,0 +1,75 @@ +name: "websocket: build/host-tests" + +on: + push: + # branches: + # - master + pull_request: + types: [opened, synchronize, reopened, labeled] +jobs: + host_test_websocket: + if: contains(github.event.pull_request.labels.*.name, 'websocket') || github.event_name == 'push' + name: Host Tests + runs-on: ubuntu-20.04 + permissions: + contents: write + container: espressif/idf:latest + env: + COMP_DIR: esp-protocols/components/esp_websocket_client + steps: + - name: Checkout esp-protocols + uses: actions/checkout@v3 + with: + path: esp-protocols + - name: Build and Test + shell: bash + run: | + . ${IDF_PATH}/export.sh + cd $GITHUB_WORKSPACE/${{ env.COMP_DIR }}/test/host_test + idf.py build + ./build/websocket-host.elf -r junit -o junit.xml + - name: Build with Coverage Enabled + shell: bash + run: | + . ${IDF_PATH}/export.sh + cd $GITHUB_WORKSPACE/${{ env.COMP_DIR }}/test/host_test + cat sdkconfig.ci.coverage >> sdkconfig.defaults + rm -rf build sdkconfig + idf.py fullclean + idf.py build + ./build/websocket-host.elf + - name: Run Coverage + shell: bash + run: | + apt-get update && apt-get install -y gcc-8 g++-8 python3-pip rsync + python -m pip install gcovr + cd $GITHUB_WORKSPACE/${{ env.COMP_DIR }} + gcov `find . -name "*gcda"` + gcovr --gcov-ignore-parse-errors -g -k -r . --html index.html -x websocket_coverage.xml + mkdir docs_gcovr_websocket + touch docs_gcovr_websocket/.nojekyll + cp $GITHUB_WORKSPACE/${{ env.COMP_DIR }}/index.html docs_gcovr_websocket + cp -rf docs_gcovr_websocket websocket_coverage.xml $GITHUB_WORKSPACE + - name: Code Coverage Summary Report + uses: irongut/CodeCoverageSummary@v1.3.0 + with: + filename: esp-protocols/**/websocket_coverage.xml + badge: true + fail_below_min: false + format: markdown + hide_branch_rate: false + hide_complexity: false + indicators: true + output: both + thresholds: '60 80' + - name: Write to Job Summary + run: cat code-coverage-results.md >> $GITHUB_STEP_SUMMARY + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + if: always() + with: + name: docs_gcovr_websocket + path: | + ${{ env.COMP_DIR }}/docs_gcovr_websocket + if-no-files-found: error diff --git a/.github/workflows/websocket__build-target-test.yml b/.github/workflows/websocket__build-target-test.yml index b86c76a03d9..2a8b2c46d3e 100644 --- a/.github/workflows/websocket__build-target-test.yml +++ b/.github/workflows/websocket__build-target-test.yml @@ -53,7 +53,7 @@ jobs: matrix: idf_ver: ["release-v5.0", "release-v5.1", "latest"] idf_target: ["esp32"] - test: [ { app: example, path: "examples" }, { app: unit_test, path: "test" } ] + test: [ { app: example, path: "examples" }, { app: unit_test, path: "test/unit_test" } ] runs-on: - self-hosted - ESP32-ETHERNET-KIT diff --git a/components/esp_websocket_client/CMakeLists.txt b/components/esp_websocket_client/CMakeLists.txt index ba53db6fa69..c528612956b 100644 --- a/components/esp_websocket_client/CMakeLists.txt +++ b/components/esp_websocket_client/CMakeLists.txt @@ -1,3 +1,5 @@ +idf_build_get_property(target IDF_TARGET) + if(NOT CONFIG_WS_TRANSPORT AND NOT CMAKE_BUILD_EARLY_EXPANSION) message(STATUS "Websocket transport is disabled so the esp_websocket_client component will not be built") # note: the component is still included in the build so it can become visible again in config @@ -6,7 +8,16 @@ if(NOT CONFIG_WS_TRANSPORT AND NOT CMAKE_BUILD_EARLY_EXPANSION) return() endif() -idf_component_register(SRCS "esp_websocket_client.c" +if(${IDF_TARGET} STREQUAL "linux") + idf_component_register(SRCS "esp_websocket_client.c" + INCLUDE_DIRS "include" + REQUIRES esp-tls tcp_transport http_parser esp_event protocol_examples_common nvs_flash esp_stubs + PRIV_REQUIRES esp_timer) +else() + idf_component_register(SRCS "esp_websocket_client.c" INCLUDE_DIRS "include" REQUIRES lwip esp-tls tcp_transport http_parser PRIV_REQUIRES esp_timer esp_event) +endif() + +target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") diff --git a/components/esp_websocket_client/esp_websocket_client.c b/components/esp_websocket_client/esp_websocket_client.c index 46bf51d77e5..dac2db2b540 100644 --- a/components/esp_websocket_client/esp_websocket_client.c +++ b/components/esp_websocket_client/esp_websocket_client.c @@ -20,6 +20,13 @@ #include "esp_timer.h" #include "esp_tls_crypto.h" +#if defined(CONFIG_IDF_TARGET_LINUX) +#include +#include +#define BIT0 0x00000001 +#define BIT1 0x00000002 +#endif + static const char *TAG = "websocket_client"; #define WEBSOCKET_TCP_DEFAULT_PORT (80) diff --git a/components/esp_websocket_client/test/host_test/CMakeLists.txt b/components/esp_websocket_client/test/host_test/CMakeLists.txt new file mode 100644 index 00000000000..3efbc723a8d --- /dev/null +++ b/components/esp_websocket_client/test/host_test/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.5) + + +set(COMPONENTS esp_websocket_client main) +set(EXTRA_COMPONENT_DIRS + ../../.. + "../../../../common_components/linux_compat/esp_timer" + "../../../../common_components/linux_compat" + "../../../../common_components/linux_compat/freertos") + +list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) +list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/protocols/linux_stubs/esp_stubs) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +project(websocket-host) diff --git a/components/esp_websocket_client/test/host_test/main/CMakeLists.txt b/components/esp_websocket_client/test/host_test/main/CMakeLists.txt new file mode 100644 index 00000000000..92fb51012d1 --- /dev/null +++ b/components/esp_websocket_client/test/host_test/main/CMakeLists.txt @@ -0,0 +1,16 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS + "." + REQUIRES esp_websocket_client) +target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") +target_compile_definitions(${COMPONENT_LIB} PRIVATE "-DCONFIG_IDF_TARGET_LINUX") + +if(CONFIG_GCOV_ENABLED) + target_compile_options(${COMPONENT_LIB} PUBLIC --coverage -fprofile-arcs -ftest-coverage) + target_link_options(${COMPONENT_LIB} PUBLIC --coverage -fprofile-arcs -ftest-coverage) + + idf_component_get_property(esp_websocket_client esp_websocket_client COMPONENT_LIB) + + target_compile_options(${esp_websocket_client} PUBLIC --coverage -fprofile-arcs -ftest-coverage) + target_link_options(${esp_websocket_client} PUBLIC --coverage -fprofile-arcs -ftest-coverage) +endif() diff --git a/components/esp_websocket_client/test/host_test/main/Kconfig.projbuild b/components/esp_websocket_client/test/host_test/main/Kconfig.projbuild new file mode 100644 index 00000000000..34de810dee8 --- /dev/null +++ b/components/esp_websocket_client/test/host_test/main/Kconfig.projbuild @@ -0,0 +1,15 @@ +menu "Host-test config" + + config GCOV_ENABLED + bool "Coverage analyzer" + default n + help + Enables coverage analyzing for host tests. + + config WEBSOCKET_URI + string "Websocket endpoint URI" + default "ws://echo.websocket.events" + help + URL of websocket endpoint this example connects to and sends echo + +endmenu diff --git a/components/esp_websocket_client/test/host_test/main/main.c b/components/esp_websocket_client/test/host_test/main/main.c new file mode 100644 index 00000000000..df0f4a28afc --- /dev/null +++ b/components/esp_websocket_client/test/host_test/main/main.c @@ -0,0 +1,123 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "esp_log.h" +#include "nvs_flash.h" +#include "protocol_examples_common.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/event_groups.h" + +#include "esp_system.h" +#include "esp_websocket_client.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif.h" + +static const char *TAG = "websocket"; + +static void log_error_if_nonzero(const char *message, int error_code) +{ + if (error_code != 0) { + ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code); + } +} + +static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) +{ + esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; + switch (event_id) { + case WEBSOCKET_EVENT_CONNECTED: + ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED"); + break; + case WEBSOCKET_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED"); + log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code); + if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) { + log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err); + log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err); + log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno); + } + break; + case WEBSOCKET_EVENT_DATA: + ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA"); + ESP_LOGI(TAG, "Received opcode=%d", data->op_code); + if (data->op_code == 0x08 && data->data_len == 2) { + ESP_LOGW(TAG, "Received closed message with code=%d", 256 * data->data_ptr[0] + data->data_ptr[1]); + } else { + ESP_LOGW(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr); + } + + // If received data contains json structure it succeed to parse + ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset); + + break; + case WEBSOCKET_EVENT_ERROR: + ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR"); + log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code); + if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) { + log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err); + log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err); + log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno); + } + break; + } +} + + +static void websocket_app_start(void) +{ + esp_websocket_client_config_t websocket_cfg = {}; + + websocket_cfg.uri = CONFIG_WEBSOCKET_URI; + + ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri); + + esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg); + esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client); + + esp_websocket_client_start(client); + char data[32]; + int i = 0; + while (i < 1) { + if (esp_websocket_client_is_connected(client)) { + int len = sprintf(data, "hello %04d", i++); + ESP_LOGI(TAG, "Sending %s", data); + esp_websocket_client_send_text(client, data, len, portMAX_DELAY); + } + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + + esp_websocket_client_destroy(client); +} + + +int main(int argc, char *argv[]) +{ + + ESP_LOGI(TAG, "[APP] Startup.."); + ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size()); + ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); + esp_log_level_set("*", ESP_LOG_INFO); + esp_log_level_set("websocket_client", ESP_LOG_DEBUG); + esp_log_level_set("transport_ws", ESP_LOG_DEBUG); + esp_log_level_set("trans_tcp", ESP_LOG_DEBUG); + + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + * Read "Establishing Wi-Fi or Ethernet Connection" section in + * examples/protocols/README.md for more information about this function. + */ + ESP_ERROR_CHECK(example_connect()); + + websocket_app_start(); + return 0; +} diff --git a/components/esp_websocket_client/test/host_test/sdkconfig.ci.coverage b/components/esp_websocket_client/test/host_test/sdkconfig.ci.coverage new file mode 100644 index 00000000000..3c490c09846 --- /dev/null +++ b/components/esp_websocket_client/test/host_test/sdkconfig.ci.coverage @@ -0,0 +1 @@ +CONFIG_GCOV_ENABLED=y diff --git a/components/esp_websocket_client/test/host_test/sdkconfig.defaults b/components/esp_websocket_client/test/host_test/sdkconfig.defaults new file mode 100644 index 00000000000..4bf76df74f4 --- /dev/null +++ b/components/esp_websocket_client/test/host_test/sdkconfig.defaults @@ -0,0 +1,5 @@ +CONFIG_IDF_TARGET="linux" +CONFIG_IDF_TARGET_LINUX=y +CONFIG_ESP_EVENT_POST_FROM_ISR=n +CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n +CONFIG_GCOV_ENABLED=y diff --git a/components/esp_websocket_client/test/CMakeLists.txt b/components/esp_websocket_client/test/unit_test/CMakeLists.txt similarity index 100% rename from components/esp_websocket_client/test/CMakeLists.txt rename to components/esp_websocket_client/test/unit_test/CMakeLists.txt diff --git a/components/esp_websocket_client/test/main/CMakeLists.txt b/components/esp_websocket_client/test/unit_test/main/CMakeLists.txt similarity index 100% rename from components/esp_websocket_client/test/main/CMakeLists.txt rename to components/esp_websocket_client/test/unit_test/main/CMakeLists.txt diff --git a/components/esp_websocket_client/test/main/test_websocket_client.c b/components/esp_websocket_client/test/unit_test/main/test_websocket_client.c similarity index 100% rename from components/esp_websocket_client/test/main/test_websocket_client.c rename to components/esp_websocket_client/test/unit_test/main/test_websocket_client.c diff --git a/components/esp_websocket_client/test/pytest_websocket.py b/components/esp_websocket_client/test/unit_test/pytest_websocket.py similarity index 100% rename from components/esp_websocket_client/test/pytest_websocket.py rename to components/esp_websocket_client/test/unit_test/pytest_websocket.py diff --git a/components/esp_websocket_client/test/sdkconfig.ci b/components/esp_websocket_client/test/unit_test/sdkconfig.ci similarity index 100% rename from components/esp_websocket_client/test/sdkconfig.ci rename to components/esp_websocket_client/test/unit_test/sdkconfig.ci diff --git a/components/esp_websocket_client/test/sdkconfig.defaults b/components/esp_websocket_client/test/unit_test/sdkconfig.defaults similarity index 100% rename from components/esp_websocket_client/test/sdkconfig.defaults rename to components/esp_websocket_client/test/unit_test/sdkconfig.defaults