summaryrefslogtreecommitdiff
path: root/test/freertos/regression/txfr_task_create_test.c
blob: 2371bcc05440571579a0fd8bb40be745de0ceef5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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);
}