summaryrefslogtreecommitdiff
path: root/test/tx/regression/threadx_trace_entry_update_test.c
diff options
context:
space:
mode:
authorFrédéric Desbiens <[email protected]>2026-10-02 10:42:44 -0400
committerGitHub <[email protected]>2026-10-02 10:42:44 -0400
commit93387b0a60382a29579bbad5045c68328e3751df (patch)
tree42be790d602cb76f55090e5a7c418df7ea2b3428 /test/tx/regression/threadx_trace_entry_update_test.c
parent44d7c95c582d415c4ad84527180b29c93c3bf664 (diff)
parent28281cd5647d4671236ca08d19bd18ed15b2265a (diff)
Merge pull request #783 from eclipse-threadx/devv6.5.2.202603_rel
Merged changes ahead of the v6.5.2.202603 release
Diffstat (limited to 'test/tx/regression/threadx_trace_entry_update_test.c')
-rw-r--r--test/tx/regression/threadx_trace_entry_update_test.c383
1 files changed, 383 insertions, 0 deletions
diff --git a/test/tx/regression/threadx_trace_entry_update_test.c b/test/tx/regression/threadx_trace_entry_update_test.c
new file mode 100644
index 00000000..f18c2d9b
--- /dev/null
+++ b/test/tx/regression/threadx_trace_entry_update_test.c
@@ -0,0 +1,383 @@
+/***************************************************************************
+ * 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
+ **************************************************************************/
+// Portions of this file were generated with AI assistance.
+
+/* This test 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, tx_thread_system_suspend.c and
+ tx_thread_system_resume.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 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"
+#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;
+
+/* 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];
+
+/* Enough storage for several 20-byte blocks on both 32-bit and 64-bit builds. */
+
+static UCHAR block_pool_area[100];
+static UCHAR byte_pool_area[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. */
+ status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 15, 15, TX_NO_TIME_SLICE, TX_AUTO_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ 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. */
+ status = tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0,
+ pointer, TEST_STACK_SIZE_PRINTF,
+ 16, 16, TX_NO_TIME_SLICE, TX_AUTO_START);
+ pointer = pointer + TEST_STACK_SIZE_PRINTF;
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #7\n");
+ test_control_return(1);
+ }
+}
+
+
+static void thread_0_entry(ULONG thread_input)
+{
+
+UINT status;
+void *block_ptr;
+void *byte_ptr;
+UINT i;
+
+
+ /* Empty the block pool. The first allocation takes the immediate-success
+ path through tx_block_allocate, which carries the first update block.
+ The exact capacity is port-dependent because each block has a pointer-
+ sized header, so allocate until the pool reports that it is empty. */
+ held_block = TX_NULL;
+ for (i = 0; i < 6u; i++)
+ {
+
+ status = tx_block_allocate(&block_pool_0, &block_ptr, TX_NO_WAIT);
+
+ if (status == TX_NO_MEMORY)
+ {
+
+ break;
+ }
+
+ 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;
+ }
+ }
+
+ if ((status != TX_NO_MEMORY) || (held_block == TX_NULL))
+ {
+
+ error++;
+ }
+
+ /* 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 and resume it goes through carry the
+ update blocks in tx_thread_system_suspend and tx_thread_system_resume. */
+ status = tx_block_allocate(&block_pool_0, &block_ptr, TX_WAIT_FOREVER);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #8\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("Running Trace Entry Update Test..................................... ERROR #9\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("Running Trace Entry Update Test..................................... ERROR #10\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, which is a third update
+ block in tx_thread_system_suspend -- the one on the path that sets
+ _tx_thread_execute_ptr to TX_NULL. The two suspensions above never reach
+ it, because the other thread was always ready to take over. */
+ status = tx_thread_sleep(5);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #11\n");
+ test_control_return(1);
+ }
+
+#ifdef TX_ENABLE_EVENT_TRACE
+
+ /* Every update block above compares a saved time stamp against the one in
+ the entry, so a port whose time source never changes makes all of them
+ take their true branch for the wrong reason: not because the entry
+ survived, but because every entry reads the same. Check the buffer holds
+ at least one entry with a time stamp the port actually supplied. The
+ events written by this point number in the dozens, so a single zero
+ reading from a real clock cannot fail this. */
+ {
+
+ TX_TRACE_BUFFER_ENTRY *entry_ptr;
+ UINT stamped;
+
+
+ stamped = TX_FALSE;
+
+ for (entry_ptr = _tx_trace_buffer_start_ptr; entry_ptr < _tx_trace_buffer_end_ptr; entry_ptr++)
+ {
+
+ if (entry_ptr -> tx_trace_buffer_entry_time_stamp != ((ULONG) 0))
+ {
+
+ stamped = TX_TRUE;
+ break;
+ }
+ }
+
+ if (stamped != TX_TRUE)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #13\n");
+ test_control_return(1);
+ }
+ }
+#endif
+
+ if (error)
+ {
+
+ printf("Running Trace Entry Update Test..................................... ERROR #12\n");
+ test_control_return(1);
+ }
+ else
+ {
+
+ printf("Running Trace Entry Update Test..................................... 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);
+}