Skip to content

Recipe tests

Recipe tests #130

Workflow file for this run

name: Recipe tests
# Runs each lux recipe end-to-end inside a fresh container per supported
# distro. Catches both "PR broke a recipe" and "upstream package /
# repo / install path drifted" — the latter via the nightly cron.
on:
pull_request:
branches: [main]
paths:
- 'crates/lux-knowledge/recipes/**'
- 'crates/lux-knowledge/src/**'
- 'crates/lux-tools/src/recipes.rs'
- 'crates/lux-cli/src/main.rs'
- '.github/workflows/recipe-tests.yml'
schedule:
- cron: '0 6 * * *' # 06:00 UTC daily — catches upstream drift
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
build:
name: Build lux binary
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Build release binary
run: cargo build --release --bin lux
- name: Upload binary
uses: actions/upload-artifact@v7
with:
name: lux-binary
path: target/release/lux
retention-days: 1
if-no-files-found: error
recipe-test:
name: ${{ matrix.recipe }} on ${{ matrix.distro }}
needs: build
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false # collect every cell — the matrix table is the diagnostic
matrix:
include:
- recipe: browser-chromium
distro: fedora
image: fedora:latest
expect: success
verify: command -v chromium-browser >/dev/null 2>&1 || command -v chromium >/dev/null 2>&1
- recipe: browser-chromium
distro: archlinux
image: archlinux:latest
expect: success
verify: command -v chromium >/dev/null 2>&1
- recipe: browser-chromium
distro: debian
image: debian:stable
expect: success
verify: command -v chromium >/dev/null 2>&1
- recipe: browser-chromium
distro: ubuntu
image: ubuntu:latest
expect: refuse
refuse_match: "snap shim"
- recipe: editor-sublime
distro: fedora
image: fedora:latest
expect: success
verify: command -v subl >/dev/null 2>&1
- recipe: editor-sublime
distro: archlinux
image: archlinux:latest
expect: success
verify: command -v subl >/dev/null 2>&1
- recipe: editor-sublime
distro: debian
image: debian:stable
expect: success
verify: command -v subl >/dev/null 2>&1
- recipe: editor-sublime
distro: ubuntu
image: ubuntu:latest
expect: success
verify: command -v subl >/dev/null 2>&1
- recipe: zoom
distro: fedora
image: fedora:latest
expect: success
verify: command -v zoom >/dev/null 2>&1
- recipe: zoom
distro: debian
image: debian:stable
expect: success
verify: command -v zoom >/dev/null 2>&1
- recipe: zoom
distro: ubuntu
image: ubuntu:latest
expect: success
verify: command -v zoom >/dev/null 2>&1
- recipe: zoom
distro: archlinux
image: archlinux:latest
expect: refuse
refuse_match: "AUR"
container:
image: ${{ matrix.image }}
steps:
- name: Install minimal container deps
shell: bash
run: |
set -eux
if command -v dnf >/dev/null 2>&1; then
dnf install -y --setopt=install_weak_deps=False ca-certificates
elif command -v pacman >/dev/null 2>&1; then
# Refresh package DB; archlinux:latest is rolling.
pacman -Sy --noconfirm ca-certificates
elif command -v apt-get >/dev/null 2>&1; then
apt-get update
apt-get install -y --no-install-recommends ca-certificates
fi
- name: Shim sudo (containers run as root by default)
shell: bash
run: |
# The recipe shells use `sudo`; in the container we're already
# root, so make `sudo` a transparent passthrough rather than
# installing the real package per distro.
printf '#!/bin/sh\nexec "$@"\n' > /usr/local/bin/sudo
chmod +x /usr/local/bin/sudo
- name: Download lux binary
uses: actions/download-artifact@v8
with:
name: lux-binary
path: .
- name: Make lux executable
shell: bash
run: chmod +x ./lux
- name: Apply recipe ${{ matrix.recipe }} (expect ${{ matrix.expect }})
shell: bash
run: |
set +e
OUTPUT=$(./lux recipe apply ${{ matrix.recipe }} --yes 2>&1)
STATUS=$?
set -e
echo "::group::lux output (exit=$STATUS)"
echo "$OUTPUT"
echo "::endgroup::"
case "${{ matrix.expect }}" in
success)
if [ "$STATUS" -ne 0 ]; then
echo "::error::Expected success on ${{ matrix.distro }}, got exit $STATUS"
exit 1
fi
# Post-install verify: catches silent-green recipes that
# exited 0 without actually installing the thing. Without
# this, a recipe whose distro branch fell through to a
# no-op would still report success.
VERIFY='${{ matrix.verify }}'
if [ -n "$VERIFY" ]; then
if ! bash -c "$VERIFY"; then
echo "::error::Recipe exited 0 but verify failed on ${{ matrix.distro }}: $VERIFY"
exit 1
fi
fi
;;
refuse)
# Recipe should refuse cleanly (exit != 0) and mention the
# reason in its output, so users know why and what to do
# instead. The needle to grep for is per-cell (matrix.refuse_match).
if [ "$STATUS" -eq 0 ]; then
echo "::error::Expected ${{ matrix.distro }} to refuse, but recipe succeeded"
exit 1
fi
NEEDLE='${{ matrix.refuse_match }}'
if [ -z "$NEEDLE" ]; then
echo "::error::expect=refuse but matrix.refuse_match is empty"
exit 1
fi
if ! printf '%s' "$OUTPUT" | grep -qi "$NEEDLE"; then
echo "::error::Expected refusal message to contain '$NEEDLE' on ${{ matrix.distro }}"
exit 1
fi
;;
*)
echo "::error::Unknown expect value: ${{ matrix.expect }}"
exit 1
;;
esac