From 38fd5421337720b8728610db62370b7c757d0c93 Mon Sep 17 00:00:00 2001 From: Roman Leonov Date: Fri, 13 Dec 2024 12:40:17 +0100 Subject: [PATCH] feature(teardown_test): Added the test_app for teardowning the cdc device --- .../test_apps/teardown_device/CMakeLists.txt | 9 + .../teardown_device/main/CMakeLists.txt | 4 + .../teardown_device/main/idf_component.yml | 5 + .../teardown_device/main/test_app_main.c | 62 +++++++ .../teardown_device/main/test_teardown.c | 164 ++++++++++++++++++ .../teardown_device/pytest_teardown_device.py | 96 ++++++++++ .../teardown_device/sdkconfig.defaults | 16 ++ 7 files changed, 356 insertions(+) create mode 100644 device/esp_tinyusb/test_apps/teardown_device/CMakeLists.txt create mode 100644 device/esp_tinyusb/test_apps/teardown_device/main/CMakeLists.txt create mode 100644 device/esp_tinyusb/test_apps/teardown_device/main/idf_component.yml create mode 100644 device/esp_tinyusb/test_apps/teardown_device/main/test_app_main.c create mode 100644 device/esp_tinyusb/test_apps/teardown_device/main/test_teardown.c create mode 100644 device/esp_tinyusb/test_apps/teardown_device/pytest_teardown_device.py create mode 100644 device/esp_tinyusb/test_apps/teardown_device/sdkconfig.defaults diff --git a/device/esp_tinyusb/test_apps/teardown_device/CMakeLists.txt b/device/esp_tinyusb/test_apps/teardown_device/CMakeLists.txt new file mode 100644 index 00000000..1645d048 --- /dev/null +++ b/device/esp_tinyusb/test_apps/teardown_device/CMakeLists.txt @@ -0,0 +1,9 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +project(test_app_teardown_device) \ No newline at end of file diff --git a/device/esp_tinyusb/test_apps/teardown_device/main/CMakeLists.txt b/device/esp_tinyusb/test_apps/teardown_device/main/CMakeLists.txt new file mode 100644 index 00000000..e81e6278 --- /dev/null +++ b/device/esp_tinyusb/test_apps/teardown_device/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRC_DIRS . + INCLUDE_DIRS . + REQUIRES unity + WHOLE_ARCHIVE) diff --git a/device/esp_tinyusb/test_apps/teardown_device/main/idf_component.yml b/device/esp_tinyusb/test_apps/teardown_device/main/idf_component.yml new file mode 100644 index 00000000..b1cb5b54 --- /dev/null +++ b/device/esp_tinyusb/test_apps/teardown_device/main/idf_component.yml @@ -0,0 +1,5 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "*" + override_path: "../../../" diff --git a/device/esp_tinyusb/test_apps/teardown_device/main/test_app_main.c b/device/esp_tinyusb/test_apps/teardown_device/main/test_app_main.c new file mode 100644 index 00000000..b1321edd --- /dev/null +++ b/device/esp_tinyusb/test_apps/teardown_device/main/test_app_main.c @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" + +void app_main(void) +{ + /* + _ _ _ + | | (_) | | + ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ + / _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \ + | __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) | + \___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/ + | |______ __/ | + |_|______| |___/ + _____ _____ _____ _____ + |_ _| ___/ ___|_ _| + | | | |__ \ `--. | | + | | | __| `--. \ | | + | | | |___/\__/ / | | + \_/ \____/\____/ \_/ + */ + + printf(" _ _ _ \n"); + printf(" | | (_) | | \n"); + printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n"); + printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n"); + printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n"); + printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n"); + printf(" | |______ __/ | \n"); + printf(" |_|______| |___/ \n"); + printf(" _____ _____ _____ _____ \n"); + printf("|_ _| ___/ ___|_ _| \n"); + printf(" | | | |__ \\ `--. | | \n"); + printf(" | | | __| `--. \\ | | \n"); + printf(" | | | |___/\\__/ / | | \n"); + printf(" \\_/ \\____/\\____/ \\_/ \n"); + + unity_utils_setup_heap_record(80); + unity_utils_set_leak_level(128); + unity_run_menu(); +} + +/* setUp runs before every test */ +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +/* tearDown runs after every test */ +void tearDown(void) +{ + unity_utils_evaluate_leaks(); +} diff --git a/device/esp_tinyusb/test_apps/teardown_device/main/test_teardown.c b/device/esp_tinyusb/test_apps/teardown_device/main/test_teardown.c new file mode 100644 index 00000000..1dd9be70 --- /dev/null +++ b/device/esp_tinyusb/test_apps/teardown_device/main/test_teardown.c @@ -0,0 +1,164 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" +// #if SOC_USB_OTG_SUPPORTED + +// +#include +#include +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +// +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +// +#include "unity.h" +#include "tinyusb.h" +#include "tusb_cdc_acm.h" + +static const char *TAG = "teardown_test"; +static const char *TEARDOWN_CMD = "teardown"; +// DElay between device connection, required for the Host to handle device disconnection/connection without errors +#define TEARDOWN_DELAY_MS 2000 +#define TEARDOWN_AMOUNT 10 + +static const tusb_desc_device_t cdc_device_descriptor = { + .bLength = sizeof(cdc_device_descriptor), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = USB_ESPRESSIF_VID, + .idProduct = 0x4002, + .bcdDevice = 0x0100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01 +}; + +static const uint16_t cdc_desc_config_len = TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN; +static const uint8_t cdc_desc_configuration[] = { + TUD_CONFIG_DESCRIPTOR(1, 4, 0, cdc_desc_config_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + TUD_CDC_DESCRIPTOR(0, 4, 0x81, 8, 0x02, 0x82, (TUD_OPT_HIGH_SPEED ? 512 : 64)), +}; + +#if (TUD_OPT_HIGH_SPEED) +static const tusb_desc_device_qualifier_t device_qualifier = { + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0 +}; +#endif // TUD_OPT_HIGH_SPEED + +static QueueHandle_t rx_queue; +static uint8_t rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE + 1]; + +typedef struct { + int itf; // Interface number + uint8_t data[CONFIG_TINYUSB_CDC_RX_BUFSIZE + 1]; // Data buffer + size_t len; // Number of bytes received +} rx_pkt_t; + +static void test_rx_enqueue_pkt(int itf, uint8_t *buf, size_t size) +{ + rx_pkt_t rx_pkt = { + .itf = itf, + .len = size, + }; + memcpy(rx_pkt.data, buf, size); + xQueueSend(rx_queue, &rx_pkt, 0); +} + +static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) +{ + size_t rx_size = 0; + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_cdcacm_read(itf, rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size)); + test_rx_enqueue_pkt(itf, rx_buf, rx_size); +} + +/** + * @brief TinyUSB Teardown specific testcase + */ +TEST_CASE("tinyusb_teardown", "[esp_tinyusb][teardown]") +{ + rx_pkt_t rx_pkt; + // Create FreeRTOS primitives + rx_queue = xQueueCreate(5, sizeof(rx_pkt_t)); + TEST_ASSERT(rx_queue); + + // TinyUSB driver configuration + const tinyusb_config_t tusb_cfg = { + .device_descriptor = &cdc_device_descriptor, + .string_descriptor = NULL, + .string_descriptor_count = 0, + .external_phy = false, +#if (TUD_OPT_HIGH_SPEED) + .fs_configuration_descriptor = cdc_desc_configuration, + .hs_configuration_descriptor = cdc_desc_configuration, + .qualifier_descriptor = &device_qualifier, +#else + .configuration_descriptor = cdc_desc_configuration, +#endif // TUD_OPT_HIGH_SPEED + }; + + // TinyUSB ACM Driver configuration + tinyusb_config_cdcacm_t acm_cfg = { + .usb_dev = TINYUSB_USBDEV_0, + .cdc_port = TINYUSB_CDC_ACM_0, + .rx_unread_buf_sz = 64, + .callback_rx = &tinyusb_cdc_rx_callback, + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = NULL, + .callback_line_coding_changed = NULL + }; + int attempts = TEARDOWN_AMOUNT; + while (1) { + vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DELAY_MS)); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg)); + // Init CDC 0 + TEST_ASSERT_FALSE(tusb_cdc_acm_initialized(TINYUSB_CDC_ACM_0)); + TEST_ASSERT_EQUAL(ESP_OK, tusb_cdc_acm_init(&acm_cfg)); + TEST_ASSERT_TRUE(tusb_cdc_acm_initialized(TINYUSB_CDC_ACM_0)); + + // Wait for the pytest poke + while (1) { + if (xQueueReceive(rx_queue, &rx_pkt, portMAX_DELAY)) { + if (rx_pkt.len) { + /* echoed back */ + TEST_ASSERT_EQUAL(rx_pkt.len, tinyusb_cdcacm_write_queue(rx_pkt.itf, rx_pkt.data, rx_pkt.len)); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_cdcacm_write_flush(rx_pkt.itf, pdMS_TO_TICKS(100))); + if (strncmp(TEARDOWN_CMD, (const char *) rx_pkt.data, rx_pkt.len) == 0) { + // Wait for the host + attempts--; + break; + } + } + } + } + vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DELAY_MS)); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_cdcacm_unregister_callback(TINYUSB_CDC_ACM_0, CDC_EVENT_RX)); + TEST_ASSERT_EQUAL(ESP_OK, tusb_cdc_acm_deinit(TINYUSB_CDC_ACM_0)); + TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall()); + } + // Remove primitives + vQueueDelete(rx_queue); + // All attempts should be completed + TEST_ASSERT_EQUAL(0, attempts); +} + +// #endif diff --git a/device/esp_tinyusb/test_apps/teardown_device/pytest_teardown_device.py b/device/esp_tinyusb/test_apps/teardown_device/pytest_teardown_device.py new file mode 100644 index 00000000..573159e5 --- /dev/null +++ b/device/esp_tinyusb/test_apps/teardown_device/pytest_teardown_device.py @@ -0,0 +1,96 @@ +# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from pytest_embedded_idf.dut import IdfDut +import serial +import serial.tools.list_ports +import time +from time import sleep + + +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32p4 +@pytest.mark.usb_device + +def find_serial_port_by_vid_pid(vid, pid): + """ + Find a serial port by its VID and PID. + """ + ports = serial.tools.list_ports.comports() + for port in ports: + if port.vid == vid and port.pid == pid: + return port.device + return None + +def wait_for_device(vid, pid, timeout=30): + """ + Wait for a device with the specified VID and PID to appear. + """ + start_time = time.time() + while time.time() - start_time < timeout: + serial_port = find_serial_port_by_vid_pid(vid, pid) + if serial_port: + return serial_port + time.sleep(0.5) # Check every 0.5 seconds + return None + +def teardown(): + TARGET_VID = 0x303A # Espressif TinyUSB VID + TARGET_PID = 0x4002 # Espressif TinyUSB VID + + # Command to send and expected response + COMMAND = "teardown" + EXPECTED_RESPONSE = "teardown" + + # Number of iterations, must be equal to ITERATIONS in the test application + ITERATIONS = 10 + + for i in range(ITERATIONS): + print(f"Iteration {i+1} of {ITERATIONS}") + + # Wait for the device to appear + print("Waiting for the device to connect...") + serial_port = wait_for_device(TARGET_VID, TARGET_PID) + if not serial_port: + print("Error: Device did not appear within the timeout period.") + break + + try: + # Open the serial port + with serial.Serial(port=serial_port, baudrate=9600, timeout=1) as set: + print(f"Opened port: {serial_port}") + + # Send the 'teardown' command + set.write(COMMAND.encode('utf-8')) + print(f"Sent command: {COMMAND.strip()}") + + # Wait for the response + response = set.readline().decode('utf-8').strip() + print(f"Received response: {response}") + + # Check if the response matches the expected response + if response == EXPECTED_RESPONSE: + print("Response matches expected value.") + else: + print("Error: Response does not match expected value.") + + except serial.SerialException as e: + print(f"Error communicating with the serial port: {e}") + break + + # Wait for the device to disconnect + print("Waiting for the device to disconnect...") + while find_serial_port_by_vid_pid(TARGET_VID, TARGET_PID): + time.sleep(0.5) # Check every 0.5 seconds + + print("Finished all iterations.") + +def test_usb_teardown_device(dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests.') + dut.write('[teardown]') + dut.expect_exact('TinyUSB: TinyUSB Driver installed') + sleep(2) # Some time for the OS to enumerate our USB device + teardown() + diff --git a/device/esp_tinyusb/test_apps/teardown_device/sdkconfig.defaults b/device/esp_tinyusb/test_apps/teardown_device/sdkconfig.defaults new file mode 100644 index 00000000..e4377094 --- /dev/null +++ b/device/esp_tinyusb/test_apps/teardown_device/sdkconfig.defaults @@ -0,0 +1,16 @@ +# Configure TinyUSB, it will be used to mock USB devices +CONFIG_TINYUSB_CDC_ENABLED=y +CONFIG_TINYUSB_CDC_COUNT=1 + +# Disable watchdogs, they'd get triggered during unity interactive menu +CONFIG_ESP_INT_WDT=n +CONFIG_ESP_TASK_WDT=n + +# Run-time checks of Heap and Stack +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y + +CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y + +CONFIG_COMPILER_CXX_EXCEPTIONS=y