Testing¶
Bilrost has test suites for every Ansible role. Tests are split into fast offline validation and full VM deployment checks.
Test Structure¶
Each role follows a consistent three-file pattern:
tests/<role>/
├── run-all.sh # Runner: orchestrates both suites
├── test-<role>-ansible.sh # Lint: role structure, defaults, templates, integration
└── test-<role>-role.sh # VM: deployment tests against a running VM
run-all.sh-- Entrypoint that runs both suites sequentially. Accepts--quickto skip VM tests.test-*-ansible.sh-- Pure offline validation. Checks role file structure, defaults values, task definitions, template syntax, and integration with the playbook and bootstrap script. No VM required.test-*-role.sh-- Runs commands inside a live VM vialimactl shell. Verifies that services are running, files exist with correct permissions, firewall rules are applied, and end-to-end behavior works.
Pass/Fail Counter Pattern¶
Every test script uses a shared pattern: a PASS and FAIL counter, colored output, and a summary at the end:
PASS=0
FAIL=0
# Each assertion increments one counter
if [[ condition ]]; then
echo -e "${GREEN}PASS${NC} description"
PASS=$((PASS + 1))
else
echo -e "${RED}FAIL${NC} description"
FAIL=$((FAIL + 1))
fi
# Summary
TOTAL=$((PASS + FAIL))
echo "Results: $PASS/$TOTAL passed"
Running Tests¶
Quick Mode (No VM Required)¶
Quick mode runs only the Ansible validation tests. This is fast (seconds) and useful during development:
# Single role
./tests/overlay/run-all.sh --quick
# All roles
./tests/overlay/run-all.sh --quick && \
./tests/sandbox/run-all.sh --quick && \
./tests/gh-cli/run-all.sh --quick && \
./tests/obsidian/run-all.sh --quick && \
./tests/cadence/run-all.sh --quick && \
./tests/gateway/run-all.sh --quick && \
./tests/buildlog/run-all.sh --quick && \
./tests/qortex/run-all.sh --quick && \
./tests/telegram/run-all.sh --quick
Tip
Use --quick for fast iteration while developing. Run full mode before opening a PR.
Full Mode (Requires Running VM)¶
Full mode runs both Ansible validation and VM deployment tests. The VM must be running:
# Ensure VM is up
bilrost status # or: limactl list
# Run full suite for a single role
./tests/overlay/run-all.sh
# Run all suites
./tests/overlay/run-all.sh && \
./tests/sandbox/run-all.sh && \
./tests/gh-cli/run-all.sh && \
./tests/obsidian/run-all.sh && \
./tests/cadence/run-all.sh && \
./tests/gateway/run-all.sh && \
./tests/buildlog/run-all.sh && \
./tests/qortex/run-all.sh && \
./tests/telegram/run-all.sh
Warning
VM tests execute real commands inside the sandbox VM. Make sure you have a running, provisioned VM before running full mode.
Test Suites¶
Overlay Tests¶
| File | Type | Checks |
|---|---|---|
test-overlay-ansible.sh |
Ansible validation | 60 |
test-overlay-role.sh |
VM deployment | 19 |
Covers: role structure, defaults, tasks, handlers, 5 templates, OverlayFS mount, overlay-status helper, overlay-reset, audit watcher, inotifywait integration.
Docker + Sandbox Tests¶
| File | Type | Description |
|---|---|---|
test-sandbox-ansible.sh |
Ansible validation | Role structure, defaults, integration |
test-sandbox-role.sh |
VM deployment | Docker CE, sandbox image, config injection |
Covers: Docker CE installation, openclaw-sandbox:bookworm-slim image, openclaw.json sandbox config, bridge networking, gh augmentation in image.
GitHub CLI Tests¶
| File | Type | Description |
|---|---|---|
test-gh-cli-ansible.sh |
Ansible validation | Role structure, secrets pipeline, integration |
test-gh-cli-role.sh |
VM deployment | gh installation, APT repo, token flow |
Covers: GitHub APT repository, gh binary, GH_TOKEN in secrets pipeline, sandbox env passthrough.
Obsidian Vault Tests¶
| File | Type | Description |
|---|---|---|
test-obsidian-ansible.sh |
Ansible validation | Overlay stale cleanup, bind mount config |
test-obsidian-role.sh |
VM deployment | Mount verification, container access |
Covers: vault overlay mount, stale unit cleanup, sandbox bind mount config, OBSIDIAN_VAULT_PATH env var.
Cadence Tests¶
| File | Type | Checks |
|---|---|---|
test-cadence-ansible.sh |
Ansible validation | 32 |
test-cadence-role.sh |
VM deployment | 22 |
test-cadence-e2e.sh |
End-to-end pipeline | 10 |
Covers: systemd service, auth-profiles.json, file watcher, LLM extraction, Telegram delivery pipeline.
Buildlog Tests¶
| File | Type | Description |
|---|---|---|
test-buildlog-ansible.sh |
Ansible validation | Role structure, CLAUDE.md pipeline, MCP config |
test-buildlog-role.sh |
VM deployment | uv installation, buildlog binary, MCP server |
Covers: uv tool install buildlog, CLAUDE.md 3-step pipeline (copy base, init-mcp, append sandbox policy).
Telegram Tests¶
| File | Type | Description |
|---|---|---|
test-telegram-ansible.sh |
Ansible validation | Pairing config, DM policy, user ID seeding |
test-telegram-role.sh |
VM deployment | Bot connection, pairing flow |
Covers: dmPolicy: "pairing", TELEGRAM_BOT_TOKEN secrets flow, pre-approved user ID.
Gateway Tests¶
| File | Type | Description |
|---|---|---|
test-gateway-ansible.sh |
Ansible validation | Role structure, systemd unit, config injection |
test-gateway-role.sh |
VM deployment | Gateway service running, port forwarding, Docker access |
Covers: systemd service, SupplementaryGroups=docker, port 18789 forwarding, openclaw.json config.
Qortex Tests¶
| File | Type | Description |
|---|---|---|
test-qortex-ansible.sh |
Ansible validation | Role structure, defaults, Docker deployment, directory setup, interop config |
test-qortex-role.sh |
VM deployment | Docker container, seed directories, signals directory, interop.yaml, health check |
Covers: Docker container deployment (ghcr.io/peleke/qortex:latest), qortex_data volume, ~/.qortex/seeds/{pending,processed,failed}, ~/.qortex/signals/, ~/.buildlog/interop.yaml, old systemd cleanup.
Python CLI Tests (pytest)¶
The Python CLI has its own test suite (273 tests) run via pytest:
cd cli && pytest
Covers: profile management, VM orchestration, MCP server tools, Lima manager, config validation.
Test Counts Summary¶
| Suite | Static (Ansible) | VM/E2E | Total |
|---|---|---|---|
| Sandbox | 117 | 49 | 166 |
| GitHub CLI | 110 | -- | 110 |
| Cadence | 32 | 32 | 64 |
| Overlay | 58 | 19 | 77 |
| Obsidian | 66 | -- | 66 |
| Qortex | 58 | -- | 58 |
| Telegram | 56 | -- | 56 |
| Gateway | 55 | -- | 55 |
| Buildlog | 51 | -- | 51 |
| Python CLI | -- | -- | 273 (pytest) |
CI/CD¶
CI runs automatically on every push to main and on every pull request.
GitHub Actions Workflow¶
The CI pipeline (.github/workflows/ci.yml) runs three jobs:
| Job | What it does |
|---|---|
| Validate Ansible | Runs yamllint -d relaxed ansible/ and validates all YAML files with Python's yaml.safe_load() |
| Quick Tests | Runs Ansible validation test suites (the --quick equivalent) |
| ShellCheck | Lints all scripts in scripts/, bootstrap.sh, and test files |
# The CI checks
- yamllint -d relaxed ansible/ # YAML syntax
- python yaml.safe_load() on all .yml # Ansible YAML validation
- ShellCheck on scripts/ and tests/ # Shell script linting
Note
CI does not run VM deployment tests (no Lima VM in GitHub Actions). VM tests are run locally before merging.
Docs Deployment¶
A separate workflow (.github/workflows/docs.yml) builds and deploys the documentation site on pushes to main that touch docs/, mkdocs.yml, or scripts/render-diagrams.sh. It uses MkDocs with GitHub Pages.
Adding Tests for a New Role¶
When you add a new Ansible role, follow this pattern:
1. Create the Test Directory¶
mkdir tests/<role-name>
2. Create the Ansible Validation Script¶
tests/<role-name>/test-<role-name>-ansible.sh:
#!/usr/bin/env bash
# <Role Name> Ansible Role Validation
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
PASS=0
FAIL=0
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
check() {
local desc="$1"
local result="$2"
if [[ "$result" == "true" ]]; then
echo -e " ${GREEN}PASS${NC} $desc"
PASS=$((PASS + 1))
else
echo -e " ${RED}FAIL${NC} $desc"
FAIL=$((FAIL + 1))
fi
}
# Check role files exist
check "defaults/main.yml exists" \
"$(test -f "$REPO_ROOT/ansible/roles/<role-name>/defaults/main.yml" && echo true || echo false)"
# Check integration with playbook
check "playbook includes role" \
"$(grep -q '<role-name>' "$REPO_ROOT/ansible/playbook.yml" && echo true || echo false)"
# ... more checks ...
# Summary
TOTAL=$((PASS + FAIL))
if [[ $FAIL -gt 0 ]]; then
echo -e " ${RED}FAILED${NC} ($PASS/$TOTAL checks)"
exit 1
else
echo -e " ${GREEN}PASSED${NC} ($PASS/$TOTAL checks)"
fi
3. Create the VM Deployment Script¶
tests/<role-name>/test-<role-name>-role.sh:
#!/usr/bin/env bash
# <Role Name> VM Deployment Tests
set -euo pipefail
VM_NAME="openclaw-sandbox"
vm_exec() {
limactl shell "$VM_NAME" -- "$@"
}
# Check service is running
vm_exec systemctl is-active <service-name>
# Check file exists
vm_exec test -f /path/to/expected/file
# ... more checks ...
4. Create the Runner¶
tests/<role-name>/run-all.sh:
#!/usr/bin/env bash
# <Role Name> Test Suite - Run All Tests
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
QUICK_MODE=false
[[ "${1:-}" == "--quick" ]] && QUICK_MODE=true
# Always run Ansible validation
bash "$SCRIPT_DIR/test-<role-name>-ansible.sh"
# Skip VM tests in quick mode
if [[ "$QUICK_MODE" == "false" ]]; then
bash "$SCRIPT_DIR/test-<role-name>-role.sh"
fi
5. Update CI¶
Add the new test to .github/workflows/ci.yml if it should run in CI, and make sure ShellCheck covers the new test scripts.
Tip
Keep Ansible validation tests thorough -- they are the primary safety net in CI since VM tests cannot run in GitHub Actions.