diff options
Diffstat (limited to 'test/freertos/regression')
| -rw-r--r-- | test/freertos/regression/txfr_queue_create_test.c | 132 | ||||
| -rw-r--r-- | test/freertos/regression/txfr_sync_create_test.c | 129 | ||||
| -rw-r--r-- | test/freertos/regression/txfr_task_create_test.c | 145 | ||||
| -rw-r--r-- | test/freertos/regression/txfr_test_harness.c | 384 | ||||
| -rw-r--r-- | test/freertos/regression/txfr_test_harness.h | 103 |
5 files changed, 893 insertions, 0 deletions
diff --git a/test/freertos/regression/txfr_queue_create_test.c b/test/freertos/regression/txfr_queue_create_test.c new file mode 100644 index 00000000..aa996070 --- /dev/null +++ b/test/freertos/regression/txfr_queue_create_test.c @@ -0,0 +1,132 @@ +/*************************************************************************** + * 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, queue creation resource accounting */ +/** */ +/**************************************************************************/ + +/* xQueueCreate() takes two byte pool allocations, the descriptor and the + backing storage, and then creates two semaphores. Since a failure returns + NULL, the caller has no handle and cannot call vQueueDelete(), so anything + the layer fails to undo on the way out is lost for good. These checks pin + down the accounting on each of those paths. + + The failure paths here are the ones reported in issue 570. */ + +#include "txfr_test_harness.h" + +#include "queue.h" + +#define QUEUE_LENGTH 8u +#define QUEUE_ITEM_SIZE 32u + +/* Storage for the static variant. */ +static StaticQueue_t queue_control_block; +static uint8_t queue_storage[QUEUE_LENGTH * QUEUE_ITEM_SIZE]; + + +void txfr_test_body(void) +{ + QueueHandle_t queue; + TXFR_COUNTERS counters; + int i; + int cycles; + + /* A successful creation takes both allocations and creates both + semaphores, and releases nothing. */ + txfr_test_account_start(TXFR_INJECT_NONE, 0); + queue = xQueueCreate(QUEUE_LENGTH, QUEUE_ITEM_SIZE); + txfr_test_account_stop(&counters); + txfr_test_check("xQueueCreate returns a handle", queue != NULL); + txfr_test_check_counts("xQueueCreate accounting", &counters, 2, 0, 2, 0); + + /* Deleting it gives both allocations back and deletes both semaphores. */ + if(queue != NULL) + { + txfr_test_account_start(TXFR_INJECT_NONE, 0); + vQueueDelete(queue); + txfr_test_account_stop(&counters); + txfr_test_check_counts("vQueueDelete accounting", &counters, 0, 2, 0, 2); + } + + /* The read semaphore fails. Nothing was created that needs deleting, but + both allocations must be released. */ + txfr_test_account_start(TXFR_INJECT_SEMAPHORE_CREATE, 1); + queue = xQueueCreate(QUEUE_LENGTH, QUEUE_ITEM_SIZE); + txfr_test_account_stop(&counters); + txfr_test_check("xQueueCreate returns NULL on read_sem failure", queue == NULL); + txfr_test_check_counts("read_sem failure releases both allocations", &counters, 2, 2, 1, 0); + + /* The write semaphore fails. The read semaphore already exists by then, so + it must be deleted as well as both allocations released. */ + txfr_test_account_start(TXFR_INJECT_SEMAPHORE_CREATE, 2); + queue = xQueueCreate(QUEUE_LENGTH, QUEUE_ITEM_SIZE); + txfr_test_account_stop(&counters); + txfr_test_check("xQueueCreate returns NULL on write_sem failure", queue == NULL); + txfr_test_check_counts("write_sem failure unwinds read_sem and memory", &counters, 2, 2, 2, 1); + + /* The static variant takes no allocations and creates both semaphores. */ + txfr_test_account_start(TXFR_INJECT_NONE, 0); + queue = xQueueCreateStatic(QUEUE_LENGTH, QUEUE_ITEM_SIZE, queue_storage, &queue_control_block); + txfr_test_account_stop(&counters); + txfr_test_check("xQueueCreateStatic returns a handle", queue != NULL); + txfr_test_check_counts("xQueueCreateStatic accounting", &counters, 0, 0, 2, 0); + + if(queue != NULL) + { + txfr_test_account_start(TXFR_INJECT_NONE, 0); + vQueueDelete(queue); + txfr_test_account_stop(&counters); + txfr_test_check_counts("vQueueDelete on static queue", &counters, 0, 0, 0, 2); + } + + /* The static variant owns no memory, but it still creates two semaphores + and still hands back nothing on failure, so the caller cannot call + vQueueDelete(). A read semaphore left behind here would stay registered + in the kernel, pointing into the caller's StaticQueue_t. */ + txfr_test_account_start(TXFR_INJECT_SEMAPHORE_CREATE, 1); + queue = xQueueCreateStatic(QUEUE_LENGTH, QUEUE_ITEM_SIZE, queue_storage, &queue_control_block); + txfr_test_account_stop(&counters); + txfr_test_check("xQueueCreateStatic returns NULL on read_sem failure", queue == NULL); + txfr_test_check_counts("static read_sem failure leaves nothing behind", &counters, 0, 0, 1, 0); + + txfr_test_account_start(TXFR_INJECT_SEMAPHORE_CREATE, 2); + queue = xQueueCreateStatic(QUEUE_LENGTH, QUEUE_ITEM_SIZE, queue_storage, &queue_control_block); + txfr_test_account_stop(&counters); + txfr_test_check("xQueueCreateStatic returns NULL on write_sem failure", queue == NULL); + txfr_test_check_counts("static write_sem failure unwinds read_sem", &counters, 0, 0, 2, 1); + + /* Repeated create and delete cycles must leave the pool able to serve the + same request. This would not catch a small leak on its own, which is why + the counts above are checked directly, but it does catch a descriptor + that is released to the wrong pool or released twice. */ + cycles = 0; + for(i = 0; i < 64; i++) + { + queue = xQueueCreate(QUEUE_LENGTH, QUEUE_ITEM_SIZE); + if(queue == NULL) + { + break; + } + + cycles++; + vQueueDelete(queue); + } + txfr_test_check("64 create and delete cycles succeed", cycles == 64); +} diff --git a/test/freertos/regression/txfr_sync_create_test.c b/test/freertos/regression/txfr_sync_create_test.c new file mode 100644 index 00000000..14bd53d8 --- /dev/null +++ b/test/freertos/regression/txfr_sync_create_test.c @@ -0,0 +1,129 @@ +/*************************************************************************** + * 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, synchronisation object accounting */ +/** */ +/**************************************************************************/ + +/* Semaphores, mutexes, event groups and timers each take a single descriptor + allocation and then create one kernel object. Every one of them must give + the descriptor back when that kernel object cannot be created. */ + +#include "txfr_test_harness.h" + +#include "semphr.h" +#include "event_groups.h" +#include "timers.h" + + +static void test_timer_callback(TimerHandle_t timer) +{ + (void)timer; +} + + +void txfr_test_body(void) +{ + SemaphoreHandle_t semaphore; + EventGroupHandle_t event_group; + TimerHandle_t timer; + TXFR_COUNTERS counters; + + /* Counting semaphore. */ + txfr_test_account_start(TXFR_INJECT_NONE, 0); + semaphore = xSemaphoreCreateCounting(4u, 0u); + txfr_test_account_stop(&counters); + txfr_test_check("xSemaphoreCreateCounting returns a handle", semaphore != NULL); + txfr_test_check_counts("xSemaphoreCreateCounting accounting", &counters, 1, 0, 1, 0); + if(semaphore != NULL) + { + vSemaphoreDelete(semaphore); + } + + txfr_test_account_start(TXFR_INJECT_SEMAPHORE_CREATE, 1); + semaphore = xSemaphoreCreateCounting(4u, 0u); + txfr_test_account_stop(&counters); + txfr_test_check("xSemaphoreCreateCounting returns NULL on failure", semaphore == NULL); + txfr_test_check_counts("counting semaphore failure releases descriptor", &counters, 1, 1, 1, 0); + + /* Binary semaphore, which shares the same descriptor path. */ + txfr_test_account_start(TXFR_INJECT_SEMAPHORE_CREATE, 1); + semaphore = xSemaphoreCreateBinary(); + txfr_test_account_stop(&counters); + txfr_test_check("xSemaphoreCreateBinary returns NULL on failure", semaphore == NULL); + txfr_test_check_counts("binary semaphore failure releases descriptor", &counters, 1, 1, 1, 0); + + /* Mutex. */ + txfr_test_account_start(TXFR_INJECT_NONE, 0); + semaphore = xSemaphoreCreateMutex(); + txfr_test_account_stop(&counters); + txfr_test_check("xSemaphoreCreateMutex returns a handle", semaphore != NULL); + txfr_test_check_counts("xSemaphoreCreateMutex accounting", &counters, 1, 0, 1, 0); + if(semaphore != NULL) + { + vSemaphoreDelete(semaphore); + } + + txfr_test_account_start(TXFR_INJECT_MUTEX_CREATE, 1); + semaphore = xSemaphoreCreateMutex(); + txfr_test_account_stop(&counters); + txfr_test_check("xSemaphoreCreateMutex returns NULL on failure", semaphore == NULL); + txfr_test_check_counts("mutex failure releases descriptor", &counters, 1, 1, 1, 0); + + /* Recursive mutex, which differs only in the inheritance argument. */ + txfr_test_account_start(TXFR_INJECT_MUTEX_CREATE, 1); + semaphore = xSemaphoreCreateRecursiveMutex(); + txfr_test_account_stop(&counters); + txfr_test_check("xSemaphoreCreateRecursiveMutex returns NULL on failure", semaphore == NULL); + txfr_test_check_counts("recursive mutex failure releases descriptor", &counters, 1, 1, 1, 0); + + /* Event group. */ + txfr_test_account_start(TXFR_INJECT_NONE, 0); + event_group = xEventGroupCreate(); + txfr_test_account_stop(&counters); + txfr_test_check("xEventGroupCreate returns a handle", event_group != NULL); + txfr_test_check_counts("xEventGroupCreate accounting", &counters, 1, 0, 1, 0); + if(event_group != NULL) + { + vEventGroupDelete(event_group); + } + + txfr_test_account_start(TXFR_INJECT_EVENT_FLAGS_CREATE, 1); + event_group = xEventGroupCreate(); + txfr_test_account_stop(&counters); + txfr_test_check("xEventGroupCreate returns NULL on failure", event_group == NULL); + txfr_test_check_counts("event group failure releases descriptor", &counters, 1, 1, 1, 0); + + /* Timer. */ + txfr_test_account_start(TXFR_INJECT_NONE, 0); + timer = xTimerCreate("t", 10u, pdFALSE, NULL, test_timer_callback); + txfr_test_account_stop(&counters); + txfr_test_check("xTimerCreate returns a handle", timer != NULL); + txfr_test_check_counts("xTimerCreate accounting", &counters, 1, 0, 1, 0); + if(timer != NULL) + { + (void)xTimerDelete(timer, 0u); + } + + txfr_test_account_start(TXFR_INJECT_TIMER_CREATE, 1); + timer = xTimerCreate("t", 10u, pdFALSE, NULL, test_timer_callback); + txfr_test_account_stop(&counters); + txfr_test_check("xTimerCreate returns NULL on failure", timer == NULL); + txfr_test_check_counts("timer failure releases descriptor", &counters, 1, 1, 1, 0); +} 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); +} diff --git a/test/freertos/regression/txfr_test_harness.c b/test/freertos/regression/txfr_test_harness.c new file mode 100644 index 00000000..b5bd45de --- /dev/null +++ b/test/freertos/regression/txfr_test_harness.c @@ -0,0 +1,384 @@ +/*************************************************************************** + * 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 test harness */ +/** */ +/**************************************************************************/ + +#include "txfr_test_harness.h" + +#include <stdio.h> +#include <stdlib.h> + +/* Accounting state. Only the single test thread touches these. */ +static int txfr_accounting; +static TXFR_INJECT_TARGET txfr_inject_target; +static int txfr_inject_fail_on; +static int txfr_inject_call_index; +static TXFR_COUNTERS txfr_counters; +static int txfr_failures; + +/* The real entry points, supplied by the linker in response to --wrap. */ +UINT __real__txe_byte_allocate(TX_BYTE_POOL *pool_ptr, VOID **memory_ptr, ULONG memory_size, ULONG wait_option); +UINT __real__txe_byte_release(VOID *memory_ptr); +UINT __real__txe_semaphore_create(TX_SEMAPHORE *semaphore_ptr, TX_NAME_CONST CHAR *name_ptr, ULONG initial_count, UINT semaphore_control_block_size); +UINT __real__txe_semaphore_delete(TX_SEMAPHORE *semaphore_ptr); +UINT __real__txe_mutex_create(TX_MUTEX *mutex_ptr, TX_NAME_CONST CHAR *name_ptr, UINT inherit, UINT mutex_control_block_size); +UINT __real__txe_mutex_delete(TX_MUTEX *mutex_ptr); +UINT __real__txe_event_flags_create(TX_EVENT_FLAGS_GROUP *group_ptr, TX_NAME_CONST CHAR *name_ptr, UINT event_control_block_size); +UINT __real__txe_event_flags_delete(TX_EVENT_FLAGS_GROUP *group_ptr); +UINT __real__txe_timer_create(TX_TIMER *timer_ptr, TX_NAME_CONST CHAR *name_ptr, VOID (*expiration_function)(ULONG input), + ULONG expiration_input, ULONG initial_ticks, ULONG reschedule_ticks, + UINT auto_activate, UINT timer_control_block_size); +UINT __real__txe_timer_delete(TX_TIMER *timer_ptr); +UINT __real__txe_thread_create(TX_THREAD *thread_ptr, TX_NAME_CONST CHAR *name_ptr, VOID (*entry_function)(ULONG entry_input), + ULONG entry_input, VOID *stack_start, ULONG stack_size, UINT priority, + UINT preempt_threshold, ULONG time_slice, UINT auto_start, + UINT thread_control_block_size); +UINT __real__txe_thread_delete(TX_THREAD *thread_ptr); +UINT __real__txe_thread_resume(TX_THREAD *thread_ptr); + + +/* Determine whether the call now being made is the one the test asked to fail. + Only calls made while accounting is active are considered, which keeps the + objects created by tx_freertos_init() and by the harness out of the count. */ +static int txfr_should_fail(TXFR_INJECT_TARGET target) +{ + if((txfr_accounting == 0) || (txfr_inject_target != target)) + { + return 0; + } + + txfr_inject_call_index++; + + return (txfr_inject_call_index == txfr_inject_fail_on) ? 1 : 0; +} + + +UINT __wrap__txe_byte_allocate(TX_BYTE_POOL *pool_ptr, VOID **memory_ptr, ULONG memory_size, ULONG wait_option) +{ + if(txfr_accounting != 0) + { + txfr_counters.byte_allocate++; + } + + return __real__txe_byte_allocate(pool_ptr, memory_ptr, memory_size, wait_option); +} + + +UINT __wrap__txe_byte_release(VOID *memory_ptr) +{ + if(txfr_accounting != 0) + { + txfr_counters.byte_release++; + } + + return __real__txe_byte_release(memory_ptr); +} + + +UINT __wrap__txe_semaphore_create(TX_SEMAPHORE *semaphore_ptr, TX_NAME_CONST CHAR *name_ptr, ULONG initial_count, UINT semaphore_control_block_size) +{ + if(txfr_should_fail(TXFR_INJECT_SEMAPHORE_CREATE) != 0) + { + txfr_counters.semaphore_create++; + return TX_SEMAPHORE_ERROR; + } + + if(txfr_accounting != 0) + { + txfr_counters.semaphore_create++; + } + + return __real__txe_semaphore_create(semaphore_ptr, name_ptr, initial_count, semaphore_control_block_size); +} + + +UINT __wrap__txe_semaphore_delete(TX_SEMAPHORE *semaphore_ptr) +{ + if(txfr_accounting != 0) + { + txfr_counters.semaphore_delete++; + } + + return __real__txe_semaphore_delete(semaphore_ptr); +} + + +UINT __wrap__txe_mutex_create(TX_MUTEX *mutex_ptr, TX_NAME_CONST CHAR *name_ptr, UINT inherit, UINT mutex_control_block_size) +{ + if(txfr_should_fail(TXFR_INJECT_MUTEX_CREATE) != 0) + { + txfr_counters.mutex_create++; + return TX_MUTEX_ERROR; + } + + if(txfr_accounting != 0) + { + txfr_counters.mutex_create++; + } + + return __real__txe_mutex_create(mutex_ptr, name_ptr, inherit, mutex_control_block_size); +} + + +UINT __wrap__txe_mutex_delete(TX_MUTEX *mutex_ptr) +{ + if(txfr_accounting != 0) + { + txfr_counters.mutex_delete++; + } + + return __real__txe_mutex_delete(mutex_ptr); +} + + +UINT __wrap__txe_event_flags_create(TX_EVENT_FLAGS_GROUP *group_ptr, TX_NAME_CONST CHAR *name_ptr, UINT event_control_block_size) +{ + if(txfr_should_fail(TXFR_INJECT_EVENT_FLAGS_CREATE) != 0) + { + txfr_counters.event_flags_create++; + return TX_GROUP_ERROR; + } + + if(txfr_accounting != 0) + { + txfr_counters.event_flags_create++; + } + + return __real__txe_event_flags_create(group_ptr, name_ptr, event_control_block_size); +} + + +UINT __wrap__txe_event_flags_delete(TX_EVENT_FLAGS_GROUP *group_ptr) +{ + if(txfr_accounting != 0) + { + txfr_counters.event_flags_delete++; + } + + return __real__txe_event_flags_delete(group_ptr); +} + + +UINT __wrap__txe_timer_create(TX_TIMER *timer_ptr, TX_NAME_CONST CHAR *name_ptr, VOID (*expiration_function)(ULONG input), + ULONG expiration_input, ULONG initial_ticks, ULONG reschedule_ticks, + UINT auto_activate, UINT timer_control_block_size) +{ + if(txfr_should_fail(TXFR_INJECT_TIMER_CREATE) != 0) + { + txfr_counters.timer_create++; + return TX_TIMER_ERROR; + } + + if(txfr_accounting != 0) + { + txfr_counters.timer_create++; + } + + return __real__txe_timer_create(timer_ptr, name_ptr, expiration_function, expiration_input, + initial_ticks, reschedule_ticks, auto_activate, timer_control_block_size); +} + + +UINT __wrap__txe_timer_delete(TX_TIMER *timer_ptr) +{ + if(txfr_accounting != 0) + { + txfr_counters.timer_delete++; + } + + return __real__txe_timer_delete(timer_ptr); +} + + +UINT __wrap__txe_thread_create(TX_THREAD *thread_ptr, TX_NAME_CONST CHAR *name_ptr, VOID (*entry_function)(ULONG entry_input), + ULONG entry_input, VOID *stack_start, ULONG stack_size, UINT priority, + UINT preempt_threshold, ULONG time_slice, UINT auto_start, + UINT thread_control_block_size) +{ + if(txfr_should_fail(TXFR_INJECT_THREAD_CREATE) != 0) + { + txfr_counters.thread_create++; + return TX_THREAD_ERROR; + } + + if(txfr_accounting != 0) + { + txfr_counters.thread_create++; + } + + return __real__txe_thread_create(thread_ptr, name_ptr, entry_function, entry_input, stack_start, + stack_size, priority, preempt_threshold, time_slice, auto_start, + thread_control_block_size); +} + + +UINT __wrap__txe_thread_delete(TX_THREAD *thread_ptr) +{ + if(txfr_accounting != 0) + { + txfr_counters.thread_delete++; + } + + return __real__txe_thread_delete(thread_ptr); +} + + +UINT __wrap__txe_thread_resume(TX_THREAD *thread_ptr) +{ + if(txfr_should_fail(TXFR_INJECT_THREAD_RESUME) != 0) + { + txfr_counters.thread_resume++; + return TX_RESUME_ERROR; + } + + if(txfr_accounting != 0) + { + txfr_counters.thread_resume++; + } + + return __real__txe_thread_resume(thread_ptr); +} + + +void txfr_test_account_start(TXFR_INJECT_TARGET target, int fail_on_call) +{ + TX_MEMSET(&txfr_counters, 0, sizeof(txfr_counters)); + txfr_inject_target = target; + txfr_inject_fail_on = fail_on_call; + txfr_inject_call_index = 0; + txfr_accounting = 1; +} + + +void txfr_test_account_stop(TXFR_COUNTERS *p_counters) +{ + txfr_accounting = 0; + txfr_inject_target = TXFR_INJECT_NONE; + + if(p_counters != NULL) + { + *p_counters = txfr_counters; + } +} + + +void txfr_test_check(const char *label, int condition) +{ + printf("%-52s %s\n", label, (condition != 0) ? "PASS" : "FAIL"); + + if(condition == 0) + { + txfr_failures++; + } +} + + +void txfr_test_check_counts(const char *label, const TXFR_COUNTERS *p_counters, + int expect_allocate, int expect_release, + int expect_object_create, int expect_object_delete) +{ + int object_create; + int object_delete; + int ok; + + /* Only one kind of kernel object is exercised per check, so the totals + identify it unambiguously. */ + object_create = p_counters->semaphore_create + p_counters->mutex_create + + p_counters->event_flags_create + p_counters->timer_create + + p_counters->thread_create; + object_delete = p_counters->semaphore_delete + p_counters->mutex_delete + + p_counters->event_flags_delete + p_counters->timer_delete + + p_counters->thread_delete; + + ok = ((p_counters->byte_allocate == expect_allocate) && + (p_counters->byte_release == expect_release) && + (object_create == expect_object_create) && + (object_delete == expect_object_delete)) ? 1 : 0; + + printf("%-52s %s\n", label, (ok != 0) ? "PASS" : "FAIL"); + + if(ok == 0) + { + printf("%-52s got alloc=%d release=%d create=%d delete=%d\n", "", + p_counters->byte_allocate, p_counters->byte_release, object_create, object_delete); + printf("%-52s expected alloc=%d release=%d create=%d delete=%d\n", "", + expect_allocate, expect_release, expect_object_create, expect_object_delete); + txfr_failures++; + } +} + + +int txfr_test_failures(void) +{ + return txfr_failures; +} + + +/* Test thread. The byte pool operations under test require thread context. */ + +static TX_THREAD txfr_test_thread; +static ULONG txfr_test_stack[4096]; + + +static void txfr_test_thread_entry(ULONG id) +{ + (void)id; + + txfr_test_body(); + + printf("\n%s\n", (txfr_failures == 0) ? "ALL CHECKS PASSED" : "THERE WERE FAILED CHECKS"); + fflush(stdout); + + /* The kernel owns the calling thread, so returning here would simply idle. + Leave the process with a status ctest can read. */ + exit((txfr_failures == 0) ? 0 : 1); +} + + +void tx_application_define(void *first_unused_memory) +{ + UINT ret; + + (void)first_unused_memory; + + ret = tx_freertos_init(); + if(ret != TX_SUCCESS) + { + printf("tx_freertos_init() failed with %u\n", ret); + exit(2); + } + + ret = tx_thread_create(&txfr_test_thread, "txfr_test", txfr_test_thread_entry, 0u, + txfr_test_stack, sizeof(txfr_test_stack), 16u, 16u, 0u, TX_AUTO_START); + if(ret != TX_SUCCESS) + { + printf("tx_thread_create() failed with %u\n", ret); + exit(2); + } +} + + +int main(void) +{ + tx_kernel_enter(); + + return 0; +} diff --git a/test/freertos/regression/txfr_test_harness.h b/test/freertos/regression/txfr_test_harness.h new file mode 100644 index 00000000..ffb6db0e --- /dev/null +++ b/test/freertos/regression/txfr_test_harness.h @@ -0,0 +1,103 @@ +/*************************************************************************** + * 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 test harness */ +/** */ +/**************************************************************************/ + +/* This harness lets a test force a ThreadX object creation call to fail and + then account for what the compatibility layer did with the resources it had + already taken. The creation entry points of the layer take a byte pool + allocation for the descriptor, sometimes a second one for backing storage, + and then create one or more kernel objects; an error path that forgets to + undo any of that is invisible to the caller, which only ever sees NULL. + + Interception is done with the linker's --wrap option rather than with hooks + in the layer itself, so tx_freertos.c is compiled exactly as it ships. Note + that tx_api.h maps the public API onto the error checking entry points, so + the symbols that exist at link time, and therefore the ones wrapped, are the + _txe_ variants. The wrap options live in the test CMakeLists. + + This mechanism is specific to GNU ld and lld, so the suite is Linux only. */ + +#ifndef TXFR_TEST_HARNESS_H +#define TXFR_TEST_HARNESS_H + +#include "FreeRTOS.h" + +/* Identifies the kernel creation call a test wants to fail. */ +typedef enum TXFR_INJECT_TARGET_ENUM +{ + TXFR_INJECT_NONE = 0, + TXFR_INJECT_SEMAPHORE_CREATE, + TXFR_INJECT_MUTEX_CREATE, + TXFR_INJECT_EVENT_FLAGS_CREATE, + TXFR_INJECT_TIMER_CREATE, + TXFR_INJECT_THREAD_CREATE, + TXFR_INJECT_THREAD_RESUME +} TXFR_INJECT_TARGET; + +/* Counts of the ThreadX primitives the layer reached for while accounting was + active. The byte pool counts stand in for txfr_malloc() and txfr_free(), + which cannot be wrapped: they are defined in tx_freertos.c and called from + within it, so the compiler resolves those calls internally. */ +typedef struct TXFR_COUNTERS_STRUCT +{ + int byte_allocate; + int byte_release; + int semaphore_create; + int semaphore_delete; + int mutex_create; + int mutex_delete; + int event_flags_create; + int event_flags_delete; + int timer_create; + int timer_delete; + int thread_create; + int thread_delete; + + /* Counted, but not part of the object totals checked below, since a + resume creates nothing. */ + int thread_resume; +} TXFR_COUNTERS; + +/* Start accounting. Pass TXFR_INJECT_NONE to only count, or a target together + with the 1 based index of the call to fail, counted from this call onward. */ +void txfr_test_account_start(TXFR_INJECT_TARGET target, int fail_on_call); + +/* Stop accounting and copy out what was counted. */ +void txfr_test_account_stop(TXFR_COUNTERS *p_counters); + +/* Record the outcome of one expectation. Prints a line per check and remembers + whether anything failed. */ +void txfr_test_check(const char *label, int condition); + +/* As above, with the observed and expected counts printed on a failure. */ +void txfr_test_check_counts(const char *label, const TXFR_COUNTERS *p_counters, + int expect_allocate, int expect_release, + int expect_object_create, int expect_object_delete); + +/* Number of failed checks so far. */ +int txfr_test_failures(void); + +/* Each test file defines this. It runs in thread context, since the byte pool + operations under test are not callable from initialization. */ +void txfr_test_body(void); + +#endif /* TXFR_TEST_HARNESS_H */ |
