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 31, 2024
1 parent fbf07ac commit 8549d81
Show file tree
Hide file tree
Showing 20 changed files with 413 additions and 36 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));
}
}
}
44 changes: 22 additions & 22 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 @@ -293,40 +293,40 @@ static bool hid_interface_present(const usb_config_desc_t *config_desc)
/**
* @brief HID Interface user callback function.
*
* @param[in] hid_iface Pointer to an Interface structure
* @param[in] event_id HID Interface event
* @param[in] iface Pointer to an Interface structure
* @param[in] event HID Interface event
*/
static inline void hid_host_user_interface_callback(hid_iface_t *hid_iface,
static inline void hid_host_user_interface_callback(hid_iface_t *iface,
const hid_host_interface_event_t event)
{
assert(hid_iface);
assert(iface);

hid_host_dev_params_t *dev_params = &hid_iface->dev_params;
hid_host_dev_params_t *dev_params = &iface->dev_params;

assert(dev_params);

if (hid_iface->user_cb) {
hid_iface->user_cb(hid_iface, event, hid_iface->user_cb_arg);
if (iface->user_cb) {
iface->user_cb(iface, event, iface->user_cb_arg);
}
}

/**
* @brief HID Device user callback function.
*
* @param[in] event_id HID Device event
* @param[in] dev_params HID Device parameters
* @param[in] iface Pointer to an Interface structure
* @param[in] event HID Device event
*/
static inline void hid_host_user_device_callback(hid_iface_t *hid_iface,
static inline void hid_host_user_device_callback(hid_iface_t *iface,
const hid_host_driver_event_t event)
{
assert(hid_iface);
assert(iface);

hid_host_dev_params_t *dev_params = &hid_iface->dev_params;
hid_host_dev_params_t *dev_params = &iface->dev_params;

assert(dev_params);

if (s_hid_driver && s_hid_driver->user_cb) {
s_hid_driver->user_cb(hid_iface, event, s_hid_driver->user_arg);
s_hid_driver->user_cb(iface, event, s_hid_driver->user_arg);
}
}

Expand Down Expand Up @@ -393,14 +393,14 @@ static esp_err_t hid_host_add_interface(hid_device_t *hid_device,
*
* Use only inside critical section
*
* @param[in] hid_iface HID interface handle
* @param[in] iface HID interface handle
* @return esp_err_t
*/
static esp_err_t _hid_host_remove_interface(hid_iface_t *hid_iface)
static esp_err_t _hid_host_remove_interface(hid_iface_t *iface)
{
hid_iface->state = HID_INTERFACE_STATE_NOT_INITIALIZED;
STAILQ_REMOVE(&s_hid_driver->hid_ifaces_tailq, hid_iface, hid_interface, tailq_entry);
free(hid_iface);
iface->state = HID_INTERFACE_STATE_NOT_INITIALIZED;
STAILQ_REMOVE(&s_hid_driver->hid_ifaces_tailq, iface, hid_interface, tailq_entry);
free(iface);
return ESP_OK;
}

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] 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: "../../"
Loading

0 comments on commit 8549d81

Please sign in to comment.