Merge branch 'feature/freertos_test_reorganization' into 'master'

FreeRTOS: Add test reorganization guidelines and initial batch of refactored tests

Closes IDF-5997

See merge request espressif/esp-idf!20233
This commit is contained in:
Darian 2022-09-21 00:36:51 +08:00
commit 5f85f507fb
6 changed files with 285 additions and 4 deletions

View File

@ -4,11 +4,19 @@ menu "FreeRTOS"
# Upstream FreeRTOS configurations go here
config FREERTOS_SMP
bool "Run the SMP FreeRTOS kernel instead (FEATURE UNDER DEVELOPMENT)"
bool "Run the Amazon SMP FreeRTOS kernel instead (FEATURE UNDER DEVELOPMENT)"
default "n"
help
This will cause the FreeRTOS component to compile with the SMP FreeRTOS kernel instead.
THIS FEATURE IS UNDER ACTIVE DEVELOPMENT, users use this at their own risk.
Amazon has released an SMP version of the FreeRTOS Kernel which can be found via the following link:
https://github.com/FreeRTOS/FreeRTOS-Kernel/tree/smp
IDF has added an experimental port of this SMP kernel located in
components/freertos/FreeRTOS-Kernel-SMP. Enabling this option will cause IDF to use the Amazon SMP
kernel. Note that THIS FEATURE IS UNDER ACTIVE DEVELOPMENT, users use this at their own risk.
Leaving this option disabled will mean the IDF FreeRTOS kernel is used instead, which is located in:
components/freertos/FreeRTOS-Kernel. Both kernel versions are SMP capable, but differ in
their implementation and features.
config FREERTOS_UNICORE
# Todo: Replace with CONFIG_NUM_CORES (IDF-4986)

View File

@ -1,3 +1,6 @@
# For refactored FreeRTOS unit tests, we need to support #include "xxx.h" of FreeRTOS headers
idf_component_get_property(FREERTOS_ORIG_INCLUDE_PATH freertos ORIG_INCLUDE_PATH)
idf_component_register(SRC_DIRS integration/event_groups
integration/queue
integration/stream_buffer
@ -6,6 +9,6 @@ idf_component_register(SRC_DIRS integration/event_groups
miscellaneous
performance
port
PRIV_INCLUDE_DIRS .
PRIV_INCLUDE_DIRS . ./integration "${FREERTOS_ORIG_INCLUDE_PATH}"
PRIV_REQUIRES cmock test_utils esp_system driver esp_timer)
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")

View File

@ -0,0 +1,73 @@
# FreeRTOS Tests Guidelines
The FreeRTOS tests are currently being refactored/reorganized with the goal of being upstreamed. This document describes the set of guidelines to which the tests are refactored/reorganized according to.
## Unity Port
These test cases assume that the FreeRTOS port has also ported the [Unity Test Framework](https://github.com/ThrowTheSwitch/Unity). Because each FreeRTOS test case will require the scheduler to be started, the way that each test case is invoked will differ form regular Unity ports.
Regular Unity ports will assume that the `main()` function invokes each test using the `RUN_TEST()` macro. However, these test cases assume the following about the Unity port:
- Each test case is invoked from a `UnityTask` instead of `main()`. Thus each test case is run from the context of the `UnityTask`.
- The `UnityTask` is created using `xTaskCreate...()` (and pinned to core 0 if SMP) from the port's startup (i.e., `main()`)
- The port's startup (i.e., `main()`) should also start the scheduler using `vTaskStartScheduler()`
- Note that this is similar to the startup of most FreeRTOS Demos.
- Each test case is defined using the `TEST_CASE(name, ...)` macro. The `VA_ARGS` of the macro allows each port to specify a set of extra arguments (such as test case labels/tags) to be used into their CI pipelines.
- A `portTestMacro.h` must be provided by each port. This header will contain
- Some constants used by test cases such as default task stack sizes (e.g., `configTEST_DEFAULT_STACK_SIZE`)
- Some port implementation specific functions/macros required by test cases such as getting current system time (e.g., `portTEST_GET_TIME()`).
## Test Organization
- Test cases are grouped into sub-directories roughly matching the header files of FreeRTOS (e.g., task, queue, semaphore, event groups etc).
- Each source file should ideally test a particular behavior (e.g., priority scheduling, queue send, scheduler suspend). This should usually result in one test case per behavior, thus one test case per source file
- Some test case behaviors may depend on configuration (e.g., priority scheduling in single core vs SMP). In such cases
- If the affect is small, use an `#if (config... == 1)` to wrap the affected areas
- If the affect is large, write a separate test case in a separate source file and wrap the entire test case with `#if (config... == 1)`.
## Test Case Template
Each test case should have the following:
- Test case description describing
- Purpose of the test case
- Test case procedure
- Excepted outcome/behavior of the test case
- The test case code wrapped in its required `config...` macros
- The expected outcomes should be tested using the `TEST_ASSERT_...()` macros provided by unity
```c
// In test_priority_scheduling.c
/*
Test Priority Scheduling (Single Core)
Purpose:
- Test that the single-core scheduler always schedules the highest priority ready task
Procedure:
- Raise the unityTask priority to (configMAX_PRIORITIES - 1)
- unityTask creates the following lower priority tasks
- task_A (configMAX_PRIORITIES - 2)
- task_B (configMAX_PRIORITIES - 3)
- UnityTask blocks for a short period of time to allow task_A to run
- Clean up and restore unityTask's original priority
Expected:
- task_A should run after unityTask blocks
- task_B should never have run
*/
#if ( configNUM_CORES == 1 )
static BaseType_t test_static_var = 0;
static void test_static_func(void)
{
...
}
TEST_CASE("Tasks: Priority scheduling single core", "[freertos]")
{
...
}
#endif /* configNUM_CORES == 1 */
```

View File

@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "test_utils.h"
#include "esp_cpu.h"
#define configTEST_DEFAULT_STACK_SIZE 4096
#define configTEST_UNITY_TASK_PRIORITY UNITY_FREERTOS_PRIORITY
#define portTEST_GET_TIME() ((UBaseType_t) esp_cpu_get_cycle_count())

View File

@ -0,0 +1,82 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "FreeRTOS.h"
#include "task.h"
#include "unity.h"
#include "portTestMacro.h"
/* ------------------------------------------------------------------------------------------------------------------ */
/*
Test Priority Scheduling (Single Core)
Purpose:
- Test that the single-core scheduler always schedules the highest priority ready task
Procedure:
- Raise the unityTask priority to (configMAX_PRIORITIES - 1)
- unityTask creates the following lower priority tasks
- task_A (configMAX_PRIORITIES - 2)
- task_B (configMAX_PRIORITIES - 3)
- UnityTask blocks for a short period of time to allow task_A to run
- Clean up and restore unityTask's original priority
Expected:
- task_A should run after unityTask blocks
- task_B should never have run
*/
#if ( configNUM_CORES == 1 )
#define UNITY_TASK_DELAY_TICKS 10
static BaseType_t task_A_ran;
static BaseType_t task_B_ran;
static void task_A(void *arg)
{
task_A_ran = pdTRUE;
/* Keeping spinning to prevent the lower priority task_B from running */
while (1) {
;
}
}
static void task_B(void *arg)
{
/* The following should never run due to task_B having a lower priority */
task_B_ran = pdTRUE;
while (1) {
;
}
}
TEST_CASE("Tasks: Test priority scheduling", "[freertos]")
{
TaskHandle_t task_A_handle;
TaskHandle_t task_B_handle;
task_A_ran = pdFALSE;
task_B_ran = pdFALSE;
/* Raise the priority of the unityTask */
vTaskPrioritySet(NULL, configMAX_PRIORITIES - 1);
/* Create task_A and task_B */
xTaskCreate(task_A, "task_A", configTEST_DEFAULT_STACK_SIZE, (void *)xTaskGetCurrentTaskHandle(), configMAX_PRIORITIES - 2, &task_A_handle);
xTaskCreate(task_B, "task_B", configTEST_DEFAULT_STACK_SIZE, (void *)xTaskGetCurrentTaskHandle(), configMAX_PRIORITIES - 3, &task_B_handle);
/* Block to allow task_A to be scheduled */
vTaskDelay(UNITY_TASK_DELAY_TICKS);
/* Test that only task_A has run */
TEST_ASSERT_EQUAL(pdTRUE, task_A_ran);
TEST_ASSERT_EQUAL(pdFALSE, task_B_ran);
vTaskDelete(task_A_handle);
vTaskDelete(task_B_handle);
/* Restore the priority of the unityTask */
vTaskPrioritySet(NULL, configTEST_UNITY_TASK_PRIORITY);
}
#endif /* configNUM_CORES == 1 */

View File

@ -0,0 +1,102 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include <string.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "unity.h"
#include "portTestMacro.h"
/* ------------------------------------------------------------------------------------------------------------------ */
/*
Test Priority Scheduling SMP
Purpose:
- Test that the SMP scheduler always schedules the highest priority ready tasks for each core
Procedure:
- Raise the unityTask priority to (configMAX_PRIORITIES - 1)
- unityTask creates the following lower priority tasks for each core
- task_A (configMAX_PRIORITIES - 2) for each core
- task_B (configMAX_PRIORITIES - 3) for each core
- unityTask blocks for a short period of time to allow all of the task_As to run
- Clean up and restore unityTask's original priority
Expected:
- All of the task_As should be run by the scheduler
- None of the task_Bs should have run
*/
#if ( defined( CONFIG_FREERTOS_SMP ) && ( configNUM_CORES > 1 ) && ( configRUN_MULTIPLE_PRIORITIES == 1 ) ) \
|| ( !defined( CONFIG_FREERTOS_SMP ) && ( configNUM_CORES > 1 ) )
#define UNITY_TASK_DELAY_TICKS 10
static BaseType_t task_A_ran[configNUM_CORES];
static BaseType_t task_B_ran[configNUM_CORES];
static void task_A(void *arg)
{
BaseType_t task_idx = (BaseType_t) arg;
task_A_ran[task_idx] = pdTRUE;
/* Keeping spinning to prevent the lower priority task_B from running */
while (1) {
;
}
}
static void task_B(void *arg)
{
/* The following should never be run due to task_B having a lower priority */
BaseType_t task_idx = (BaseType_t) arg;
task_B_ran[task_idx] = pdTRUE;
while (1) {
;
}
}
TEST_CASE("Tasks: Test priority scheduling (SMP)", "[freertos]")
{
TaskHandle_t task_A_handles[configNUM_CORES];
TaskHandle_t task_B_handles[configNUM_CORES];
memset(task_A_ran, pdFALSE, sizeof(task_A_ran));
memset(task_B_ran, pdFALSE, sizeof(task_B_ran));
/* Raise the priority of the unityTask */
vTaskPrioritySet(NULL, configMAX_PRIORITIES - 1);
/* Create task_A for each core */
for (UBaseType_t x = 0; x < configNUM_CORES; x++) {
xTaskCreate(task_A, "task_A", configTEST_DEFAULT_STACK_SIZE, (void *)x, configMAX_PRIORITIES - 2, &task_A_handles[x]);
}
/* Create task_B for each core */
for (UBaseType_t x = 0; x < configNUM_CORES; x++) {
xTaskCreate(task_B, "task_B", configTEST_DEFAULT_STACK_SIZE, (void *)x, configMAX_PRIORITIES - 3, &task_B_handles[x]);
}
/* Block to ensure all the task_As to be scheduled */
vTaskDelay(UNITY_TASK_DELAY_TICKS);
/* Check that all the task_As have run, and none of the task_Bs have run */
for (UBaseType_t x = 0; x < configNUM_CORES; x++) {
TEST_ASSERT_EQUAL(pdTRUE, task_A_ran[x]);
TEST_ASSERT_EQUAL(pdFALSE, task_B_ran[x]);
}
/* Cleanup */
for (UBaseType_t x = 0; x < configNUM_CORES; x++) {
vTaskDelete(task_A_handles[x]);
vTaskDelete(task_B_handles[x]);
}
/* Restore the priority of the unityTask */
vTaskPrioritySet(NULL, configTEST_UNITY_TASK_PRIORITY);
}
#endif /* ( defined( CONFIG_FREERTOS_SMP ) && ( configNUM_CORES > 1 ) && ( configRUN_MULTIPLE_PRIORITIES == 1 ) )
|| ( !defined( CONFIG_FREERTOS_SMP ) && ( configNUM_CORES > 1 ) ) */