Testing
Prove that roles apply and are idempotent on a clean machine. pre-commit lints, but only an apply on a fresh OS proves a role actually works there. The Ubuntu converge + idempotence harness ships (tests/playbook.yml, the .test:roles CI job across ubuntu:22.04 / 24.04 / 26.04; see TESTING.md); what remains is the platforms GitLab's shared runners cannot host, then per-role coverage.
macOS and Windows/WSL on GitHub Actions
GitLab's shared runners cannot run macOS, and WSL needs a real Windows host (it will not run in a container; WSL2 needs nested virtualization). GitHub Actions has free runners for both, so a GitHub mirror of the repo covers the two platforms GitLab cannot, while GitLab keeps the Ubuntu containers. The workflows below run the same converge + idempotence steps (apply tests/playbook.yml twice).
macOS
macos-13 is Intel, macos-14 / macos-15 are Apple Silicon; Homebrew is preinstalled, so the harness runs as-is.
# .github/workflows/macos.yml
name: macOS
on: [push, pull_request]
jobs:
smoke:
strategy:
fail-fast: false
matrix:
os: [macos-13, macos-14, macos-15]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- run: brew install ansible
- run: |
work=$(mktemp -d); mkdir -p "$work/ansible_collections/dotfilespro"
ln -s "$PWD" "$work/ansible_collections/dotfilespro/roles"
export ANSIBLE_COLLECTIONS_PATH=$work
ansible-galaxy collection install -r requirements.yml -p "$work"
ansible-playbook -i localhost, tests/playbook.yml # converge
ansible-playbook -i localhost, tests/playbook.yml # idempotence: expect changed=0Windows / WSL
A windows-latest runner with the Vampire/setup-wsl action installs a WSL distro and exposes a wsl-bash shell to run the harness inside it. This job needs a WSL-inclusive playbook (the wsl role added) and a final step that asserts the Windows-side artifacts the role creates: the %USERPROFILE%\bin shims and the Windows user PATH / WSLENV entries.
# .github/workflows/windows-wsl.yml
name: Windows / WSL
on: [push, pull_request]
jobs:
wsl:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: Vampire/setup-wsl@v3
with:
distribution: Ubuntu-24.04
additional-packages: ansible sudo git curl ca-certificates locales wslu
- name: Converge and assert idempotence inside WSL
shell: wsl-bash {0}
# with the wsl role added to the test playbook
run: |
work=$(mktemp -d); mkdir -p "$work/ansible_collections/dotfilespro"
ln -s "$PWD" "$work/ansible_collections/dotfilespro/roles"
export ANSIBLE_COLLECTIONS_PATH=$work
ansible-galaxy collection install -r requirements.yml -p "$work"
ansible-playbook -i localhost, tests/playbook.yml # converge
ansible-playbook -i localhost, tests/playbook.yml # idempotence: expect changed=0Caveats:
setup-wslinstalls WSL 1 on hosted runners; WSL2 needs nested virtualization the free runners lack, so full WSL2 fidelity needs a larger or self-hosted runner. Thewslrole uses only generic interop (wslpath,wslvar,powershell.exe), which works under WSL1, so WSL1 still exercises most of it.- Asserting the Windows-side mutations (shims,
PATH,WSLENV) is the fiddly part, and the reason this lands after Ubuntu and macOS.
Excluded roles
aws— held out of the test matrix (tests/playbook.ymland theubuntu:awsjob). It installs AWSume with a barepip:into the system interpreter, which fails withexternally-managed-environment(PEP 668) on Ubuntu 24.04+. Move the install topipx(or--user/ a venv) so it works across the LTS matrix, then addawsback totest_roles_defaultand restore theubuntu:awsjob.docker— held out of the test matrix. The role installs Docker Compose v1 (thedocker-composeapt package), which Ubuntu 26.04 removed, sodocker-compose --versionalways fails there and the Compose version-update step (adebugwithchanged_when: true) fires on every run, breaking idempotence — and Compose is not actually installed. Migrate the role to Compose v2: install the plugin (docker-compose-v2, providingdocker compose) and detect withdocker compose versionacross 22.04 / 24.04 / 26.04, then adddockerback totest_roles_defaultand restore theubuntu:dockerjob.
Further
- Per-role coverage: apply each role on its own (
--tags <role>) and assert idempotence, rather than one combined smoke playbook. Molecule is the standard harness for that if the extra dependency proves worth it.