Radish alpha
r
rad:zwTxygwuz5LDGBq255RA2CbNGrz8
Radicle CI broker
Radicle
Git
Make scenario steps more stakholder oriented
Merged liw opened 1 year ago
4 files changed +674 -292 b4f379ad 45129128
modified ci-broker.md
@@ -206,6 +206,176 @@ rad inspect --identity
rad id list
~~~

+
# Custom scenario steps
+

+
In this document we use scenarios to show how to verify that the CI
+
broker does what we expect of it. For this, we define several custom
+
scenario steps. In this chapter we describe those steps, and also
+
verify that the steps work.
+

+
## Set up a node
+

+
This step creates a Radicle node, the Radicle CI broker, and a CI
+
adapter.
+

+
> `given a Radicle node, with CI configured with {config} and adapter {adapter}`
+

+
The captured parts of the step are:
+

+
* `config` — the name of the embedded file (somewhere in this
+
  document) with the configuration for the CI broker
+
* `adapter` — the name of the embedded file with the CI adapter
+
  implementation; we use simple shell script dummy adapter
+
  implementations, as in this document we only care about the
+
  broker/adapter interface, not that the adapter actually performs a
+
  CI run
+

+
This step installs binaries (or makes them available to be run), and
+
creates some files. It doesn't not start long-lived processes, in
+
particular not the Radicle node process.
+

+
We verify that this scenario works by examining the results. For
+
clarity, we split the scenario into many snippets.
+

+
~~~scenario
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
~~~
+

+
The programs we'll need are available to run. To check this, we use a
+
helper shell script to verify that. This avoids us to work around
+
limitations in Subplot for command parsing: Subplot does not parse
+
steps the way the shell does, so there is no way to pass text that
+
contains space characters to command as a single argument.
+

+
~~~scenario
+
given file which.sh
+
when I run bash which.sh rad
+
when I run bash which.sh cib
+
when I run bash which.sh cibtool
+
when I run bash which.sh synthetic-events
+
then command is successful
+
~~~
+

+
~~~{#which.sh .file .sh}
+
#!/bin/bash
+
# We use Bash build-in command as that's portable. "which" is not.
+
command -v "$1"
+
~~~
+

+
The configuration file must now exist.
+

+
~~~scenario
+
then file broker.yaml exists
+
~~~
+

+
The adapter is to be installed as `adapter.sh` and it must be
+
executable.
+

+
~~~scenario
+
then file adapter.sh exists
+
when I run ls -l adapter.sh
+
then stdout matches regex ^-rwx
+
~~~
+

+
There is a Radicle home directory.
+

+
~~~scenario
+
then directory .radicle exists
+
then directory .radicle/keys exists
+
then file .radicle/keys/radicle exists
+
then file .radicle/keys/radicle.pub exists
+
then directory .radicle/storage exists
+
then file .radicle/config.json exists
+
~~~
+

+
We also need way to set up environment variables for commands we run,
+
especially for `rad` to use the right node. Subplot does not have
+
built in support for this (at least not yet), but we work around that
+
by creating a shell script `env.sh` that sets them up.
+

+
~~~scenario
+
then file env.sh exists
+
when I run ls -l env.sh
+
then stdout matches regex ^-rwx
+
when I run ./env.sh env
+
then stdout matches regex ^PATH=
+
then stdout matches regex ^HOME=
+
then stdout matches regex ^RAD_HOME=
+
then stdout matches regex ^RAD_PASSPHRASE=
+
then stdout matches regex ^RAD_SOCKET=
+
~~~
+

+
## Create a repository
+

+
This step creates a Git repository and makes it into a Radicle
+
repository.
+

+
> `given a Git repository {name} in the Radicle node`
+

+
The captured part of the step is:
+

+
* `name` — the Git and Radicle repository name
+

+
We run the step and look at the results. We need the node creation
+
step first.
+

+
~~~scenario
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository reppy in the Radicle node
+
~~~
+

+
The Git repository must exist.
+

+
~~~scenario
+
then directory reppy exists
+
then directory reppy/.git exists
+
when I run, in reppy, git show
+
then stdout matches regex ^commit
+
~~~
+

+
It must also be a Radicle repository and in the local node.
+

+
~~~scenario
+
when I run ./env.sh rad ls --all
+
then stdout contains "reppy"
+
~~~
+

+
## Queue a node event for processing
+

+
This step queues a node event to be processed later by the
+
`synthetic-events` test helper tool that is part of the CI broker. The
+
step does this by creating a fake `refsUpdated` node event and writing
+
that to file with a specific name.
+

+
> `given the Radicle node emits a refsUpdated event for {repodir}`
+

+
The captured part of the step is:
+

+
* `repodir` — the directory where the repository is for which
+
  the event is created
+

+
To set up this step, we need to have node and a repository first.
+

+
~~~scenario
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository reppy in the Radicle node
+
given the Radicle node emits a refsUpdated event for reppy
+
~~~
+

+
We check that the event file looks roughly correct by querying it with
+
the `jq` tool.
+

+
~~~scenario
+
then file event.json exists
+
when I run jq .type event.json
+
then stdout contains ""refsFetched""
+
~~~
+

+
This is a very rudimentary check, but if the event file is incorrect,
+
then Radicle code will reject it. We don't want to duplicate the logic
+
to do that verification in detail.
+

+

# Acceptance criteria

## Shows config as JSON
@@ -223,8 +393,7 @@ output is in the JSON format. It does not try to make sure the JSON
matches the YAML semantically.

~~~scenario
-
given an installed CI broker
-
given file broker.yaml
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
when I run cib --config broker.yaml config --output actual.json
when I run jq . actual.json
then command is successful
@@ -241,22 +410,13 @@ nothing else has a hope of working.
_Who:_ `cib-devs`

~~~scenario
-
given an installed CI broker
-
given a CI adapter adapter.sh from dummy.sh
-

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

-
given file refsfetched.json
-
given file set-rid
-
when I run bash radenv.sh env HOME=../homedir python3 set-rid refsfetched.json testy
-
when I run synthetic-events synt.sock refsfetched.json --log log.txt
-

+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+
given the Radicle node emits a refsUpdated event for xyzzy
+
when I run ./env.sh synthetic-events synt.sock event.json --log log.txt
given a directory reports
-
given file broker.yaml
+
when I run ./env.sh cib --config broker.yaml process-events

-
when I run bash radenv.sh RAD_SOCKET=synt.sock cib --config broker.yaml process-events
then stderr contains "CI broker starts"
then stderr contains "loaded configuration"
then stderr contains "CI broker ends successfully"
@@ -283,10 +443,7 @@ reports.
_Who:_ `cib-devs`

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh

when I run cib --version
then stdout matches regex ^radicle-ci-broker \d+\.\d+\.\d+$
@@ -312,23 +469,12 @@ user can access individual logs.
_Who:_ `cib-devs`

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter adapter-with-url.sh
+
given a Git repository xyzzy in the Radicle node
+
given the Radicle node emits a refsUpdated event for xyzzy

-
given file refsfetched.json
-
given file set-rid
-
when I run bash radenv.sh env HOME=../homedir python3 set-rid refsfetched.json testy
-
when I run synthetic-events synt.sock refsfetched.json --log log.txt
-

-
given file adapter.sh from adapter-with-url.sh
-
when I run chmod +x adapter.sh
-

-
given file broker.yaml
-
given a directory reports
-

-
when I try to run bash radenv.sh RAD_SOCKET=synt.sock cib --config broker.yaml insert
+
when I run ./env.sh synthetic-events synt.sock event.json --log log.txt
+
when I try to run ./env.sh cib --config broker.yaml insert
then command is successful
~~~

@@ -351,15 +497,10 @@ reduces the support burden on the Radicle project.
_Who:_ `cib-devs`

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
-

-
given file broker.yaml
-
when I try to run bash radenv.sh RAD_SOCKET=xyzzy.sock cib --config broker.yaml insert
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
when I try to run ./env.sh cib --config broker.yaml insert
then command fails
-
then stderr contains "node control socket does not exist: xyzzy.sock"
+
then stderr contains "node control socket does not exist: synt.sock"
~~~


@@ -379,14 +520,8 @@ a thing that can always be made better. We can later add more
scenarios if we tighten the acceptance criteria.

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
-

-
given file broker.yaml
-
given file not-yaml.yaml
-
when I try to run env HOME=homedir cib --config not-yaml.yaml config
+
given a Radicle node, with CI configured with not-yaml.yaml and adapter dummy.sh
+
when I try to run cib --config not-yaml.yaml config
then command fails
then stderr contains "failed to parse configuration file as YAML: not-yaml.yaml"
~~~
@@ -409,14 +544,11 @@ handled by a dedicated system, such as `systemd`.
_Who:_ `cib-devs`

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
-

-
when I run synthetic-events synt.sock --log log.txt
-
given file broker.yaml
-
when I try to run bash radenv.sh RAD_SOCKET=synt.sock cib --config broker.yaml insert
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+
given the Radicle node emits a refsUpdated event for xyzzy
+
when I run ./env.sh synthetic-events synt.sock event.json --log log.txt
+
when I try to run ./env.sh cib --config broker.yaml insert
then command is successful
~~~

@@ -437,15 +569,11 @@ an object id of all zeros. This should be sufficiently impossible to
happen in real life.

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
-

-
given file broker.yaml
-
when I run synthetic-events synt.sock --log synt.log
-
when I try to run bash radenv.sh RAD_SOCKET=synt.sock cib --config broker.yaml insert
-
when I run cat synt.log
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+
given the Radicle node emits a refsUpdated event for xyzzy
+
when I run ./env.sh synthetic-events synt.sock event.json --log log.txt
+
when I try to run ./env.sh cib --config broker.yaml insert
then command is successful
when I run cibtool --db ci-broker.db run list
then stdout is empty
@@ -462,18 +590,14 @@ _Why:_ This is useful for diagnosis, if nothing else.
_Who:_ `cib-devs`

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
-

-
when I run bash radenv.sh cibtool --db x.db run add --repo testy --branch main --commit HEAD --failure
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node

given a directory reports
-
when I run bash radenv.sh cibtool --db x.db report --output-dir reports
-

+
when I run ./env.sh cibtool --db x.db run add --repo xyzzy --branch main --commit HEAD --failure
+
when I run ./env.sh cibtool --db x.db report --output-dir reports
then file reports/index.html exists
-
then file reports/index.html contains "testy"
+
then file reports/index.html contains "xyzzy"
~~~

This doesn't check that there is a per-repository HTML file, because
@@ -490,20 +614,16 @@ error output. This makes it easier to debug adapter problems.
_Who:_ `adapter-devs`, `node-ops`

~~~scenario
-
given an installed CI broker
-
given a CI adapter adapter.sh from dummy.sh
-

-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+
given the Radicle node emits a refsUpdated event for xyzzy
+
when I run ./env.sh synthetic-events synt.sock event.json --log log.txt

-
when I run bash radenv.sh cibtool --db ci-broker.db trigger --repo testy --ref main --commit HEAD
-
when I run bash radenv.sh cibtool --db ci-broker.db event list --json
+
when I run ./env.sh cibtool --db ci-broker.db trigger --repo xyzzy --ref main --commit HEAD
+
when I run ./env.sh cibtool --db ci-broker.db event list --json

-
given file broker.yaml
given a directory reports
-

-
when I run bash radenv.sh cib --config broker.yaml queued
+
when I run ./env.sh cib --config broker.yaml queued
then stderr contains "Mordor"
~~~

@@ -517,18 +637,12 @@ _Why:_ This allows controlling how much log spew log admins have to see.
_Who:_ `node-ops`

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh

-
given file broker.yaml
-
given a directory reports
-

-
when I run bash radenv.sh cib --config broker.yaml config
+
when I run ./env.sh cib --config broker.yaml config
then stderr contains "CI broker starts"

-
when I run bash radenv.sh cib --config broker.yaml --log-level error config
+
when I run ./env.sh cib --config broker.yaml --log-level error config
then stderr is exactly ""
~~~

@@ -554,8 +668,8 @@ this scenario that we can run `rad` at all.
_Who:_ `cib-devs`

~~~scenario
-
given file radenv.sh
-
when I run bash radenv.sh rad --version
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
when I try to run rad --version
then command is successful
~~~

@@ -616,7 +730,7 @@ time: if the time is too short, the scenario fails spuriously, and if
it's very long, the scenario takes longer than necessary.

~~~scenario
-
given an installed CI broker
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh

then file synt.sock does not exist

@@ -653,7 +767,7 @@ work of multiple processes, either.
_Who:_ `cib-devs`

~~~scenario
-
given an installed CI broker
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
then file count.db does not exist
when I run cibtool --db count.db counter show
then stdout is exactly "0\n"
@@ -675,23 +789,19 @@ in a node change.
_Who:_ `cib-devs`

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
-

-
given file refsfetched.json
-
when I run synthetic-events synt.sock refsfetched.json --log synt.log
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+
given the Radicle node emits a refsUpdated event for xyzzy
+
when I run ./env.sh synthetic-events synt.sock event.json --log log.txt

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

when I run cibtool --db ci-broker.db event list --json
then stdout contains "BranchUpdated"
then stdout contains ""branch": "main""
-
then stdout contains ""tip": "0000000000000000000000000000000000000000""
-
then stdout contains ""old_tip": "0000000000000000000000000000000000000000""
+
then stdout contains ""tip":"
+
then stdout contains ""old_tip":"
~~~

## Insert many events into queue
@@ -702,19 +812,17 @@ _Why:_ We need at least some rudimentary performance testing.

_Who:_ `cib-devs`

-
~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh

-
given file refsfetched.json
when I run synthetic-events synt.sock refsfetched.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
+
~~~scenario
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+
given the Radicle node emits a refsUpdated event for xyzzy
+
when I run ./env.sh synthetic-events synt.sock event.json --log log.txt --repeat 1000

+
when I try to run ./env.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"
@@ -731,16 +839,12 @@ in a node change.
_Who:_ `cib-devs`

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
-

-
given file refsfetched.json
-
when I run synthetic-events synt.sock refsfetched.json --log synt.log
+
given a Radicle node, with CI configured with broker-allow-nothing.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+
given the Radicle node emits a refsUpdated event for xyzzy
+
when I run ./env.sh synthetic-events synt.sock event.json --log log.txt

-
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
+
when I try to run ./env.sh env RAD_SOCKET=synt.sock cib --config broker-allow-nothing.yaml insert
then command is successful

when I run cibtool --db ci-broker.db event count
@@ -763,20 +867,14 @@ events. We carefully add a shutdown event so that the CI broker shuts
down.

~~~scenario
-
given an installed CI broker
-
given a CI adapter adapter.sh from dummy.sh
-

-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository testy in the Radicle node

-
when I run bash radenv.sh cibtool --db ci-broker.db event add --repo testy --ref main --commit HEAD
+
when I run ./env.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
given a directory reports
-
when I run ls -l adapter.sh
-
when I run bash radenv.sh cib --config broker.yaml queued
+
when I run ./env.sh cib --config broker.yaml queued
then stderr contains "Action: run:"
then stderr contains "Action: shutdown"

@@ -805,9 +903,9 @@ count to a desired goal. The script then verifies that everything went
correctly.

~~~scenario
-
given an installed CI broker
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
given file count.sh
-
when I run env bash -x count.sh 100 10
+
when I run bash -x count.sh 100 10
then stdout contains "OK\n"
~~~

@@ -888,15 +986,13 @@ itself for this test, but the id shouldn't matter, it just needs to
be of the correct form.

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node

when I run cibtool --db x.db event list
then stdout is empty

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

when I run cibtool --db x.db event show --id-file id.txt
then stdout contains "rad:"
@@ -919,20 +1015,18 @@ operator wants to prevent many CI runs from happening.
_Who:_ `cib-devs`, `node-ops`

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository testy in the Radicle node

when I run cibtool --db x.db event list
then stdout is empty

-
when I run bash radenv.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
-
when I run bash radenv.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
-
when I run bash radenv.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
-
when I run bash radenv.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
-
when I run bash radenv.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
-
when I run bash radenv.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
+
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
+
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
+
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
+
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
+
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD
+
when I run ./env.sh cibtool --db x.db event add --repo testy --ref main --commit HEAD --base HEAD

when I run cibtool --db x.db event remove --all
when I run cibtool --db x.db event list
@@ -950,7 +1044,8 @@ to be able to do this cleanly.
_Who:_ `cib-devs`

~~~scenario
-
given an installed CI broker
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+

when I run cibtool --db x.db event list
then stdout is empty

@@ -971,12 +1066,10 @@ _Who:_ `cib-devs`


~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node

-
when I run bash radenv.sh cibtool --db x.db trigger --repo testy --ref main --commit HEAD --id-file id.txt
+
when I run ./env.sh cibtool --db x.db trigger --repo xyzzy --ref main --commit HEAD --id-file id.txt

when I run cibtool --db x.db event show --id-file id.txt
then stdout contains "rad:"
@@ -993,17 +1086,17 @@ _Why:_ This is primarily needed for testing.
_Who:_ `cib-devs`

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+
given the Radicle node emits a refsUpdated event for xyzzy
+
when I run ./env.sh synthetic-events synt.sock event.json --log log.txt

when I run cibtool --db x.db run list
then stdout is empty

-
when I run bash radenv.sh cibtool --db x.db run add --id xyzzy --repo testy --branch main --commit main --triggered
+
when I run ./env.sh cibtool --db x.db run add --id runny --repo xyzzy --branch main --commit main --triggered
when I run cibtool --db x.db run list --json
-
then stdout contains "xyzzy"
+
then stdout contains "runny"
then stdout contains ""state": "triggered""
~~~

@@ -1016,17 +1109,15 @@ _Why:_ This is primarily needed for testing.
_Who:_ `cib-dev`.

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node

when I run cibtool --db x.db run list
then stdout is empty

-
when I run bash radenv.sh cibtool --db x.db run add --repo testy --url https://x/1 --branch main --commit HEAD --running
+
when I run ./env.sh cibtool --db x.db run add --repo xyzzy --url https://x/1 --branch main --commit HEAD --running
when I run cibtool --db x.db run list --json
-
then stdout contains ""repo_name": "testy""
+
then stdout contains ""repo_name": "xyzzy""
then stdout contains ""state": "running""
~~~

@@ -1041,15 +1132,13 @@ _Why:_ This is primarily needed for testing.
_Who:_ `cib-dev`.

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node

when I run cibtool --db x.db run list
then stdout is empty

-
when I run bash radenv.sh cibtool --db x.db run add --id xyzzy --repo testy --branch main --commit HEAD --success
+
when I run ./env.sh cibtool --db x.db run add --id runny --repo xyzzy --branch main --commit HEAD --success
when I run cibtool --db x.db run list --json
then stdout contains ""state": "finished""
then stdout contains ""result": "success""
@@ -1066,15 +1155,13 @@ _Why:_ This is primarily needed for testing.
_Who:_ `cib-dev`.

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository testy in the Radicle node

when I run cibtool --db x.db run list
then stdout is empty

-
when I run bash radenv.sh cibtool --db x.db run add --id xyzzy --repo testy --branch main --commit HEAD --failure
+
when I run ./env.sh cibtool --db x.db run add --id xyzzy --repo testy --branch main --commit HEAD --failure
when I run cibtool --db x.db run list --json
then stdout contains ""state": "finished""
then stdout contains ""result": "failure""
@@ -1093,15 +1180,13 @@ _Why:_ This is primarily needed for testing.
_Who:_ `cib-dev`.

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository testy in the Radicle node

when I run cibtool --db x.db run list
then stdout is empty

-
when I run bash radenv.sh cibtool --db x.db run add --id x --repo testy --branch main --commit HEAD --triggered
+
when I run ./env.sh cibtool --db x.db run add --id x --repo testy --branch main --commit HEAD --triggered
when I run cibtool --db x.db run list
then stdout has one line
when I run cibtool --db x.db run show x
@@ -1143,24 +1228,22 @@ Note that we verify both lookup by name and by repository ID, and by
`cibtool event add` and `cibtool trigger`, to cover all the cases.

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository testy in the Radicle node

-
when I try to run bash radenv.sh cibtool --db x.db event add --repo missing --ref main --commit HEAD --base c0ffee
+
when I try to run ./env.sh cibtool --db x.db event add --repo missing --ref main --commit HEAD --base c0ffee
then command fails
then stderr contains "missing"

-
when I try to run bash radenv.sh cibtool --db x.db event add --repo rad:z3byzFpcfbMJBp4tKYyuuTZiP8WUB --ref main --commit HEAD --base c0ffee
+
when I try to run ./env.sh cibtool --db x.db event add --repo rad:z3byzFpcfbMJBp4tKYyuuTZiP8WUB --ref main --commit HEAD --base c0ffee
then command fails
then stderr contains "rad:z3byzFpcfbMJBp4tKYyuuTZiP8WUB"

-
when I try to run bash radenv.sh cibtool --db x.db trigger --repo missing --ref main --commit HEAD --id-file id.txt
+
when I try to run ./env.sh cibtool --db x.db trigger --repo missing --ref main --commit HEAD --id-file id.txt
then command fails
then stderr contains "missing"

-
when I try to run bash radenv.sh cibtool --db x.db trigger --repo rad:z3byzFpcfbMJBp4tKYyuuTZiP8WUB --ref main --commit HEAD --id-file id.txt
+
when I try to run ./env.sh cibtool --db x.db trigger --repo rad:z3byzFpcfbMJBp4tKYyuuTZiP8WUB --ref main --commit HEAD --id-file id.txt
then command fails
then stderr contains "rad:z3byzFpcfbMJBp4tKYyuuTZiP8WUB"

@@ -1177,17 +1260,12 @@ useful for gathering data for trying out event filters.
_Who:_ `cib-devs`, `node-ops`

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+
given the Radicle node emits a refsUpdated event for xyzzy
+
when I run ./env.sh synthetic-events synt.sock event.json --log log.txt

-
given file refsfetched.json
-
given file set-rid
-
when I run bash radenv.sh env HOME=../homedir python3 set-rid refsfetched.json testy
-
when I run synthetic-events synt.sock refsfetched.json --log log.txt
-

-
when I run bash radenv.sh cibtool event record --output events.json
+
when I run ./env.sh cibtool event record --output events.json
then file events.json contains ""type":"refsFetched""
~~~

@@ -1203,18 +1281,12 @@ It's also helpful for CI broker developers as a development tool.
_Who:_ `cib-dev`, `node-ops`

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
-

-
given file refsfetched.json
-
given file set-rid
-
when I run bash radenv.sh env HOME=../homedir python3 set-rid refsfetched.json testy
-
when I run synthetic-events synt.sock refsfetched.json --log log.txt
-

-
when I run bash radenv.sh cibtool event record --output node-events.json
-
when I run bash radenv.sh cibtool event ci --output ci-events.json node-events.json
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+
given the Radicle node emits a refsUpdated event for xyzzy
+
when I run ./env.sh synthetic-events synt.sock event.json --log log.txt
+
when I run ./env.sh cibtool event record --output node-events.json
+
when I run ./env.sh cibtool event ci --output ci-events.json node-events.json
when I run cat ci-events.json
then file ci-events.json contains "BranchUpdated""
~~~
@@ -1230,18 +1302,13 @@ event filters work as they expect.
_Who:_ `cib-dev`, `node-ops`

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
-

-
given file refsfetched.json
-
given file set-rid
-
when I run bash radenv.sh env HOME=../homedir python3 set-rid refsfetched.json testy
-
when I run synthetic-events synt.sock refsfetched.json --log log.txt
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository xyzzy in the Radicle node
+
given the Radicle node emits a refsUpdated event for xyzzy
+
when I run ./env.sh synthetic-events synt.sock event.json --log log.txt

-
when I run bash radenv.sh cibtool event record --output node-events.json
-
when I run bash radenv.sh cibtool event ci --output ci-events.json node-events.json
+
when I run ./env.sh cibtool event record --output node-events.json
+
when I run ./env.sh cibtool event ci --output ci-events.json node-events.json

given file allow.yaml
when I run cibtool event filter  allow.yaml ci-events.json
@@ -1301,16 +1368,10 @@ any events, so `cib` just terminates at once. All of this will work,
when properly set up.

~~~scenario
-
given an installed CI broker
-
given a CI adapter adapter.sh from dummy.sh
-

-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh

-
given file broker.yaml
given a directory reports
-
when I run bash radenv.sh cib --config broker.yaml queued
+
when I run ./env.sh cib --config broker.yaml queued

then stderr contains "CI broker starts"
then stderr contains "CI broker ends successfully"
@@ -1330,10 +1391,8 @@ We check this by running the CI broker without a local node. This is
an error it can't recover from.

~~~scenario
-
given an installed CI broker
-
given file broker.yaml
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
when I try to run env RAD_HOME=/does/not/exist cib --config broker.yaml queued
-

then stderr contains "CI broker starts"
then stderr contains "CI broker ends in unrecoverable error"
~~~
@@ -1355,15 +1414,10 @@ monitoring system.
_Who:_ `node-ops`

~~~scenario
-
given an installed CI broker
-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
-

-
given file broker.yaml
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
given a directory reports

-
when I run bash radenv.sh cib --config broker.yaml queued
+
when I run cib --config broker.yaml queued
then file reports/status.json exists

when I run jq .event_queue_length reports/status.json
@@ -1408,21 +1462,16 @@ the Git commit identifiers instead. In any case, this scenario needs
to be updated when a new release is made.

~~~scenario
-
given an installed CI broker
-
given a CI adapter adapter.sh from dummy.sh
-

-
given file radenv.sh
-
given file setup-node.sh
-
when I run bash radenv.sh bash setup-node.sh
+
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+
given a Git repository testy in the Radicle node

-
given file broker.yaml
given file verify-upgrade
given a directory reports

when I touch file run-list.txt
-
when I run bash radenv.sh bash -x verify-upgrade run-list.txt 535b1592904125fbe62c3c8c383d7741d9f432ac
-
when I run bash radenv.sh bash -x verify-upgrade run-list.txt 869b451728d16719b560df142b2d901cbaf3764c
-
when I run bash radenv.sh bash -x verify-upgrade run-list.txt HEAD
+
when I run ./env.sh bash -x verify-upgrade run-list.txt 535b1592904125fbe62c3c8c383d7741d9f432ac
+
when I run ./env.sh bash -x verify-upgrade run-list.txt 869b451728d16719b560df142b2d901cbaf3764c
+
when I run ./env.sh bash -x verify-upgrade run-list.txt HEAD
~~~

~~~{#verify-upgrade .file .sh}
@@ -1463,6 +1512,7 @@ nl "$config"
# from there to avoid having to fetch things from the network.
rm -rf ci-broker html
mkdir ci-broker html
+
export SRCDIR="$CARGO_MANIFEST_DIR"
(cd "$SRCDIR" && git archive "$VERSION") | tar -C ci-broker -xf -

# Do things in the exported CI broker source tree. Capture stdout to a
modified ci-broker.yaml
@@ -1,12 +1,24 @@
-
- given: "an installed CI broker"
+
- given: "a Radicle node, with CI configured with {config} and adapter {adapter}"
+
  types:
+
    config: file
+
    adapter: file
  impl:
    rust:
-
      function: install_ci_broker
+
      function: setup_node

-
- given: "a CI adapter {filename:path} from {embedded:file}"
+
- given: "a Git repository {name} in the Radicle node"
+
  types:
+
    name: word
  impl:
    rust:
-
      function: install_adapter
+
      function: create_repo
+

+
- given: "the Radicle node emits a refsUpdated event for {repodir}"
+
  types:
+
    repodir: path
+
  impl:
+
    rust:
+
      function: add_event_file

- then: "stdout has one line"
  impl:
modified src/logger.rs
@@ -110,7 +110,7 @@ pub fn start_cib() {
pub fn end_cib_successfully() {
    info!(
        kind = %Kind::Shutdown,
-
        succss = true,
+
        success = true,
        "CI broker ends successfully"
    );
}
@@ -118,7 +118,7 @@ pub fn end_cib_successfully() {
pub fn end_cib_in_error() {
    error!(
        kind = %Kind::Shutdown,
-
        succss = false,
+
        success = false,
        "CI broker ends in unrecoverable error"
    );
}
modified src/subplot.rs
@@ -1,14 +1,23 @@
// Implementations of Subplot scenario steps for the CI broker.

use std::{
-
    fs::{set_permissions, Permissions},
+
    fs::{metadata, set_permissions},
    io::Write,
    os::unix::fs::PermissionsExt,
    path::{Path, PathBuf},
+
    process::Command,
+
    str::FromStr,
};

+
use radicle::{
+
    git::RefString,
+
    node::{Event, NodeId},
+
    prelude::RepoId,
+
    storage::RefUpdate,
+
};
+
use radicle_git_ext::Oid;
+

use subplotlib::steplibrary::datadir::Datadir;
-
use subplotlib::steplibrary::files::Files;
use subplotlib::steplibrary::runcmd::Runcmd;

#[derive(Debug, Default)]
@@ -18,9 +27,12 @@ impl ContextElement for SubplotContext {}

#[step]
#[context(SubplotContext)]
+
#[context(Datadir)]
#[context(Runcmd)]
-
fn install_ci_broker(context: &ScenarioContext) {
+
fn setup_node(context: &ScenarioContext, config: SubplotDataFile, adapter: SubplotDataFile) {
+
    // Install binaries.
    let target_path = bindir();
+
    println!("check CI broker binaries are in {}", target_path.display());
    assert!(target_path.join("cib").exists());
    assert!(target_path.join("cibtool").exists());
    assert!(target_path.join("synthetic-events").exists());
@@ -31,6 +43,337 @@ fn install_ci_broker(context: &ScenarioContext) {
        },
        false,
    )?;
+

+
    // Create configuration file.
+
    if let Some(parent) = config.name().parent() {
+
        if parent != Path::new("") {
+
            println!(
+
                "create directory for configuration file: {:?}",
+
                parent.display()
+
            );
+
            context.with_mut(
+
                |context: &mut Datadir| {
+
                    context.create_dir_all(parent)?;
+
                    Ok(())
+
                },
+
                false,
+
            )?;
+
        }
+
    }
+

+
    println!("write configuration file {}", config.name().display());
+
    context.with_mut(
+
        |context: &mut Datadir| {
+
            context
+
                .open_write(config.name())?
+
                .write_all(config.data())?;
+
            Ok(())
+
        },
+
        false,
+
    )?;
+

+
    // Create an executable adapter.
+
    if let Some(parent) = adapter.name().parent() {
+
        if parent != Path::new("") {
+
            println!("create directory for adapter: {}", parent.display());
+
            context.with_mut(
+
                |context: &mut Datadir| {
+
                    context.create_dir_all(parent)?;
+
                    Ok(())
+
                },
+
                false,
+
            )?;
+
        }
+
    }
+

+
    context.with_mut(
+
        |context: &mut Datadir| {
+
            // Unix mode bits for an executable file: read/write/exec for
+
            // owner, read/exec for group and others
+
            const EXECUTABLE: u32 = 0o755;
+

+
            println!("create env file");
+
            let home = context.canonicalise_filename(".")?;
+
            let home_str = home.display().to_string();
+

+
            let rad_home = context.canonicalise_filename(".radicle")?;
+
            let rad_home_str = rad_home.display().to_string();
+

+
            let envs = &[
+
                ("HOME", home_str.as_str()),
+
                ("RAD_HOME", rad_home_str.as_str()),
+
                ("RAD_PASSPHRASE", "secret"),
+
                ("RAD_SOCKET", "synt.sock"),
+
            ];
+
            {
+
                let mut file = context.open_write("env")?;
+
                for (k, v) in envs.iter() {
+
                    file.write_all(format!("export {k}='{v}'\n").as_bytes())?;
+
                }
+
            }
+

+
            println!("create env.sh script");
+
            {
+
                const SCRIPT: &str = r#"#!/bin/bash
+
echo "env.sh starts"
+
if [ -e env ]; then . ./env; fi
+
exec "$@"
+
"#;
+
                let mut file = context.open_write("env.sh")?;
+
                file.write_all(SCRIPT.as_bytes())?;
+
            }
+

+
            println!("make env.sh executable");
+
            let filename = context.canonicalise_filename("env.sh")?;
+
            let meta = metadata(&filename)?;
+
            let mut perm = meta.permissions();
+
            perm.set_mode(EXECUTABLE);
+
            set_permissions(&filename, perm)?;
+

+
            let filename = Path::new("adapter.sh");
+

+
            println!(
+
                "write adapter file {} from {}",
+
                filename.display(),
+
                adapter.name().display()
+
            );
+
            context.open_write(filename)?.write_all(adapter.data())?;
+

+
            println!("make {} executable", filename.display());
+
            let filename = context.canonicalise_filename("adapter.sh")?;
+
            let meta = metadata(&filename)?;
+
            let mut perm = meta.permissions();
+
            perm.set_mode(EXECUTABLE);
+
            set_permissions(&filename, perm)?;
+

+
            Ok(())
+
        },
+
        false,
+
    )?;
+

+
    // Create node by running "rad auth".
+

+
    context.with_mut(
+
        |context: &mut Datadir| {
+
            let home = context.canonicalise_filename(".")?;
+
            let rad_home = context.canonicalise_filename(".radicle")?;
+

+
            rad_in(
+
                &["auth", "--alias=brokertest"],
+
                &[
+
                    ("RAD_HOME", &rad_home.display().to_string()),
+
                    ("RAD_PASSPHRASE", "secret"),
+
                    ("RAD_SOCKET", "synt.sock"),
+
                ],
+
                &home,
+
            )?;
+

+
            Ok(())
+
        },
+
        false,
+
    )?;
+
}
+

+
#[step]
+
#[context(SubplotContext)]
+
#[context(Datadir)]
+
#[context(Runcmd)]
+
fn create_repo(context: &ScenarioContext, name: &str) {
+
    // Create a Git repository and add it to the Radicle node.
+
    context.with_mut(
+
        |context: &mut Datadir| {
+
            let home = context.canonicalise_filename(".")?;
+
            let home_str = home.display().to_string();
+

+
            let rad_home = context.canonicalise_filename(".radicle")?;
+
            let rad_home_str = rad_home.display().to_string();
+

+
            let envs = &[
+
                ("HOME", home_str.as_str()),
+
                ("RAD_HOME", rad_home_str.as_str()),
+
                ("RAD_PASSPHRASE", "secret"),
+
                ("RAD_SOCKET", "synt.sock"),
+
            ];
+

+
            git_in(
+
                &["config", "--global", "user.email", "radicle@example.com"],
+
                envs,
+
                &home,
+
            )?;
+

+
            git_in(
+
                &["config", "--global", "user.name", "TestyMcTestFace"],
+
                envs,
+
                &home,
+
            )?;
+

+
            git_in(&["init", "-b", "main", name], envs, &home)?;
+

+
            {
+
                let filename = Path::new(name).join("file.dat");
+
                let mut file = context.open_write(filename)?;
+
                file.write_all(b"hello, world")?;
+
            }
+

+
            let repodir = context.canonicalise_filename(name)?;
+
            git_in(&["add", "."], envs, &repodir)?;
+
            git_in(&["commit", "-am", "test"], envs, &repodir)?;
+

+
            rad_in(
+
                &[
+
                    "init",
+
                    "--name",
+
                    name,
+
                    "--description=test",
+
                    "--default-branch=main",
+
                    "--private",
+
                    "--no-confirm",
+
                    "--no-seed",
+
                ],
+
                envs,
+
                &repodir,
+
            )?;
+

+
            // rad init --name testy --description test --default-branch main --private --no-confirm --no-seed
+
            // rad inspect --identity
+
            // rad id list
+

+
            Ok(())
+
        },
+
        false,
+
    )?;
+
}
+

+
fn rad_in(args: &[&str], envs: &[(&str, &str)], cwd: &Path) -> Result<(), std::io::Error> {
+
    run_in("rad", args, envs, cwd)
+
}
+

+
fn git_in(args: &[&str], envs: &[(&str, &str)], cwd: &Path) -> Result<(), std::io::Error> {
+
    run_in("git", args, envs, cwd)
+
}
+

+
fn run_in(
+
    argv0: &str,
+
    args: &[&str],
+
    envs: &[(&str, &str)],
+
    cwd: &Path,
+
) -> Result<(), std::io::Error> {
+
    println!("running command {argv0} {args:?}");
+
    println!("envs: {envs:?}");
+
    println!("cwd: {cwd:?}; exists? {}", cwd.exists());
+

+
    let output = Command::new(argv0)
+
        .args(args)
+
        .envs(envs.iter().copied())
+
        .current_dir(cwd)
+
        .output()?;
+
    println!("{argv0} exit code: {:?}", output.status.code());
+
    println!(
+
        "{argv0}: stdout:\n{}\n====================",
+
        String::from_utf8_lossy(&output.stdout)
+
    );
+
    println!(
+
        "{argv0}: stderr:\n{}\n=====================",
+
        String::from_utf8_lossy(&output.stderr)
+
    );
+
    if !output.status.success() {
+
        panic!("command failed");
+
    }
+
    Ok(())
+
}
+

+
#[step]
+
#[context(SubplotContext)]
+
#[context(Datadir)]
+
#[context(Runcmd)]
+
fn add_event_file(context: &ScenarioContext, repodir: &Path) {
+
    // Write embedded file to "event.json". We only need one, at least for now.
+

+
    context.with_mut(
+
        |datadir: &mut Datadir| {
+
            let rad_home = datadir.canonicalise_filename(".radicle")?;
+
            println!("rad_home: {rad_home:#?}");
+

+
            let nid = nid(&rad_home)?;
+
            println!("nid: {nid:#?}");
+

+
            let repodir = datadir.canonicalise_filename(repodir)?;
+
            let rid = rid(&rad_home, &repodir)?;
+
            println!("rid: {rid:#?}");
+

+
            let head = head(&rad_home, &repodir)?;
+

+
            let node_event = Event::RefsFetched {
+
                remote: nid,
+
                rid,
+
                updated: vec![RefUpdate::Updated {
+
                    name: RefString::try_from(
+
                        format!("refs/namespaces/{nid}/refs/heads/main").as_str(),
+
                    )?,
+
                    old: head,
+
                    new: head,
+
                }],
+
            };
+

+
            println!("node_event: {node_event:#?}");
+
            let node_event = serde_json::to_string(&node_event)?;
+

+
            let event_json = Path::new("event.json");
+
            let filename = datadir.canonicalise_filename(event_json)?;
+
            assert!(!filename.exists());
+

+
            let mut file = datadir.open_write(event_json)?;
+
            file.write_all(node_event.as_bytes())?;
+
            Ok(())
+
        },
+
        false,
+
    )?;
+
}
+

+
fn nid(rad_home: &Path) -> Result<NodeId, Box<dyn std::error::Error>> {
+
    let output = Command::new("rad")
+
        .arg("self")
+
        .arg("--nid")
+
        .env("RAD_HOME", rad_home.display().to_string().as_str())
+
        .output()?;
+
    if !output.status.success() {
+
        panic!("rad self --nid failed");
+
    }
+

+
    Ok(NodeId::from_str(
+
        String::from_utf8_lossy(&output.stdout).to_string().trim(),
+
    )?)
+
}
+

+
fn rid(rad_home: &Path, repo: &Path) -> Result<RepoId, Box<dyn std::error::Error>> {
+
    let output = Command::new("rad")
+
        .arg(".")
+
        .env("RAD_HOME", rad_home.display().to_string().as_str())
+
        .current_dir(repo)
+
        .output()?;
+
    if !output.status.success() {
+
        panic!("rad . failed");
+
    }
+

+
    Ok(RepoId::from_str(
+
        String::from_utf8_lossy(&output.stdout).to_string().trim(),
+
    )?)
+
}
+

+
fn head(rad_home: &Path, repo: &Path) -> Result<Oid, Box<dyn std::error::Error>> {
+
    let output = Command::new("git")
+
        .arg("rev-parse")
+
        .arg("HEAD")
+
        .env("RAD_HOME", rad_home.display().to_string().as_str())
+
        .current_dir(repo)
+
        .output()?;
+
    if !output.status.success() {
+
        panic!("git rev-parse HEAD failed");
+
    }
+

+
    Ok(Oid::from_str(
+
        String::from_utf8_lossy(&output.stdout).to_string().trim(),
+
    )?)
}

fn bindir() -> PathBuf {
@@ -44,29 +387,6 @@ fn bindir() -> PathBuf {

#[step]
#[context(SubplotContext)]
-
#[context(Datadir)]
-
#[context(Files)]
-
fn install_adapter(context: &Datadir, filename: &Path, embedded: SubplotDataFile) {
-
    eprintln!(
-
        "install adapter {} from {}:\n{}",
-
        filename.display(),
-
        embedded.name().display(),
-
        String::from_utf8_lossy(embedded.data()),
-
    );
-
    eprintln!("write {}", filename.display());
-
    context.open_write(filename)?.write_all(embedded.data())?;
-
    let realpath = context.canonicalise_filename(filename)?;
-
    eprintln!(
-
        "chmod {} (exists? {})",
-
        realpath.display(),
-
        realpath.exists()
-
    );
-
    let executable = Permissions::from_mode(0o755);
-
    set_permissions(realpath, executable)?;
-
}
-

-
#[step]
-
#[context(SubplotContext)]
#[context(Runcmd)]
fn stdout_has_one_line(runcmd: &Runcmd) {
    let linecount = runcmd.stdout_as_string().lines().count();