Use Spread to test commands in documentation¶
It’s challenging to keep documentation in sync with products as they evolve. This process is aided by Spread, a test distributor that can work through your documentation and report failures in GitHub workflows.
By using Spread tests, you can rely on the tests as the source of truth for commands in your documentation, enabling fully tested documentation at build time.
What you’ll need¶
Warning
Spread requires elevated permissions to run as root. Use the Go install method recommended in the Spread README to install Spread.
Create a test suite¶
From the root of your project, create the file spread.yaml and insert the following
contents:
project: project_name
path: /project_name
Match the project name to your main directory’s name. The path designates the
directory where the Spread materials exist.
So that Spread knows about your tests, add the following section to the end of
spread.yaml:
project: project_name
path: /project_name
suites:
tests/spread/:
summary: example test
systems:
- ubuntu-24.04-64
The suites section is how you tell Spread about the various Spread tests in your
project along with the systems you want Spread to use. In this example, Spread looks for
tests in the project_name/tests/spread directory and runs them on Ubuntu 24.04 LTS.
If you create a new task.yaml file in a different directory, remember to add a
corresponding suite for it in spread.yaml.
Set up the Multipass backend¶
Each job in Spread has a backend, or a way to obtain a machine for running your Spread tests. Spread can run on various backends, like Google, QEMU, or, as this guide sets up, Multipass.
Copy the following backends section of spread.yaml between the path and
suites sections:
project: project_name
path: /project_name
backends:
multipass:
type: adhoc
allocate: |
multipass_image=24.04
instance_name="example-multipass-vm"
# Launch Multipass VM
multipass launch --cpus 2 --disk 10G --memory 2G --name "${instance_name}" "${multipass_image}"
# Enable PasswordAuthentication for root over SSH.
multipass exec "$instance_name" -- \
sudo sh -c "echo root:${SPREAD_PASSWORD} | sudo chpasswd"
multipass exec "$instance_name" -- \
sudo sh -c \
"if [ -d /etc/ssh/sshd_config.d/ ]
then
echo 'PasswordAuthentication yes' > /etc/ssh/sshd_config.d/10-spread.conf
echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config.d/10-spread.conf
else
sed -i /etc/ssh/sshd_config -E -e 's/^#?PasswordAuthentication.*/PasswordAuthentication yes/' -e 's/^#?PermitRootLogin.*/PermitRootLogin yes/'
fi"
multipass exec "$instance_name" -- \
sudo systemctl restart ssh
# Get the IP from the instance
ip=$(multipass info --format csv "$instance_name" | tail -1 | cut -d\, -f3)
ADDRESS "$ip"
discard: |
instance_name="example-multipass-vm"
multipass delete --purge "${instance_name}"
systems:
- ubuntu-24.04-64:
workers: 1
suites:
tests/spread/:
summary: example test
systems:
- ubuntu-24.04-64
The backends section contains the following pieces:
The backend is designated as
type: adhocas you must explicitly script the procedure to allocate and discard the Multipass VM.The
allocatesection defines the image and name of the VM, launches the VM, and sets up the proper SSH permissions Spread then logs in to the VM with root permissions and inserts the Spread test. The last two lines tell Spread the IP address of the Multipass VM and set the environment variableADDRESS.The
discardsection deletes the Multipass VM once the Spread test has finished running.The
systemskey notes which systems the backend uses. Note that this key must match thesystemsused by at least one test undersuites.
Create a Spread task¶
Put your Spread files alongside your project’s existing tests. The rest of this guide
assumes they’re in a top-level tests/spread directory.
Each Spread test requires a dedicated task.yaml file that contains all the commands
you want to test. A single task.yaml can help you validate an entire assumed
workflow, for instance, an end-to-end tutorial.
An example task.yaml file is shown below:
summary: Example Spread test
kill-timeout: 5m
prepare: |
echo "Use this section to install any prerequisites"
execute: |
echo "This is the first command that Spread will run"
echo "This is the second command that Spread will run"
The summary section contains a brief description of the documentation you’re
testing, the prepare section contains any initial setup your test needs, and the
execute section contains your documentation’s commands. The kill-timeout option
has a default of 10 minutes and doesn’t need to be included if you expect your test to
complete in that time frame.
Note
For a real-world example, see task.yaml for
the Rockcraft Go tutorial.
Include the tested commands in documentation¶
By using the literalinclude directive in Sphinx, you can insert the exact commands
from task.yaml in your documentation file.
For example, consider the following task.yaml file:
summary: Clone and build the Sphinx STack
kill-timeout: 5m
execute: |
# [docs:clone-sphinx-stack]
git clone https://github.com/canonical/sphinx-stack.git
# [docs:clone-sphinx-stack-end]
# [docs:build-documentation]
cd sphinx-stack/docs
make run
# [docs:build-documentation-end]
Include the commands from task.yaml in your documentation with:
literalinclude blocks¶Clone the Sphinx Stack:
.. literalinclude:: relative-path-to/task.yaml
:language: bash
:start-after: [docs:clone-sphinx-stack]
:end-before: [docs:clone-sphinx-stack-end]
:dedent: 2
Enter the ``docs`` directory and build the project:
.. literalinclude:: relative-path-to/task.yaml
:language: bash
:start-after: [docs:build-documentation]
:end-before: [docs:build-documentation-end]
:dedent: 2
literalinclude blocks¶Clone the Sphinx Stack:
```{literalinclude} relative-path-to/task.yaml
:language: bash
:start-after: [docs:clone-sphinx-stack]
:end-before: [docs:clone-sphinx-stack-end]
:dedent: 2
```
Enter the `docs` directory and build the project:
```{literalinclude} relative-path-to/task.yaml
:language: bash
:start-after: [docs:build-documentation]
:end-before: [docs:build-documentation-end]
:dedent: 2
```
By using the options :start-after: and :end-before:, the documentation file
sources and includes all commands appearing in task.yaml between the specified
lines.
Run tests locally¶
List all available Spread tests in the code repository:
spread --list
The terminal should respond with all the tests defined in spread.yaml. For example:
user@host:project_name$ spread --list
multipass:ubuntu-24.04-64:tests/spread/example_documentation_test
Run all Spread tests locally with spread. You can also run a single Spread test by
specifying:
spread -vv -debug multipass:ubuntu-24.04-64:tests/spread/example_documentation_test
Depending on the complexity of your test, Spread can take several minutes to complete.
The -vv -debug flags provide useful debugging information as the test runs.
Check the results¶
During runtime, the terminal outputs various messages about allocating the Multipass VM, connecting to the VM, sending the Spread test to the VM and executing the test. If the test is successful, the terminal will output something similar to the following:
2025-02-04 16:17:10 Successful tasks: 1
2025-02-04 16:17:10 Aborted tasks: 0
Another sign of a successful test is whether the Multipass VM was deleted as expected.
Check by running multipass list, and if the Spread test was successful (and you have
no other Multipass VMs created at the time), the terminal should respond with:
user@host:project_name$ multipass list
No instances found.
If the Spread test failed, then the -debug flag will open a shell into the Multipass
VM so that additional debugging can happen. In that case, the terminal will output
something similar to the following:
2025-02-04 16:17:10 Starting shell to debug...
2025-02-04 16:17:10 Sending script for multipass:ubuntu-24.04-64 (multipass:ubuntu-24.04-64:tests/spread/example_documentation_test):