| Age | Commit message (Collapse) | Author |
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
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]>
|
|
Co-authored-by: TiejunZhou <[email protected]>
|
|
* Fix path to test reports in pipeline
* Fix test case when CPU starves, the thread 2 can run 14 ronuds.
|
|
(#246)
Depending on the starting time, thread 1 can run either 32 or 33 rounds.
|
|
|
|
|