| + |
# Introduction
|
| + |
|
| + |
This document describes the acceptance criteria for the Radicle CI
|
| + |
broker, as well as how to verify that they are met. Acceptance
|
| + |
criteria here means a requirement that must be met for the software to
|
| + |
be acceptable to its stakeholders.
|
| + |
|
| + |
This file is used by [Subplot](https://subplot.tech/) to generate and
|
| + |
run test code as part of running `cargo test`.
|
| + |
|
| + |
# Data files shared between scenarios
|
| + |
|
| + |
## Broker configuration
|
| + |
|
| + |
~~~{#broker.yaml .file .yaml}
|
| + |
db: ci-broker.db
|
| + |
report_dir: reports
|
| + |
default_adapter: dummy
|
| + |
adapters:
|
| + |
dummy:
|
| + |
command: ./dummy.sh
|
| + |
env:
|
| + |
RADICLE_NATIVE_CI: native-ci.yaml
|
| + |
sensitive_env: {}
|
| + |
filters:
|
| + |
- !Branch "main"
|
| + |
~~~
|
| + |
|
| + |
## A dummy adapter
|
| + |
|
| + |
This adapter does nothing, just reports a run ID and a successful run.
|
| + |
|
| + |
~~~{#dummy.sh .file .sh}
|
| + |
#!/bin/bash
|
| + |
set -euo pipefail
|
| + |
echo '{"response":"triggered","run_id":{"id":"xyzzy"}}'
|
| + |
echo '{"response":"finished","result":"success"}'
|
| + |
~~~
|
| + |
|
| + |
## A trigger message
|
| + |
|
| + |
This is a request message from the CI broker to the adapter to trigger
|
| + |
a run on a repository. The repository is imaginary as is the commit.
|
| + |
|
| + |
~~~{#trigger.json .file .json}
|
| + |
{
|
| + |
"type": "refsFetched",
|
| + |
"remote": "z6MkgEMYod7Hxfy9qCvDv5hYHkZ4ciWmLFgfvm3Wn1b2w2FV",
|
| + |
"rid": "rad:zwTxygwuz5LDGBq255RA2CbNGrz8",
|
| + |
"updated": [
|
| + |
{
|
| + |
"updated": {
|
| + |
"name": "refs/heads/main",
|
| + |
"old": "0000000000000000000000000000000000000000",
|
| + |
"new": "0000000000000000000000000000000000000000"
|
| + |
}
|
| + |
}
|
| + |
]
|
| + |
}
|
| + |
~~~
|
| + |
|
| + |
## Set rid in trigger message
|
| + |
|
| + |
This is a helper script that reads a trigger message and changes its
|
| + |
`rid` field to be the id of the given repository. It also sets the
|
| + |
`name` field for updated refs to include the repository ID. It writes
|
| + |
the message back to its file.
|
| + |
|
| + |
~~~{#set-rid .file .python}
|
| + |
#!/usr/bin/python3
|
| + |
|
| + |
import json, sys
|
| + |
from subprocess import run, PIPE
|
| + |
|
| + |
filename = sys.argv[1]
|
| + |
cwd = sys.argv[2]
|
| + |
|
| + |
p = run(["rad", "."], check=True, capture_output=True, cwd=cwd)
|
| + |
rid = p.stdout.decode().strip()
|
| + |
|
| + |
p = run(["rad", "self", "--nid"], check=True, capture_output=True, cwd=cwd)
|
| + |
nid = p.stdout.decode().strip()
|
| + |
|
| + |
p = run(["git", "rev-parse", "HEAD"], check=True, capture_output=True, cwd=cwd)
|
| + |
oid = p.stdout.decode().strip()
|
| + |
|
| + |
o = json.load(open(filename))
|
| + |
|
| + |
o["rid"] = rid
|
| + |
|
| + |
if "updated" in o:
|
| + |
x = o["updated"]
|
| + |
for oo in x:
|
| + |
name = oo["updated"]["name"]
|
| + |
oo["updated"]["name"] = f"refs/namespaces/{nid}/{name}"
|
| + |
oo["updated"]["new"] = oid
|
| + |
o["updated"] = x
|
| + |
|
| + |
with open(filename, "w") as f:
|
| + |
json.dump(o, fp=f, indent=4)
|
| + |
~~~
|
| + |
|
| + |
# Acceptance criteria
|
| + |
|
| + |
## Smoke test: Runs adapter
|
| + |
|
| + |
_Requirement:_ CI broker can run its adapter.
|
| + |
|
| + |
_Justification:_ This is obviously necessary.
|
| + |
|
| + |
_Stakeholder:_ Lars.
|
| + |
|
| + |
~~~scenario
|
| + |
given a directory homedir
|
| + |
when I run env HOME=homedir RAD_PASSPHRASE= rad auth --alias brokertest
|
| + |
|
| + |
when I run env HOME=homedir git config --global user.email radicle@example.com
|
| + |
when I run env HOME=homedir git config --global user.name TestyMcTestFace
|
| + |
when I run env HOME=homedir git init testy
|
| + |
given file testy/test.txt from dummy.sh
|
| + |
when I run, in testy, env HOME=../homedir git add .
|
| + |
when I run, in testy, env HOME=../homedir git commit -am test
|
| + |
when I run, in testy, env HOME=../homedir git status
|
| + |
when I run, in testy, env HOME=../homedir RAD_PASSPHRASE= rad init --name testy --description test --default-branch master --private --no-confirm --no-seed
|
| + |
when I run, in testy, env HOME=../homedir rad .
|
| + |
|
| + |
given an installed synthetic-events
|
| + |
given file trigger.json
|
| + |
given file shutdown.json
|
| + |
given file set-rid
|
| + |
when I run env HOME=../homedir python3 set-rid trigger.json testy
|
| + |
when I run synthetic-events synt.sock trigger.json shutdown.json
|
| + |
|
| + |
given an installed ci-broker
|
| + |
given file broker.yaml
|
| + |
given file dummy.sh
|
| + |
when I run chmod +x dummy.sh
|
| + |
|
| + |
when I run env HOME=homedir rad ls --all
|
| + |
when I run env HOME=homedir rad self
|
| + |
when I run find homedir -type f
|
| + |
when I run cat trigger.json
|
| + |
when I run sed -i 's/"auto"/false/' homedir/.radicle/config.json
|
| + |
when I run env HOME=homedir RAD_SOCKET=synt.sock RUST_LOG=debug ci-broker broker.yaml
|
| + |
then command is successful
|
| + |
~~~
|
| + |
|
| + |
|
| + |
## Gives helpful error message if node socket can't be found
|
| + |
|
| + |
_Requirement:_ If the CI broker can't connect to the Radicle node
|
| + |
control socket, it gives an error message that helps the user to
|
| + |
understand the problem.
|
| + |
|
| + |
_Justification:_ This helps users deal with problems themselves and
|
| + |
reduces the support burden on the Radicle project.
|
| + |
|
| + |
_Stakeholder:_ Lars.
|
| + |
|
| + |
~~~scenario
|
| + |
given a directory homedir
|
| + |
when I run env HOME=homedir RAD_PASSPHRASE= rad auth --alias brokertest
|
| + |
|
| + |
given an installed ci-broker
|
| + |
given file broker.yaml
|
| + |
when I run sed -i 's/"auto"/false/' homedir/.radicle/config.json
|
| + |
when I try to run env HOME=homedir RAD_SOCKET=xyzzy.sock ci-broker broker.yaml
|
| + |
then command fails
|
| + |
then stderr contains "ERROR: node control socket does not exist: xyzzy.sock"
|
| + |
~~~
|
| + |
|
| + |
## Gives helpful error message if it doesn't understand its configuration file
|
| + |
|
| + |
_Requirement:_ If the CI broker is given a configuration file that it
|
| + |
can't understand, it gives an error message that explains the problem
|
| + |
to the user.
|
| + |
|
| + |
_Justification:_ This helps users deal with problems themselves and
|
| + |
reduces the support burden on the Radicle project.
|
| + |
|
| + |
_Stakeholder:_ Lars.
|
| + |
|
| + |
_Comment:_ This is a very basic scenario. Error handling is by nature
|
| + |
a thing that can always be made better. We can later add more
|
| + |
scenarios if we tighten the acceptance criteria.
|
| + |
|
| + |
~~~scenario
|
| + |
given a directory homedir
|
| + |
when I run env HOME=homedir RAD_PASSPHRASE= rad auth --alias brokertest
|
| + |
|
| + |
given an installed ci-broker
|
| + |
given file broker.yaml
|
| + |
given file not-yaml.yaml
|
| + |
when I run sed -i 's/"auto"/false/' homedir/.radicle/config.json
|
| + |
when I try to run env HOME=homedir ci-broker not-yaml.yaml
|
| + |
then command fails
|
| + |
then stderr contains "ERROR: failed to parse configuration file as YAML: not-yaml.yaml"
|
| + |
~~~
|
| + |
|
| + |
|
| + |
~~~{#not-yaml.yaml .file}
|
| + |
This file is not YAML.
|
| + |
~~~
|
| + |
|
| + |
## Stops if the node connection breaks
|
| + |
|
| + |
_Requirement:_ If the connection to the Radicle node, via its control
|
| + |
socket, breaks, the CI broker terminates with a message saying why.
|
| + |
|
| + |
_Justification:_ The CI broker can either keep running and trying to
|
| + |
re-connect, or it can terminate. Either is workable. However, it's a
|
| + |
simpler design and less code to terminate and allow re-starting to be
|
| + |
handled by a dedicated system, such as `systemd`.
|
| + |
|
| + |
_Stakeholder:_ Lars.
|
| + |
|
| + |
~~~scenario
|
| + |
given a directory homedir
|
| + |
when I run env HOME=homedir RAD_PASSPHRASE= rad auth --alias brokertest
|
| + |
|
| + |
given an installed ci-broker
|
| + |
given an installed synthetic-events
|
| + |
when I run synthetic-events synt.sock
|
| + |
given file broker.yaml
|
| + |
when I run sed -i 's/"auto"/false/' homedir/.radicle/config.json
|
| + |
when I try to run env HOME=homedir RAD_SOCKET=synt.sock ci-broker broker.yaml
|
| + |
then command fails
|
| + |
then stderr contains "connection to the node control socket broke"
|
| + |
~~~
|
| + |
|
| + |
|
| + |
## Shuts down when requested
|
| + |
|
| + |
_Requirement:_ The test suite can request the CI broker to shut down
|
| + |
cleanly, and it doesn't result in an error.
|
| + |
|
| + |
_Justification:_ In the integration test suite, we need to start and
|
| + |
stop the CI broker many times. We need to easily detect errors.
|
| + |
|
| + |
_Stakeholder:_ Lars.
|
| + |
|
| + |
We use a special magic fake node event to signal shutdown: a
|
| + |
`RefsFetched` event with a skipped update for a ref "`shutdown`" and
|
| + |
an object id of all zeros. This should be sufficiently impossible to
|
| + |
happen in real life.
|
| + |
|
| + |
~~~scenario
|
| + |
given a directory homedir
|
| + |
when I run env HOME=homedir RAD_PASSPHRASE= rad auth --alias brokertest
|
| + |
|
| + |
given an installed ci-broker
|
| + |
given an installed synthetic-events
|
| + |
given file shutdown.json
|
| + |
given file broker.yaml
|
| + |
when I run synthetic-events synt.sock shutdown.json
|
| + |
when I run sed -i 's/"auto"/false/' homedir/.radicle/config.json
|
| + |
when I run sed -i 's/"auto"/false/' homedir/.radicle/config.json
|
| + |
when I try to run env RUST_LOG=trace HOME=homedir RAD_SOCKET=synt.sock ci-broker broker.yaml
|
| + |
then command is successful
|
| + |
~~~
|
| + |
|
| + |
~~~{#shutdown.json .file .json}
|
| + |
{
|
| + |
"type": "refsFetched",
|
| + |
"remote": "z6MkgEMYod7Hxfy9qCvDv5hYHkZ4ciWmLFgfvm3Wn1b2w2FV",
|
| + |
"rid": "rad:zwTxygwuz5LDGBq255RA2CbNGrz8",
|
| + |
"updated": [
|
| + |
{
|
| + |
"skipped": {
|
| + |
"name": "shutdown",
|
| + |
"oid": "0000000000000000000000000000000000000000"
|
| + |
}
|
| + |
}
|
| + |
]
|
| + |
}
|
| + |
~~~
|
| + |
|
| + |
|
| + |
# Acceptance criteria for test tooling
|
| + |
|
| + |
The event synthesizer is a helper to feed the CI broker node events in
|
| + |
a controlled fashion.
|
| + |
|
| + |
## Dummy adapter runs successfully
|
| + |
|
| + |
_Requirement:_ The dummy adapter (in embedded file `dummy.sh`) runs
|
| + |
successfully.
|
| + |
|
| + |
_Justification:_ Test scenarios using the dummy adapter need to be
|
| + |
able to rely that it works.
|
| + |
|
| + |
_Stakeholder:_ Lars
|
| + |
|
| + |
~~~scenario
|
| + |
given file dummy.sh
|
| + |
when I run chmod +x dummy.sh
|
| + |
when I try to run ./dummy.sh
|
| + |
then command is successful
|
| + |
~~~
|
| + |
|
| + |
## Event synthesizer terminates after first connection
|
| + |
|
| + |
_Requirement:_ The event synthesizer runs in the background, but
|
| + |
terminates after the first connection.
|
| + |
|
| + |
_Justification:_ This is needed so that it can be invoked in Subplot
|
| + |
scenarios.
|
| + |
|
| + |
_Stakeholder:_ Lars.
|
| + |
|
| + |
The following scenario may only work on Linux, as it's using `pgrep`
|
| + |
and `nc` and those may not be portable. If so, this may need to be
|
| + |
changed for other platforms.
|
| + |
|
| + |
~~~scenario
|
| + |
given an installed synthetic-events
|
| + |
|
| + |
when I try to run pgrep -f synthetic-events
|
| + |
then command fails
|
| + |
|
| + |
when I run synthetic-events synt.sock
|
| + |
when I run pgrep -f synthetic-events
|
| + |
then command is successful
|
| + |
|
| + |
when I run nc -U synt.sock
|
| + |
then command is successful
|
| + |
|
| + |
when I try to run pgrep -f synthetic-events
|
| + |
then command fails
|
| + |
~~~
|