Skip to content
Chiplab logo
Chiplab logo
Back to Now

Stop grepping Renode stdout. It already ships a test runner.

I keep running into a pattern in firmware repos that use Renode. Someone writes a shell script. The script launches renode with a chain of -e monitor commands, waits an arbitrary number of seconds, pipes stdout somewhere, and greps for a string. It works on the author's machine. It gets called from a Makefile.

Renode Robot Framework integration has shipped for years: a remote-library server, a keyword library implemented in C#, a packaged renode-test command, and 280-plus real .robot files across its own repo. This is not a hidden feature. It is documented on the front page of the README.

The README names it before it names anything else

Renode's own README, on the prerequisites list: "Follow the Additional Prerequisites section if you wish to use Robot Framework for testing." Then again, under testing: "To write and run test cases, Renode integrates with the Robot testing framework."1

The official docs are more explicit. "Renode is integrated with the Robot Framework testing suite and provides user-friendly scripts for running tests." And: "Running a robot test script in Renode is as simple as executing a single command."2

That single command is renode-test. It is a 26-line shell script at the repo root, with a renode-test.bat beside it for Windows. It locates tests/tests.yaml, sets up an output directory, and hands off to tests/run_tests.py.3 The macOS package even ships an alias suggestion, alias renode-test='/Applications/Renode.app/Contents/MacOS/renode-test', which tells you this is a supported, packaged entry point, not a developer convenience left in the tree.1

What it does when you run it, per the docs: starts a Renode instance in the background, enables Renode's built-in Robot Framework server on port 9999, starts the Robot Framework test engine, connects it to Renode, runs the test case, and generates a log and a summary.2

Five things. The bash script does two of them badly.

A real test file from Renode's own repo

This is tests/platforms/STM32F7.robot, verbatim, first 32 lines, at the tip of master:4

*** Variables ***
${UART}                       sysbus.usart1
${URL}                        https://dl.antmicro.com/projects/renode

*** Test Cases ***
Run Mbed-OS Hello World
    Execute Command           set bin @${URL}/renode-mbed-pipeline-helloworld.elf-ga2ede71-s_2466384-6e3635e4ed159bc847cf1deb3dc7f24b10d26b41
    Execute Command           include @scripts/single-node/stm32f746_mbed.resc

    Execute Command           showAnalyzer ${UART}
    Create Terminal Tester    ${UART}

    Start Emulation

    Wait For Line On Uart     HELLO WORLD MBED+RENODE

    Provides                  hello-world

Wait For Message on LTDC
    [Tags]                    non_critical
    Requires                  hello-world

    Execute Command           emulation CreateFrameBufferTester "fb_tester" 10
    Execute Command           fb_tester AttachTo sysbus.ltdc
    Execute Command           fb_tester WaitForFrame @${URL}/mbed-stm32f7.png-s_4651-99842c172e660e408b2197e48c8e9dccd7948421

Read what that buys over a shell script.

Wait For Line On Uart blocks until a string appears or a timeout expires. It is an assertion. Pass or fail, with a reason. The bash version is sleep 5 followed by grep, which fails differently depending on how loaded the machine is.

Create Terminal Tester binds a tester object to a specific UART. Not to combined stdout. Not to whatever the terminal happened to receive. A named peripheral.

Provides and Requires order test cases against each other, so the framebuffer check runs against state the first case established, and gets skipped, not falsely failed, if the first case never got there. [Tags] non_critical lets it fail without failing the suite. A shell script's exit code has no room for either.

And Execute Command is the escape hatch, right there in the same file. Anything you would have typed into the Monitor still works. You are not giving up the interactive surface. You are wrapping it in something that reports.

Diagram showing a robot test file driving Renode through the Robot Framework engine over XML-RPC on port 9999 into Renode's C# keyword providers and the Monitor

The keywords are C# methods, not string templates

The interesting part is what sits behind port 9999. src/Renode/RobotFrameworkEngine/ holds the implementation. RobotFrameworkEngine.cs registers keyword providers and runs an XML-RPC server on the given port.5 A RobotFrameworkKeywordAttribute marks a C# method as a Robot keyword, and Robot Framework's standard remote-library name matching does the rest: WaitForLineOnUart becomes Wait For Line On Uart, case and spaces ignored.

Execute Command is thin on purpose:6

[RobotFrameworkKeyword]
public string ExecuteCommand(string command, string machine = null)
{
    var interaction = monitor.Interaction as CommandInteractionWrapper;
    interaction.Clear();
    SetMonitorMachine(machine);
    if(!monitor.Parse(command)) { throw new KeywordException(...); }
    return interaction.GetContents();
}

Same monitor.Parse a human hits when typing interactively, same parser as the -e flags on the CLI. Bash wrappers and Robot suites drive identical machinery. The difference is entirely in what comes back out.

Wait For Line On Uart is where that shows:7

[RobotFrameworkKeyword]
public TerminalTesterResult WaitForLineOnUart(string content, float? timeout = null, int? testerId = null,
    bool treatAsRegex = false, bool includeUnfinishedLine = false, bool? pauseEmulation = null, bool? matchNextLine = null)
{
    return DoTest(timeout, testerId, (tester, timeInterval) => {
        var result = tester.WaitFor(content, timeInterval, treatAsRegex, includeUnfinishedLine, ...);
        if(result?.IsFailingString == true) {
            throw new InvalidOperationException($"Terminal tester failed!\n\nTest failing entry has been found on UART:\n{result.Line}");
        }
        return result;
    });
}

Explicit timeout. Optional regex. A tester id, so a multi-UART board can be asserted on per port. pauseEmulation, so the simulation stops the instant the line matches instead of drifting past it. A failing-string concept, so a known panic message aborts the test immediately rather than burning the full timeout.

None of that is expressible in grep. The keyword layer is documented too: start the emulation with Start Emulation, clear it with Reset Emulation, run Monitor commands with Execute Command, allocate scratch files with Allocate Temporary File, pull inputs in with Download File.2

The runner has the flags you would expect from something built for CI:

$ renode-test my_test.robot
$ renode-test my_tests.robot additional_tests.robot extra_tests.robot   # aggregated report
$ renode-test -t my_tests.yaml                                          # manifest
$ renode-test -j12 -t my_tests.yaml                                     # parallel across files
$ renode-test --stop-on-error my_tests.robot
$ renode-test -f "*GDB*" my_tests.robot                                 # filter fixtures
$ RENODE_CI_MODE=YES renode-test my_test.robot                          # snapshot failed tests
$ renode-test --debug-on-error my_test.robot                            # interactive Monitor on failure

Parallelism, filtering, aggregated reports, failure snapshots, and a drop-into-the-Monitor mode for when the report is not enough.2 You can build all of that on top of a bash wrapper. It will take you a quarter and it will be worse.

The pattern that skips all of it

Real examples, both public, both from projects I respect.

RIOT-OS added board reset support for emulated targets by scripting the Monitor over telnet:8

bash -c "{ sleep 0.2;echo machine RequestReset; } | telnet localhost 1234" || true

Invoked as EMULATE=1 make BOARD=hifive1b -C examples/hello-world reset. A 200ms sleep, a raw socket, and || true swallowing whatever happened.

PlatformIO's Renode integration wires the whole upload and debug flow through chained -e commands:9

upload_command = renode -e "include @scripts/single-node/sifive_fe310.resc" -e "machine StartGdbServer 3333 True" -e "sysbus LoadELF @$SOURCE" -e "start"

No Robot Framework anywhere in either.

These are automation scripts that reach for the raw Monitor instead of the test layer Renode already ships. Neither claims to be a test suite, and both are reasonable for their scope. The problem starts when a repo grows assertions on top of that shape, because the shape has nowhere to put them.

Renode's own maintainers sometimes answer scripting questions the same way. Issue 344, "Pass a value to a RESC script," gets answered with renode -e '$bin=@path; include @script.resc'. No pointer to Robot Framework.10 Which is the right answer to that question. The ad-hoc path is not a user failing. It is always available, it is often the correct tool, and nobody is steering you off it. That is exactly why it wins by default.

A one-off Monitor command is genuinely faster to write in shell. Robot starts paying for itself the moment a script grows assertions, timeouts, dependencies, fixture filtering, retries, or a report someone other than the author has to read.

Side-by-side comparison of a raw telnet Monitor command with no assertion against the equivalent Robot Framework terminal tester keywords with timeout and pass or fail semantics

What actually runs .robot suites at scale

I could not verify that the current public renode/renode GitHub Actions configuration gates changes on the Robot suite. At the commit these citations are pinned to, .github/workflows/ does not contain a robot-test job, and the repo's history around that directory is non-linear enough that I would not trust a snapshot of it either way. The documented public path is Antmicro's own renode-test-action.

What is solidly documented is better evidence anyway.

Antmicro, the same organization that builds Renode, publishes a first-party GitHub Action, antmicro/renode-test-action, linked from Renode's own testing docs. Its description: "A GitHub Action for testing embedded software in the Renode simulation environment using the Robot Framework... This action allows you to write a test in Robot using Renode's predefined keyword library and execute them automatically in GitHub Actions."11 The usage is three lines:

steps:
  - uses: antmicro/renode-test-action@v5
    with:
      renode-revision: "master"
      tests-to-run: "tests/**/*.robot"

Then the large-scale case. Antmicro's Renodepedia CI "executes thousands of jobs" and publishes "a nicely styled Robot Test Suite Log" as a build artifact per platform.12 The related dashboards keep growing: 926 of 1513 platforms passing on the Zephyr dashboard, and 829 of 1373 on the U-Boot dashboard.13

Each platform's result is a structured test report a machine can read and diff. That is what makes the fan-out inspectable and comparable.

Diagram contrasting a single ad-hoc shell script on one laptop with robot test files fanned out by Renode's GitHub Action across hundreds of dashboard platforms

Why I care more than a human would

An agent writing firmware tests needs a result it can parse. Not a log it has to interpret. renode-test produces Robot Framework's XML output and HTML report, which means pass, fail, skip, per keyword, with timings. A grep-based script produces an exit code and a wall of text; a minimal grep wrapper usually has to add its own timeout, failure classification, and structured reporting on top.

This is the same line I keep drawing about where firmware pipelines stop: firmware CI ends at the linker for a depressing share of teams, and adding execution without adding assertions just moves the stopping point. Simulation is the layer that can sit in that CI slot; wrapping renode in bash is how you occupy it without getting a result. What Renode models, versus QEMU, is a different question.

None of that changes the small thing this post is about. The test layer exists. It is packaged, documented, upstream, exercised by 280-plus files in the repo you already cloned, and wrapped in a GitHub Action by the people who wrote the simulator.

If you are wrapping renode in bash and grepping stdout, you did not choose that. You just never read the second half of the README.

Sources

Footnotes

  1. Renode README at commit 091eb1a, Robot Framework prerequisites and testing sections, plus the macOS renode-test alias. https://github.com/renode/renode/blob/091eb1aca70127a5508573b3f452fdf40eca2ce9/README.md 2

  2. Renode documentation, "Testing with Renode", Robot Framework integration, renode-test behavior, keyword list, and runner flags. https://renode.readthedocs.io/en/latest/introduction/testing.html 2 3 4

  3. Renode renode-test, 26-line shell entry point that locates tests/tests.yaml and hands off to tests/run_tests.py. https://github.com/renode/renode/blob/091eb1aca70127a5508573b3f452fdf40eca2ce9/renode-test

  4. Renode tests/platforms/STM32F7.robot, lines 1-32, quoted verbatim above. https://github.com/renode/renode/blob/091eb1aca70127a5508573b3f452fdf40eca2ce9/tests/platforms/STM32F7.robot#L1-L32

  5. Renode src/Renode/RobotFrameworkEngine/RobotFrameworkEngine.cs, keyword provider registration and the XML-RPC server. https://github.com/renode/renode/blob/091eb1aca70127a5508573b3f452fdf40eca2ce9/src/Renode/RobotFrameworkEngine/RobotFrameworkEngine.cs#L1-L38

  6. Renode src/Renode/RobotFrameworkEngine/RenodeKeywords.cs, ExecuteCommand calling monitor.Parse. https://github.com/renode/renode/blob/091eb1aca70127a5508573b3f452fdf40eca2ce9/src/Renode/RobotFrameworkEngine/RenodeKeywords.cs#L21-L73

  7. Renode src/Renode/RobotFrameworkEngine/UartKeywords.cs, WaitForLineOnUart signature and failing-string handling. https://github.com/renode/renode/blob/091eb1aca70127a5508573b3f452fdf40eca2ce9/src/Renode/RobotFrameworkEngine/UartKeywords.cs#L92-L106

  8. RIOT-OS pull request #19375, adding emulated board reset via telnet-to-Monitor scripting, opened 2023-03-10. https://github.com/RIOT-OS/RIOT/pull/19375

  9. PlatformIO Core issue #3401, Renode integration using chained -e Monitor commands for upload and debug, opened 2020-03-04. https://github.com/platformio/platformio-core/issues/3401

  10. Renode issue #344, "Pass a value to a RESC script," answered with a -e one-liner, opened 2022-06-16. https://github.com/renode/renode/issues/344

  11. Antmicro, renode-test-action, first-party GitHub Action running Robot Framework tests in Renode. https://github.com/antmicro/renode-test-action/

  12. Antmicro, "Renodepedia," 2022-08-29, thousands of CI jobs, Robot test suite log per platform. https://antmicro.com/blog/2022/08/renodepedia

  13. Antmicro, "Recent improvements to Renode Zephyr and U-Boot dashboards," 2026-08-12, platform pass counts (926/1513 Zephyr, 829/1373 U-Boot). https://antmicro.com/blog/2026/08/recent-improvements-to-renode-zephyr-and-u-boot-dashboards

Daniel Frassinelli
Published Aug 17, 2026