| # 9. Worked Example: Pigweed System Stress Test |
| |
| This example models a realistic "Pigweed System" configuration, stressing the hierarchy with: |
| 1. **Global Constraints**: Hardware targets limiting OS choices. |
| 2. **Cross-Module Constraints**: `pw_async2` logic depending on Host vs Embedded. |
| 3. **Recursive Scope**: `pw_rpc` having internal backends. |
| 4. **Hybrid Coverage**: Jointly optimizing "RPC internals" with "Global Platforms". |
| |
| ### 9.1 The Hierarchy Definition |
| |
| ```python |
| # --- Root Component (The System) --- |
| pigweed = Component( |
| name="Pigweed", |
| parameters=[ |
| # Global Build Args |
| Parameter("Target", [Option("Host"), Option("STM32"), Option("RP2040")]), |
| Parameter("HostOS", [Option("Linux"), Option("Mac"), Option("Win")]), |
| Parameter("Toolchain", [Option("Clang"), Option("GCC"), Option("ARM-EABI")]), |
| Parameter("RTOS", [Option("None"), Option("FreeRTOS"), Option("Zephyr")]), |
| ], |
| constraints=[ |
| # Constraint: Hardware dictates valid RTOS |
| Constraint(If(Eq(Val("Target"), Lit("STM32")), In(Val("RTOS"), {"None", "FreeRTOS"}))), |
| Constraint(If(Eq(Val("Target"), Lit("RP2040")), In(Val("RTOS"), {"None", "FreeRTOS"}))), |
| # Constraint: Host only runs "None" (simulated) or special host-RTOS? Let's say None. |
| Constraint(If(Eq(Val("Target"), Lit("Host")), Eq(Val("RTOS"), Lit("None")))), |
| |
| # Constraint: ARM-EABI only for embedded |
| Constraint(If(Eq(Val("Toolchain"), Lit("ARM-EABI")), Neq(Val("Target"), Lit("Host")))), |
| ], |
| coverage=[ |
| # System-Level Matrix: Ensure we test all valid Target/RTOS/Toolchain combos |
| Coverage(axes=["Target", "RTOS", "Toolchain"]) |
| ], |
| sub_components=[ |
| # --- Sub-Component: pw_async2 --- |
| Component( |
| name="pw_async2", |
| parameters=[ |
| Parameter("Dispatcher", [ |
| Option("Basic"), # Works everywhere |
| Option("LibEvent"), # Host only |
| Option("FreeRTOS"), # FreeRTOS only |
| Option("WorkQueue"), # Generic |
| ]), |
| ], |
| constraints=[ |
| # Logic: LibEvent backend requires Host target (and maybe not Windows?) |
| Constraint(If( |
| Eq(Val("Dispatcher"), Lit("LibEvent")), |
| And( |
| Eq(Val("//Target"), Lit("Host")), |
| Neq(Val("//HostOS"), Lit("Win")) |
| ) |
| )), |
| # Logic: FreeRTOS backend requires FreeRTOS OS |
| Constraint(If( |
| Eq(Val("Dispatcher"), Lit("FreeRTOS")), |
| Eq(Val("//RTOS"), Lit("FreeRTOS")) |
| )), |
| ], |
| coverage=[ |
| # Module Goal: Test all Dispatchers *where valid* |
| Coverage(axes=["Dispatcher"]) |
| ] |
| ), |
| |
| # --- Sub-Component: pw_rpc --- |
| Component( |
| name="pw_rpc", |
| parameters=[ |
| # Implicit "Enabled" param logic: |
| # If we select any internal option, this must be "On". |
| # If we select "Off", all internal options are IsDisabled(). |
| Parameter("Enabled", [Option(True), Option(False)]), |
| |
| Parameter("ChannelID", [Option(1), Option(42)]), |
| Parameter("DynamicAlloc", [Option(True), Option(False)]), |
| ], |
| constraints=[ |
| # Logic: Hierarchical Enabling |
| # 1. If 'Enabled' is FALSE, all child params MUST be Disabled. (Enforcement) |
| Constraint(If( |
| Eq(Val("Enabled"), Lit(False)), |
| And(IsDisabled("ChannelID"), IsDisabled("DynamicAlloc")) |
| )), |
| |
| # 2. If 'Enabled' is TRUE, child params MUST be Enabled (have values). |
| Constraint(If( |
| Eq(Val("Enabled"), Lit(True)), |
| And(IsEnabled("ChannelID"), IsEnabled("DynamicAlloc")) |
| )), |
| ], |
| coverage=[ |
| # Verify we test both channel IDs and alloc modes |
| Coverage(axes=["ChannelID", "DynamicAlloc"]) |
| ] |
| ), |
| |
| # --- Sub-Component: pw_sync --- |
| Component( |
| name="pw_sync", |
| parameters=[ |
| Parameter("Backend", [ |
| Option("BinarySemaphore"), |
| Option("RecursiveMutex"), |
| Option("InterruptSpinLock") |
| ]) |
| ], |
| constraints=[ |
| # Backend availability depends on RTOS |
| Constraint(If( |
| Eq(Val("Backend"), Lit("BinarySemaphore")), |
| Neq(Val("//RTOS"), Lit("None")) |
| )) |
| ], |
| coverage=[ |
| Coverage(axes=["Backend"]) |
| ] |
| ) |
| ] |
| ) |
| ``` |
| |
| ### 9.2 The "Hybrid" Solving Process |
| |
| When we solve this via **Strategy C (System Matrix)**: |
| |
| 1. **System Matrix**: The solver identifies the "Platform Matrix" from the root coverage: |
| * `{Target=Host, RTOS=None, Toolchain=Clang}` |
| * `{Target=STM32, RTOS=FreeRTOS, Toolchain=ARM-EABI}` |
| * ... etc ... |
| |
| 2. **Aggregation**: It sees `pw_async2` wants to cover `Dispatcher=*`. |
| |
| 3. **Joint Optimization**: |
| * Requirement: Cover `Dispatcher=LibEvent`. |
| * Constraint Check: Requires `Target=Host`. |
| * Selection: Solver piggybacks this onto a `Time=Host` global test. |
| |
| * Requirement: Cover `Dispatcher=FreeRTOS`. |
| * Constraint Check: Requires `RTOS=FreeRTOS`. |
| * Selection: Solver piggybacks this onto the `STM32 + FreeRTOS` global test. |
| |
| ### 9.3 The Resulting efficiency |
| |
| Instead of N*M isolated tests, we get a minimal set of System Configurations that: |
| 1. **Satisfy Constraints** (No invalid builds). |
| 2. **Exercise Internals**: `pw_async2` gets fully exercised across relevant platforms. |
| 3. **Minimize Redundancy**: If `pw_rpc` doesn't care about OS, it just gets standard coverage via valid configurations chosen for other modules. |