summaryrefslogtreecommitdiff
path: root/test/freertos/regression/txfr_task_create_test.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/freertos/regression/txfr_task_create_test.c')
-rw-r--r--test/freertos/regression/txfr_task_create_test.c145
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);
+}