feat(usb_host): Host tests for individual USB Host layers

- Public API CMock based Host tests
    - USB Host layer: driver install/uninstall unit test
    - USBH layer: driver install/uninstall unit test
    - Host tests are run in CI
This commit is contained in:
peter.marcisovsky
2025-04-23 12:09:25 +02:00
parent 18ae6c38a9
commit 6dfb7269a1
17 changed files with 567 additions and 0 deletions
@@ -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)
@@ -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
```
@@ -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)
@@ -0,0 +1,2 @@
dependencies:
espressif/catch2: "^3.4.0"
@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <catch2/catch_session.hpp>
#include <catch2/catch_test_macros.hpp>
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);
}
@@ -0,0 +1,94 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <catch2/catch_test_macros.hpp>
#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());
}
}
}
@@ -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)
@@ -0,0 +1,4 @@
CONFIG_IDF_TARGET="linux"
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n