AOI Unit Tests
Rungs uses YAML-based test cases to validate your AOI logic. Tests define inputs, advance the simulation, and check outputs — verifying behavior across single scans, multi-step sequences, and timed progressions.
YAML Format
Each test file is a YAML array of test cases. Every test case has a name and one or more steps.
- name: motor starts on command
steps:
- in:
StartButton: 1
advance:
scans: 1
expect:
MotorRun: 1
Step Fields
| Field | Required | Description |
|---|---|---|
in | No | Sets input tag values before execution |
advance | Yes | How far to advance the simulation |
expect | Yes | Output tag values to verify after execution |
in — Set Inputs
Assigns values to input tags before the step executes. Only tags with usage: input are valid. Values must be numbers (0, 1, 3.14).
in:
StartButton: 1
Temperature: 72.5
Omit in when testing default behavior:
- name: green light when idle
steps:
- advance:
scans: 1
expect:
GreenLight: 1
advance — Progress the Simulation
Every step must specify how to advance. Two modes are available:
Scan-based — run a specific number of PLC scan cycles (each scan = 100ms):
advance:
scans: 1
Time-based — specify a duration in milliseconds or seconds:
advance:
time: 500ms
advance:
time: 3.1s
Time values are converted to scans: floor(ms / 100), minimum 1 scan.
| Duration | Scans |
|---|---|
100ms | 1 |
500ms | 5 |
1s | 10 |
3.1s | 31 |
expect — Verify Outputs
Checks output tag values after the step finishes. Only tags with usage: output are valid. Structured types (TIMER, COUNTER) and array tags cannot be checked directly.
expect:
MotorRun: 1
FaultActive: 0
Test Patterns
Single-Step Tests
Test combinatorial logic that resolves in one scan:
- name: both inputs required for series
steps:
- in:
InputA: 1
InputB: 1
advance:
scans: 1
expect:
SeriesResult: 1
Multi-Step Tests
Verify stateful behavior by chaining steps. State persists across all steps in a test case — local variables, latches, and accumulators carry forward.
Latch persistence:
- name: latch persists after command released
steps:
- in:
LatchCmd: 1
advance:
scans: 1
expect:
LatchedOut: 1
- in:
LatchCmd: 0
advance:
scans: 1
expect:
LatchedOut: 1
Fault seal-in and reset:
- name: clears fault on reset
steps:
- in:
OverloadTrip: 1
advance:
scans: 1
expect:
FaultActive: 1
- in:
OverloadTrip: 0
ResetFault: 1
advance:
scans: 1
expect:
FaultActive: 0
Input Persistence
Input values set via in persist into subsequent steps until explicitly changed. If step 1 sets StartButton: 1, step 2 will still see StartButton: 1 unless overridden.
- name: motor stays running after start released
steps:
- in:
StartButton: 1
advance:
scans: 1
expect:
MotorRun: 1
- in:
StartButton: 0
advance:
scans: 1
expect:
MotorRun: 1
Step 2 explicitly sets StartButton: 0 — without this, it would remain 1 from step 1.
Timer Tests
Use advance: time: to progress timers through their phases:
- name: timer completes after preset elapses
steps:
- in:
Enable: 1
advance:
time: 3.1s
expect:
TimerDone: 1
TimerTiming: 0
Timer reset on disable (TON behavior):
- name: timer resets when enable drops
steps:
- in:
Enable: 1
advance:
time: 1s
expect:
TimerTiming: 1
- in:
Enable: 0
advance:
scans: 1
expect:
TimerTiming: 0
TimerAcc: 0
Multi-Scan Accumulation
Advance multiple scans in a single step to test accumulating behavior:
- name: triggers high alarm above 80
steps:
- in:
FillCmd: 1
advance:
scans: 24
expect:
HighAlarm: 1
Edge-Triggered Counters
LD counters (CTU/CTD) count on rising edges. Toggle inputs between steps to generate multiple counts:
- name: multiple pulses increment counter
steps:
- in:
CountUpPulse: 1
advance:
scans: 1
expect:
CountValue: 1
- in:
CountUpPulse: 0
advance:
scans: 1
expect:
CountValue: 1
- in:
CountUpPulse: 1
advance:
scans: 1
expect:
CountValue: 2
Phased Sequences
Combine time and scan advances to test state machines with multiple phases:
- name: transitions to red after yellow
steps:
- in:
WalkRequest: 1
advance:
scans: 1
expect:
YellowLight: 1
- in:
WalkRequest: 0
advance:
time: 1s
expect:
RedLight: 1
WalkSign: 1
Validation Rules
The editor validates tests in real-time and highlights errors inline.
Structural Rules
- Top level must be a YAML array
- Each item needs
name(string) andsteps(array) - Each step needs
advanceandexpect - Only valid step keys:
in,advance,expect
Tag Rules
invalues must reference tags withusage: inputexpectvalues must reference tags withusage: output- Structured types (TIMER, COUNTER, FBD_TIMER, FBD_COUNTER) cannot appear in
expect - Array tags cannot appear in
inorexpect - All values must be finite numbers (no booleans, strings, or
NaN)
Time Format
Time strings must match the pattern <number>ms or <number>s:
✓ 100ms 2.5s 0.5s 1500ms
✗ 2 seconds 100 1m
Editor Features
Autocomplete
Press Ctrl+Space or type : to trigger context-aware suggestions:
- Top level: test case snippets
- Inside a step:
in,advance,expectkeys - Inside
in: input tag names from your AOI - Inside
expect: output tag names from your AOI - Inside
advance:scansortime
Snippets
Full test case and step templates are available as snippets with tab stops for quick authoring.
Execution
Tests run via the Run Tests button in the toolbar. Each test case:
- Initializes tag state from default values
- For each step:
- Applies
invalues to the current state - Executes the AOI logic routine for the specified number of scans
- Compares output values against
expect
- Applies
- Reports pass/fail with duration and failure details
A safety limit of 10,000 total scan cycles prevents infinite loops.
Results
Results appear in the status panel as a log stream. Each test prints its name, status, and duration.
All tests passing:
● [TEST] Running tests for MotorControl_ST...
✓ [TEST] latches run on start (21ms)
✓ [TEST] motor stays running after start released (3ms)
✓ [TEST] stop has priority over start (1ms)
✓ [TEST] stop drops sealed-in motor (1ms)
✓ [TEST] fault latches and blocks run (1ms)
✓ [TEST] fault persists after trip removed (1ms)
✓ [TEST] clears fault on reset (1ms)
✓ [TEST] cannot start while fault active (1ms)
✓ [TEST] ── Summary ── 8 passed, 0 failed (8 total)
With failures:
● [TEST] Running tests for FBD_COUNTER...
✓ [TEST] increments accumulator when CountUp is asserted (21ms)
✕ [TEST] reaches preset and asserts done (10ms)
step 1: CounterAcc — expected 5, got 1
step 1: CountDone — expected 1, got 0
✓ [TEST] clears accumulator when Reset is asserted (0ms)
✕ [TEST] ── Summary ── 2 passed, 1 failed (3 total)
With validation errors:
● [TEST] Running tests for MyAOI...
✕ [TEST] (Line 4): "advance" is required in every step
✕ [TEST] (Line 8): "BadTag" is not a valid input tag
Result Types
| Symbol | Meaning |
|---|---|
● | Info — test run started |
✓ | Pass — all expectations met |
✕ | Fail — expectation mismatch, validation error, or compilation failure |
Failure Details
When a step fails, each mismatch is listed with the step number, tag name, expected value, and actual value:
step 1: TagName — expected X, got Y
If the AOI fails to compile, the error message replaces individual test results.