Run your PlatformIO unit tests with no hardware. You install nothing.
Category: Tutorial
A pio test on real hardware needs a board on the desk.
The test needs a USB cable.
You need a free hand to press reset on push.
LabWired needs none of those items.
This guide connects your PlatformIO pio test suite to the LabWired simulator.
The LabWired simulator is a deterministic digital twin of your MCU.
Your tests do not change.
PlatformIO still parses the usual Unity output.
Only the board is absent.
You can use the GitHub Actions path below.
That path installs nothing on your local machine.
LabWired connects to PlatformIO through the same hook as Renode and QEMU.
The hook is the built-in test_testing_command.
PlatformIO does not need information about LabWired.
PlatformIO runs a command.
PlatformIO reads the Unity results from the output of the command.
Start in GitHub Actions with no install
Fork w1ne/labwired-core.
Put this text in .github/workflows/firmware-tests.yml.
Push the file.
name: Firmware tests (LabWired simulator)
on: [push, pull_request]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install PlatformIO
run: pipx install platformio
- uses: dtolnay/rust-toolchain@stable
- name: Build the LabWired CLI and put it on PATH
run: |
cargo build --release -p labwired-cli
echo "$PWD/target/release" >> "$GITHUB_PATH"
- name: Run unit tests in the simulator
working-directory: examples/platformio/nrf52840-unity
run: pio test -e nrf52840_dk
The GitHub runner installs PlatformIO. The runner builds the firmware. The runner runs the firmware in the simulator. The runner reports the result of each test. This work does not use your machine.
test/test_smoke/test_main.c:50: test_addition [PASSED]
test/test_smoke/test_main.c:51: test_uart_is_enabled [PASSED]
test/test_smoke/test_main.c:52: test_string_length [PASSED]
================== 3 test cases: 3 succeeded in 00:00:01.4 ==================
You can use a terminal instead.
Open the repository in a GitHub Codespace.
Run cd examples/platformio/nrf52840-unity && pio test.
This path keeps all work off your machine.
The Codespace runs in your browser.
How the tests run
flowchart LR
A["pio test<br/>builds firmware.elf"] -- ELF --> B["LabWired simulator<br/>boots the ELF · deterministic"]
B -- "UART → stdout" --> C["PlatformIO<br/>parses Unity PASS/FAIL"]
C --> D(("✓"))
classDef ok fill:#27c93f,stroke:#1a1a1a,stroke-width:2px,color:#0d2b12;
class D ok;
Caption: A process does the work of the board. The rest of the pipeline does not change.
The two sides are plain headless processes. A headless process has no display. A pipe connects the two processes. You do not flash a device. You do not enumerate a probe. Board state does not change between runs. A change in board state causes most hardware-in-the-loop flakiness.
The full integration
The integration is a few lines of platformio.ini.
[platformio] ; so the ${platformio.*} vars resolve
[env:nrf52840_dk]
platform = nordicnrf52
board = nrf52840_dk
test_testing_command =
labwired test --script labwired.test.yaml
--firmware ${platformio.build_dir}/${this.__env__}/firmware.elf
pio test builds firmware.elf.
pio test then runs this command.
pio test parses the output of the command.
Unity output must reach the UART that LabWired mirrors to stdout.
For the Arduino, Zephyr, or mbed frameworks, that UART is already your normal test serial port.
What you get from the tests
- Determinism. The same inputs produce the same results on every run. A failed build means a real regression. The failure is not a timing error that happens once.
- Parallelism. CI can run every board and every configuration at the same time. A bench full of hardware cannot run every board and every configuration at the same time.
- No hardware in the loop. Contributors do not need a board to run the test suite. CI runners do not need a board to run the test suite.
- Observability. Every run writes UART logs. Every run writes VCD traces. Every run writes a machine-readable
result.json.
The simulator executes your firmware on a full Cortex-M core model. The model includes peripherals. LabWired validates the peripherals against real silicon. The instructions that run in CI are the instructions that would run on the board.