Radish alpha
r
Radicle CI broker
Radicle
Git (anonymous pull)
Log in to clone via SSH
test: update the subplot scenarios to verify the changes
Lars Wirzenius committed 1 year ago
commit 0db9dfe34cee8b24574ab9449ee4310950c36f47
parent 003e9012792694586d59764c8db2d59c38a3cd7d
3 files changed +484 -59
modified ci-broker.md
@@ -26,6 +26,20 @@ filters:
  - !Branch "main"
~~~

+
~~~{#broker-allow-nothing.yaml .file .yaml}
+
db: ci-broker.db
+
report_dir: reports
+
default_adapter: mcadapterface
+
adapters:
+
  mcadapterface:
+
    command: ./adapter.sh
+
    env:
+
      RADICLE_NATIVE_CI: native-ci.yaml
+
    sensitive_env: {}
+
filters:
+
  - !Branch "this-branch-does-not-exist"
+
~~~
+

## A dummy adapter

This adapter does nothing, just reports a run ID and a successful run.
@@ -37,10 +51,10 @@ echo '{"response":"triggered","run_id":{"id":"xyzzy"}}'
echo '{"response":"finished","result":"success"}'
~~~

-
## A trigger message
+
## A `refsFetched` node event

-
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.
+
This is a node event from the node to signal that some git refs have
+
changed in repository.

~~~{#trigger.json .file .json}
{
@@ -50,7 +64,7 @@ a run on a repository. The repository is imaginary as is the commit.
  "updated": [
    {
      "updated": {
-
        "name": "refs/heads/main",
+
        "name": "refs/namespaces/DUMMYNID/refs/heads/main",
        "old": "0000000000000000000000000000000000000000",
        "new": "0000000000000000000000000000000000000000"
      }
@@ -59,7 +73,7 @@ a run on a repository. The repository is imaginary as is the commit.
}
~~~

-
## A shutdown message
+
## A shutdown event

This asks the CI broker to shut down cleanly.

@@ -79,6 +93,7 @@ This asks the CI broker to shut down cleanly.
}
~~~

+

## Set rid in trigger message

This is a helper script that reads a trigger message and changes its
@@ -120,8 +135,87 @@ with open(filename, "w") as f:
    json.dump(o, fp=f, indent=4)
~~~

+
## Set environment variables and run command
+

+
To avoid having to repeat environment variables to set up and use a
+
Radicle node for verification scenarios, we provide a script that
+
sets them and runs a command.
+

+
~~~{#radenv.sh .file .sh}
+
#!/bin/bash
+

+
set -euo pipefail
+

+
homedir="$(pwd)/homedir"
+

+
env \
+
	HOME="$homedir" \
+
	RAD_PASSPHRASE=secret \
+
	RAD_HOME="$homedir/.radicle" \
+
	RAD_SOCKET=synt.sock \
+
	RUST_LOG=trace \
+
	RADICLE_CI_BROKER_LOG=trace \
+
	"$@"
+
~~~
+

+

+
## Set up a node with repository
+

+
Most of our verification scenarios will need to set up a Radicle node
+
and a test repository there. Rather than repeat it in every scenario,
+
we use this helper script.
+

+
~~~{#setup-node.sh .file .sh}
+
#!/bin/bash
+

+
set -xeuo pipefail
+

+
mkdir -p "$HOME"
+

+
env | grep RAD
+
rad auth --alias brokertest
+

+
git config --global user.email radicle@example.com
+
git config --global user.name TestyMcTestFace
+
git init -b main testy
+

+
cd testy
+
echo "test file" > test.txt
+

+
git add .
+
git commit -am test
+
git status
+
git show
+
rad init --name testy --description test --default-branch main --private --no-confirm --no-seed
+
rad inspect --identity
+
rad id list
+
~~~
+

# Acceptance criteria

+
## Shows config as JSON
+

+
_Requirement:_ The CI broker can write out the configuration is uses
+
at run time as JSON.
+

+
_Justification:_ This is helpful for the node operator to verify that
+
they have configured the program correctly.
+

+
_Stakeholder:_ Lars
+

+
Our verification here is quite simplistic, and only checks that the
+
output is in the JSON format. It does not try to make sure the JSON
+
matches the YAML semantically.
+

+
~~~scenario
+
given an installed cib
+
given file broker.yaml
+
when I run cib --config broker.yaml config --output actual.json
+
when I run jq . actual.json
+
then command is successful
+
~~~
+

+

## Smoke test: Runs adapter

_Requirement:_ CI broker can run its adapter.
@@ -132,18 +226,9 @@ nothing else has a hope of working.
_Stakeholder:_ Lars.

~~~scenario
-
given a directory homedir
-
when I run env HOME=homedir RAD_PASSPHRASE= RAD_HOME=homedir/.radicle 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_HOME=../homedir/.radicle RAD_PASSPHRASE= rad init --name testy --description test --default-branch master --private --no-confirm --no-seed
+
given file radenv.sh
+
given file setup-node.sh
+
when I run bash radenv.sh bash setup-node.sh

given an installed synthetic-events
given file trigger.json
@@ -152,16 +237,15 @@ 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 --log log.txt

-
given an installed ci-broker
+
given an installed cib
given a directory reports
given file broker.yaml
given file adapter.sh from dummy.sh
when I run chmod +x adapter.sh

when I run sed -i 's/"auto"/false/' homedir/.radicle/config.json
-
when I try to run env HOME=homedir RAD_HOME=homedir/.radicle RAD_SOCKET=synt.sock RUST_LOG=debug ci-broker broker.yaml
+
when I try to run bash radenv.sh RAD_SOCKET=synt.sock cib --config broker.yaml insert
then command is successful
-
then file reports/index.html contains "xyzzy"
~~~


@@ -181,18 +265,9 @@ user can access individual logs.
_Stakeholder:_ Lars.

~~~scenario
-
given a directory homedir
-
when I run env HOME=homedir RAD_PASSPHRASE= RAD_HOME=homedir/.radicle 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 adapter-with-url.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_HOME=../homedir/.radicle RAD_PASSPHRASE= rad init --name testy --description test --default-branch master --private --no-confirm --no-seed
+
given file radenv.sh
+
given file setup-node.sh
+
when I run bash radenv.sh bash setup-node.sh

given an installed synthetic-events
given file trigger.json
@@ -201,21 +276,18 @@ 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 --log log.txt

-
given an installed ci-broker
-
given a directory reports
-
given file broker.yaml
given file adapter.sh from adapter-with-url.sh
when I run chmod +x adapter.sh

+
given an installed cib
+
given file broker.yaml
+
given a directory reports
+

when I run sed -i 's/"auto"/false/' homedir/.radicle/config.json
-
when I try to run env HOME=homedir RAD_HOME=homedir/.radicle RAD_SOCKET=synt.sock RUST_LOG=debug ci-broker broker.yaml
+
when I try to run bash radenv.sh RAD_SOCKET=synt.sock cib --config broker.yaml insert
then command is successful
-

-
when I run cat reports/index.html
-
then file reports/index.html contains "https://ci.example.com/xyzzy"
~~~

-

~~~{#adapter-with-url.sh .file .sh}
#!/bin/bash
set -euo pipefail
@@ -235,17 +307,19 @@ reduces the support burden on the Radicle project.
_Stakeholder:_ Lars.

~~~scenario
-
given a directory homedir
-
when I run env HOME=homedir RAD_HOME=homedir/.radicle RAD_PASSPHRASE= rad auth --alias brokertest
+
given file radenv.sh
+
given file setup-node.sh
+
when I run bash radenv.sh bash setup-node.sh

-
given an installed ci-broker
+
given an installed cib
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_HOME=homedir/.radicle RAD_SOCKET=xyzzy.sock ci-broker broker.yaml
+
when I try to run bash radenv.sh RAD_SOCKET=xyzzy.sock cib --config broker.yaml insert
then command fails
-
then stderr contains "ERROR: node control socket does not exist: xyzzy.sock"
+
then stderr contains "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
@@ -262,16 +336,17 @@ 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_HOME=homedir/.radicle RAD_PASSPHRASE= rad auth --alias brokertest
+
given file radenv.sh
+
given file setup-node.sh
+
when I run bash radenv.sh bash setup-node.sh

-
given an installed ci-broker
+
given an installed cib
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
+
when I try to run env HOME=homedir cib --config not-yaml.yaml config
then command fails
-
then stderr contains "ERROR: failed to parse configuration file as YAML: not-yaml.yaml"
+
then stderr contains "failed to parse configuration file as YAML: not-yaml.yaml"
~~~


@@ -292,20 +367,20 @@ handled by a dedicated system, such as `systemd`.
_Stakeholder:_ Lars.

~~~scenario
-
given a directory homedir
-
when I run env HOME=homedir RAD_HOME=homedir/.radicle RAD_PASSPHRASE= rad auth --alias brokertest
+
given file radenv.sh
+
given file setup-node.sh
+
when I run bash radenv.sh bash setup-node.sh

-
given an installed ci-broker
+
given an installed cib
given an installed synthetic-events
when I run synthetic-events synt.sock --log log.txt
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_HOME=homedir/.radicle RAD_SOCKET=synt.sock ci-broker broker.yaml
+
when I try to run bash radenv.sh RAD_SOCKET=synt.sock cib --config broker.yaml insert
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
@@ -322,16 +397,17 @@ 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_HOME=homedir/.radicle RAD_PASSPHRASE= rad auth --alias brokertest
+
given file radenv.sh
+
given file setup-node.sh
+
when I run bash radenv.sh bash setup-node.sh

-
given an installed ci-broker
+
given an installed cib
given an installed synthetic-events
given file shutdown.json
given file broker.yaml
when I run synthetic-events synt.sock shutdown.json --log synt.log
when I run sed -i 's/"auto"/false/' homedir/.radicle/config.json
-
when I try to run env RUST_LOG=trace HOME=homedir RAD_HOME=homedir/.radicle RAD_SOCKET=synt.sock ci-broker broker.yaml
+
when I try to run bash radenv.sh RAD_SOCKET=synt.sock cib --config broker.yaml insert
then command is successful
~~~

@@ -408,3 +484,312 @@ when I run nc -U synt.sock
when I run sleep 0.1
then file synt.sock does not exist
~~~
+

+

+
# Acceptance criteria for persistent database
+

+
The CI broker uses an SQLite database for persistent data. Many
+
processes may need to access or modify the database at the same time.
+
While SQLite is good at managing that, it needs to be used in the
+
right way for everything to work correctly. The acceptance criteria in
+
this chapter address that.
+

+
To enable the verification of these acceptance criteria, the CI broker
+
database allows for a "counter", as a single row in a dedicated table.
+
Concurrency is tested by having multiple processes update the counter
+
at the same time and verifying the end result is as intended and that
+
every value is set exactly once.
+

+
## Count in a single process
+

+
_Requirement:_ A single process can increment the test counter
+
correctly.
+

+
_Justification:_ If this doesn't work with a single process, it won't
+
work of multiple processes, either.
+

+
_Stakeholder:_ Lars.
+

+
~~~scenario
+
given an installed cibtool
+
then file count.db does not exist
+
when I run cibtool --db count.db counter show
+
then stdout is exactly "0\n"
+
when I run cibtool --db count.db counter count --goal 1000
+
when I run cibtool --db count.db counter show
+
then stdout is exactly "1000\n"
+
~~~
+

+

+
## Insert events into queue
+

+
_Requirement:_ Insert broker events generated from node events into
+
persistent event queue in the database, when allowed by the CI broker
+
event filter.
+

+
_Justification:_ This is fundamental for running CI when repositories
+
in a node change.
+

+
_Stakeholder:_ Lars.
+

+
~~~scenario
+
given file radenv.sh
+
given file setup-node.sh
+
when I run bash radenv.sh bash setup-node.sh
+

+
given an installed cib
+
given an installed synthetic-events
+

+
given file trigger.json
+
given file shutdown.json
+
when I run synthetic-events synt.sock trigger.json shutdown.json --log synt.log
+

+
given file broker.yaml
+
when I try to run bash radenv.sh env RAD_SOCKET=synt.sock cib --config broker.yaml insert
+
then command is successful
+

+
when I run cibtool --db ci-broker.db event list --verbose
+
then stdout contains "RefChanged"
+
then stdout contains "oid: Oid(0000000000000000000000000000000000000000)"
+
then stdout contains "old: Some(Oid(0000000000000000000000000000000000000000))"
+
~~~
+

+
## Insert many events into queue
+

+
_Requirement:_ Insert many events that arrive quickly.
+

+
_Justification:_ We need at least some rudimentary performance testing.
+

+
_Stakeholder:_ Lars.
+

+
~~~scenario
+
given file radenv.sh
+
given file setup-node.sh
+
when I run bash radenv.sh bash setup-node.sh
+

+
given an installed cib
+
given an installed synthetic-events
+

+
given file trigger.json
+
given file shutdown.json
+
when I run synthetic-events synt.sock trigger.json shutdown.json --log synt.log --repeat 1000
+

+
given file broker.yaml
+
when I try to run bash radenv.sh env RAD_SOCKET=synt.sock cib --config broker.yaml insert
+
then command is successful
+

+

+
when I run cibtool --db ci-broker.db event count
+
then stdout is exactly "1000\n"
+
~~~
+

+
## Don't insert events into queue when not allowed by filter
+

+
_Requirement:_ Nothing is inserted into the persistent event queue
+
then the CI broker's filter does not allow any events.
+

+
_Justification:_ This is fundamental for running CI when repositories
+
in a node change.
+

+
_Stakeholder:_ Lars.
+

+
~~~scenario
+
given file radenv.sh
+
given file setup-node.sh
+
when I run bash radenv.sh bash setup-node.sh
+

+
given an installed cib
+
given an installed synthetic-events
+

+
given file trigger.json
+
given file shutdown.json
+
when I run synthetic-events synt.sock trigger.json shutdown.json --log synt.log
+

+
given file broker.yaml from broker-allow-nothing.yaml
+
when I try to run bash radenv.sh env RAD_SOCKET=synt.sock cib --config broker.yaml insert
+
then command is successful
+

+
when I run cibtool --db ci-broker.db event count
+
then stdout is exactly "0\n"
+
~~~
+

+
## Process queued events
+

+
_Requirement:_ It's possible to run the CI broker in a mode where it
+
only processes events from its persistent event queue.
+

+
_Justification:_ This is primarily useful for testing the CI broker
+
queuing implementation.
+

+
_Stakeholders:_ Lars.
+

+
We verify this by adding events to the queue with `cibtool`, and then
+
running the CI broker and verifying it terminates after processing the
+
events. We carefully add a shutdown event so that the CI broker shuts
+
down.
+

+
~~~scenario
+
given file radenv.sh
+
given file setup-node.sh
+
when I run bash radenv.sh bash setup-node.sh
+

+
given file adapter.sh from dummy.sh
+
when I run chmod +x adapter.sh
+

+
given an installed cib
+
given an installed cibtool
+

+
when I run bash radenv.sh cibtool --db ci-broker.db event add --repo testy --ref main --commit HEAD
+
when I run cibtool --db ci-broker.db event shutdown
+

+
given file broker.yaml
+
when I run ls -l adapter.sh
+
when I run bash radenv.sh cib --config broker.yaml queued
+
then stderr contains "Action: run:"
+
then stderr contains "Action: shutdown"
+

+
when I run cibtool --db ci-broker.db event list
+
then stdout is exactly ""
+
~~~
+

+

+
## Count in concurrent processes
+

+
_Requirement:_ Two process can concurrently increment the test counter
+
correctly.
+

+
_Justification:_ This is necessary, if not necessarily sufficient, for
+
concurrent database use to work correctly.
+

+
_Stakeholder:_ Lars.
+

+
Due to limitations in Subplot we mange the concurrent processes using
+
a helper shell script,k `count.sh`, found below. It runs two
+
concurrent `cibtool` processes that update the same database file, and
+
count to a desired goal. The script then verifies that everything went
+
correctly.
+

+
~~~scenario
+
given an installed cibtool
+
given file count.sh
+
when I run bash -x count.sh 100 10
+
then stdout contains "OK\n"
+
~~~
+

+
~~~{#count.sh .file .sh}
+
#!/bin/bash
+

+
set -euo pipefail
+

+
run() {
+
	cibtool --db "$DB" counter count --goal "$goal"
+
}
+

+
DB=count.db
+

+
goal="$1"
+
reps="$2"
+

+
for x in $(seq "$reps"); do
+
	echo "Repetition $x"
+

+
	rm -f "$DB" ./?.out
+

+
	run >1.out 2>&1 &
+
	one=$!
+

+
	run >2.out 2>&1 &
+
	two=$!
+

+
	if ! wait "$one"; then
+
		echo "first run failed"
+
		cat 1.out
+
		exit 1
+
	fi
+

+
	if ! wait "$two"; then
+
		echo "second run failed"
+
		cat 2.out
+
		exit 1
+
	fi
+

+
	if grep ERROR ./?.out; then
+
		echo found ERRORs
+
		exit 1
+
	fi
+

+
	n="$(sqlite3 "$DB" 'select counter from counter_test')"
+
	[ "$n" == "$goal" ] || (
+
		echo "wrong count $n"
+
		exit 1
+
	)
+

+
	if awk '/increment to/ { print $NF }' ./?.out | sort -n | uniq -d | grep .; then
+
		echo "duplicate increments"
+
		exit 1
+
	fi
+
done
+

+
echo OK
+
~~~
+
# Acceptance criteria for event queue management
+

+
The CI builder database contains a queue of broker events. The
+
`cibtool` management tool can be used to examine and manipulate the
+
queue.
+

+
## Events can be queued and removed from queue
+

+
_Requirement:_ `cibtool` can show the queued events, can inject an
+
event, and remove an event.
+

+
_Justification:_ This is the minimum functionality needed to manage
+
the event queue.
+

+
_Stakeholder:_ Lars.
+

+
We verify that this works by adding a new broker event, and then
+
removing it. We randomly choose the repository id for the CI broker
+
itself for this test, but the id shouldn't matter, it just needs to
+
be of the correct form.
+

+
~~~scenario
+
given file radenv.sh
+
given file setup-node.sh
+
when I run bash radenv.sh bash setup-node.sh
+

+
given an installed cibtool
+
when I run cibtool --db x.db event list
+
then stdout is exactly ""
+

+
when I run bash radenv.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base c0ffee --id-file id.txt
+

+
when I run cibtool --db x.db event show --id-file id.txt
+
then stdout contains "rad:"
+
then stdout contains "main"
+
then stdout contains "c0ffee"
+

+
when I run cibtool --db x.db event remove --id-file id.txt
+

+
when I run cibtool --db x.db event list
+
then stdout is exactly ""
+
~~~
+

+
## Can add shutdown event to queue
+

+
_Requirement:_ `cibtool` can add a shutdown event to the queued
+
events.
+

+
_Justification:_ This is needed for testing, and for the node operator
+
to be able to do this cleanly.
+

+
~~~scenario
+
given an installed cibtool
+
when I run cibtool --db x.db event list
+
then stdout is exactly ""
+

+
when I run cibtool --db x.db event shutdown --id-file id.txt
+

+
when I run cibtool --db x.db event show --id-file id.txt
+
then stdout contains "Shutdown"
+
~~~
modified ci-broker.yaml
@@ -3,6 +3,16 @@
    rust:
      function: install_ci_broker

+
- given: "an installed cib"
+
  impl:
+
    rust:
+
      function: install_cib
+

+
- given: "an installed cibtool"
+
  impl:
+
    rust:
+
      function: install_cibtool
+

- given: "an installed synthetic-events"
  impl:
    rust:
modified src/subplot.rs
@@ -27,6 +27,36 @@ fn install_ci_broker(context: &ScenarioContext) {
#[step]
#[context(SubplotContext)]
#[context(Runcmd)]
+
fn install_cib(context: &ScenarioContext) {
+
    let target_path = bindir();
+
    assert!(target_path.join("cib").exists());
+
    context.with_mut(
+
        |context: &mut Runcmd| {
+
            context.prepend_to_path(target_path);
+
            Ok(())
+
        },
+
        false,
+
    )?;
+
}
+

+
#[step]
+
#[context(SubplotContext)]
+
#[context(Runcmd)]
+
fn install_cibtool(context: &ScenarioContext) {
+
    let target_path = bindir();
+
    assert!(target_path.join("cibtool").exists());
+
    context.with_mut(
+
        |context: &mut Runcmd| {
+
            context.prepend_to_path(target_path);
+
            Ok(())
+
        },
+
        false,
+
    )?;
+
}
+

+
#[step]
+
#[context(SubplotContext)]
+
#[context(Runcmd)]
fn install_synthetic_events(context: &ScenarioContext) {
    let target_path = bindir();
    assert!(target_path.join("synthetic-events").exists());