Skip to content

Commit a444bcb

Browse files
committed
feature(teardown_test): Added the test_app for teardowning the cdc device
1 parent c5f34d5 commit a444bcb

File tree

8 files changed

+317
-0
lines changed

8 files changed

+317
-0
lines changed

.github/workflows/build_and_run_test_app_usb.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ jobs:
6969
- uses: actions/download-artifact@v4
7070
with:
7171
name: usb_test_app_bin_${{ matrix.idf_ver }}
72+
- name: ⚙️ Install System tools
73+
run: |
74+
apt update
75+
apt install -y usbutils
7276
- name: Install Python packages
7377
env:
7478
PIP_EXTRA_INDEX_URL: "https://dl.espressif.com/pypi/"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# The following lines of boilerplate have to be in your project's
2+
# CMakeLists in this exact order for cmake to work correctly
3+
cmake_minimum_required(VERSION 3.16)
4+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
5+
6+
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
7+
set(COMPONENTS main)
8+
9+
project(test_app_teardown_device)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
idf_component_register(SRC_DIRS .
2+
INCLUDE_DIRS .
3+
REQUIRES unity
4+
WHOLE_ARCHIVE)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## IDF Component Manager Manifest File
2+
dependencies:
3+
espressif/esp_tinyusb:
4+
version: "*"
5+
override_path: "../../../"
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stdio.h>
8+
#include <string.h>
9+
#include "unity.h"
10+
#include "unity_test_runner.h"
11+
#include "unity_test_utils_memory.h"
12+
13+
void app_main(void)
14+
{
15+
/*
16+
_ _ _
17+
| | (_) | |
18+
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
19+
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
20+
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
21+
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
22+
| |______ __/ |
23+
|_|______| |___/
24+
_____ _____ _____ _____
25+
|_ _| ___/ ___|_ _|
26+
| | | |__ \ `--. | |
27+
| | | __| `--. \ | |
28+
| | | |___/\__/ / | |
29+
\_/ \____/\____/ \_/
30+
*/
31+
32+
printf(" _ _ _ \n");
33+
printf(" | | (_) | | \n");
34+
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
35+
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
36+
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
37+
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
38+
printf(" | |______ __/ | \n");
39+
printf(" |_|______| |___/ \n");
40+
printf(" _____ _____ _____ _____ \n");
41+
printf("|_ _| ___/ ___|_ _| \n");
42+
printf(" | | | |__ \\ `--. | | \n");
43+
printf(" | | | __| `--. \\ | | \n");
44+
printf(" | | | |___/\\__/ / | | \n");
45+
printf(" \\_/ \\____/\\____/ \\_/ \n");
46+
47+
unity_utils_setup_heap_record(80);
48+
unity_utils_set_leak_level(128);
49+
unity_run_menu();
50+
}
51+
52+
/* setUp runs before every test */
53+
void setUp(void)
54+
{
55+
unity_utils_record_free_mem();
56+
}
57+
58+
/* tearDown runs after every test */
59+
void tearDown(void)
60+
{
61+
unity_utils_evaluate_leaks();
62+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include "soc/soc_caps.h"
8+
#if SOC_USB_OTG_SUPPORTED
9+
10+
//
11+
#include <stdio.h>
12+
#include <string.h>
13+
//
14+
#include "freertos/FreeRTOS.h"
15+
#include "freertos/task.h"
16+
#include "freertos/semphr.h"
17+
//
18+
#include "esp_system.h"
19+
#include "esp_log.h"
20+
#include "esp_err.h"
21+
//
22+
#include "unity.h"
23+
#include "tinyusb.h"
24+
#include "tusb_cdc_acm.h"
25+
26+
static const char *TAG = "teardown";
27+
28+
SemaphoreHandle_t wait_mount = NULL;
29+
30+
#define TEARDOWN_DEVICE_INIT_DELAY_MS 1000
31+
#define TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS 1000
32+
#define TEARDOWN_DEVICE_DETACH_DELAY_MS 1000
33+
34+
#define TEARDOWN_AMOUNT 10
35+
36+
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN)
37+
static uint8_t const test_configuration_descriptor[] = {
38+
// Config number, interface count, string index, total length, attribute, power in mA
39+
TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
40+
};
41+
42+
static const tusb_desc_device_t test_device_descriptor = {
43+
.bLength = sizeof(test_device_descriptor),
44+
.bDescriptorType = TUSB_DESC_DEVICE,
45+
.bcdUSB = 0x0200,
46+
.bDeviceClass = TUSB_CLASS_MISC,
47+
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
48+
.bDeviceProtocol = MISC_PROTOCOL_IAD,
49+
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
50+
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
51+
.idProduct = 0x4002,
52+
.bcdDevice = 0x100,
53+
.iManufacturer = 0x01,
54+
.iProduct = 0x02,
55+
.iSerialNumber = 0x03,
56+
.bNumConfigurations = 0x01
57+
};
58+
59+
#if (TUD_OPT_HIGH_SPEED)
60+
static const tusb_desc_device_qualifier_t device_qualifier = {
61+
.bLength = sizeof(tusb_desc_device_qualifier_t),
62+
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
63+
.bcdUSB = 0x0200,
64+
.bDeviceClass = TUSB_CLASS_MISC,
65+
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
66+
.bDeviceProtocol = MISC_PROTOCOL_IAD,
67+
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
68+
.bNumConfigurations = 0x01,
69+
.bReserved = 0
70+
};
71+
#endif // TUD_OPT_HIGH_SPEED
72+
73+
// Invoked when device is mounted
74+
void tud_mount_cb(void)
75+
{
76+
xSemaphoreGive(wait_mount);
77+
}
78+
79+
/**
80+
* @brief TinyUSB Teardown specific testcase
81+
*
82+
* Scenario:
83+
* 1. Install TinyUSB device without any class
84+
* 2. Wait SetConfiguration() (tud_mount_cb)
85+
* 3. If attempts == 0 goto step 8
86+
* 4. Wait TEARDOWN_DEVICE_DETACH_DELAY_MS
87+
* 5. Uninstall TinyUSB device
88+
* 6. Wait TEARDOWN_DEVICE_INIT_DELAY_MS
89+
* 7. Decrease attempts by 1, goto step 3
90+
* 8. Wait TEARDOWN_DEVICE_DETACH_DELAY_MS
91+
* 9. Uninstall TinyUSB device
92+
*/
93+
TEST_CASE("tinyusb_teardown", "[esp_tinyusb][teardown]")
94+
{
95+
wait_mount = xSemaphoreCreateBinary();
96+
TEST_ASSERT_NOT_EQUAL(NULL, wait_mount);
97+
98+
// TinyUSB driver configuration
99+
const tinyusb_config_t tusb_cfg = {
100+
.device_descriptor = &test_device_descriptor,
101+
.string_descriptor = NULL,
102+
.string_descriptor_count = 0,
103+
.external_phy = false,
104+
#if (TUD_OPT_HIGH_SPEED)
105+
.fs_configuration_descriptor = test_configuration_descriptor,
106+
.hs_configuration_descriptor = test_configuration_descriptor,
107+
.qualifier_descriptor = &device_qualifier,
108+
#else
109+
.configuration_descriptor = test_configuration_descriptor,
110+
#endif // TUD_OPT_HIGH_SPEED
111+
};
112+
113+
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
114+
// Wait for the usb event
115+
ESP_LOGD(TAG, "wait mount...");
116+
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS)));
117+
ESP_LOGD(TAG, "mounted");
118+
119+
// Teardown routine
120+
int attempts = TEARDOWN_AMOUNT;
121+
while (attempts--) {
122+
// Keep device attached
123+
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DETACH_DELAY_MS));
124+
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
125+
// Teardown
126+
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_INIT_DELAY_MS));
127+
// Reconnect
128+
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
129+
// Wait for the usb event
130+
ESP_LOGD(TAG, "wait mount...");
131+
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS)));
132+
ESP_LOGD(TAG, "mounted");
133+
}
134+
135+
// Teardown
136+
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DETACH_DELAY_MS));
137+
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
138+
// Remove primitives
139+
vSemaphoreDelete(wait_mount);
140+
}
141+
142+
#endif
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import pytest
5+
from pytest_embedded_idf.dut import IdfDut
6+
import subprocess
7+
from time import sleep, time
8+
9+
class DeviceNotFoundError(Exception):
10+
"""Custom exception for device not found within the timeout period."""
11+
pass
12+
13+
def tusb_dev_in_list(vid, pid):
14+
try:
15+
output = subprocess.check_output(["lsusb"], text=True)
16+
search_string = f"{vid}:{pid}"
17+
return search_string in output
18+
except Exception as e:
19+
print(f"Error while executing lsusb: {e}")
20+
raise
21+
22+
def wait_tusb_dev_appeared(vid, pid, timeout):
23+
start_time = time()
24+
while True:
25+
if tusb_dev_in_list(vid, pid):
26+
return True
27+
if time() - start_time > timeout:
28+
raise DeviceNotFoundError(f"Device with VID: 0x{vid:04x}, PID: 0x{pid:04x} not found within {timeout} seconds.")
29+
sleep(0.5)
30+
31+
def wait_tusb_dev_removed(vid, pid, timeout):
32+
start_time = time()
33+
while True:
34+
if not tusb_dev_in_list(vid, pid):
35+
return True
36+
if time() - start_time > timeout:
37+
raise DeviceNotFoundError(f"Device with VID: 0x{vid:04x}, PID: 0x{pid:04x} wasn't removed within {timeout} seconds.")
38+
sleep(0.5)
39+
40+
def tusb_device_teardown(iterations, timeout):
41+
TUSB_VID = "303a" # Espressif TinyUSB VID
42+
TUSB_PID = "4002" # Espressif TinyUSB VID
43+
44+
for i in range(iterations):
45+
# Wait until the device is present
46+
print(f"Waiting for device ...")
47+
wait_tusb_dev_appeared(TUSB_VID, TUSB_PID, timeout)
48+
print("Device detected.")
49+
50+
# Wait until the device is removed
51+
print("Waiting for the device to be removed...")
52+
wait_tusb_dev_removed(TUSB_VID, TUSB_PID, timeout)
53+
print("Device removed.")
54+
print("Monitoring completed.")
55+
56+
@pytest.mark.esp32s2
57+
@pytest.mark.esp32s3
58+
@pytest.mark.esp32p4
59+
@pytest.mark.usb_device
60+
def test_usb_teardown_device(dut: IdfDut) -> None:
61+
dut.expect_exact('Press ENTER to see the list of tests.')
62+
dut.write('[teardown]')
63+
dut.expect_exact('TinyUSB: TinyUSB Driver installed')
64+
sleep(2) # Some time for the OS to enumerate our USB device
65+
66+
try:
67+
tusb_device_teardown(10, 10) # Teardown tusb device: amount, timeout
68+
69+
except DeviceNotFoundError as e:
70+
print(f"Error: {e}")
71+
raise
72+
73+
except Exception as e:
74+
print(f"An unexpected error occurred: {e}")
75+
raise
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Configure TinyUSB, it will be used to mock USB devices
2+
CONFIG_TINYUSB_CDC_ENABLED=y
3+
CONFIG_TINYUSB_CDC_COUNT=1
4+
5+
# Disable watchdogs, they'd get triggered during unity interactive menu
6+
CONFIG_ESP_INT_WDT=n
7+
CONFIG_ESP_TASK_WDT=n
8+
9+
# Run-time checks of Heap and Stack
10+
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
11+
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
12+
CONFIG_COMPILER_STACK_CHECK=y
13+
14+
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
15+
16+
CONFIG_COMPILER_CXX_EXCEPTIONS=y

0 commit comments

Comments
 (0)