Emulate CI run locally for a Radicle repository

By: Lars Wirzenius, The Radicle Project

2025-09-06 05:42

Table of Contents

1 Introduction

rad-ci makes it easy to see if the current state of the source tree would pass CI, without committing anything, or pushing to another node. This is great when you're working on a change and want to see if it would pass the checks done by CI. It's much less waiting than doing it via the CI server, because everything runs on your machine.

Radicle CI runs CI for a change to a repository by using a CI adapter to get execute the run on some external CI system or by using a local CI engine. For example, the adapter might trigger CI to run on an external CI system such as Concourse, or it might execute the steps that CI would run locally.

For projects that use either the Radicle native CI adapter, or the Ambient adapter, rad-ci executes the steps locally, without pushing changes to another node, or even to the local node.

  • For the native CI adapter, rad-ci reads .radicle/native.yaml, extracts the shell snippet from the shell field, and executes the using the Bash shell. This is what the native CI adapter also does.

  • For the Ambient adapter, rad-ci invokes ambient-driver to execute the pre-plan, plan, and post-plan defined in .radicle/ambient.yaml, the same way the Ambient adapter does.

This document describes the acceptance criteria for rad-ci, and how to automatically verify that they are met.

2 Data file for verification

2.1 Native CI run specification

shell: |
  echo hello, world

2.2 Ambient CI run specification

plan:
- action: shell
  shell: |
    echo hello, world

3 Acceptance criteria

3.1 Smoke test: reports its version

Want: The tool can report its version.

Why: This is useful information for supporting a remote user.

This is also a useful "smoke test": it verifies that the rad-ci executable exists, can be invoked, and it can run at all. If this doesn't work, there's no real hope that anything else works, either.

given an installed rad-ci
when I run rad-ci --version
then stdout matches regex ^rad-ci \d+\.\d+\.\d+

3.2 Shows its actual configuration

Want: The tool can show the actual configuration it uses at run time, including the effect of command line options.

Why: This allows the user to know what the configuration is, without having to guess.

given an installed rad-ci
when I run rad-ci config
then stdout contains ""dry_run": false"
then stdout contains ""engine": null"
then stdout contains ""ambient_image": null"
when I run rad-ci --dry-run config
then stdout contains ""dry_run": true"
when I run rad-ci --engine ambient config
then stdout contains ""engine": "Ambient""
when I run rad-ci --engine native config
then stdout contains ""engine": "Native""
when I run rad-ci --ambient-image ambient.qcow2 config
then stdout contains ""ambient_image": "/"

3.3 User can choose the configuration file

Want: The user can specify a configuration file when invoking rad-ci, or the default one can be used, with built-in defaults as a fallback.

Why: Ideally, the user doesn't have to specify a file when invoking the command, for convenience, but can, if they need to.

given an installed rad-ci
when I run rad-ci config
then stdout contains ""ambient_image": null,"
given file rad-ci-config.yaml
when I run rad-ci --config rad-ci-config.yaml config
then stdout contains ""ambient_image": "/"
given file .config/rad-ci/config.yaml from rad-ci-config.yaml
when I run rad-ci config
then stdout contains ""ambient_image": "/"

3.4 Reports error when it can't determine what to emulate

Want: When user queries what CI engine will be emulated, the tool gives an error when it can't determine that.

Why: This helps users to be confident about what will happen if they run the tool to emulate CI.

given an installed rad-ci
when I try to run rad-ci --dry-run
then command fails
then stderr contains "CI engine"

3.5 Show selected CI to emulate: native

Want: User can query the tool for what CI engine to emulate, and why, when the native CI is chosen.

Why: This helps users to be confident about what will happen if they run the tool to emulate CI.

given an installed rad-ci
given file .radicle/native.yaml from native.yaml
when I run rad-ci --dry-run
then stdout contains ""Native":"
then stdout contains ""shell": "echo hello, world\n""

3.6 Show selected CI to emulate: Ambient

Want: User can query the tool for what CI engine to emulate, and why, when the Ambient engine is chosen.

Why: This helps users to be confident about what will happen if they run the tool to emulate CI.

given an installed rad-ci
given file .radicle/ambient.yaml from ambient.yaml
when I run env RAD_CI_LOG=debug rad-ci --dry-run
then stdout contains ""Ambient":"
then stdout contains ""shell": "echo hello, world\n""

3.7 Show selected CI to emulate: force

Want: User can force the choice of CI engine to emulate.

Why: This helps users to be confident about what will happen if they run the tool to emulate CI.

given an installed rad-ci
given file .radicle/native.yaml from native.yaml
given file .radicle/ambient.yaml from ambient.yaml
when I run env RAD_CI_LOG=debug rad-ci --dry-run
then stdout contains ""Ambient":"
when I run env RAD_CI_LOG=debug rad-ci --dry-run --engine ambient
then stdout contains ""Ambient":"
when I run env RAD_CI_LOG=debug rad-ci --dry-run --engine native
then stdout contains ""Native":"

3.8 Emulate native CI

What: The tool can emulate the native CI adapter running CI.

Why: This is fundamental to the purpose of the tool.

given an installed rad-ci
given file .radicle/native.yaml from native.yaml
when I run env RAD_CI_LOG=debug rad-ci --vague
then stdout is exactly "hello, world "

3.9 Emulate Ambient CI

What: The tool can emulate the Ambient CI adapter.

Why: This is fundamental to the purpose of the tool.

given an installed rad-ci
given file xyzzy/.radicle/ambient.yaml from ambient.yaml
given file rad-ci-config.yaml
given file .config/ambient/config.yaml from ambient-config.yaml
given a directory state/projects
when I run env RAD_CI_LOG=trace AMBIENT_LOG=trace rad-ci --config rad-ci-config.yaml --source xyzzy
then stdout doesn't contain "hello, world"
when I run env RAD_CI_LOG=trace AMBIENT_LOG=trace rad-ci --config rad-ci-config.yaml --source xyzzy --verbose
then stdout contains "hello, world"
projects: "/dev/null"
state: "state"
executor: "/usr/bin/ambient-execute-plan"
qemu:
  cpus: 1
  memory: "1 GB"
artifacts_max_size: "1 GB"
cache_max_size: "1 GB"
ambient_image: ambient.qcow2