/*************************************************************************** * 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 #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); }