diff options
Diffstat (limited to 'test/freertos/regression/txfr_task_create_test.c')
| -rw-r--r-- | test/freertos/regression/txfr_task_create_test.c | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/test/freertos/regression/txfr_task_create_test.c b/test/freertos/regression/txfr_task_create_test.c new file mode 100644 index 00000000..2371bcc0 --- /dev/null +++ b/test/freertos/regression/txfr_task_create_test.c @@ -0,0 +1,145 @@ +/*************************************************************************** + * Copyright (C) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +/**************************************************************************/ +/** */ +/** ThreadX Component */ +/** */ +/** FreeRTOS compatibility layer, task creation resource accounting */ +/** */ +/**************************************************************************/ + +/* xTaskCreate() takes two byte pool allocations, the stack and the task + structure, then creates a notification semaphore and a thread. It is the + function the rest of the layer's error handling is modelled on, so these + checks also serve as the reference for what correct unwinding looks like. */ + +#include "txfr_test_harness.h" + +#include "task.h" + +#define TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE) +#define TASK_PRIORITY (3u) + +static StaticTask_t task_control_block; +static StackType_t task_stack[TASK_STACK_DEPTH]; + +/* Each failure case gets its own buffers, for two reasons. vTaskDelete() only + queues a task for the idle task to reap, so the control block of a task + deleted above is still live while this test thread runs. And if one of these + paths ever regresses and leaves an object behind, a shared buffer would + carry that damage into the next case and report misleading counts there. */ +static StaticTask_t semaphore_failure_control_block; +static StackType_t semaphore_failure_stack[TASK_STACK_DEPTH]; +static StaticTask_t thread_failure_control_block; +static StackType_t thread_failure_stack[TASK_STACK_DEPTH]; +static StaticTask_t resume_failure_control_block; +static StackType_t resume_failure_stack[TASK_STACK_DEPTH]; + + +static void test_task_entry(void *p_arg) +{ + (void)p_arg; + + /* Never scheduled at this priority while the test thread runs, but a task + entry point must not return. */ + for(;;) + { + vTaskDelay(portMAX_DELAY); + } +} + + +void txfr_test_body(void) +{ + BaseType_t result; + TaskHandle_t task; + TXFR_COUNTERS counters; + + /* A successful creation takes both allocations, creates the notification + semaphore and the thread, and releases nothing. */ + task = NULL; + txfr_test_account_start(TXFR_INJECT_NONE, 0); + result = xTaskCreate(test_task_entry, "ok", TASK_STACK_DEPTH, NULL, TASK_PRIORITY, &task); + txfr_test_account_stop(&counters); + txfr_test_check("xTaskCreate reports success", result == pdPASS); + txfr_test_check("xTaskCreate returns a handle", task != NULL); + txfr_test_check_counts("xTaskCreate accounting", &counters, 2, 0, 2, 0); + + if(task != NULL) + { + vTaskDelete(task); + } + + /* The notification semaphore fails before anything else is created, so + both allocations must come back and nothing needs deleting. */ + task = NULL; + txfr_test_account_start(TXFR_INJECT_SEMAPHORE_CREATE, 1); + result = xTaskCreate(test_task_entry, "sem", TASK_STACK_DEPTH, NULL, TASK_PRIORITY, &task); + txfr_test_account_stop(&counters); + txfr_test_check("xTaskCreate fails when the semaphore fails", result != pdPASS); + txfr_test_check_counts("semaphore failure releases stack and task", &counters, 2, 2, 1, 0); + + /* The thread creation fails with the semaphore already in place, so that + semaphore must be deleted and both allocations released. */ + task = NULL; + txfr_test_account_start(TXFR_INJECT_THREAD_CREATE, 1); + result = xTaskCreate(test_task_entry, "thread", TASK_STACK_DEPTH, NULL, TASK_PRIORITY, &task); + txfr_test_account_stop(&counters); + txfr_test_check("xTaskCreate fails when the thread fails", result != pdPASS); + txfr_test_check_counts("thread failure unwinds the semaphore too", &counters, 2, 2, 2, 1); + + /* The static variant takes no allocations, and creates the semaphore and + the thread from the caller's buffers. */ + txfr_test_account_start(TXFR_INJECT_NONE, 0); + task = xTaskCreateStatic(test_task_entry, "static", TASK_STACK_DEPTH, NULL, TASK_PRIORITY, + task_stack, &task_control_block); + txfr_test_account_stop(&counters); + txfr_test_check("xTaskCreateStatic returns a handle", task != NULL); + txfr_test_check_counts("xTaskCreateStatic accounting", &counters, 0, 0, 2, 0); + + if(task != NULL) + { + vTaskDelete(task); + } + + /* As with the queues, the static variant owns no memory but still creates + kernel objects and still returns nothing on failure. A semaphore or a + thread left behind here stays registered in the kernel, pointing into + the caller's StaticTask_t. */ + txfr_test_account_start(TXFR_INJECT_SEMAPHORE_CREATE, 1); + task = xTaskCreateStatic(test_task_entry, "s_sem", TASK_STACK_DEPTH, NULL, TASK_PRIORITY, + semaphore_failure_stack, &semaphore_failure_control_block); + txfr_test_account_stop(&counters); + txfr_test_check("xTaskCreateStatic returns NULL on semaphore failure", task == NULL); + txfr_test_check_counts("static semaphore failure leaves nothing behind", &counters, 0, 0, 1, 0); + + txfr_test_account_start(TXFR_INJECT_THREAD_CREATE, 1); + task = xTaskCreateStatic(test_task_entry, "s_thr", TASK_STACK_DEPTH, NULL, TASK_PRIORITY, + thread_failure_stack, &thread_failure_control_block); + txfr_test_account_stop(&counters); + txfr_test_check("xTaskCreateStatic returns NULL on thread failure", task == NULL); + txfr_test_check_counts("static thread failure unwinds the semaphore", &counters, 0, 0, 2, 1); + + /* The resume is the last step, so by then both kernel objects exist. The + thread has to be terminated before it can be deleted, which is the order + the idle task uses when it reaps a deleted task. */ + txfr_test_account_start(TXFR_INJECT_THREAD_RESUME, 1); + task = xTaskCreateStatic(test_task_entry, "s_res", TASK_STACK_DEPTH, NULL, TASK_PRIORITY, + resume_failure_stack, &resume_failure_control_block); + txfr_test_account_stop(&counters); + txfr_test_check("xTaskCreateStatic returns NULL on resume failure", task == NULL); + txfr_test_check_counts("static resume failure unwinds thread and semaphore", &counters, 0, 0, 2, 2); +} |
