summaryrefslogtreecommitdiff
path: root/test/tx
AgeCommit message (Collapse)Author
2026-09-23Excluded the regression-test hook code from the coverage denominatorFrédéric Desbiens
The test build defines TX_REGRESSION_TEST for every configuration, which opens a block in the Linux port header defining eight *_EXTENSION macros. They expand to live code -- flag tests, state changes and calls -- at eight sites in common/src, and an application defines none of them and compiles none of it. gcov attributes a macro expansion to the file that invokes it, so the harness's own scaffolding was being counted as certified source. coverage_union.py now drops those expansions and prints what it dropped beside the configurations it merged, so the exclusion is auditable, and a site that no longer matches the report fails the run rather than being skipped. tx_timer_initialize.c:275 keeps its line and loses only its branches: it is the one macro whose tx_api.h fallback is not empty, so under TX_MISRA_ENABLE that line still carries a statement in a build that ships. gcovr's merged report is not touched. The hook code is 8 lines and 22 branch outcomes, all covered, 0.183% of lines and 0.694% of outcomes. The union goes from 4383/4383 and 3150/3170 to 4376/4376 and 3128/3148: line coverage stays at 100.00%, and the branch rate falls from 99.36909% to 99.364676%, since removing an all-covered block from a figure below 100% moves it down. Both thresholds are unchanged and both pass, the branch one by 0.15 of an outcome. Seven configurations from a wiped tree, 115/115 tests in each, and the twenty uncovered outcomes are the same twenty at the same sixteen sites. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-16Raised the coverage gate to the certified baseline figureFrédéric Desbiens
The gate carried a margin below the measured figure, set while the coverage work was still in progress so that run-to-run variance could not redden a tree nobody had changed. The work is finished and the variance is gone: the same seven configurations now measure the same figure on a workstation and on a clean runner, so the margin only bought room for a regression to pass. The gate is now the figure the tree achieves -- 100% of the lines of common/src, and every branch outcome but the twenty defensive ones no test can reach. The branch value is written to the two decimal places the report prints, which reads a hundredth of a point below 3150/3170; that is under a third of one outcome, so losing a single line or branch still fails. Verified against the seven per-configuration tracefiles: 4383/4383 lines and 3150/3170 branches pass, 4382/4383 lines and 3149/3170 branches do not. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-16Covered the byte allocate's overwritten trace entry via the port hookFrédéric Desbiens
The byte allocate saves the time stamp of its own trace entry and comes back to fill in the address it allocated, unless a later event overwrote the entry meanwhile. Interrupts are off across all of the service but the pool search, and nothing in the kernel writes a trace event in that gap, so the other arm never ran. The Linux port already calls out to the harness inside that gap: TX_BYTE_ALLOCATE_EXTENSION exists for coverage work and sits between the restore and the disable that bracket the search. It gains a second clause, under its own flag, calling a routine the harness declares weakly. A new test filters the byte pool event class so the allocate's own insert writes nothing and leaves the buffer parked on the saved entry, then has the hook write a user event, which lands on exactly that entry. Neither half reaches the arm alone. One configuration cannot take it at all, and that is a property of the port rather than of the test: under TX_MISRA_ENABLE the trace time stamp comes from a function whose body returns zero, so every entry reads zero and the comparison is 0 == 0 whatever overwrites it. The test drives the overwrite there too and asserts the consequence only where the time source is real. 805/805 in each of the seven configurations. Union coverage is 4383/4383 lines and 3150/3170 branches against 3147/3168; the denominator gains the new clause's two outcomes and both are covered. Gate raised to 99.30. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-16Covered the MISRA loop exits by interposing the shims they testFrédéric Desbiens
Under TX_MISRA_ENABLE three kernel loops test a value produced by a function whose whole body returns a constant, so the arm that goes round again cannot execute; the six configurations without the macro compile a plain call, with no loop and no branch at all. Two of the three are the mutex put's prioritize loops and the third is the timer thread's own body. Two new tests define those shim functions themselves. The kernel is built shared and every internal call to either goes through the procedure linkage table, so the executable's definition is the one the kernel reaches and no build file changes. A shim that withholds success on alternate calls sends each mutex loop round exactly twice; the prioritize processing runs unaltered and is idempotent, so the release still leaves the mutex unowned, the suspension list empty and the inherited priority given back, which the test asserts. The timer thread's loop is that thread's whole body, so leaving it ends the thread: that test proves the timer is doing its work first, arms the shim last, and depends on no tick afterwards. Both files record which symbol they replace, why replacing it preserves what surrounds it, and where the replaced function's own coverage comes from -- the other tests, which call both shims through these same constructs. 798/798 in each of the seven configurations. Union coverage is 4383/4383 lines and 3147/3168 branches against 3144 before, the three being one loop exit each; no other outcome moved. Gate raised to 99.25. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-16Covered the stack check's null arm by constructing an idle systemFrédéric Desbiens
Four scheduler services check the stack of the thread they are about to hand the processor to, and reach that check through a test of whether it differs from the thread that is running. An idle system satisfies that test with no thread to execute at all, and the kernel counts the case explicitly, but no ordinary sequence produces it: the one path that leaves no thread to execute returns to the scheduler from inside the suspend, without reaching any of the four. A new test clears the thread to execute and enters one of the four services inside a single interrupt lockout, so the tick cannot put a thread back between the clear and the test the service makes of it. The ready list, the priority map and the highest ready priority are left correct, so the first scheduler operation afterwards recomputes the pointer; a periodic one-tick timer runs throughout, so each idle period ends at the next tick rather than at a retry. The test asserts that the thread was rescheduled and is the thread to execute again, so a service that returned without going through the scheduler fails it rather than covering nothing. 784/784 in each of the seven configurations. Union coverage is 4383/4383 lines and 3144/3168 branches against 3140 before, the four being the null arm at each of the four sites; no other outcome moved. Gate raised to 99.15. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Added the MISRA event trace configuration, completing the denominatorFrédéric Desbiens
common/src/tx_misra.c holds a region behind TX_ENABLE_EVENT_TRACE with six functions in it: the MISRA form of the in-line trace insert and five pointer conversions the trace sources use. No build configuration compiled it. misra_build defines TX_MISRA_ENABLE alone and trace_build defines TX_ENABLE_EVENT_TRACE alone, so the region was absent from the coverage denominator rather than uncovered in it - which is the reason misra_build was added in the first place. misra_trace_build defines both. It needs TX_SOURCE_CODE as well, because tx_misra.c is the only one of the 185 files in common/src that does not define it for itself and the prototypes for those five conversions sit behind it in tx_trace.h; without it the file defines five functions with no previous declaration and the build fails. The define goes on the kernel target rather than on every target, because TX_SOURCE_CODE also suppresses the public API mappings that the tests and the sample need. Its empty value matches the plain define the other 184 files carry, so no object code changes in any configuration. The defect itself is in the header and is reported upstream as eclipse-threadx/threadx#745. The existing trace tests drive the region as it stands: all twelve outcomes of the insert and all but one line arrive covered without new work. The exception is _tx_misra_entry_to_uchar_pointer_convert, whose macro no kernel source uses at all, so the MISRA shim test asserts it alongside the conversions it already covers. common/src is at 4383/4383 lines and 3140/3168 branches unioned over the seven configurations, up 44 lines and 12 outcomes on the denominator and covering all of them. The 28 uncovered outcomes are the same 28 in the same 14 files as before. 111/111 tests pass in each of the seven. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Reported coverage of the source rather than of the compiled configurationsFrédéric Desbiens
The merged report's branch denominator is not the number of branches in common/src. gcovr keys each branch by the basic-block pair gcov assigned it, and those numbers shift when a file compiles to a different amount of code, so a source branch that survives block renumbering between two configurations is counted twice. Measured over the six configurations: 521 of 1123 branch sites carry more branches in the merged report than any single configuration has, and the denominator reads 4627 where the source carries 3156. The inflation is symmetric for a branch covered everywhere, so the percentage stays plausible and the fault is easy to miss. It is not harmless. A figure quoted as branch coverage of the source has to have the source in its denominator, and this one moves when a build configuration is added for reasons that have nothing to do with the code. coverage_union.py reports the union instead: a line is covered if any configuration executed it, and the Nth branch on a line is covered if any configuration took it, keyed by position within the line rather than by block number. gcovr's merged report is still produced and published unchanged; the ratchet now gates on the union, so the floor tracks the source. Both figures agree on what is uncovered: the same 28 branch outcomes in the same 14 files either way. Over the six configurations the union reads 4339/4339 lines and 3128/3156 branches, against 4339/4339 and 4599/4627 merged, with the gates set to 99.95 and 99.00. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Removed the thread sleep test's unbuildable interrupt condition blockFrédéric Desbiens
Six conditional regions in the thread sleep test sit behind TEST_INTERRUPT_CONDITION, a macro no build in the repository defines. Between them they hold an adaptive calibration loop, a delay function, a semaphore and eleven file-scope variables, none of which is ever compiled. Left in place they read to any later reader as though the kernel path they target has a dedicated test, when it does not; that path is covered by other tests. The regions are removed, along with the two kernel headers whose only users in the file were inside them and a commented-out define that only enabled prints inside them. Nothing outside the regions is touched, and the SMP copy is left alone. Merged coverage over the six build configurations is bit identical across the deletion: 4339/4339 lines and 4599/4627 branches, with zero lines and zero branch outcomes differing anywhere in common/src. 111/111 tests pass in each of the six. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Covered the trace object registry's name copy and reuse choiceFrédéric Desbiens
The trace object registry carried three decisions ordinary use never reaches. The name copy stops at the string's terminator, so it never left the loop by its own count; it had no caller that passed no name at all, so the check that recognises one was never true and the line behind it never ran; and the search that reuses an old entry prefers a later free entry to an earlier one unless the earlier one has never held a valid object, which nothing the kernel registers produces. A new regression test creates one object whose name is longer than a registry entry keeps room for and one with no name at all, and asserts the entry each leaves behind. It then fills the registry, registering one entry with the object type the registry treats as not valid, frees that entry last so the search starts there, and registers again - so the search meets a free entry that has never held a valid object and keeps it rather than moving on. The test asserts the new object landed in that entry. The registry is now at 65/65 lines and 42/42 branches, and common/src is at 4339/4339 lines - every line of the certified source is reached in at least one of the six build configurations. Branches are at 4599/4627, up 3 outcomes and the last uncovered line, with the gates raised to 99.95 and 99.30 to hold them. 111/111 tests pass in each of the six. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Covered the trace inserts in the tracing machinery's own servicesFrédéric Desbiens
Five in-line trace inserts sit in the trace component itself rather than in a service that uses it: two in the enable, one in the interrupt control, one in each of the two inserts a port's interrupt shim makes around an ISR, and one in the user event insert. Between them they scored four to seven of the twelve branch outcomes the macro carries. A new regression test walks the four ordinary ones once per condition the macro distinguishes: tracing off altogether, thread context with every event enabled, their three event classes filtered out, interrupt context through the test ISR dispatch, initialization context, the state an interrupt presents, and a positioned buffer wrap with and without a buffer-full notification registered. None of these services has an error checking shell in front of it and none refuses a caller on the strength of the system state, so the published entry points reach every insert in every state. The interrupt control is driven as the kernel's own lockout macros drive it, a disable whose returned posture is handed straight back, so the pair is balanced whatever posture it is called in. The buffer is positioned and the service called under one lockout, because the tick's own ISR inserts would otherwise take the boundary. A second test drives the two inserts the enable writes itself. They run a few lines after the buffer pointer and the event enable bits are both set, so the only things left free are the system state, whether the buffer holds a single entry, and what a notification called from inside the first insert does before the second runs. The enable is given a buffer sized from the kernel's own structures to hold exactly one event, with three notifications: one that counts, one that filters the running event's class, and one that turns tracing off - the shape an application capturing a single bufferful of history uses. Four of the five inserts now score twelve of twelve. The user event insert reaches eleven: its own guard on the buffer pointer, a few lines above and inside the same lockout, makes the insert's null arm unreachable. The enable's first insert reaches ten for the same kind of reason - the buffer pointer and the enable bits are assigned above it. Merged over the six build configurations: 4338/4339 lines and 4596/4627 branches, up 36 outcomes, with the branch gate raised to 99.20 to hold them. 110/110 tests pass in each of the six. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Covered the MISRA compliance shim's stack check and conversionsFrédéric Desbiens
A MISRA build routes the kernel's pointer conversions, its user timer derivation and its thread stack check through functions in the compliance shim. Four of them had no caller in any build configuration: the stack check and the user timer derivation are only reached from code the shim's own configuration compiles out, and two of the conversions are only used by the trace sources, which a MISRA build does not compile either. The shim carried 24 uncovered lines and 14 uncovered branch outcomes, all of it certified source. A new regression test drives each of them through the macro the kernel itself uses, so the shim and the in-line form the other five configurations compile are asserted to agree. The stack check is the one with logic in it, and it is driven the way the in-line form is: a scratch thread is created on a buffer of the test's own with a fence on each side, given one fault at a time, and checked with the fault in place. The faults reach the stack going deeper than recorded, each of the two fences overwritten, the lowest recorded pointer dropped below the stack, the word under that pointer used, a control block that is no longer a thread, and no thread at all. Nothing is written outside the scratch thread's control block and its own fenced stack, and the thread is never started, so no faulted state is ever visible to the scheduler. The compliance shim is now at 81/81 lines and 18/18 branches. Merged over the six build configurations: 4338/4339 lines and 4560/4627 branches, up 24 lines and 14 outcomes, with the gates raised to 99.90 and 98.40 to hold them. 108/108 tests pass in each of the six. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Decorrelated the wait abort ISR test's interrupt source from the thread it ↵Frédéric Desbiens
samples threadx_thread_wait_abort_and_isr_test waits for the periodic timer interrupt to land while the preempt disable flag is set. The same interrupt also puts the semaphore that wakes the thread being sampled, so the two run in lockstep: the tick wakes thread 0, thread 0 does a fixed amount of work and suspends, and the next tick arrives a fixed interval later with thread 0 in the same place every time. That is the resonance the ISR's own comment describes, and perturbing the handler's duration only shifts the phase rather than breaking the correlation. The window itself is a few instructions wide, so a sampler locked to the thread's own cycle can miss it indefinitely. The simulator's timer thread waits on _tx_linux_timer_semaphore with a one-tick deadline and delivers an interrupt early when the semaphore is posted, which the port already relies on elsewhere. The test now runs a plain POSIX thread that posts it, injecting interrupts at moments unrelated to the tick grid and sampling thread 0 at arbitrary points in its cycle rather than the same one. The injector posts only when nothing is outstanding, so interrupts can never be queued faster than they are serviced. It is confined to the Linux simulation port and no port file changes. Measured over eight runs each, one configuration, before and after: the loop went from 2 to 17 seconds, mean about 10, to under 2 seconds with the variance collapsing as well, which is the decorrelation rather than merely more sampling. The count of windows asked for goes back to ten, which is what it was before it had to be lowered to three to fit a budget. The reasoning for three was to ask for what a run can actually reach; a run now reaches ten in one to three seconds, against the 148 to 726 seconds the unbounded version took for the same ten. The budget and the zero-window ceiling stay exactly as they were, so a run that somehow still falls behind behaves as it does today. Across the six build configurations the test now takes 0.21 to 0.99 seconds each, against 43 to 181 previously, with three of the six formerly pinned at the budget. 107 of 107 tests pass in every configuration and merged coverage is unchanged at 4314/4339 lines and 4546/4627 branches, identical line for line and branch for branch. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Covered the byte pool services' trace inserts and the allocate's trace updateFrédéric Desbiens
The six in-line trace inserts in the memory byte pool services were reached in one system state only, so each scored one or five of the twelve branch outcomes the macro carries. A new regression test walks the six services once per condition the macro distinguishes: thread context with every event enabled, the byte pool trace event class filtered out, interrupt context through the test ISR dispatch, initialization context, the state an interrupt presents, and a positioned buffer wrap with and without a buffer-full notification registered. Only the prioritize and information services are driven from the ISR: the shell rejects a byte allocate and a byte release from an interrupt outright, where the block pool permits an allocate that does not ask to suspend. The forced-state passes drive the internal entry points, and no byte pool service reads the system state itself, so the internal entry point reaches every insert in every state. The byte pool search restores interrupts to their previous posture rather than enabling them outright, so the lockout the forced-state pass holds survives the allocate. A second test drives the allocate service's trace update in place, the same way its block pool counterpart is driven: the path that finds memory immediately, the path that suspends until a release hands it some, and then the suspending path again with the trace buffer written all the way round while the thread is suspended, so the service correctly declines to update an entry that now belongs to a different event. Every byte pool insert now scores twelve of twelve, and the subsystem is at 413/413 lines and 381/382 branches. The one outcome left is the same one the block pool leaves: the arm that needs an event written over the same slot between the insert and the check on the immediate path. Merged over the six build configurations: 4314/4339 lines and 4546/4627 branches, up 5 lines and 64 outcomes, with the gates raised to 99.30 and 98.00 to hold them. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Covered the block pool services' trace inserts and the allocate's trace updateFrédéric Desbiens
The six in-line trace inserts in the memory block pool services were reached in one system state only, so each scored one or five of the twelve branch outcomes the macro carries. A new regression test walks the six services once per condition the macro distinguishes: thread context with every event enabled, the block pool trace event class filtered out, interrupt context through the test ISR dispatch, initialization context, the state an interrupt presents, and a positioned buffer wrap with and without a buffer-full notification registered. The forced-state passes drive the internal entry points, because the error checking shell rejects a create from an interrupt and a delete from any caller that is not a thread, so the inserts behind those two cannot be reached through the published ones. No block pool service reads the system state itself, so the internal entry point reaches every insert in every state. A second test drives the allocate service's trace update in place. The service records its event, remembers the entry and its time stamp, and comes back to write the address of the block it handed out into that same entry, but only if the time stamp says no later event has taken the slot. Neither of the two regions had ever run with tracing enabled over the service, so both the update and the lines behind it were dead in the report. The test walks the path that finds a block immediately and the path that suspends until a release hands one over, and then drives the suspending path again with the trace buffer written all the way round while the thread is suspended, so the service correctly declines to update an entry that now belongs to a different event. Every block pool insert now scores twelve of twelve, and the subsystem is at 278/278 lines and 251/252 branches. The one outcome left is the arm that needs an event written over the same slot between the insert and the check on the immediate path, where the whole window is interrupt-locked and contains no other insert. Merged over the six build configurations: 4309/4339 lines and 4482/4627 branches, up 5 lines and 64 outcomes, with the gates raised to 99.20 and 96.50 to hold them. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Covered the event flags services' trace inserts and the zero-flag requestFrédéric Desbiens
The six in-line trace inserts in the event flags services were reached in one system state only, so each scored one or five of the twelve branch outcomes the macro carries. A new regression test walks the six services once per condition the macro distinguishes: thread context with every event enabled, the event flags trace event class filtered out, interrupt context through the test ISR dispatch, initialization context, the state an interrupt presents, and a positioned buffer wrap with and without a buffer-full notification registered. The forced-state passes drive the internal entry points, because the error checking shell rejects a create from an interrupt and a delete from any caller that is not a thread, so the inserts behind those two cannot be reached through the published ones. A second test covers the remaining arm of the get service's suspension check. A request for no flags at all cannot be satisfied under any option, so a thread that suspended on it would never be resumed and the service refuses the suspension instead. The companion arm, a suspension requested while preemption is disabled, is already driven by the test control preempt-disable safeguard. Every event flags insert now scores twelve of twelve, and the subsystem is at 396/396 lines and 421/421 branches. Merged over the six build configurations: 4304/4339 lines and 4418/4627 branches, up 60 outcomes, with the branch gate raised to 95.00 to hold them. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Covered the mutex services' trace inserts in every system stateFrédéric Desbiens
The six in-line trace inserts in the mutex services were reached in one system state only, so each scored one or five of the twelve branch outcomes the macro carries. A new regression test walks the six services once per condition the macro distinguishes: thread context with every event enabled, the mutex trace event class filtered out, interrupt context through the test ISR dispatch, initialization context, the state an interrupt presents, and a positioned buffer wrap with and without a buffer-full notification registered. The forced-state passes drive the internal entry points, because the error checking shell rejects a create, a get and a put from an interrupt and a delete from any caller that is not a thread, so the inserts behind those four cannot be reached through the published ones. The resident mutex carries no priority inheritance, so a release with nothing suspended on it takes the simple path and the pass leaves the calling thread's priority alone. Every mutex insert now scores twelve of twelve, and the subsystem is at 517/517 lines and 513/517 branches. The four outcomes left are the exit arms of the four TX_MISRA_ENABLE release loops, whose condition is produced unconditionally. Merged over the six build configurations: 4304/4339 lines and 4358/4627 branches, up 58 outcomes, with the branch gate raised to 94.00 to hold them. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Covered the semaphore services' trace inserts in every system stateFrédéric Desbiens
The eight in-line trace inserts in the counting semaphore services were reached in one system state only, so each scored one or five of the twelve branch outcomes the macro carries. A new regression test walks the eight services once per condition the macro distinguishes: thread context with every event enabled, the semaphore trace event class filtered out, interrupt context through the test ISR dispatch, initialization context, the state an interrupt presents, and a positioned buffer wrap with and without a buffer-full notification registered. The forced-state passes drive the internal entry points, because the error checking shell rejects a create from an interrupt and a delete from any caller that is not a thread, so the inserts behind those two cannot be reached through the published ones. Every semaphore insert now scores twelve of twelve, and the subsystem is at 356/356 lines and 340/340 branches. Merged over the six build configurations: 4304/4339 lines and 4300/4627 branches, up 80 outcomes, with the branch gate raised to 92.50 to hold them. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Covered the timer and time services' trace inserts in every system stateFrédéric Desbiens
The eight in-line trace inserts in the application timer and system time services were reached in one system state only, so each scored one or five of the twelve branch outcomes the macro carries. A new regression test walks the eight services once per condition the macro distinguishes: thread context with every event enabled, both of the services' trace event classes filtered out, interrupt context through the test ISR dispatch, initialization context, the state an interrupt presents, and a positioned buffer wrap with and without a buffer-full notification registered. The forced-state passes drive the internal entry points, because the error checking shell rejects a create from an interrupt, a delete from any caller that is not a thread, and a change from initialization, so the inserts behind those three cannot be reached through the published ones. All eight sites now score twelve of twelve. Merged over the six build configurations: 4304/4339 lines and 4220/4627 branches, up 80 outcomes, with the branch gate raised to 91.00 to hold them. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Covered the thread stack check's fault arms from the test sideFrédéric Desbiens
TX_THREAD_STACK_CHECK sits on ten sites across the scheduler and scores seven conditions at each of them. Ordinary use only ever takes the clean arm of five: the stack pointer never goes below the lowest recorded, neither fence word is overwritten, the lowest recorded pointer stays inside the stack, the word under it is still fill, and the control block is always a thread. The ten sites sat between eight and eleven of fourteen for that reason, 47 outcomes in all. The new test gives a thread a fault, drives a site, and takes the fault out again. Five faults, each written into the thread's own control block or into the fence words around its stack: the lowest recorded stack pointer put above the thread's stack pointer, the fence above the stack overwritten, the lowest recorded pointer dropped below the stack, the word under it made used, and the fence below the stack overwritten. A sixth pass clears the thread identifier, which is the arm that makes the check skip the block entirely. Three things about it need stating. The fault is applied and removed around each step rather than around a pass. The first site to see a thread whose recorded stack pointer is wrong repairs that pointer on its way through, so a pass driven with one application of the fault covers the first site and nothing after it. Two sites look at the thread that is going to run rather than the one being operated on, and reaching them needs the two to differ. A time slice rotation leaves the peer as the thread to execute while the test thread is still the one running, which is the only arrangement that separates them; preemption is held off across the rotation and the service that follows it, because a tick in between would act on the rotation and the peer repairs itself on its way back. The threads that run while a thread identifier is cleared use the internal entry points. The error checking shell rejects such a block before the service behind it is reached, so a peer that relinquishes through the published entry point would stop relinquishing for as long as the fault is in place. The file also closes the two parts of the stack machinery no fault reaches: a create handed an odd stack address, which rounds it up and takes another word off the size, and the error handler's MISRA build, where there is no application handler to call and the function exists only to keep its argument referenced. The ten sites reach thirteen of fourteen, tx_thread_create.c and tx_thread_stack_error_handler.c reach 100%, and every tx_thread_* file is at 100% line. What is left in the batch is eighteen branch outcomes, all of them arms that need something the test side cannot produce: a null thread pointer at nine of the sites, which every caller of the scheduler makes impossible; the second check in _tx_thread_system_resume seeing a stack pointer the first one has already recorded; the sleep service's trace insert, which its own system state test puts out of reach at anything but thread level; and the trace update's "a later event overwrote this one", which needs an event inserted inside a window the kernel holds interrupts off across. Merged coverage over the six build configurations rises from 4097/4627 branches to 4140/4627 and from 4300/4339 lines to 4304/4339, identical across three clean runs. The gates go to 99.10 and 89.00. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Covered the thread services' trace inserts in every system stateFrédéric Desbiens
Every thread service carries one TX_TRACE_IN_LINE_INSERT, and gcov scores twelve branch outcomes for the macro. Seventeen of those inserts sat between one and nine of twelve, because exactly one test in the suite ever turns tracing on and it does not drive the thread API. The code runs -- the sites are hit hundreds of thousands of times -- but tracing is never enabled when it does. The new test follows the shape the queue subsystem established. It enables tracing and walks all sixteen services once per condition the macro distinguishes: in thread context, in interrupt context from the test ISR dispatch, with the system state forced to TX_INITIALIZE_IN_PROGRESS and again to the value an interrupt presents, with the thread and internal event classes filtered out, and across a buffer wrap with and without a buffer-full notification registered. Three things are specific to this subsystem rather than inherited from the queue test. The scheduler's inserts sit behind the published ones. A resume writes an API event and then _tx_thread_system_resume writes an internal event of its own, so positioning the buffer one entry short of its end puts the API insert on the boundary and never the scheduler's. The wrap pass therefore drives the resume and the suspend twice, positioned one entry short and then two. The scheduler also updates the entry it wrote, once it knows which thread runs next, and only if the timestamp says nothing overwrote it in between. That update is unreachable unless a resume actually preempts while tracing is on, so the test ends with a pass that resumes a higher-priority thread and takes the processor back from it four times. The three regions of _tx_thread_system_suspend and the two of _tx_thread_system_resume are reached this way; what is left uncovered in them is the arm where the timestamp differs, which needs a second event inserted inside a window the kernel holds interrupts off across. The sleep service tests the system state itself, ahead of its own insert, and refuses every caller that is not a thread. Its insert is therefore only ever reached at system state zero and stops at nine of twelve. Neither the published entry point nor the internal one can reach the other three. A thread's lifecycle does not close on itself the way a queue's does: a reset is only legal from the terminated state, which a reset does not leave the thread in. So the reset is driven against a second scratch thread that the service terminates again on its way out, after its own insert has run, and the first scratch thread is created and deleted once per pass as the queue was. Merged coverage over the six build configurations rises from 3927/4627 branches to 4097/4627, with lines at 4300/4339. Every thread trace insert but the sleep is now at twelve of twelve. The gates go to 99.00 and 88.00, below the measured figures by the margin the resume-from-ISR variance needs: two clean runs agree exactly on lines and differ by one branch outcome. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Restored the missing licence headers on the ThreadX regression testsFrédéric Desbiens
96 of the 98 files in test/tx/regression carry no copyright notice and no licence text at all. The two that do are a generated file and one added this week. Everything else in the repository -- common/src, common_smp/src, the ports, the samples -- carries the standard header, so this is an oversight in one directory rather than a deliberate exemption, and it has been there since the tests were first published. The header added here is byte identical to the one in common/src, including its SPDX identifier. Nothing else in these files changes: every hunk is ten added lines and none removed. There is deliberately no "Eclipse ThreadX contributors" line and no AI disclosure paragraph. This commit contributes no code -- it records the licence the files were always under. A later change that does contribute code to one of these files adds the current year contributor line and its own attribution at that point, which is when there is something to attribute. Verified: all six build configurations build clean and pass 96 of 96, and the merged coverage is unmoved. Two places with the same gap are left alone. test/tx/cmake/samples/fake.c is a build stub rather than a test. The SMP regression tree has it too and should be fixed the same way, but not in a commit whose subject says ThreadX. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Instrumented a sixth build configuration that defines TX_MISRA_ENABLEFrédéric Desbiens
common/src/tx_misra.c is 835 lines of certified source whose whole body sits under TX_MISRA_ENABLE. None of the five build configurations defined that macro, so the file compiled to nothing in all of them: an object and a note file existed, no instrumented code did, and the merged report covered 178 of the 185 files in common/src while claiming to cover common/src. The decisive argument is not the file count. The shim exists to make the kernel's pointer conversions MISRA compliant, and the MISRA analysis will be run against the source with the macro defined. Without this configuration the build that is analysed and the build that is covered are different builds of the same tree, and the evidence would have to say which one each number came from. The configuration is instrumented and merged like the other five. All 96 tests pass in it unchanged. It also settles the file count. The port defines TX_INLINE_INITIALIZATION only when TX_MISRA_ENABLE is absent, so the six *_initialize.c files that are empty in the other five configurations carry their bodies here, and all six are at 100%. The merged report now covers 185 files of 185, and the scope statement the evidence needs is that the denominator is common/src entire, with no port specific caveat about which files contribute code. The gates are re-baselined rather than carried forward. The denominator gained 119 lines and 519 branches that were never counted before, so the line percentage falls while the covered line count rises: 4296/4339 lines and 3919/4627 branches over the six configurations, identical across two clean runs on the pinned toolchain. What the sixth configuration does not yet cover -- 24 lines and 14 branch outcomes in the shim itself, and thirteen branch outcomes in the TX_MISRA_ENABLE regions of the mutex, thread and timer sources -- is real certified source that was previously invisible to the report rather than covered by it. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Covered the queue services' trace inserts in every system stateFrédéric Desbiens
Each queue service carries one TX_TRACE_IN_LINE_INSERT, and gcov scores twelve branch outcomes for the macro. Nine of those inserts sat at one or five of twelve, 91 uncovered outcomes in all, because exactly one test in the suite ever turns tracing on and it does not drive the queue API. The code runs -- the sites are hit hundreds of times -- but tracing is never enabled when it does. The new test enables tracing and walks all nine services once per condition the macro distinguishes: in thread context, in interrupt context from the test ISR dispatch, with the system state forced to TX_INITIALIZE_IN_PROGRESS, with the queue event class filtered out, and across a buffer wrap with and without a buffer-full notification registered. That is every arm of the macro, so all nine sites reach twelve of twelve and the whole queue subsystem -- 22 files, 596 lines, 394 branches -- is at 100% line and branch in the trace configuration and in the merged report. Two techniques need stating, because both reach into the kernel from the test rather than through the API. The system state is written directly, with interrupts held off across the pass. The initialization arm of the macro is otherwise unreachable: the only caller that runs at that state is tx_application_define, and under the ctest harness that entry point belongs to the test control code, not to the test. Interrupts are disabled for the pass so the timer thread cannot observe the forced value, and the same variable's interrupt nesting count is restored before anything else reads it. Create and delete are driven through _tx_queue_create and _tx_queue_delete in the forced-state passes. The error checking shell rejects both from any caller that is not a thread, so the inserts behind it cannot be reached any other way. The shell itself is already at 100% from its own tests; what these passes cover is the insert behind it. The trace buffer is positioned one entry short of its end before each service in the wrap passes. Filling the buffer instead wraps it on whichever service happens to write the last entry, which covers one site and leaves the other eight; positioning puts each service's own insert on the boundary in turn. The buffer is also small enough that ordinary use wraps it many times over, and the test fails unless the notification is called once per service. This is the test shape the remaining subsystems will follow, worked end to end on one of them first. Merged coverage over the five build configurations rises from 3329/4108 branches to 3421/4108, with lines unchanged at 4204/4220. The branch gate goes to 82.50, below the measured figure by the same margin the line gate carries, because the resume-from-ISR sites still move between runs. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Bounded the event flag ISR tests' waits for their race windowsFrédéric Desbiens
threadx_event_flag_isr_set_clear_test and threadx_event_flag_isr_wait_abort_test are built from the same template as threadx_thread_wait_abort_and_isr_test, which was bounded in an earlier commit. Each loops until the periodic ISR has fired forty times while the kernel happens to hold _tx_thread_preempt_disable, waits for that window rather than forcing it, and has no iteration cap, no wall clock escape and no failure path. Each carries the same ineffective resonance mitigation, with the same three faults: a perturbation under a microsecond against a 10 ms tick, a counter the perturbation resets so it cannot fire again for another hundred misses, and a loop body that does not execute at all when the system clock is a multiple of 100. Neither has been observed to fail. They reach forty windows in about one second in every configuration, which is why the count asked for is left at forty and only a budget is added: there is nothing to gain by asking for fewer, and a healthy run stays two orders of magnitude clear of the bound. What is being fixed is the shape, so that a run which cannot reach the window ends by saying so instead of being killed by ctest at its timeout with nothing in the log. The budget is in wall clock seconds rather than ticks, for the reason established by the earlier commits: a tick arrives only when the port's timer thread runs, so the tick clock falls behind real time under exactly the load and instrumentation that make the wait long. The bound is in the loop condition rather than a break in the body, so the loop exits where it always did and the accounting check that follows sees the state it has always seen. That check compares gets against sets and does not depend on the window count, so it is left in place. A run that reaches the window no times at all is different: the window is the thing these tests exist to exercise, and the accounting check would pass on bookkeeping that says nothing about it, so that case now fails with ERROR #11. Verified in default_build_coverage for both tests: they pass normally reporting 40 of 40 windows in one second; with the count raised past reach and the budget cut to three seconds they stop at four seconds, report 195 and 226 windows reached, and pass; and with the window made unreachable they report reaching none, print ERROR #11 and exit 1. Monoprocessor copies only. The SMP suite needs its own measurement before the same change is made there. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Bounded threadx_thread_delayed_suspension_test's wait for its race windowFrédéric Desbiens
The test waits for an interrupt to land while thread 2 is part way through suspending, and waits for it with no bound. It is the same construction as threadx_thread_wait_abort_and_isr_test, one scale smaller. Measured across the five configurations on this branch it ranges from 0.31 to 7.72 seconds, a 25x spread, against 1.84 on the CI runner. That is the largest ratio in the whole monoprocessor suite, and every other test outside these two is reproducible to within about half a per cent. The absolute worst case is small today, so nothing has failed because of it; the shape is what is being fixed, not an observed failure. Backported from upstream. A wall clock budget for the same reason as the last one: a tick is delivered only when the port's timer thread runs, so the tick clock falls behind real time under exactly the load and instrumentation that make the wait long. The check after the loop needed care the other one did not. It compares thread_2_counter against thread_2_counter_capture, and the capture is taken inside the interrupt handler at the moment the window is hit, so leaving it in place after a run that never reached the window would compare a live counter against the zero it was initialised to and report a defect that is not there. The check is skipped when the window was not reached and the run says so. The hunks are byte identical to upstream's, monoprocessor copy only, for the reason given in the previous commit. Verified both ways: the window is reached as it always was and the test passes, and with the budget forced to zero the test reports the window not reached and passes without the dependent check firing. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-15Bounded threadx_thread_wait_abort_and_isr_test's wait for its race windowFrédéric Desbiens
The test loops until the periodic ISR has fired ten times while the kernel happens to hold _tx_thread_preempt_disable. It waits for that window rather than forcing it, with no iteration cap, no wall clock escape and no failure path, so a run that does not see the window does not end. Measured on this branch, same binary and same tree: between 10.3 and more than 1000 seconds. ctest killed it at its 1000 second timeout once, and one standalone run in thirty six exceeded 300 seconds on an otherwise idle machine, so the tail is a property of the test rather than of contention. On the CI runner the same test takes 2.3 to 9.5 seconds, a sixfold difference in the rate at which the window is reached between two environments running identical code. The test's own mitigation cannot work. It perturbs the ISR duration after a hundred misses, but the perturbation is at most 99 iterations of an empty decrement loop, well under a microsecond against a 10 ms tick; it reuses miss_count as that loop's counter, so every perturbation leaves it at zero and the next cannot happen for another hundred misses; and when the system clock is a multiple of 100 the loop body never executes at all. Backported from upstream, which has already solved this: a wall clock budget rather than a tick budget, because a tick arrives only when the port's timer thread runs and so falls behind real time under load or instrumentation; three windows asked for instead of ten, because the later hits repeat what the first establish; and a run that reaches the window no times at all now fails rather than passing on bookkeeping no window ever touched. The hunks are byte identical to upstream's. Only the monoprocessor copy is taken here; the SMP copy is left alone deliberately, since upstream measured it reaching twenty windows in under half a second in all five of its configurations and kept its count, and the SMP suite needs its own measurement before it is changed. Verified all three paths on this branch: the test passes normally in 9 to 17 seconds across configurations, against 10 to 300 before; with the budget forced to zero it reports reaching one of three windows and passes; and with the window made unreachable it reports reaching none, prints ERROR #8 and exits 1. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-14Widened the coverage gates to clear the measured run-to-run varianceFrédéric Desbiens
The gates were set from local runs alone and left about three lines of headroom each. CI has since measured both suites and the margin is too thin to survive an environment sampled that few times. observed range gate headroom ThreadX line 4202-4204 / 4220 99.40 7 lines ThreadX branch 3310-3329 / 4108 79.50 44 branches SMP line 5150-5152 / 5178 99.30 8 lines SMP branch 3994-3999 / 4773 83.00 32 branches Every gate now sits at two to six times the spread actually observed, where they previously sat at about one and a half. Nothing real is let through by that: a regression worth catching moves tens of lines, not two, and these ratchet upward as each batch of coverage work lands. The variance is the resume-from-ISR path and the timer information path, which are reached or missed according to how the host schedules the run rather than by anything the tests arrange. Covering them by construction is what removes the need for the margin. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-14Pinned the certification build environment and enabled CI on the ↵Frédéric Desbiens
certification branch The regression workflows ran on ubuntu-latest with nothing pinned, and the coverage numbers they produce are certification evidence. Two things follow from that, and the first is urgent rather than theoretical. These sources declare cmake_minimum_required(VERSION 3.0.0) and CMake removed compatibility below 3.5 in 4.0, so this branch stops configuring the day the hosted image moves to CMake 4.x -- no change here, no warning. Worse, the branch did not need GitHub's help to get there: scripts/install.sh ran pip install --upgrade cmake which installs exactly the CMake version that cannot configure these sources. It also installed gcovr 4.1, which is far too old for the tracefile and threshold options the coverage scripts use, and called sudo, which does not exist in a container. The job now runs inside debian:trixie, which carries GCC 14, gcc-multilib and CMake 3.31.6. Being below 4.x, that configures the sources with no policy override and no edit to CMakeLists.txt. The coverage build is 32-bit, so multilib is not optional. install.sh installs the distribution toolchain plus gcovr 8.6, prints all three versions into the job log, and then asserts them: GCC major 14, CMake major below 4, gcovr exactly 8.6. The image tag is a moving target that point releases rebuild, and a toolchain that drifts should fail by name and number rather than quietly produce evidence built with something else. git is installed before the checkout, because actions/checkout otherwise falls back to a REST API tarball and leaves no .git behind, and the commit the evidence was built from is part of the evidence. The Pages preparation step no longer calls sudo, since the steps run as root in the container. Nothing gated on the coverage number before: it was published, not enforced. coverage.sh --merge now ends with a gcovr pass carrying --fail-under-line and --fail-under-branch, after the reports are written so a failed gate still leaves the report that explains it. ThreadX 99.50% line 80.00% branch SMP 99.40% line 83.00% branch These are a ratchet, raised as each batch of gaps closes, and deliberately not 100%: a gate nothing can pass gets switched off. They sit below the measured figures rather than at them, because the same tree does not measure identically twice -- branches on the resume-from-ISR path are reached or missed depending on how the host schedules the run, and two clean runs of the ThreadX suite differ by 2 lines and 19 branches. A gate set at the best observation fails on a tree nobody changed. Verified by raising each threshold above the measurement: line returns 2, branch returns 4, the merged report is still written, and run.sh reports the failure rather than swallowing it -- a run with all 95 tests passing exits 2 when the line gate is not met. regression_test.yml now triggers on the certification branch as well as master. regression_template.yml needed no filter; it is workflow_call only. Publishing stays on master. The deploy job replaces the project's public coverage site wholesale and its condition fires on any push, so without a guard the first push of this branch would have published its numbers as the project's. The certification branch produces its evidence as workflow artifacts instead. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-14Instrumented every build configuration and merged their coverageFrédéric Desbiens
Only default_build_coverage carried -fprofile-arcs, because the gate was the build type and it is the only one of five configurations whose name ends in _coverage. The other four built and ran every test and their coverage was discarded. That is not redundancy thrown away: each configuration selects a different set of TX_ feature macros, so the code the other four compile was absent from the denominator rather than uncovered in it, and 100% against that report would not have been a measurement of the kernel. TX_COVERAGE instruments a build regardless of its name, defaulting to OFF so a single configuration built by hand behaves as before. coverage.sh gains a --merge mode that unions the per-configuration JSON tracefiles, and cmake_bootstrap.sh runs it after the test loop so a local run produces the same merged report CI reads. The workflow template sets TX_COVERAGE for build and test, and coverage_name moves to the merged report. Measured on the ThreadX suite, all 95 tests passing in each configuration: default_build_coverage 3827 valid 3827 covered disable_notify_callbacks_build 3767 3767 stack_checking_build 3857 3856 stack_checking_rand_fill_build 3862 3861 trace_build 4123 4108 merged 4220 4204 The denominator grows by 393 lines and the figure moves from 100.00% to 99.62%. The second one is honest, and the drop is the point rather than a regression: the denominator now includes code the old report never counted. The union also contains a file the old report did not contain at all -- tx_thread_stack_error_handler.c compiles only under TX_ENABLE_STACK_CHECKING, so it was not listed at 0%, it was simply absent. 177 files becomes 178. The scoping is the positional search path, not --object-directory. Left as --object-directory while the gcovr root moved to the repository root, all five configurations reported the union of every configuration's gcda: five identical reports, and a merge of five copies of the same thing. Nothing about the resulting percentage looks wrong, which is what makes it worth stating here. Coverage collection moved out of test(). A failing ctest used to abort the function before coverage.sh was reached, so the run whose behaviour changed was the one that produced no report, and the gcda already existed by that point. Collection now runs after the whole test loop, one configuration at a time, which is also what --merge needs: every tracefile has to exist before the union is taken. A coverage failure turns the run red but does not overwrite a test failure's status. Per-configuration output moved under coverage_report/per_configuration/ and is excluded from the deployed artifact. The deploy merges the ThreadX and SMP artifacts into one tree and every configuration directory has the same name in both, so left at the top level one suite's would overwrite the other's on the published site. coverage.sh now fails when a report contains no files. gcovr treats an empty report as success: it exits 0 and writes line-rate="1.0" alongside lines-valid="0", so every downstream consumer reads "no data at all" as "100% covered", and a coverage threshold cannot catch it because an empty report passes any threshold. The tx and smp runners are symlinks to scripts/cmake_bootstrap.sh, so they are covered by the one change there. Assisted-by: Claude Code (Opus 5) <[email protected]>
2023-05-12Add random number stack filling option. (#257)Xiuwen Cai
Co-authored-by: TiejunZhou <[email protected]>
2023-04-17Fix path to test reports in pipeline (#247)TiejunZhou
* Fix path to test reports in pipeline * Fix test case when CPU starves, the thread 2 can run 14 ronuds.
2023-04-14Fix random failure in test case threadx_event_flag_suspension_timeout_test.c ↵TiejunZhou
(#246) Depending on the starting time, thread 1 can run either 32 or 33 rounds.
2023-04-12Add Azure DevOps pipelines for ThreadX testTiejun Zhou
2023-04-04Release ThreadX regression systemTiejun Zhou