product: maestro audience: test-developer authority: normative
Runtime Control Fields
enabled — static disable
false → step IS immediately SKIPPED without executing.
- name: "Legacy step"
type: mock
enabled: false
precondition — conditional execution
A DynamicExpresso boolean expression evaluated at runtime.
false → SKIPPED. Exception during evaluation → ABORTED.
- name: "Calibrate"
type: mock
precondition: "runCalibration == true"
Supported syntax:
variable == true/variable == falsetemperature > 25a == true && b < 3skipCal == true || forceRun == true
cfg.*, exec.*, and mes.* MUST NOT be used in precondition: expressions.
These namespaces use dot notation which DynamicExpresso interprets as C# member access.
To branch on a cfg.* value, read it into a bare variable in a preceding step.
force — verdict override
- name: "Noisy measurement"
type: mock
force: "pass"
| Value | Effect |
|---|---|
pass |
Force PASS (VerdictForced = true) |
fail |
Force FAIL |
skip |
Do not execute; SKIPPED |
run |
Execute even if precondition is false |
retry — retry on failure
- name: "Flaky instrument read"
runner: dotnet
runner_type: net10.0
assembly: "Tests.dll"
class: "Tests.Instruments"
method: "ReadDMM"
retry:
count: 3 # 3 retries → 4 total attempts
delay_ms: 500
repeat — condition-based loop
Two built-in variables are set before each iteration:
| Variable | Type | Description |
|---|---|---|
__iteration__ |
int | Zero-based counter (0, 1, 2, …) |
__cycle__ |
int | One-based counter (1, 2, 3, …) — use this for human-readable names |
max_iterations IS required as a safety cap.
- name: "Sweep 5 points"
runner: python
runner_type: python3.11
module: "sweep"
function: "measure_point"
parameters:
step: "{{__iteration__}}"
repeat:
condition: "__iteration__ < 4" # iterations 0–4 = 5 total
max_iterations: 10
Looping a sub-sequence
repeat works on type: sequence steps. __iteration__ and __cycle__ are
available inside the child YAML through the shared variable scope.
Use __cycle__ to produce readable measurement names like VOUT_CYCLE1, VOUT_CYCLE2.
Every sub-sequence file MUST have a
test:root element withname:andversion:. The format is identical to a top-level test file; the engine ignoressetup:andteardown:blocks inside sub-sequences. Only thesteps:list is executed inline.This is intentional — see Standalone / Composite Test Pattern for how to use this behaviour to write sub-sequences that work both as independent tests and as reusable modules in a full suite.
# poe.yaml — top-level test
steps:
- name: "Power cycle"
type: sequence
sequence: "tests/poe-cycle.yaml"
repeat:
condition: "__iteration__ < 2" # 2 cycles
max_iterations: 5
post_execution_action: terminate-on-fail
# tests/poe-cycle.yaml — the reusable sub-sequence
test:
name: "PoE power cycle (sub-sequence)"
version: "1.0.0"
steps:
- name: "[Cycle {{__cycle__}}] Enable detection"
runner: dotnet
runner_type: net10.0
assembly: "regressiontests.dll"
class: "regressiontests.Poe"
method: "EnableDetectionAndClassification"
post_execution_action: terminate-on-fail
- name: "[Cycle {{__cycle__}}] Read registers"
runner: dotnet
runner_type: net10.0
assembly: "regressiontests.dll"
class: "regressiontests.Poe"
method: "DumpRegisters"
outputs:
opmode: "{{opmode_ch1}}"
vin: "{{vin}}"
measurements:
- { name: "OPMODE_CYCLE{{__cycle__}}", type: string, value: "{{opmode_ch1}}" }
- { name: "VIN_CYCLE{{__cycle__}}", value: "{{vin}}", unit: "V", operator: log }
timeout_ms — per-attempt wall-clock timeout
Hardware steps MUST always carry timeout_ms. An instrument that stops responding
will otherwise block the runner indefinitely.
- name: "Flash firmware"
runner: dotnet
runner_type: net10.0
assembly: "FlashTools.dll"
class: "FlashTools.Programmer"
method: "Flash"
timeout_ms: 30000
run_on_abort — guaranteed cleanup
true → the step IS executed even when the test is aborting. MUST be set on all
power-down, disconnect, and instrument-release steps.
- name: "Power down (always runs)"
runner: dotnet
runner_type: net10.0
assembly: "Tests.dll"
class: "Tests.Cleanup"
method: "PowerDown"
run_on_abort: true
post_execution_action
| Value | Effect |
|---|---|
continue |
Default. Proceed regardless of verdict |
terminate-on-fail |
Stop the test if this step FAILs or ABORTs |
terminate-always |
Stop the test after this step regardless of verdict |
MUST be set to terminate-on-fail on all power-on, connect, and init steps.
requires — shared-instrument locking (parallel stations)
A list of named shared resources the step needs exclusive access to. When the station
runs more than one test at a time (MaxParallelExecutions > 1), concurrent tests share the
bench instruments; requires: tells the engine to serialise access to each named resource.
The whole set is acquired atomically before the step runs and released after.
- name: "Measure 3V3 Rail"
runner: dotnet
runner_type: net10.0
assembly: "PowerTests.dll"
class: "MyTests.PowerTests"
method: "MeasureVoltage"
requires: [dmm, mux] # exclusive DMM + mux for the duration of this step
measurement:
name: "RAIL_3V3"
value: "{{result}}"
low_limit: 3.135
high_limit: 3.465
unit: "V"
Rules:
- Names are free-form — use one consistent name per physical instrument across the
whole station (e.g.
dmm,psu,mux,scope). - The set is acquired in a fixed order, so declaring
requires:can never deadlock. - On a single-execution station (
MaxParallelExecutions = 1, the default) this has no effect — but it is always safe to declare, and makes the package correct on a parallel station. - Any step that touches a shared instrument MUST declare it. Omitting
requires:is the common cause of corrupted measurements when two tests run at once.