Unit Test Results¶
406 total | 406 passed | 0 failed — 2026-05-11 10:26
By complexity¶
| Level | Count | Description |
|---|---|---|
smoke |
15 | no-crash / lifecycle only |
format |
46 | string or schema format checks |
behavioral |
317 | actual module behavior: output, state, counts |
integration |
29 | multi-module pipeline or cross-device |
See Testing standards for the full classification guide.
test_artnet.cpp¶
✅ 7/7 passed — test_artnet.cpp
- ✅
smokeArtNetOutModule - lifecycle without layer (should not crash) - ✅
behavioralArtNetOutModule - sends packets when wired to a DriverLayer with pixels - ✅
behavioralArtNetOutModule - multi-universe: 170 pixels use 1 universe, 171 use 2 - ✅
behavioralArtNetOutModule - packet header bytes are correct Art-Net OpDmx - ✅
smokeArtNetInModule - lifecycle without layer (should not crash) - ✅
formatArtNetInModule - healthReport format - ✅
integrationArtNetOut loopback to ArtNetIn on 127.0.0.1
Controls and KV Store¶
✅ 33/33 passed — test_controls_and_kv.cpp
- ✅
behavioralStatefulModuleBase - addControl registers controls correctly - ✅
behavioralStatefulModuleBase - clearControls removes non-system control descriptors - ✅
behavioralStatefulModuleBase - setControl float writes through to field - ✅
behavioralStatefulModuleBase - setControl uint8 writes through to field - ✅
behavioralStatefulModuleBase - setControl uint32 writes through to field - ✅
behavioralStatefulModuleBase - setControl bool writes through to field - ✅
behavioralStatefulModuleBase - setControl unknown key is a no-op - ✅
behavioralStatefulModuleBase - setControl JsonVariant writes through to field - ✅
behavioralStatefulModuleBase - onUpdate is called after setControl - ✅
behavioralStatefulModuleBase - onUpdate is NOT called for unknown key - ✅
formatStatefulModuleBase - getSchema produces correct JSON structure - ✅
formatStatefulModuleBase - getSchema reflects current field value after setControl - ✅
formatSineEffectModule - getSchema exposes frequency and amplitude - ✅
behavioralSineEffectModule - setControl updates live frequency - ✅
formatPreviewModule - getSchema has only enabled control - ✅
behavioralKvStore - setFloat / getFloat round-trip - ✅
behavioralKvStore - setUint8 / getUint8 round-trip - ✅
behavioralKvStore - setBool / getBool round-trip - ✅
behavioralKvStore - getFloat returns default for unknown key - ✅
behavioralKvStore - getUint8 returns default for unknown key - ✅
behavioralKvStore - getBool returns default for unknown key - ✅
behavioralKvStore - getFloat returns default when key holds different type - ✅
behavioralKvStore - exists returns true for present key, false for absent - ✅
behavioralKvStore - setFloat updates an existing entry - ✅
behavioralKvStore - setFloat returns false when table is full - ✅
behavioralKvStore - clear removes all entries - ✅
behavioralclearControls - rebuild preserves value of re-registered control - ✅
behavioralclearControls - values of removed controls are not applied to unrelated fields - ✅
behavioralclearControls - system control enabled survives rebuild - ✅
formatclearControls - schemaDirty_ is set and can be cleared - ✅
formatclearControls - burst of rebuilds produces exactly one schemaDirty flag - ✅
behavioralSineEffectModule - rebuildControls switches to Ripples parameters - ✅
behavioralSineEffectModule - frequency preserved across type switch and back
Coord3D and Pixel Addressing¶
✅ 7/7 passed — test_coord3d.cpp
- ✅
behavioralCoord3D - default construction is (0,0,0) - ✅
behavioralCoord3D - equality and inequality - ✅
behavioralcoords_to_index - flat 2D grid (depth=1) - ✅
behavioralcoords_to_index - 3D volume - ✅
behavioralindex_to_coords - flat 2D grid (depth=1) - ✅
behavioralindex_to_coords - 3D round-trip with coords_to_index - ✅
behavioralcoords_to_index - matches EffectsLayer pixel layout
test_effects_2d.cpp¶
✅ 7/7 passed — test_effects_2d.cpp
- ✅
behavioralGameOfLifeEffect - glider moves (+1,+1) after 4 generations - ✅
behavioralGameOfLifeEffect - extinction triggers re-seed - ✅
behavioralGameOfLifeEffect - renders non-zero pixels after loop - ✅
behavioralNoiseEffect2D - renders non-zero pixels - ✅
behavioralNoiseEffect2D - output varies with position (scale>0 produces spatial variation) - ✅
behavioralDistortionWaves2DEffect - renders non-zero pixels - ✅
behavioralDistortionWaves2DEffect - spatial variation across pixels
Health Checks¶
✅ 8/8 passed — test_health_checks.cpp
- ✅
formatSineEffectModule - healthReport format after one tick - ✅
formatPreviewModule - healthReport format after one tick - ✅
behavioralHealth check - SineEffectModule and Preview checksums match - ✅
behavioralHealth check - zero-residue: PreviewModule state reset after teardown - ✅
formatScheduler - printHealthReports fires for participating modules - ✅
behavioralEffectsLayer - disableSelf marks setup failed and skips loop - ✅
behavioralEffectsLayer - setupOk resets to true on re-setup after disableSelf - ✅
behavioralDriverLayer - disableSelf marks setup failed
HTTP Server¶
✅ 17/17 passed — test_http_server.cpp
- ✅
formatSineEffectModule - getSchema includes category 'effect' - ✅
formatEffectsLayer - getSchema includes category 'layer' - ✅
formatDriverLayer - getSchema includes category 'layer' - ✅
behavioralSineEffectModule - setControl updates frequency live - ✅
behavioralSineEffectModule - setControl ignores unknown key - ✅
behavioralModuleManager - getModulesJson emits id + name + category + controls - ✅
behavioralModuleManager - setModuleControl returns true for valid id+key - ✅
behavioralModuleManager - setModuleControl returns false for unknown module id - ✅
behavioralModuleManager - setModuleControl returns false for unknown key - ✅
behavioralModuleManager - flushIfDirty does not flush before debounce window - ✅
behavioralModuleManager - getModulesJson reflects live value after setModuleControl - ✅
behavioralModuleManager - flushIfDirty writes state file when debounce is 0 - ✅
behavioralModuleManager - addModule creates a root module at runtime - ✅
behavioralModuleManager - addModule creates a child module with parent_id - ✅
behavioralModuleManager - removeModule returns HasChildren when children exist - ✅
behavioralModuleManager - removeModule returns NotFound for unknown id - ✅
behavioralModuleManager - validateControls: all slider controls have valid min < max
REST and WebSocket Integration¶
✅ 14/14 passed — test_integration.cpp
- ✅
integrationintegration/A: GET /api/modules returns 200 with a JSON array - ✅
integrationintegration/A: POST /api/control with valid id+key returns 200 ok:true - ✅
integrationintegration/A: POST /api/control with unknown module id returns 404 - ✅
integrationintegration/A: POST /api/control with unknown key returns 404 - ✅
integrationintegration/A: POST /api/control with malformed JSON returns 400 - ✅
integrationintegration/A: GET /api/types returns array of known type names - ✅
integrationintegration/B: pixel snapshot header is stable across consecutive ticks - ✅
integrationintegration/B: pixel snapshot contains non-zero pixels after SineEffectModule runs - ✅
integrationintegration/B: GET /api/modules + pixel snapshot are consistent - ✅
integrationintegration/D: POST /api/control value appears in WS state within 600 ms - ✅
integrationintegration/E: first WS connection receives a valid state frame within 3 s - ✅
integrationintegration/E: WS state frame contains expected module ids - ✅
integrationintegration/D4: binary WS frame header byte and size match pixelSnapshot - ✅
integrationintegration/schema: WS receives schema frame after first and second rebuildControls
Effect and Driver Layers¶
✅ 29/29 passed — test_layers.cpp
- ✅
smokeEffectsLayer - zero residue after teardown - ✅
integrationDriverLayer - zero sources produces all-black blend - ✅
integrationDriverLayer - single producer passes through unchanged - ✅
integrationDriverLayer - two producers blend to pixel-wise saturating add - ✅
smokeDriverLayer - zero residue after teardown - ✅
integrationPreviewModule - reads blended output from DriverLayer - ✅
behavioralGridLayout - extent() returns configured dimensions - ✅
behavioralGridLayout - extent() via setProps - ✅
behavioralDriverLayer - props-only sizing (no Layout children, backward compat) - ✅
behavioralDriverLayer - GridLayout child overrides props size via onChildrenReady - ✅
behavioralDriverLayer - onChildrenReady called via addChild (addModule path) - ✅
behavioralEffectsLayer - onSizeChanged ignored when useDriverSize is false - ✅
behavioralEffectsLayer - full-extent driver sizing (start=0 end=1) - ✅
behavioralEffectsLayer - partial driver sizing via start/end props - ✅
behavioralEffectsLayer - resizes when driver resizes via onChildrenReady - ✅
behavioralEffectsLayer - EffectsLayer before DriverLayer setup ordering - ✅
behavioralGridLayout - saveState/loadState round-trips width/height/depth - ✅
integrationDriverLayer - partial EffectsLayer (startX=0.5) does not OOB blend - ✅
behavioralEffectsLayer - saveState/loadState round-trips start/end fractions - ✅
behavioralEffectsLayer - onUpdate resizes buffer when start/end slider changes - ✅
behavioralGridLayout - requestMappings straight (identity for non-serpentine) - ✅
behavioralGridLayout - requestMappings serpentine reverses odd rows - ✅
behavioralEffectsLayer preferredCore returns 0 - ✅
behavioralDriverLayer preferredCore returns 1 - ✅
formatDriverLayer healthReport contains expected fields - ✅
formatDriverLayer healthReport reports source count - ✅
behavioralStatefulModuleBase timing_ accumulates self loop() time - ✅
behavioralStatefulModuleBase parent self-timing excludes child time - ✅
behavioralStatefulModuleBase timing resets on runSetup
test_layouts.cpp¶
✅ 21/21 passed — test_layouts.cpp
- ✅
behavioralRingLayout - extent is (2*radius+1) squared by 1 - ✅
behavioralRingLayout - physCount matches ledCount - ✅
behavioralRingLayout - LED 0 maps to rightmost grid cell (angle=0) - ✅
behavioralRingLayout - LEDs are distributed around the circle (no clustering at one point) - ✅
behavioralWheelLayout - extent is (2*ledsPerSpoke+1) squared by 1 - ✅
behavioralWheelLayout - physCount equals spokes x ledsPerSpoke - ✅
behavioralWheelLayout - spoke 0 innermost LED is one step right of centre - ✅
behavioralWheelLayout - spoke 0 outermost LED is 15 steps right of centre - ✅
behavioralXmasTreeLayout - extent is (2h-1) x h x (2h-1) - ✅
behavioralXmasTreeLayout - depth equals width (square footprint) - ✅
behavioralXmasTreeLayout - physCount equals strings * ledsPerString - ✅
behavioralXmasTreeLayout - first LED maps to bottom layer (y=0) - ✅
behavioralXmasTreeLayout - last LED maps to tip at centre top (y=height-1, x=cx, z=cz) - ✅
behavioralXmasTreeLayout - all virtual indices within 3D grid bounds - ✅
behavioralXmasTreeLayout - two strings produce double the LED count - ✅
behavioralMulti-layout: two offset GridLayouts produce union bounding box - ✅
behavioralMulti-layout: composed PhysMap routes each sub-layout to its virtual sub-region - ✅
behavioralMulti-layout: physMap covers entire union canvas without gaps - ✅
behavioralGridLayout - growing dimensions updates mappingCount - ✅
behavioralGridLayout - shrinking dimensions updates mappingCount - ✅
formatGridLayout - healthReport reflects current dimensions after resize
Logger¶
✅ 10/10 passed — test_logger.cpp
- ✅
behavioralLogger - logLevelFromString parses all levels - ✅
behavioralLogger - logLevelFromString rejects unknown string - ✅
behavioralLogger - logLevelToString round-trips all levels - ✅
behavioralLogger - g_logLevel gate: LOG_SETUP suppressed below Setup level - ✅
behavioralLogger - WS push function receives stripped message - ✅
behavioralLogger - WS push not called when fn is null - ✅
behavioralLogger ring - logPush stores entries in order - ✅
behavioralLogger ring - overflow evicts oldest entries - ✅
behavioralLogger ring - empty string is not stored - ✅
behavioralLogger ring - logPush calls WS push fn
Memory and Filesystem¶
✅ 3/3 passed — test_memory_stats.cpp
- ✅
behavioralMemoryStats - filesystem stats are available - ✅
behavioralMemoryStats - memory sanity checks - ✅
behavioralMemoryStats - multiple snapshots are consistent
test_modifiers.cpp¶
✅ 36/36 passed — test_modifiers.cpp
- ✅
behavioralCoord3D masked sentinel round-trips through isMasked - ✅
behavioralMirrorModifier modifyDims halves each active axis (rounding up) - ✅
behavioralMirrorModifier modifyDims rounds up for odd sizes - ✅
behavioralMirrorModifier modifyXYZ reflects coordinates above midpoint - ✅
behavioralMirrorModifier isStatic returns true - ✅
behavioralMirrorModifier all-axes: dims reduce to 1/8 for 8x8x8 - ✅
behavioralCheckerboardModifier masks pixels where (x+y+z) is odd - ✅
behavioralCheckerboardModifier does not change dims - ✅
behavioralCheckerboardModifier isStatic returns true - ✅
behavioralScrollModifier isStatic: true when all speeds 0, false when any non-zero - ✅
behavioralScrollModifier does not change dims - ✅
behavioralScrollModifier wraps coordinate by tick and speed - ✅
behavioralEffectsLayer with no modifiers: hasStaticModifierChain is true - ✅
behavioralEffectsLayer with no modifiers: applyModifiers is identity - ✅
behavioralDriverLayer with MirrorModifier(X): physical output mirrors left half - ✅
behavioralDriverLayer with CheckerboardModifier: odd pixels are black - ✅
behavioralDriverLayer with ScrollModifier: output changes across frames - ✅
behavioralDriverLayer rebuilds maps when MirrorModifier control changes at runtime - ✅
behavioralDisabling a MirrorModifier reverts to identity output - ✅
integrationDriverLayer two static layers blend additively - ✅
behavioralRotateModifier isStatic: true at speed=0, false when speed!=0 - ✅
behavioralRotateModifier modifyDims: identity at 0 degrees - ✅
behavioralRotateModifier modifyDims: 90 degrees swaps width and height - ✅
behavioralRotateModifier modifyDims: 45 degrees on 8x8 expands to bounding box - ✅
behavioralRotateModifier modifyXYZ: identity at 0 degrees - ✅
behavioralRotateModifier modifyXYZ: 180 degrees maps (0,0) to (w-1,h-1) - ✅
behavioralRotateModifier modifyXYZ: 90 degrees maps corners correctly on 4x8 - ✅
behavioralRotateModifier modifyXYZ: center pixel maps to center of bbox - ✅
behavioralRotateModifier dynamic: angle advances with tick - ✅
behavioralTileModifier is always static - ✅
behavioralTileModifier modifyDims: each axis divided by repeat count - ✅
behavioralTileModifier modifyDims: ceiling division on odd region size - ✅
behavioralTileModifier modifyDims: repeats=1 leaves all dims unchanged - ✅
behavioralTileModifier modifyXYZ: wraps XY coordinates via modulo - ✅
behavioralTileModifier modifyXYZ: Z axis tiling - ✅
behavioralTileModifier bakes into EffectsLayer as a static modifier chain
Module Manager¶
✅ 29/29 passed — test_module_manager.cpp
- ✅
behavioralTypeRegistry - registered types are findable - ✅
behavioralTypeRegistry - create returns correct type - ✅
behavioralTypeRegistry - create returns nullptr for unknown type - ✅
behavioralEffectsLayer - setProps sets width and height - ✅
behavioralSineEffect - setProps and saveState/loadState round-trip - ✅
behavioralModuleManager - loads config and registers all modules - ✅
integrationModuleManager - wires modules correctly (pipeline runs without crash) - ✅
behavioralModuleManager - addModule happy path - ✅
behavioralModuleManager - addModule duplicate id rejected - ✅
behavioralModuleManager - addModule unknown type rejected - ✅
behavioralModuleManager - removeModule happy path - ✅
behavioralModuleManager - removeModule not found returns NotFound - ✅
behavioralModuleManager - removeModule add then remove ok - ✅
behavioralTypeRegistry - getTypes returns all registered type names - ✅
behavioralModuleManager - addModule with valid parent_id succeeds - ✅
behavioralModuleManager - addModule with invalid parent_id rejected - ✅
behavioralModuleManager - removeModule with children returns HasChildren - ✅
behavioralModuleManager - parent_id round-tripped through getModulesJson - ✅
behavioralModuleManager - child module NOT registered in Scheduler (roots-only) - ✅
behavioralModuleManager - child wired into parent's children_ list - ✅
behavioralModuleManager - addModule child: added to parent, not Scheduler - ✅
behavioralModuleManager - removeModule child: detached from parent, not Scheduler - ✅
behavioralModuleManager - auto-creates full default set when no top-level modules exist - ✅
behavioralModuleManager - does NOT auto-create default set when any top-level module exists - ✅
behavioralreplaceModule - leaf effect child changes type, preserves id - ✅
behavioralreplaceModule - layout child triggers DriverLayer remap - ✅
behavioralreplaceModule - parent with children re-wires children to new instance - ✅
behavioralreplaceModule - unknown id returns false - ✅
behavioralreplaceModule - unknown type returns false and leaves original intact
Network and WiFi¶
✅ 37/37 passed — test_network.cpp
- ✅
behavioralNetworkModule - default device_name on PC starts with MM- and has length 7 - ✅
behavioralNetworkModule - setProps overrides device_name - ✅
behavioralNetworkModule - loadState/saveState round-trip for device_name - ✅
formatNetworkModule - getSchema includes device_name and mac_address - ✅
formatNetworkModule - category is network - ✅
formatNetworkModule - healthReport contains device= and mac= - ✅
behavioralEditStr - setControl round-trip via JsonVariant - ✅
behavioralEditStr - setControl(float) returns false for EditStr type - ✅
formatEditStr - getSchema includes value - ✅
smokeWifiStaModule - lifecycle on PC does not crash - ✅
behavioralWifiStaModule - password present in getControlValues - ✅
formatWifiStaModule - password in getSchema has value field - ✅
behavioralWifiStaModule - saveState/loadState round-trip for ssid - ✅
formatWifiStaModule - category is network - ✅
behavioralWifiStaModule - setControl ssid triggers pendingConnect via loop - ✅
behavioralWifiStaModule - password value is empty when unset, matches when set - ✅
behavioralWifiStaModule - password control uiType is password - ✅
smokeWifiApModule - lifecycle on PC does not crash - ✅
behavioralWifiApModule - setInput network accepted without crash - ✅
formatWifiApModule - category is network - ✅
smokeEthernetModule - lifecycle does not crash - ✅
behavioralEthernetModule - status is unsupported on PC (no Ethernet hardware) - ✅
formatEthernetModule - category is network - ✅
formatEthernetModule - healthReport is eth=unsupported on PC - ✅
behavioralEthernetModule - isConnected returns false on PC - ✅
behavioralEthernetModule - loadState/saveState round-trip for mode and static_ip - ✅
behavioralEthernetModule - default static_subnet is 255.255.255.0 - ✅
smokeDeviceDiscoveryModule - lifecycle on PC does not crash - ✅
formatDeviceDiscoveryModule - category is network - ✅
behavioralDeviceDiscoveryModule - setup registers broadcast_interval, status, and 8 device controls - ✅
formatDeviceDiscoveryModule - healthReport starts with devices=0 - ✅
behavioralDeviceDiscoveryModule - setInput network accepted without crash - ✅
behavioralDeviceDiscoveryModule - broadcast_interval saveState/loadState round-trip - ✅
behavioralDeviceDiscoveryModule - getControlValues includes status field - ✅
behavioralWifiApModule - onUpdate enabled false sets status to disabled - ✅
behavioralWifiApModule - onUpdate enabled true after false restores AP (no WiFi on PC) - ✅
behavioralWifiStaModule - onUpdate enabled false sets status to disabled
test_observability.cpp¶
✅ 16/16 passed — test_observability.cpp
- ✅
behavioralModuleTiming record accumulates min/max/total/count correctly - ✅
behavioralModuleTiming avgMs divides totalUs by count * 1000 - ✅
behavioralModuleTiming minMs and maxMs convert microseconds to milliseconds - ✅
behavioralModuleTiming fps = count * 1e6 / totalUs - ✅
behavioralModuleTiming reset clears all fields - ✅
behavioralModuleTiming avgMs and fps return 0 when count is 0 - ✅
behavioralScheduler timing accumulator tracks SpinModule within 5% - ✅
behavioralTiming balance: total module time + idle == tick budget within 2% - ✅
behavioralsetupHeapDelta_ is 0 on PC because pal::free_heap_bytes() stubs to 0 - ✅
behavioralFrag formula: largest == 50% of free gives frag == 50 (below threshold) - ✅
behavioralFrag formula: largest == 49% of free gives frag == 51 (above threshold) - ✅
behavioralFrag formula: zero totalFree returns 0 (guard against divide-by-zero) - ✅
behavioralFrag formula: zero largestBlock gives frag == 100 - ✅
behavioralFrag formula: largest == totalFree gives frag == 0 (fully contiguous) - ✅
behavioralScheduler memWarnings and timingSpikes start at 0 - ✅
behavioralScheduler timingSpikes increments when module exceeds 5x rolling average
test_physmap.cpp¶
✅ 18/18 passed — test_physmap.cpp
- ✅
behavioralPhysMap - default constructed is empty - ✅
behavioralPhysMap - resize initialises all slots to NO_VIRT - ✅
behavioralPhysMap - set and at round-trip - ✅
behavioralPhysMap - out-of-range at returns NO_VIRT - ✅
behavioralPhysMap - out-of-range set is a no-op - ✅
behavioralPhysMap - clear resets to empty - ✅
behavioralPhysMap - resize again re-initialises - ✅
behavioralPhysMap - 1:0 mapping (NO_VIRT slot) - ✅
behavioralPhysMap - 1:1 identity mapping - ✅
behavioralPhysMap - 1:1 shuffled mapping - ✅
behavioralPhysMap - 1:N mapping (shared virtual index) - ✅
behavioralGridLayout buildPhysMap - straight 2x2 is identity - ✅
behavioralGridLayout buildPhysMap - straight 3x2 is identity - ✅
behavioralGridLayout buildPhysMap - serpentine 4x2: row 0 forward, row 1 reversed - ✅
behavioralGridLayout buildPhysMap - straight same as buildMappings identity - ✅
integrationDriverLayer with straight GridLayout: output byte-identical to no-layout blend - ✅
behavioralDriverLayer with serpentine GridLayout: physical pixel at strip 4 matches logical (3,1) - ✅
behavioralDriverLayer without Layout still produces output (identity path)
Preview Pipeline¶
✅ 3/3 passed — test_preview_e2e.cpp
- ✅
integrationPreview pipeline - non-zero output after 50 ticks - ✅
integrationPreview pipeline - checksum varies across ticks - ✅
integrationPreview pipeline - PreviewModule listed with fps>0 via HTTP
test_producer_consumer.cpp¶
✅ 7/7 passed — test_producer_consumer.cpp
- ✅
behavioralbuffer is null before setup - ✅
behavioraldeclareBuffer registers pointer, length and elemSize - ✅
behavioralbufferPtr gives read access to the underlying array - ✅
behavioralproducer_ is null before wiring - ✅
behavioralsetInput('producer', ...) wires the producer - ✅
behavioralsetInput ignores unknown keys - ✅
behavioralconsumer reads producer buffer after wiring
test_reorder.cpp¶
✅ 12/12 passed — test_reorder.cpp
- ✅
behavioralScheduler::reorderModules - execution order changes after reorder - ✅
behavioralScheduler::reorderModules - timing entry moves with its module - ✅
behavioralScheduler::reorderModules - wrong size returns false - ✅
behavioralScheduler::reorderModules - unknown pointer returns false - ✅
behavioralModuleManager::reorderChildren - root order changes in JSON and loop - ✅
behavioralModuleManager::reorderChildren - root: unknown id returns false - ✅
behavioralModuleManager::reorderChildren - root: wrong count returns false - ✅
behavioralModuleManager::reorderChildren - child order changes in JSON - ✅
behavioralModuleManager::reorderChildren - child: parent childCount unchanged - ✅
behavioralModuleManager::reorderChildren - child: unknown parent returns false - ✅
behavioralModuleManager::reorderChildren - child: unknown child id returns false - ✅
behavioralEffectsLayer modifierGen bumps after modifier reorder
Scenario Replay¶
✅ 2/2 passed — test_scenarios.cpp
- ✅
behavioralscenario replay - all scenarios build and run without crash - ✅
integrationscenario replay - base-pipeline fps bound is met
Scheduler¶
✅ 4/4 passed — test_scheduler.cpp
- ✅
integrationphase 1 sanity - ✅
behavioralScheduler loopCore runs only matching-core modules - ✅
behavioralScheduler loop() runs all modules via loopCore(0) then loopCore(1) - ✅
behavioralScheduler loopCore timing is tracked per module
Sine Effect¶
✅ 3/3 passed — test_sine_effect.cpp
- ✅
behavioralSineEffectModule - deterministic output for fixed frequency/amplitude - ✅
smokeSineEffectModule - zero residue after teardown - ✅
integrationPreviewModule - consumer sees producer frame checksum via layers
Stateful Module¶
✅ 23/24 passed — test_stateful_module.cpp
- ✅
behavioralsprint9/B: SystemStatusModule has 'progress' type controls - ✅
behavioralsprint9/B: SystemStatusModule progress controls have min=0 and max>0 - ✅
formatsprint9/D: SystemStatusModule string controls have string value in schema - ❓
behavioralsprint9/E: WifiApModule loadState/saveState round-trip for ap_password - ✅
smokesprint9/F: ModuleManager with disableStatePersistence does not write state files - ✅
behavioralsprint9/G: SineEffectModule frequency and amplitude are uint8_t - ✅
formatsprint9/G: SineEffectModule getSchema includes integer=true for frequency - ✅
behavioralsprint9/G: SineEffectModule setControl with integer value clamps to uint8_t range - ✅
behavioralsprint9/G: SineEffectModule saveState/loadState round-trips integer frequency - ✅
behavioralsprint7/D: meta() const on uninitialised store returns empty document - ✅
behavioralsprint7/D: meta() stores and retrieves non-control fields - ✅
behavioralsprint7/D: meta() store survives teardown/runSetup cycle - ✅
behavioralR4S9/A: defVal captured from field initializer, not overwritten by setControl - ✅
behavioralR4S9/A: defVal is unchanged after saveState/loadState round-trip - ✅
formatR7S9: select control - getSchema emits options array and value - ✅
behavioralR7S9: select control - setControl updates value - ✅
behavioralR7S9: select control - setControl rejects out-of-range via clamp - ✅
behavioralR7S9: select control - saveState and loadState round-trip - ✅
behavioralR7S9: select control - addControlValue dynamic form - ✅
behavioralR7S9: select control - hot-reload frees owned options without leak - ✅
behavioralR7S9: SineEffect - waveform select registered with 4 options - ✅
behavioralR7S9: SineEffect - square waveform produces different checksum than sine - ✅
behavioralR7S9: LinesEffect - axis select registered with 4 options - ✅
behavioralR7S9: LinesEffect - axis=x produces different output than axis=all
System Status¶
✅ 27/27 passed — test_system_info.cpp
- ✅
behavioralLogLevel enum ordering is cumulative - ✅
behaviorallogLevelFromString parses all valid names - ✅
behaviorallogLevelFromString rejects unknown names and nullptr - ✅
behavioralEffectsLayer heapSize equals 2 * width * height * sizeof(projectMM::RGB) - ✅
behavioralDriverLayer heapSize equals width * height * sizeof(projectMM::RGB) (single physical buffer) - ✅
behavioralEffectsLayer heapSize is non-zero for default-constructed (10x10x10) instance - ✅
formatEffectsLayer healthReport contains geometry and counters - ✅
formathealthReport respects buffer length limit - ✅
smokeSystemStatusModule lifecycle: setup / loop / teardown - ✅
behavioralSystemStatusModule exposes key controls by name - ✅
formatSystemStatusModule has no heap allocation - ✅
formatSystemStatusModule classSize is sizeof(SystemStatusModule) - ✅
formatSystemStatusModule healthReport writes non-empty string - ✅
formatSystemStatusModule healthReport contains expected keys after loop1s sampling - ✅
behavioralfillMemoryJson returns all expected top-level keys - ✅
behavioralfillMemoryJson modules array is non-empty and has id + setup_delta_kb fields - ✅
behavioralfillMemoryJson numeric values are non-negative on all platforms - ✅
behavioralpal::ota_begin PC stub returns true and sets handle to 0 - ✅
behavioralpal::ota_write PC stub returns true for non-empty data - ✅
behavioralpal::ota_end PC stub returns true - ✅
smokepal::ota_abort PC stub does not crash - ✅
behavioralg_otaStatus initial value is 'idle' - ✅
behavioralg_otaStatus and g_otaPct can be written and read back - ✅
smokeFirmwareUpdateModule lifecycle: setup / loop / loop1s / teardown - ✅
behavioralFirmwareUpdateModule exposes update_status and update_pct controls - ✅
behavioralFirmwareUpdateModule loop reflects g_otaStatus changes - ✅
formatFirmwareUpdateModule has no heap allocation
System Utility Modules¶
✅ 10/10 passed — test_system_utils.cpp
- ✅
smokeTasksModule - lifecycle does not crash - ✅
formatTasksModule - healthReport format contains task_count= - ✅
formatTasksModule - getSchema exposes tasks display and task_count display - ✅
behavioralTasksModule - task_count is non-negative after setup - ✅
smokeFileManagerModule - lifecycle does not crash - ✅
formatFileManagerModule - healthReport format contains state_dir= and file_count= - ✅
formatFileManagerModule - getSchema exposes files display, filename text, delete button - ✅
behavioralFileManagerModule - delete with empty filename sets error result - ✅
behavioralFileManagerModule - delete nonexistent file sets not-found result - ✅
behavioralFileManagerModule - delete existing file succeeds
Code Analysis (classSize)¶
✅ 2/2 passed — test_techdebt.cpp
- ✅
formattechdebt - classSize per registered type - ✅
formattechdebt - classSize for core infrastructure classes
WebSocket¶
✅ 21/21 passed — test_websocket.cpp
- ✅
behavioralgetControlValues returns empty object for module with no controls - ✅
behavioralgetControlValues reflects live field values - ✅
behavioralgetControlValues updates after second setControl - ✅
behavioralPreviewModule snapshot returns false before first loop() - ✅
behavioralPreviewModule snapshot returns true after DriverLayer loop() and fills buffer - ✅
behavioralPreviewModule snapshot header: byte 0 is 0x02 (pixel frame type) - ✅
behavioralPreviewModule snapshot header: width and height encoded little-endian - ✅
behavioralPreviewModule snapshot payload size matches width * height * 3 - ✅
behavioralPreviewModule snapshot pixel values are non-zero after SineEffectModule runs - ✅
behavioralgetStateJson returns one entry per module - ✅
behavioralgetStateJson entries have id, controls, and timing fields - ✅
behavioralgetStateJson timing fps is positive after one tick - ✅
behavioralgetStateJson control values reflect live state - ✅
behavioralpixelSnapshot returns true for preview1 after one tick - ✅
behavioralpixelSnapshot returns false for unknown module id - ✅
behavioralgetStateJson valid before and after runtime addModule (root) - ✅
behavioralgetStateJson valid before and after runtime removeModule (child) - ✅
formatrebuildControls: schemaDirty set on first type change - ✅
formatrebuildControls: schemaDirty re-set after clear and second type change - ✅
formatrebuildControls: ModuleManager hasSchemaDirty tracks consecutive rebuilds - ✅
behavioralrebuildControls: getModulesJson control set changes after type switch
Scenarios¶
Unit tests replay each scenario file in-process via ModuleManager (no HTTP server). The 2 test cases above exercise all scenario files; the table below shows measured fps per step.
On PC, heap stats are 0 (
pal::free_heap_kb()returns 0). Timing bounds are checked in-process; fps values are PC throughput only, not representative of ESP32 performance.
| Scenario | Step | fps (PC) |
|---|---|---|
| base-pipeline-32x32 | add-ripples | 239,001 |
| base-pipeline-32x32 | resize-32h | 186,456 |
| base-pipeline-64x64 | add-ripples | 245,122 |
| base-pipeline-64x64 | resize-32h | 195,146 |
| base-pipeline-64x64 | resize-64h | 55,525 |
| base-pipeline-speed | add-ripples | 244,526 |
| base-pipeline-speed | speed-slow | 242,461 |
| base-pipeline-speed | speed-default | 244,228 |
| base-pipeline-speed | speed-fast | 243,636 |
| base-pipeline | add-status | 5,432,432 |
| base-pipeline | add-driver | 939,252 |
| base-pipeline | add-ripples | 231,567 |
| base-pipeline | set-speed | 244,526 |
| four-layers | add-ripples | 246,022 |
| four-layers | add-lines | 210,692 |
| four-layers | add-gameoflife | 158,893 |
| four-layers | add-noise | 133,822 |
| two-layers | add-ripples | 246,022 |
| two-layers | add-lines | 212,698 |
| xmas-tree-3d | add-ripples | 169,334 |