Skip to content

Commit

Permalink
feat(cmock): Enable linux target build to run Cmock tests on class dr…
Browse files Browse the repository at this point in the history
…ivers

    - HID, CDC-ACM, UVC class drivers can be build on linux target
    - Added linux build test and simple Cmock test run in CI
  • Loading branch information
peter-marcisovsky committed Oct 29, 2024
1 parent fbf07ac commit 74598f7
Show file tree
Hide file tree
Showing 20 changed files with 395 additions and 18 deletions.
9 changes: 9 additions & 0 deletions .build-test-rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ host/class:
enable:
- if: SOC_USB_OTG_SUPPORTED == 1

# Host tests
host/class/cdc/usb_host_cdc_acm/host_test:
enable:
- if: IDF_TARGET in ["linux"] and (IDF_VERSION_MAJOR >= 5 and IDF_VERSION_MINOR >= 4)

host/class/hid/usb_host_hid/host_test:
enable:
- if: IDF_TARGET in ["linux"] and (IDF_VERSION_MAJOR >= 5 and IDF_VERSION_MINOR >= 4)

host/class/uvc/usb_host_uvc/host_test:
enable:
- if: IDF_TARGET in ["linux"] and (IDF_VERSION_MAJOR >= 5 and IDF_VERSION_MINOR >= 4)
1 change: 1 addition & 0 deletions host/class/cdc/usb_host_cdc_acm/host_test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

This directory contains test code for `USB Host CDC-ACM` driver. Namely:
* Descriptor parsing
* Simple public API call with mocked USB component to test Linux build and Cmock run for this class driver

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.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@

/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <stdio.h>
#include <catch2/catch_test_macros.hpp>

#include "usb/cdc_acm_host.h"

extern "C" {
#include "Mockusb_host.h"
#include "Mockqueue.h"
#include "Mocktask.h"
#include "Mockidf_additions.h"
#include "Mockportmacro.h"
#include "Mockevent_groups.h"
}

SCENARIO("CDC-ACM Host install")
{
// CDC-ACM Host driver config set to nullptr
GIVEN("NO CDC-ACM Host driver config, driver not installed") {
TaskHandle_t task_handle;
int sem;
int event_group;

// Call cdc_acm_host_install with cdc_acm_host_driver_config set to nullptr, fail to create EventGroup
SECTION("Fail to create EventGroup") {
// Create an EventGroup, return nullptr, so the EventGroup is not created successfully
xEventGroupCreate_ExpectAndReturn(nullptr);
// We should be calling xSemaphoreCreteMutex_ExpectAnyArgsAndRetrun instead of xQueueCreateMutex_ExpectAnyArgsAndReturn
// Because of missing Freertos Mocks
// Create a semaphore, return the semaphore handle (Queue Handle in this scenario), so the semaphore is created successfully
xQueueCreateMutex_ExpectAnyArgsAndReturn(reinterpret_cast<QueueHandle_t>(&sem));
// Create a task, return pdTRUE, so the task is created successfully
xTaskCreatePinnedToCore_ExpectAnyArgsAndReturn(pdTRUE);
// Return task handle by pointer
xTaskCreatePinnedToCore_ReturnThruPtr_pxCreatedTask(&task_handle);

// goto err: (xEventGroupCreate returned nullptr), delete the queue and the task
vQueueDelete_Expect(reinterpret_cast<QueueHandle_t>(&sem));
vTaskDelete_Expect(task_handle);

// Call the DUT function, expect ESP_ERR_NO_MEM
REQUIRE(ESP_ERR_NO_MEM == cdc_acm_host_install(nullptr));
}

// Call cdc_acm_host_install, expect to successfully install the CDC ACM host
SECTION("Successfully install CDC ACM Host") {
// Create an EventGroup, return event group handle, so the EventGroup is created successfully
xEventGroupCreate_ExpectAndReturn(reinterpret_cast<EventGroupHandle_t>(&event_group));
// We should be calling xSemaphoreCreteMutex_ExpectAnyArgsAndRetrun instead of xQueueCreateMutex_ExpectAnyArgsAndReturn
// Because of missing Freertos Mocks
// Create a semaphore, return the semaphore handle (Queue Handle in this scenario), so the semaphore is created successfully
xQueueCreateMutex_ExpectAnyArgsAndReturn(reinterpret_cast<QueueHandle_t>(&sem));
// Create a task, return pdTRUE, so the task is created successfully
vPortEnterCritical_Expect();
vPortExitCritical_Expect();
xTaskCreatePinnedToCore_ExpectAnyArgsAndReturn(pdTRUE);
// Return task handle by pointer
xTaskCreatePinnedToCore_ReturnThruPtr_pxCreatedTask(&task_handle);

// Call mocked function from USB Host
// return ESP_OK, so the client si registered successfully
usb_host_client_register_ExpectAnyArgsAndReturn(ESP_OK);

// Resume the task
vTaskResume_Expect(task_handle);

// Call the DUT Function, expect ESP_OK
REQUIRE(ESP_OK == cdc_acm_host_install(nullptr));
}
}
}
8 changes: 4 additions & 4 deletions host/class/hid/usb_host_hid/hid_host.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ typedef struct hid_class_request {
static void event_handler_task(void *arg)
{
ESP_LOGD(TAG, "USB HID handling start");
while (hid_host_handle_events(portMAX_DELAY) == ESP_OK) {
while (hid_host_handle_events((uint32_t)portMAX_DELAY) == ESP_OK) {
}
ESP_LOGD(TAG, "USB HID handling stop");
vTaskDelete(NULL);
Expand Down Expand Up @@ -765,7 +765,7 @@ static esp_err_t hid_control_transfer(hid_device_t *hid_device,
/**
* @brief USB class standard request get descriptor
*
* @param[in] hidh_device Pointer to HID device structure
* @param[in] hid_device Pointer to HID device structure
* @param[in] req Pointer to a class specific request structure
* @return esp_err_t
*/
Expand All @@ -788,7 +788,7 @@ static esp_err_t usb_class_request_get_descriptor(hid_device_t *hid_device, cons
ESP_ERROR_CHECK(usb_host_device_info(hid_device->dev_hdl, &dev_info));
// reallocate the ctrl xfer buffer for new length
ESP_LOGD(TAG, "Change HID ctrl xfer size from %d to %d",
ctrl_size,
(int) ctrl_size,
(int) (USB_SETUP_PACKET_SIZE + req->wLength));

usb_host_transfer_free(hid_device->ctrl_xfer);
Expand Down Expand Up @@ -829,7 +829,7 @@ static esp_err_t usb_class_request_get_descriptor(hid_device_t *hid_device, cons
/**
* @brief HID Host Request Report Descriptor
*
* @param[in] hidh_iface Pointer to HID Interface configuration structure
* @param[in] hid_iface Pointer to HID Interface configuration structure
* @return esp_err_t
*/
static esp_err_t hid_class_request_report_descriptor(hid_iface_t *iface)
Expand Down
11 changes: 11 additions & 0 deletions host/class/hid/usb_host_hid/host_test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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/"
"$ENV{IDF_PATH}/tools/mocks/freertos/"
)

project(host_test_usb_hid)
30 changes: 30 additions & 0 deletions host/class/hid/usb_host_hid/host_test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
| Supported Targets | Linux |
| ----------------- | ----- |

# Description

This directory contains test code for `USB Host HID` driver. Namely:
* Simple public API call with mocked USB component to test Linux build and Cmock run for this class driver

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.

# 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:

```
./build/host_test_usb_hid.elf
```

The test executable have some options provided by the test framework.
8 changes: 8 additions & 0 deletions host/class/hid/usb_host_hid/host_test/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
idf_component_register(SRC_DIRS .
REQUIRES cmock usb
INCLUDE_DIRS .
WHOLE_ARCHIVE)

# Currently 'main' for IDF_TARGET=linux is defined in freertos component.
# Since we are using a freertos mock here, need to let Catch2 provide 'main'.
target_link_libraries(${COMPONENT_LIB} PRIVATE Catch2WithMain)
5 changes: 5 additions & 0 deletions host/class/hid/usb_host_hid/host_test/main/idf_component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dependencies:
espressif/catch2: "^3.4.0"
usb_host_hid:
version: "*"
override_path: "../../"
145 changes: 145 additions & 0 deletions host/class/hid/usb_host_hid/host_test/main/test_unit_public_api.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <stdio.h>
#include <catch2/catch_test_macros.hpp>

#include "usb/hid_host.h"
#include "usb/hid.h"

extern "C" {
#include "Mockusb_host.h"
#include "Mockqueue.h"
#include "Mocktask.h"
#include "Mockidf_additions.h"
#include "Mockportmacro.h"
}

SCENARIO("HID Host install")
{
hid_host_driver_config_t hid_host_driver_config = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 4096,
.core_id = 0,
.callback = (reinterpret_cast<hid_host_driver_event_cb_t>(0xdeadbeef)),
.callback_arg = nullptr
};

// HID Host driver config set to nullptr
GIVEN("NO HID Host driver config, driver not already installed") {

// Call hid_host_install with hid_host_driver_config set to nullptr
SECTION("Config is nullptr") {
REQUIRE(ESP_ERR_INVALID_ARG == hid_host_install(nullptr));
}
}

// HID Host driver config set to config from HID Host example, but without callback
GIVEN("Minimal HID Host driver config, driver not already installed") {
hid_host_driver_config.callback = nullptr;

SECTION("Config error: no callback") {
// Call the DUT function, expect ESP_ERR_INVALID_ARG
REQUIRE(ESP_ERR_INVALID_ARG == hid_host_install(&hid_host_driver_config));
}

SECTION("Config error: stack size is 0") {
hid_host_driver_config.stack_size = 0;
// Call the DUT function, expect ESP_ERR_INVALID_ARG
REQUIRE(ESP_ERR_INVALID_ARG == hid_host_install(&hid_host_driver_config));
}

SECTION("Config error: task priority is 0") {
hid_host_driver_config.task_priority = 0;
// Call the DUT function, expect ESP_ERR_INVALID_ARG
REQUIRE(ESP_ERR_INVALID_ARG == hid_host_install(&hid_host_driver_config));
}
}

// HID Host driver config set to config from HID Host example
GIVEN("Full HID Host config, driver not already installed") {
int mtx;

// Install error: failed to create semaphore
SECTION("Install error: unable to create semaphore") {
// We must call xQueueGenericCreate_ExpectAnyArgsAndReturn instead of xSemaphoreCreateBinary_ExpectAnyArgsAndReturn
// Because of missing Freertos Mocks
// Create a semaphore, return nullptr, so the semaphore is not created successfully
xQueueGenericCreate_ExpectAnyArgsAndReturn(nullptr);

// Call the DUT function, expect ESP_ERR_NO_MEM
REQUIRE(ESP_ERR_NO_MEM == hid_host_install(&hid_host_driver_config));
}

// Unable to register client: Invalid state, goto fail
SECTION("Client register not successful: goto fail") {
// We must call xQueueGenericCreate_ExpectAnyArgsAndReturn instead of xSemaphoreCreateBinary_ExpectAnyArgsAndReturn
// Because of missing Freertos Mocks
// Create a semaphore, return the semaphore handle (Queue Handle in this scenario), so the semaphore is created successfully
xQueueGenericCreate_ExpectAnyArgsAndReturn(reinterpret_cast<QueueHandle_t>(&mtx));
// Register a client, return ESP_ERR_INVALID_STATE, so the client is not registered successfully
usb_host_client_register_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_STATE);

// goto fail: delete the semaphore (Queue in this scenario)
vQueueDelete_Expect(reinterpret_cast<QueueHandle_t>(&mtx));

// Call the DUT function, expect ESP_ERR_INVALID_STATE
REQUIRE(ESP_ERR_INVALID_STATE == hid_host_install(&hid_host_driver_config));
}

SECTION("Client register successful: create background task fail") {
usb_host_client_handle_t client_handle;

// Create a semaphore, return the semaphore handle (Queue Handle in this scenario), so the semaphore is created successfully
xQueueGenericCreate_ExpectAnyArgsAndReturn(reinterpret_cast<QueueHandle_t>(&mtx));
// Register a client, return ESP_OK, so the client is registered successfully
usb_host_client_register_ExpectAnyArgsAndReturn(ESP_OK);
// Fill the pointer to the client_handle, which is used in goto fail, to deregister the client
usb_host_client_register_ReturnThruPtr_client_hdl_ret(&client_handle);

// Create a background task, return pdFALSE, so the task in not created successfully
vPortEnterCritical_Expect();
vPortExitCritical_Expect();
xTaskCreatePinnedToCore_ExpectAnyArgsAndReturn(pdFALSE);

// goto fail: delete the semaphore and deregister the client
// usb_host_client_deregister is not being checked for the return value !! Consider adding it to the host driver
usb_host_client_deregister_ExpectAndReturn(client_handle, ESP_OK);
// Delete the semaphore (Queue in this scenario)
vQueueDelete_Expect(reinterpret_cast<QueueHandle_t>(&mtx));

// Call the DUT function, expect ESP_ERR_NO_MEM
REQUIRE(ESP_ERR_NO_MEM == hid_host_install(&hid_host_driver_config));
}

// Call hid_host_install and expect successful installation
SECTION("Client register successful: hid_host_install successful") {
// Create semaphore, return semaphore handle (Queue Handle in this scenario), so the semaphore is created successfully
xQueueGenericCreate_ExpectAnyArgsAndReturn(reinterpret_cast<QueueHandle_t>(&mtx));
// return ESP_OK, so the client is registered successfully
usb_host_client_register_ExpectAnyArgsAndReturn(ESP_OK);

// Create a background task successfully by returning pdTRUE
vPortEnterCritical_Expect();
vPortExitCritical_Expect();
xTaskCreatePinnedToCore_ExpectAnyArgsAndReturn(pdTRUE);

// Call the DUT function, expect ESP_OK
REQUIRE(ESP_OK == hid_host_install(&hid_host_driver_config));
}
}

// HID Host driver config set to config from HID Host example
GIVEN("Full HID Host config, driver already installed") {

// Driver is already installed
SECTION("Install error: driver already installed") {
// Call the DUT function again to get the ESP_ERR_INVALID_STATE error
REQUIRE(ESP_ERR_INVALID_STATE == hid_host_install(&hid_host_driver_config));
}
}
}
8 changes: 8 additions & 0 deletions host/class/hid/usb_host_hid/host_test/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration
#
CONFIG_IDF_TARGET="linux"
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_ESP_MAIN_TASK_STACK_SIZE=12000
CONFIG_FREERTOS_HZ=1000
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
4 changes: 0 additions & 4 deletions host/class/hid/usb_host_hid/idf_component.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,3 @@ description: USB Host HID driver
url: https://github.com/espressif/esp-usb/tree/master/host/class/hid/usb_host_hid
dependencies:
idf: ">=4.4"
targets:
- esp32s2
- esp32s3
- esp32p4
11 changes: 11 additions & 0 deletions host/class/uvc/usb_host_uvc/host_test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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/"
"$ENV{IDF_PATH}/tools/mocks/freertos/"
)

project(host_test_usb_uvc)
Loading

0 comments on commit 74598f7

Please sign in to comment.