summaryrefslogtreecommitdiff
path: root/test/smp/regression/threadx_trace_entry_update_test.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/smp/regression/threadx_trace_entry_update_test.c')
-rw-r--r--test/smp/regression/threadx_trace_entry_update_test.c367
1 files changed, 367 insertions, 0 deletions
diff --git a/test/smp/regression/threadx_trace_entry_update_test.c b/test/smp/regression/threadx_trace_entry_update_test.c
new file mode 100644
index 00000000..17c36600
--- /dev/null
+++ b/test/smp/regression/threadx_trace_entry_update_test.c
@@ -0,0 +1,367 @@
+/***************************************************************************
+ * 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
+ **************************************************************************/
+
+/* This is the SMP counterpart of the ThreadX suite's trace entry update test.
+ It drives the trace entry update paths -- the blocks guarded by
+ TX_ENABLE_EVENT_TRACE that go back and patch a trace entry after the call
+ they belong to has finished its work.
+
+ Those blocks all have the same shape:
+
+ if (entry_ptr != TX_NULL)
+ {
+ if (time_stamp == entry_ptr -> tx_trace_buffer_entry_time_stamp)
+ {
+ entry_ptr -> tx_trace_buffer_entry_information_field_N = ...;
+ }
+ }
+
+ entry_ptr comes from _tx_trace_buffer_current_ptr, which stays TX_NULL until
+ tx_trace_enable() is called at run time. Compiling with TX_ENABLE_EVENT_TRACE
+ is therefore not enough to reach them, and the trace_build configuration on
+ its own never does: the only other test that enables tracing --
+ threadx_trace_basic_test -- exercises the enable API itself and never calls
+ tx_block_allocate or tx_byte_allocate at all. So the update blocks in
+ tx_block_allocate.c, tx_byte_allocate.c and tx_thread_system_suspend.c were
+ absent from the coverage report's covered set while appearing in its
+ denominator.
+
+ Both allocators are driven twice, because they carry two of these blocks
+ each and only one is on the path that succeeds immediately: the second sits
+ after a suspension, where the allocation completes in the releasing thread's
+ context. Hence the second thread, which exists only to release what
+ thread 0 is waiting on.
+
+ The one thing this version has to do that the ThreadX version does not is
+ pin both threads to a single core. On four cores the two threads run at the
+ same time, and thread 1 would give the block back before thread 0 had
+ suspended waiting for it -- the suspension the second update block sits
+ behind would never happen. Excluding both from cores 1 to 3 restores the
+ strict alternation the ThreadX suite gets for free.
+
+ The test is built in all five configurations, and four of them do not define
+ TX_ENABLE_EVENT_TRACE. There tx_trace_enable reports TX_FEATURE_NOT_ENABLED,
+ the update blocks do not exist, and the allocation sequence below is still a
+ valid exercise of both allocators' suspension paths -- so the test is
+ meaningful in every configuration rather than skipped in four. */
+
+#include <stdio.h>
+#include "tx_api.h"
+#include "threadx_test_port.h"
+#define TX_SOURCE_CODE
+#include "tx_trace.h"
+
+
+void test_control_return(UINT status);
+
+
+static TX_THREAD thread_0;
+static TX_THREAD thread_1;
+
+static TX_BLOCK_POOL block_pool_0;
+static TX_BYTE_POOL byte_pool_0;
+static TX_SEMAPHORE semaphore_0;
+
+/* Every thread this test creates is confined to core 0, so that the two of
+ them take turns the way they would on a uniprocessor. Bits 1, 2 and 3 set
+ means cores 1, 2 and 3 are excluded. */
+
+#define TRACE_ENTRY_UPDATE_CORE_0_ONLY ((ULONG) 0xE)
+
+/* The trace buffer is sized generously on purpose. The inner test of each
+ update block compares the saved time stamp against the one in the entry, and
+ a buffer small enough to wrap between the call and its completion would
+ overwrite the entry and take the false branch every time -- the block would
+ be reached and its body still never run. */
+
+static UCHAR trace_buffer[16384];
+
+/* Four blocks of 20 bytes. */
+
+static UCHAR block_pool_area[TX_TEST_BLOCK_POOL_BYTES(20, 4)];
+static UCHAR byte_pool_area[TX_TEST_BYTE_POOL_BYTES(512)];
+
+/* Allocated by thread 0 and released by thread 1, which is what lets thread 0's
+ suspended allocations complete. */
+
+static void *held_block;
+static void *held_bytes;
+
+static UINT error = 0;
+
+
+static void thread_0_entry(ULONG thread_input);
+static void thread_1_entry(ULONG thread_input);
+
+
+/* Define what the initial system looks like. */
+
+#ifdef CTEST
+void test_application_define(void *first_unused_memory)
+#else
+void threadx_trace_entry_update_application_define(void *first_unused_memory)
+#endif
+{
+
+UINT status;
+CHAR *pointer;
+
+
+ /* Setup a pointer. */
+ pointer = (CHAR *) first_unused_memory;
+
+ /* Create the pools before tracing is enabled, so that the events this test
+ cares about are the allocations rather than the creates. */
+ status = tx_block_pool_create(&block_pool_0, "trace entry update block pool",
+ 20, block_pool_area, sizeof(block_pool_area));
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #1\n");
+ test_control_return(1);
+ }
+
+ status = tx_byte_pool_create(&byte_pool_0, "trace entry update byte pool",
+ byte_pool_area, sizeof(byte_pool_area));
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #2\n");
+ test_control_return(1);
+ }
+
+ /* Turn tracing on. This is the whole point of the test: without it
+ _tx_trace_buffer_current_ptr stays TX_NULL and every update block below
+ takes its guarding false branch. */
+ _tx_trace_initialize();
+ status = tx_trace_enable(trace_buffer, sizeof(trace_buffer), 16);
+
+#ifdef TX_ENABLE_EVENT_TRACE
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #3\n");
+ test_control_return(1);
+ }
+#else
+
+ /* Four of the five build configurations reach this branch. The rest of the
+ test still runs; it just is not exercising any update block. */
+ if (status != TX_FEATURE_NOT_ENABLED)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #4\n");
+ test_control_return(1);
+ }
+#endif
+
+ /* Register an object with no name at all, now that tracing is on. The name
+ copy in _tx_trace_object_register guards against a TX_NULL name and
+ breaks out of the copy loop, and nothing else in the suite takes that
+ branch with tracing enabled -- threadx_trace_basic_test makes the
+ equivalent call only under #ifndef TX_ENABLE_EVENT_TRACE, against the
+ no-op stub.
+
+ A semaphore rather than a thread, deliberately: for a thread object the
+ register function dereferences the pointer it is handed to read the
+ thread's priority, so that type needs a real TX_THREAD behind it. */
+ status = tx_semaphore_create(&semaphore_0, TX_NULL, 1);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #5\n");
+ test_control_return(1);
+ }
+
+ /* Thread 0 drives the test and must run first, so it takes the higher
+ priority -- the lower number. Both threads are created stopped so that
+ the core exclusion is in place before either of them is schedulable. */
+ status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 15, 15, TX_NO_TIME_SLICE, TX_DONT_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+ status += tx_thread_smp_core_exclude(&thread_0, TRACE_ENTRY_UPDATE_CORE_0_ONLY);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #6\n");
+ test_control_return(1);
+ }
+
+ /* Thread 1 releases what thread 0 waits on. It runs only while thread 0 is
+ suspended, which is exactly when it is needed -- and only because it
+ shares thread 0's single core. */
+ status = tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 16, 16, TX_NO_TIME_SLICE, TX_DONT_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+ status += tx_thread_smp_core_exclude(&thread_1, TRACE_ENTRY_UPDATE_CORE_0_ONLY);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #7\n");
+ test_control_return(1);
+ }
+
+ /* Start them now that both exclusions are set. Priority decides the order,
+ not this one. */
+ status = tx_thread_resume(&thread_1);
+ status += tx_thread_resume(&thread_0);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #8\n");
+ test_control_return(1);
+ }
+}
+
+
+static void thread_0_entry(ULONG thread_input)
+{
+
+UINT status;
+void *block_ptr;
+void *byte_ptr;
+UINT i;
+
+
+ /* Inform user. */
+ printf("Running Trace Entry Update Test..................................... ");
+
+ /* Empty the block pool. The first of these takes the immediate-success path
+ through tx_block_allocate, which carries the first update block. */
+ held_block = TX_NULL;
+ for (i = 0; i < 4; i++)
+ {
+
+ status = tx_block_allocate(&block_pool_0, &block_ptr, TX_NO_WAIT);
+
+ if (status != TX_SUCCESS)
+ {
+
+ error++;
+ break;
+ }
+
+ /* Keep the first one for thread 1 to give back. */
+ if (held_block == TX_NULL)
+ {
+
+ held_block = block_ptr;
+ }
+ }
+
+ /* The pool is empty now, so this suspends. It completes in thread 1's
+ context when the block comes back, which is the second update block in
+ tx_block_allocate -- and the suspend it goes through carries the update
+ block in tx_thread_system_suspend that names the next thread to run on
+ this core. */
+ status = tx_block_allocate(&block_pool_0, &block_ptr, TX_WAIT_FOREVER);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("ERROR #9\n");
+ test_control_return(1);
+ }
+
+ /* Now the same shape for the byte pool. Take most of it first, on the path
+ that succeeds immediately. */
+ status = tx_byte_allocate(&byte_pool_0, &held_bytes, 300, TX_NO_WAIT);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("ERROR #10\n");
+ test_control_return(1);
+ }
+
+ /* Not enough left, so this one suspends and is satisfied by thread 1's
+ release. */
+ status = tx_byte_allocate(&byte_pool_0, &byte_ptr, 300, TX_WAIT_FOREVER);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("ERROR #11\n");
+ test_control_return(1);
+ }
+
+ /* Sleep, so that thread 1 gets to run out its entry function and suspend
+ itself. That leaves nothing ready to execute anywhere, which is a second
+ update block in tx_thread_system_suspend -- the one on the branch taken
+ when the priority map is empty. The two suspensions above never reach it,
+ because thread 1 was always ready to take over. */
+ status = tx_thread_sleep(5);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("ERROR #12\n");
+ test_control_return(1);
+ }
+
+ if (error)
+ {
+
+ printf("ERROR #13\n");
+ test_control_return(1);
+ }
+ else
+ {
+
+ /* Successful test. */
+ printf("SUCCESS!\n");
+ test_control_return(0);
+ }
+}
+
+
+static void thread_1_entry(ULONG thread_input)
+{
+
+UINT status;
+
+
+ /* Reached only when thread 0 has suspended on the empty block pool. */
+ status = tx_block_release(held_block);
+
+ if (status != TX_SUCCESS)
+ {
+
+ error++;
+ }
+
+ /* And again when thread 0 has suspended on the byte pool. Thread 0 preempts
+ on the release above, runs until it suspends a second time, and control
+ comes back here. */
+ status = tx_byte_release(held_bytes);
+
+ if (status != TX_SUCCESS)
+ {
+
+ error++;
+ }
+
+ /* Thread 0 reports the result; this thread has nothing left to do. */
+ tx_thread_suspend(&thread_1);
+}