Added a regression suite for the FreeRTOS compatibility layer - #583
Merged
fdesbiens merged 2 commits intoAug 9, 2026
Merged
Conversation
xQueueCreate() allocated the queue descriptor and its backing memory, then created two ThreadX semaphores, and returned NULL on either semaphore failure without releasing anything. Since no handle reached the caller, vQueueDelete() could not be used to recover, so both allocations were lost. A failure on the second semaphore additionally abandoned the read semaphore it had already created, leaving a live ThreadX control block inside freed memory. Release the backing memory and the descriptor on both paths, and delete the read semaphore before returning when the write semaphore cannot be created. This is the teardown order vQueueDelete() already uses, and it matches the cleanup xTaskCreate() performs on its own error paths. Verified with a fault injection harness that intercepts the ThreadX byte pool and semaphore entry points to force tx_semaphore_create() to fail on a chosen call. On a read semaphore failure the layer previously performed 2 allocations and 0 releases, and on a write semaphore failure 2 allocations, 0 releases and 0 semaphore deletions. It now performs 2 releases in both cases and deletes the read semaphore in the second, with the byte pool restored to its prior state. Fixes eclipse-threadx#570 Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
The compatibility layer had no tests in this repository, which is awkward for its creation functions in particular. Each of them takes one or two byte pool allocations for its bookkeeping and then creates ThreadX kernel objects, and each returns NULL when a kernel object cannot be created. The caller is left without a handle, so it cannot call the matching delete function, and anything the layer failed to release is gone until the system restarts. A leaking version and a correct version are indistinguishable from the outside, which is how the leak in issue 570 went unnoticed. Add a suite that counts what the layer takes and gives back. A test asks the harness to fail a chosen kernel creation call, then checks the number of byte pool allocations, releases, object creations and object deletions performed. The ThreadX entry points are intercepted with the linker's --wrap so that tx_freertos.c is compiled exactly as it ships, with no test hooks in it. Note that tx_api.h maps the public API onto the error checking entry points, so the _txe_ symbols are the ones wrapped. Coverage is the creation and teardown paths of queues, tasks, semaphores, mutexes, event groups and timers, including a regression test for the two paths fixed for issue 570. The suite follows the layout of the existing ThreadX and SMP suites, is registered with ctest, and runs in CI through the shared regression template. It is built 32 bit because the Linux port defines ULONG as unsigned int on x86_64 while the layer passes pointers through ULONG arguments, so a 64 bit build truncates them. It is Linux only because --wrap has no MSVC equivalent, and the CMake configuration says so rather than failing at link time. Validated by building the suite against the layer as it stands before the issue 570 fix, where the two expected checks fail with the leaked counts, and against the fixed layer, where all three tests pass. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
This was referenced Aug 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Gives the FreeRTOS compatibility layer its first regression coverage in this repository.
Depends on #582. This branch is stacked on the #570 fix, which appears here as the first of two commits, because the queue tests assert the behaviour that PR corrects. Merge #582 first and I will rebase.
Why this suite exists
Every creation function in the layer takes one or two byte pool allocations for its bookkeeping and then creates one or more ThreadX kernel objects. When a kernel object cannot be created, the function returns
NULL, orpdFAILforxTaskCreate(). The caller has no handle, so it cannot call the matching delete function, and whatever the layer failed to release is gone until the system restarts.That makes these paths invisible from the outside: a leaking version and a correct version return exactly the same thing to the caller. It is how the leak in #570 survived, and inspection is the only thing standing between us and the next one.
So the suite does not test return values alone. It counts the ThreadX primitives the layer reaches for, and checks that each error path gives back precisely what it took.
What it covers
txfr_queue_create_testxQueueCreate,xQueueCreateStatic,vQueueDelete, including the two paths fixed in #582txfr_task_create_testxTaskCreatesemaphore and thread failure paths,xTaskCreateStatictxfr_sync_create_test25 checks across three ctest cases. This is deliberately a first course rather than full coverage of a 2,800 line layer: the infrastructure is the hard part, and coverage can now grow a test at a time.
How the injection works
A test asks the harness to fail a chosen kernel creation call, then reads back the counts. Interception uses the linker's
--wrap, sotx_freertos.cis compiled exactly as it ships, with no test hooks in product code.Two traps worth knowing, both documented in
test/freertos/readme.md:tx_api.hmaps the public API onto the error checking entry points, so the symbols that exist at link time are the_txe_variants. Wrappingtx_semaphore_createdoes nothing. Worse,--wrapon a name that does not resolve is silently ignored, so a typo produces a test that quietly never injects anything and passes for the wrong reason.txfr_malloc()andtxfr_free()cannot be wrapped at all, since they are defined intx_freertos.cand called from within it, so the compiler resolves those calls internally. The byte pool counts stand in for them.Validation
A test suite that has only ever seen correct code proves nothing, so I built it against the layer as it stands on
dev, without the #582 fix. Exactly the two expected checks fail, with the leaked counts:Against the fixed layer, all three tests pass. Both CI scripts were run end to end locally.
Two findings from building this
Neither is fixed here, since both are in shipped code rather than in the tests:
FreeRTOS.hselects the interrupt primitives by compiler, not by target. A GNU build resolvesportDISABLE_INTERRUPTS()to the bare metal__disable_interrupts()intrinsic, so hosting the layer on Linux with GCC fails to link invPortEnterCritical(). The macros are#ifndefguarded and the header already carries a ThreadX based fallback for the#elsebranch, so the test config simply pre-empts the choice. A proper fix would gate the__GNUC__branch on the target rather than the compiler.The layer passes pointers through
ULONGarguments — the task argument and the timer identifier among them — while the Linux port definesULONGasunsigned inton x86_64. A 64 bit build truncates them, which GCC reports as-Wpointer-to-int-castand which would crash the timer callback wrapper. The suite builds 32 bit, as the ThreadX and SMP suites do, so this is contained for now, but it is a real constraint on hosting the layer on a 64 bit target.Notes for review
--wraphas no MSVC equivalent. The CMake configuration stops with an explicit message rather than failing later at link time..github/workflows/regression_test.ymlas afreertosjob alongsidetxandsmp, withskip_coverage: truesince there is no coverage build configuration yet. Thedeployjob'sneedslist is left alone, matching how the RISC-V job was configured.-Wextratoday, and that is not this suite's to fix.