diff --git a/components/usb/host_test/.build-test-rules.yml b/components/usb/host_test/.build-test-rules.yml new file mode 100644 index 0000000000..23eb8e9b6f --- /dev/null +++ b/components/usb/host_test/.build-test-rules.yml @@ -0,0 +1,7 @@ +# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps + +components/usb/host_test: + enable: + - if: IDF_TARGET == "linux" + depends_components: + - usb diff --git a/components/usb/host_test/usb_host_layer_test/CMakeLists.txt b/components/usb/host_test/usb_host_layer_test/CMakeLists.txt new file mode 100644 index 0000000000..6fea6e4ad7 --- /dev/null +++ b/components/usb/host_test/usb_host_layer_test/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) + +list(APPEND EXTRA_COMPONENT_DIRS + "$ENV{IDF_PATH}/tools/mocks/usb/usb_host_layer_mock/usb" + + # The following line would be needed to include the freertos mock component if this test used mocked FreeRTOS. + #"$ENV{IDF_PATH}/tools/mocks/freertos/" + ) + +project(host_test_usb_host_layer) diff --git a/components/usb/host_test/usb_host_layer_test/README.md b/components/usb/host_test/usb_host_layer_test/README.md new file mode 100644 index 0000000000..07071d4359 --- /dev/null +++ b/components/usb/host_test/usb_host_layer_test/README.md @@ -0,0 +1,37 @@ +| Supported Targets | Linux | +| ----------------- | ----- | + +# Description + +This directory contains test code for `USB Host layer` of USB Host stack. Namely: +* USB Host public API calls to install and uninstall the USB Host driver with partially mocked USB Host stack to test Linux build and Cmock run for this partial Mock +* Mocked are all layers of the USB Host stack below the USB Host layer, which is used as a real component + +Tests are written using [Catch2](https://github.com/catchorg/Catch2) test framework, use CMock, so you must install Ruby on your machine to run them. + +This test directory uses freertos as real component + +# Build + +Tests build regularly like an idf project. Currently only working on Linux machines. + +``` +idf.py --preview set-target linux +idf.py build +``` + +# Run + +The build produces an executable in the build folder. + +Just run: + +``` +idf.py monitor +``` + +or run the executable directly: + +``` +./build/host_test_usb_host_layer.elf +``` \ No newline at end of file diff --git a/components/usb/host_test/usb_host_layer_test/main/CMakeLists.txt b/components/usb/host_test/usb_host_layer_test/main/CMakeLists.txt new file mode 100644 index 0000000000..4c5f2bc29c --- /dev/null +++ b/components/usb/host_test/usb_host_layer_test/main/CMakeLists.txt @@ -0,0 +1,12 @@ +set(srcs) +list(APPEND srcs "test_main.cpp" + "usb_host_install_unit_test.cpp" + ) + +idf_component_register(SRCS ${srcs} + REQUIRES cmock usb + WHOLE_ARCHIVE) + +# The following line would be needed to provide the 'main' function if this test used mocked FreeRTOS. +# As this test uses the real FreeRTOS implementation, we don't need Catch2 to provide 'main'. +#target_link_libraries(${COMPONENT_LIB} PRIVATE Catch2WithMain) diff --git a/components/usb/host_test/usb_host_layer_test/main/idf_component.yml b/components/usb/host_test/usb_host_layer_test/main/idf_component.yml new file mode 100644 index 0000000000..f7982136b9 --- /dev/null +++ b/components/usb/host_test/usb_host_layer_test/main/idf_component.yml @@ -0,0 +1,2 @@ +dependencies: + espressif/catch2: "^3.4.0" diff --git a/components/usb/host_test/usb_host_layer_test/main/test_main.cpp b/components/usb/host_test/usb_host_layer_test/main/test_main.cpp new file mode 100644 index 0000000000..43729e8838 --- /dev/null +++ b/components/usb/host_test/usb_host_layer_test/main/test_main.cpp @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +extern "C" void app_main(void) +{ + int argc = 1; + const char *argv[2] = { + "target_test_main", + NULL + }; + + auto result = Catch::Session().run(argc, argv); + if (result != 0) { + printf("Test failed with result %d\n", result); + } else { + printf("Test passed.\n"); + } + fflush(stdout); + exit(result); +} diff --git a/components/usb/host_test/usb_host_layer_test/main/usb_host_install_unit_test.cpp b/components/usb/host_test/usb_host_layer_test/main/usb_host_install_unit_test.cpp new file mode 100644 index 0000000000..b1cbb4fa9f --- /dev/null +++ b/components/usb/host_test/usb_host_layer_test/main/usb_host_install_unit_test.cpp @@ -0,0 +1,254 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "usb_host.h" // Real implementation of usb_host.h + +// Test all the mocked headers defined for this mock +extern "C" { +#include "Mockusb_phy.h" +#include "Mockhcd.h" +#include "Mockusbh.h" +#include "Mockenum.h" +#include "Mockhub.h" +} + +SCENARIO("USB Host pre-uninstall") +{ + // No USB Host driver previously installed + GIVEN("No USB Host previously installed") { + + // Uninstall not-installed USB Host + SECTION("Uninstalling not installed USB Host") { + + // Call the DUT function, expect ESP_ERR_INVALID_STATE + REQUIRE(ESP_ERR_INVALID_STATE == usb_host_uninstall()); + } + } +} + +SCENARIO("USB Host install") +{ + // USB Host config is not valid, USB Host driver is not installed from previous test case + GIVEN("No USB Host config, USB Host driver not installed") { + + // Try to install the USB Host driver with usb_host_config set to nullptr + SECTION("USB Host config is nullptr") { + + // Call the DUT function, expect ESP_ERR_INVALID_ARG + REQUIRE(ESP_ERR_INVALID_ARG == usb_host_install(nullptr)); + } + } + + // USB Host config struct + usb_host_config_t usb_host_config = { + .skip_phy_setup = false, + .root_port_unpowered = false, + .intr_flags = 1, + .enum_filter_cb = nullptr, + }; + + // USB host config is valid, USB Host driver is not installed from previous test case + GIVEN("USB Host config, USB Host driver not installed") { + + // Try to install the USB Host driver, with PHY install error + SECTION("Fail to install USB Host - PHY Install error") { + + // Make the PHY install to fail + usb_new_phy_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_STATE); + + // Call the DUT function, expect ESP_ERR_INVALID_STATE + REQUIRE(ESP_ERR_INVALID_STATE == usb_host_install(&usb_host_config)); + } + + // Try to install the USB Host driver, with HCD Install error, use internal PHY + SECTION("Fail to install USB Host - HCD Install error (internal PHY)") { + + // Make the PHY install to pass + usb_phy_handle_t phy_handle; + usb_new_phy_ExpectAnyArgsAndReturn(ESP_OK); + usb_new_phy_ReturnThruPtr_handle_ret(&phy_handle); + + // make the HCD port install to fail + hcd_install_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_STATE); + + // goto hcd_err: We must uninstall the PHY + usb_del_phy_ExpectAndReturn(phy_handle, ESP_OK); + + // Call the DUT function, expect ESP_ERR_INVALID_STATE + REQUIRE(ESP_ERR_INVALID_STATE == usb_host_install(&usb_host_config)); + } + + // Try to install the USB Host driver, with HCD Install error, use external PHY + SECTION("Fail to install USB Host - HCD Install error (external PHY)") { + + // Skip the PHY Setup (external phy) + usb_host_config.skip_phy_setup = true; + + // make the HCD port install to fail + hcd_install_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_STATE); + + // Call the DUT function, expect ESP_ERR_INVALID_STATE + REQUIRE(ESP_ERR_INVALID_STATE == usb_host_install(&usb_host_config)); + } + + // Try to install the USB Host driver, with USBH Install error, use internal PHY + SECTION("Fail to install USB Host - USBH install error") { + + // Make the PHY install to pass + usb_phy_handle_t phy_handle; + usb_new_phy_ExpectAnyArgsAndReturn(ESP_OK); + usb_new_phy_ReturnThruPtr_handle_ret(&phy_handle); + + // make the HCD port install to pass + hcd_install_ExpectAnyArgsAndReturn(ESP_OK); + + // Make the USBH install to fail + usbh_install_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_STATE); + + // goto usbh_err: We must uninstall HCD port and PHY + hcd_uninstall_ExpectAndReturn(ESP_OK); + usb_del_phy_ExpectAndReturn(phy_handle, ESP_OK); + + // Call the DUT function, expect ESP_ERR_INVALID_STATE + REQUIRE(ESP_ERR_INVALID_STATE == usb_host_install(&usb_host_config)); + } + + // Try to install the USB Host driver, with Enum driver Install error, use internal PHY + SECTION("Fail to install USB Host - Enum driver install error") { + + // Make the PHY install to pass + usb_phy_handle_t phy_handle; + usb_new_phy_ExpectAnyArgsAndReturn(ESP_OK); + usb_new_phy_ReturnThruPtr_handle_ret(&phy_handle); + + // make the HCD port install to pass + hcd_install_ExpectAnyArgsAndReturn(ESP_OK); + + // Make the USBH install to pass + usbh_install_ExpectAnyArgsAndReturn(ESP_OK); + + // Make the ENUM Driver to fail + enum_install_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_STATE); + + // goto enum_err: We must uninstall USBH, HCD port and PHY + usbh_uninstall_ExpectAndReturn(ESP_OK); + hcd_uninstall_ExpectAndReturn(ESP_OK); + usb_del_phy_ExpectAndReturn(phy_handle, ESP_OK); + + // Call the DUT function, expect ESP_ERR_INVALID_STATE + REQUIRE(ESP_ERR_INVALID_STATE == usb_host_install(&usb_host_config)); + } + + // Try to install the USB Host driver, with Hub driver Install error, use internal PHY + SECTION("Fail to install USB Host - Hub driver install error") { + + // Make the PHY install to pass + usb_phy_handle_t phy_handle; + usb_new_phy_ExpectAnyArgsAndReturn(ESP_OK); + usb_new_phy_ReturnThruPtr_handle_ret(&phy_handle); + + // make the HCD port install to pass + hcd_install_ExpectAnyArgsAndReturn(ESP_OK); + + // Make the USBH install to pass + usbh_install_ExpectAnyArgsAndReturn(ESP_OK); + + // Make the ENUM Driver to pass + enum_install_ExpectAnyArgsAndReturn(ESP_OK); + + // Make the HUB Driver to fail + hub_install_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_STATE); + + // goto hub_err: We must uninstall Enum driver, USBH, HCD port and PHY + enum_uninstall_ExpectAndReturn(ESP_OK); + usbh_uninstall_ExpectAndReturn(ESP_OK); + hcd_uninstall_ExpectAndReturn(ESP_OK); + usb_del_phy_ExpectAndReturn(phy_handle, ESP_OK); + + // Call the DUT function, expect ESP_ERR_INVALID_STATE + REQUIRE(ESP_ERR_INVALID_STATE == usb_host_install(&usb_host_config)); + } + + // Successfully install the USB Host driver + SECTION("Successfully install the USB Host driver") { + + // Make the PHY install to pass + usb_phy_handle_t phy_handle; + usb_new_phy_ExpectAnyArgsAndReturn(ESP_OK); + usb_new_phy_ReturnThruPtr_handle_ret(&phy_handle); + + // make the HCD port install to pass + hcd_install_ExpectAnyArgsAndReturn(ESP_OK); + + // Make the USBH install to pass + usbh_install_ExpectAnyArgsAndReturn(ESP_OK); + + // Make the ENUM Driver to pass + enum_install_ExpectAnyArgsAndReturn(ESP_OK); + + // Make the HUB Driver to pass + hub_install_ExpectAnyArgsAndReturn(ESP_OK); + + // Make hub_root_start() to pass + hub_root_start_ExpectAndReturn(ESP_OK); + + // Call the DUT function, expect ESP_OK + REQUIRE(ESP_OK == usb_host_install(&usb_host_config)); + } + } + + // USB Host config is valid, USB Host driver was successfully installed in previous test case + GIVEN("USB Host config, USB Host driver previously installed") { + + // Try to install USB Host driver again, after it has been successfully installed + SECTION("Fail to install already installed USB Host driver") { + + // Call the DUT function, expect ESP_ERR_INVALID_STATE + REQUIRE(ESP_ERR_INVALID_STATE == usb_host_install(&usb_host_config)); + } + } + +} + +SCENARIO("USB Host post-uninstall") +{ + // USB Host driver successfully installed from previous test case + GIVEN("USB Host previously installed") { + + // Uninstall successfully installed USB Host driver + SECTION("Successfully uninstall the USB Host driver") { + + // Make the hub_root_stop() to pass + hub_root_stop_ExpectAndReturn(ESP_OK); + + // Make uninstalling of all the drivers to pass + hub_uninstall_ExpectAndReturn(ESP_OK); + enum_uninstall_ExpectAndReturn(ESP_OK); + usbh_uninstall_ExpectAndReturn(ESP_OK); + hcd_uninstall_ExpectAndReturn(ESP_OK); + + // Make the usb_del_phy() to pass + usb_del_phy_ExpectAnyArgsAndReturn(ESP_OK); + + // Call the DUT function, expect ESP_OK + REQUIRE(ESP_OK == usb_host_uninstall()); + } + } + + // USB Host driver successfully uninstalled from previous test case + GIVEN("USB Host successfully uninstalled") { + + // USB Host successfully uninstalled, try to uninstall it again + SECTION("Uninstall already uninstalled USB Host driver") { + + // Call the DUT function, expect ESP_ERR_INVALID_STATE + REQUIRE(ESP_ERR_INVALID_STATE == usb_host_uninstall()); + } + } +} diff --git a/components/usb/host_test/usb_host_layer_test/pytest_usb_host_layer_linux.py b/components/usb/host_test/usb_host_layer_test/pytest_usb_host_layer_linux.py new file mode 100644 index 0000000000..15933483c1 --- /dev/null +++ b/components/usb/host_test/usb_host_layer_test/pytest_usb_host_layer_linux.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.host_test +@idf_parametrize('target', ['linux'], indirect=['target']) +def test_usb_host_host_layer_linux(dut: Dut) -> None: + dut.expect_exact('All tests passed', timeout=5) diff --git a/components/usb/host_test/usb_host_layer_test/sdkconfig.defaults b/components/usb/host_test/usb_host_layer_test/sdkconfig.defaults new file mode 100644 index 0000000000..f9bc9d6780 --- /dev/null +++ b/components/usb/host_test/usb_host_layer_test/sdkconfig.defaults @@ -0,0 +1,4 @@ + +CONFIG_IDF_TARGET="linux" +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n diff --git a/components/usb/host_test/usbh_layer_test/CMakeLists.txt b/components/usb/host_test/usbh_layer_test/CMakeLists.txt new file mode 100644 index 0000000000..60b0b79172 --- /dev/null +++ b/components/usb/host_test/usbh_layer_test/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) + +list(APPEND EXTRA_COMPONENT_DIRS + "$ENV{IDF_PATH}/tools/mocks/usb/usbh_layer_mock/usb" + + # The following line would be needed to include the freertos mock component if this test used mocked FreeRTOS. + #"$ENV{IDF_PATH}/tools/mocks/freertos/" + ) + +project(host_test_usbh_layer) diff --git a/components/usb/host_test/usbh_layer_test/README.md b/components/usb/host_test/usbh_layer_test/README.md new file mode 100644 index 0000000000..44200aed3b --- /dev/null +++ b/components/usb/host_test/usbh_layer_test/README.md @@ -0,0 +1,37 @@ +| Supported Targets | Linux | +| ----------------- | ----- | + +# Description + +This directory contains test code for `USBH layer` of USB Host stack. Namely: +* USBH public API calls to install and uninstall the USBH driver with partially mocked USB Host stack to test Linux build and Cmock run for this partial Mock +* Mocked are all layers of the USB Host stack below the USBH layer, which is used as a real component + +Tests are written using [Catch2](https://github.com/catchorg/Catch2) test framework, use CMock, so you must install Ruby on your machine to run them. + +This test directory uses freertos as a real component + +# Build + +Tests build regularly like an idf project. Currently only working on Linux machines. + +``` +idf.py --preview set-target linux +idf.py build +``` + +# Run + +The build produces an executable in the build folder. + +Just run: + +``` +idf.py monitor +``` + +or run the executable directly: + +``` +./build/host_test_usbh_layer.elf +``` \ No newline at end of file diff --git a/components/usb/host_test/usbh_layer_test/main/CMakeLists.txt b/components/usb/host_test/usbh_layer_test/main/CMakeLists.txt new file mode 100644 index 0000000000..b2e1716276 --- /dev/null +++ b/components/usb/host_test/usbh_layer_test/main/CMakeLists.txt @@ -0,0 +1,12 @@ +set(srcs) +list(APPEND srcs "test_main.cpp" + "usbh_install_unit_test.cpp" + ) + +idf_component_register(SRCS ${srcs} + REQUIRES cmock usb + WHOLE_ARCHIVE) + +# The following line would be needed to provide the 'main' function if this test used mocked FreeRTOS. +# As this test uses the real FreeRTOS implementation, we don't need Catch2 to provide 'main'. +#target_link_libraries(${COMPONENT_LIB} PRIVATE Catch2WithMain) diff --git a/components/usb/host_test/usbh_layer_test/main/idf_component.yml b/components/usb/host_test/usbh_layer_test/main/idf_component.yml new file mode 100644 index 0000000000..f7982136b9 --- /dev/null +++ b/components/usb/host_test/usbh_layer_test/main/idf_component.yml @@ -0,0 +1,2 @@ +dependencies: + espressif/catch2: "^3.4.0" diff --git a/components/usb/host_test/usbh_layer_test/main/test_main.cpp b/components/usb/host_test/usbh_layer_test/main/test_main.cpp new file mode 100644 index 0000000000..43729e8838 --- /dev/null +++ b/components/usb/host_test/usbh_layer_test/main/test_main.cpp @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +extern "C" void app_main(void) +{ + int argc = 1; + const char *argv[2] = { + "target_test_main", + NULL + }; + + auto result = Catch::Session().run(argc, argv); + if (result != 0) { + printf("Test failed with result %d\n", result); + } else { + printf("Test passed.\n"); + } + fflush(stdout); + exit(result); +} diff --git a/components/usb/host_test/usbh_layer_test/main/usbh_install_unit_test.cpp b/components/usb/host_test/usbh_layer_test/main/usbh_install_unit_test.cpp new file mode 100644 index 0000000000..7467eff3e3 --- /dev/null +++ b/components/usb/host_test/usbh_layer_test/main/usbh_install_unit_test.cpp @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "usbh.h" // Real implementation of usbh.h + +// Test all the mocked headers defined for this mock +extern "C" { +#include "Mockhcd.h" +#include "Mockusb_private.h" +} + +SCENARIO("USBH pre-uninstall") +{ + // No USBH driver previously installed + GIVEN("No USBH previously installed") { + + // Uninstall not-installed USBH driver + SECTION("Uninstalling not installed USBH driver") { + + // Call the DUT function, expect ESP_ERR_INVALID_STATE + REQUIRE(ESP_ERR_INVALID_STATE == usbh_uninstall()); + } + } +} + +SCENARIO("USBH install") +{ + // USBH config is not valid, USBH driver is not previously installed + GIVEN("No USBH config, USBH driver not installed") { + + // Try to install the USBH driver with usbh_config set to nullptr + SECTION("USBH config is nullptr") { + + // Call the DUT function, expect ESP_ERR_INVALID_ARG + REQUIRE(ESP_ERR_INVALID_ARG == usbh_install(nullptr)); + } + } + + // USBH config struct + usbh_config_t usbh_config = {}; + + // USBH config is valid, USBH driver is not previously installed + GIVEN("USBH config, USBH driver not installed") { + + // Successfully install the USBH Driver + SECTION("Successfully install the USBH Driver") { + + // Call the DUT function, expect ESP_OK + REQUIRE(ESP_OK == usbh_install(&usbh_config)); + } + } + + // USBH config is valid, USBH driver is previously installed + GIVEN("USBH config, USBH driver previously installed") { + + SECTION("Fail to install already installed USBH driver") { + + // Call the DUT function, expect ESP_ERR_INVALID_STATE + REQUIRE(ESP_ERR_INVALID_STATE == usbh_install(&usbh_config)); + } + } +} + +SCENARIO("USBH post-uninstall") +{ + + // USBH driver successfully installed from previous test case + GIVEN("USBH driver previously installed") { + + // Uninstall successfully installed USBH driver + SECTION("Successfully uninstall the USBH driver") { + + // Call the DUT function, expect ESP_OK + REQUIRE(ESP_OK == usbh_uninstall()); + } + } + + // USBH driver successfully uninstalled from previous test case + GIVEN("USBH successfully uninstalled") { + + // USBH successfully uninstalled, try to uninstall it again + SECTION("Uninstall already uninstalled USBH driver") { + + // Call the DUT function, expect ESP_ERR_INVALID_STATE + REQUIRE(ESP_ERR_INVALID_STATE == usbh_uninstall()); + } + } +} diff --git a/components/usb/host_test/usbh_layer_test/pytest_usbh_layer_linux.py b/components/usb/host_test/usbh_layer_test/pytest_usbh_layer_linux.py new file mode 100644 index 0000000000..3742598f9e --- /dev/null +++ b/components/usb/host_test/usbh_layer_test/pytest_usbh_layer_linux.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.host_test +@idf_parametrize('target', ['linux'], indirect=['target']) +def test_usbh_layer_linux(dut: Dut) -> None: + dut.expect_exact('All tests passed', timeout=5) diff --git a/components/usb/host_test/usbh_layer_test/sdkconfig.defaults b/components/usb/host_test/usbh_layer_test/sdkconfig.defaults new file mode 100644 index 0000000000..f9bc9d6780 --- /dev/null +++ b/components/usb/host_test/usbh_layer_test/sdkconfig.defaults @@ -0,0 +1,4 @@ + +CONFIG_IDF_TARGET="linux" +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n