Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,15 @@ jobs:
fi


# The smoke stages run as parallel groups (tests/smoke.sh <group>); the job
# below keeps the single required check name and fails if any group failed.
test:
name: Bootstrap & Smoke Tests
name: Smoke (${{ matrix.group }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
group: [themes, layers, setup, lifecycle]
steps:
- name: Checkout repository
uses: actions/checkout@v7
Expand Down Expand Up @@ -84,4 +90,19 @@ jobs:
- name: Run smoke tests
run: |
chmod +x tests/*.sh layers/wm/*/tests/*.sh layers/system/tests/*.sh bin/dotfiles install.sh
./tests/smoke.sh
./tests/smoke.sh "${{ matrix.group }}"

smoke:
name: Bootstrap & Smoke Tests
needs: test
if: always()
runs-on: ubuntu-latest
steps:
- name: Require every smoke group to pass
env:
RESULT: ${{ needs.test.result }}
run: |
if [[ "$RESULT" != success ]]; then
echo "::error::smoke test groups finished with: $RESULT"
exit 1
fi
2 changes: 2 additions & 0 deletions README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ Ejecuta la suite de smoke tests localmente:
./tests/smoke.sh
```

Ejecuta todas las etapas en orden. `./tests/smoke.sh <grupo>` ejecuta un solo grupo (`themes`, `layers`, `setup` o `lifecycle`); CI ejecuta los cuatro grupos como jobs en paralelo, y el check requerido `Bootstrap & Smoke Tests` solo pasa cuando pasan todos.

---

## 👤 Autor
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ Run the automated smoke test suite locally:
./tests/smoke.sh
```

It runs every stage in order. `./tests/smoke.sh <group>` runs one group (`themes`, `layers`, `setup` or `lifecycle`); CI runs the four groups as parallel jobs, and the required `Bootstrap & Smoke Tests` check passes only when all of them do.

---

## 👤 Author
Expand Down
49 changes: 49 additions & 0 deletions tests/smoke-runner-smoke.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# tests/smoke.sh: argument errors exit 2 before any stage runs, every stage is
# in exactly one group, and the CI matrix runs exactly the runner's groups.

set -Eeuo pipefail

SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
REPO_ROOT=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
RUNNER="$SCRIPT_DIR/smoke.sh"
WORKFLOW="$REPO_ROOT/.github/workflows/ci.yml"

fail() {
printf 'FAIL: %s\n' "$*" >&2
exit 1
}

# Runs the runner with arguments $@ and prints "<exit> <output>".
runner() {
local status=0 output

output=$("$RUNNER" "$@" 2>&1) || status=$?
printf '%s %s' "$status" "$output"
}

output=$(runner nope)
[[ "$output" == 2\ *'unknown smoke test group: nope'* ]] || fail "un grupo desconocido no fue error de uso: $output"
[[ "$output" != *'-->'* ]] || fail "un grupo desconocido ejecutó etapas: $output"
output=$(runner themes layers)
[[ "$output" == 2\ *'Usage:'* ]] || fail "dos grupos no fueron error de uso: $output"

# The group table covers stages 1..STAGE_COUNT exactly once.
groups=$(sed -n "s/^ \[\([a-z]*\)\]='\([0-9 ]*\)'$/\1 \2/p" "$RUNNER")
stage_count=$(sed -n 's/^STAGE_COUNT=\([0-9]*\)$/\1/p' "$RUNNER")
[[ -n "$groups" && -n "$stage_count" ]] || fail 'no se pudo leer la tabla de grupos de smoke.sh'
stages=$(cut -d' ' -f2- <<< "$groups" | tr ' ' '\n' | sort -n | tr '\n' ' ')
[[ "$stages" == "$(seq -s ' ' 1 "$stage_count") " ]] || fail "los grupos no cubren cada etapa una vez: $stages"
for (( stage = 1; stage <= stage_count; stage++ )); do
grep -q "^stage_$stage() {" "$RUNNER" || fail "falta la etapa $stage en smoke.sh"
done

# CI runs the same groups (matrix) and keeps the required check name.
runner_groups=$(cut -d' ' -f1 <<< "$groups" | sort | tr '\n' ' ')
ci_groups=$(sed -n 's/^ *group: \[\(.*\)\]$/\1/p' "$WORKFLOW" | tr -d ' ' | tr ',' '\n' | sort | tr '\n' ' ')
[[ "$ci_groups" == "$runner_groups" ]] || fail "la matriz de CI ($ci_groups) no coincide con smoke.sh ($runner_groups)"
grep -Fq 'name: Bootstrap & Smoke Tests' "$WORKFLOW" || fail 'CI perdió el check requerido Bootstrap & Smoke Tests'
# shellcheck disable=SC2016 # GitHub Actions expression, matched literally
grep -Fq './tests/smoke.sh "${{ matrix.group }}"' "$WORKFLOW" || fail 'CI no ejecuta cada grupo de la matriz'

printf 'OK: grupos del runner de smoke tests validados\n'
204 changes: 145 additions & 59 deletions tests/smoke.sh
Original file line number Diff line number Diff line change
@@ -1,65 +1,151 @@
#!/usr/bin/env bash
# Canonical smoke tests. Without arguments every stage runs, in order; with a
# group name only that group's stages run, so CI can run the groups in
# parallel jobs. Every stage belongs to exactly one group.

set -Eeuo pipefail

SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
REPO_ROOT=$(cd -- "$SCRIPT_DIR/.." && pwd -P)

printf '==> Running canonical smoke tests for dotfiles (base)...\n'

printf '\n--> 1/13: Executing shared engine (lib/) and external download smoke tests...\n'
"$SCRIPT_DIR/lib-smoke.sh"
"$SCRIPT_DIR/external-smoke.sh"

printf '\n--> 2/13: Executing layer hook smoke tests...\n'
"$SCRIPT_DIR/layer-hooks-smoke.sh"

printf '\n--> 3/13: Executing shared theme engine, Sanctuary palette, theme picker, mimeapps, font and image viewer smoke tests...\n'
"$SCRIPT_DIR/theme-smoke.sh"
"$SCRIPT_DIR/theme-schema-smoke.sh"
"$SCRIPT_DIR/theme-apps-smoke.sh"
"$SCRIPT_DIR/sanctuary-smoke.sh"
"$SCRIPT_DIR/theme-picker-smoke.sh"
"$SCRIPT_DIR/mimeapps-smoke.sh"
"$SCRIPT_DIR/fonts-smoke.sh"
"$SCRIPT_DIR/cli-tools-smoke.sh"
"$SCRIPT_DIR/cursor-smoke.sh"
"$SCRIPT_DIR/imv-folder-smoke.sh"
"$SCRIPT_DIR/checkout-copy-smoke.sh"

printf '\n--> 4/13: Executing WM, opt-in layer and overlay smoke tests...\n'
"$SCRIPT_DIR/wm-layers-smoke.sh"
"$SCRIPT_DIR/optional-layers-smoke.sh"
"$SCRIPT_DIR/overlay-smoke.sh"

printf '\n--> 5/13: Executing MangoWM scaffold and menu smoke tests...\n'
"$REPO_ROOT/layers/wm/mangowm/tests/scaffold-smoke.sh"
"$REPO_ROOT/layers/wm/mangowm/tests/menu-smoke.sh"

printf '\n--> 6/13: Executing WM session smoke tests...\n'
"$REPO_ROOT/layers/wm/bspwm/tests/session-smoke.sh"
"$REPO_ROOT/layers/wm/mangowm/tests/session-smoke.sh"

printf '\n--> 7/13: Executing system layer smoke tests...\n'
"$REPO_ROOT/layers/system/tests/smoke.sh"
"$SCRIPT_DIR/system-smoke.sh"

printf '\n--> 8/13: Executing bootstrap smoke tests...\n'
"$SCRIPT_DIR/bootstrap-smoke.sh"

printf '\n--> 9/13: Executing wizard orchestrator smoke tests...\n'
"$SCRIPT_DIR/wizard-smoke.sh"

printf '\n--> 10/13: Executing external component smoke tests...\n'
"$SCRIPT_DIR/components-smoke.sh"

printf '\n--> 11/13: Executing guided setup smoke tests...\n'
"$SCRIPT_DIR/setup-smoke.sh"

printf '\n--> 12/13: Executing dropped selection retire smoke tests...\n'
"$SCRIPT_DIR/retire-smoke.sh"

printf '\n--> 13/13: Executing checkout switch smoke tests...\n'
"$SCRIPT_DIR/switch-smoke.sh"

printf '\nOK: all base dotfiles smoke tests passed successfully\n'
STAGE_COUNT=13

# Groups balanced by their CI duration; .github/workflows/ci.yml lists the same names.
declare -A SMOKE_GROUPS=(
[themes]='3'
[layers]='1 2 4 5 6 7'
[setup]='8 9 10 11'
[lifecycle]='12 13'
)
GROUP_NAMES=(themes layers setup lifecycle)

usage() {
printf 'Usage: %s [%s]\n' "${0##*/}" "$(IFS='|'; printf '%s' "${GROUP_NAMES[*]}")" >&2
}

stage_1() {
printf '\n--> 1/13: Executing shared engine (lib/), smoke runner and external download smoke tests...\n'
"$SCRIPT_DIR/lib-smoke.sh"
"$SCRIPT_DIR/smoke-runner-smoke.sh"
"$SCRIPT_DIR/external-smoke.sh"
}

stage_2() {
printf '\n--> 2/13: Executing layer hook smoke tests...\n'
"$SCRIPT_DIR/layer-hooks-smoke.sh"
}

stage_3() {
printf '\n--> 3/13: Executing shared theme engine, Sanctuary palette, theme picker, mimeapps, font and image viewer smoke tests...\n'
"$SCRIPT_DIR/theme-smoke.sh"
"$SCRIPT_DIR/theme-schema-smoke.sh"
"$SCRIPT_DIR/theme-apps-smoke.sh"
"$SCRIPT_DIR/sanctuary-smoke.sh"
"$SCRIPT_DIR/theme-picker-smoke.sh"
"$SCRIPT_DIR/mimeapps-smoke.sh"
"$SCRIPT_DIR/fonts-smoke.sh"
"$SCRIPT_DIR/cli-tools-smoke.sh"
"$SCRIPT_DIR/cursor-smoke.sh"
"$SCRIPT_DIR/imv-folder-smoke.sh"
"$SCRIPT_DIR/checkout-copy-smoke.sh"
}

stage_4() {
printf '\n--> 4/13: Executing WM, opt-in layer and overlay smoke tests...\n'
"$SCRIPT_DIR/wm-layers-smoke.sh"
"$SCRIPT_DIR/optional-layers-smoke.sh"
"$SCRIPT_DIR/overlay-smoke.sh"
}

stage_5() {
printf '\n--> 5/13: Executing MangoWM scaffold and menu smoke tests...\n'
"$REPO_ROOT/layers/wm/mangowm/tests/scaffold-smoke.sh"
"$REPO_ROOT/layers/wm/mangowm/tests/menu-smoke.sh"
}

stage_6() {
printf '\n--> 6/13: Executing WM session smoke tests...\n'
"$REPO_ROOT/layers/wm/bspwm/tests/session-smoke.sh"
"$REPO_ROOT/layers/wm/mangowm/tests/session-smoke.sh"
}

stage_7() {
printf '\n--> 7/13: Executing system layer smoke tests...\n'
"$REPO_ROOT/layers/system/tests/smoke.sh"
"$SCRIPT_DIR/system-smoke.sh"
}

stage_8() {
printf '\n--> 8/13: Executing bootstrap smoke tests...\n'
"$SCRIPT_DIR/bootstrap-smoke.sh"
}

stage_9() {
printf '\n--> 9/13: Executing wizard orchestrator smoke tests...\n'
"$SCRIPT_DIR/wizard-smoke.sh"
}

stage_10() {
printf '\n--> 10/13: Executing external component smoke tests...\n'
"$SCRIPT_DIR/components-smoke.sh"
}

stage_11() {
printf '\n--> 11/13: Executing guided setup smoke tests...\n'
"$SCRIPT_DIR/setup-smoke.sh"
}

stage_12() {
printf '\n--> 12/13: Executing dropped selection retire smoke tests...\n'
"$SCRIPT_DIR/retire-smoke.sh"
}

stage_13() {
printf '\n--> 13/13: Executing checkout switch smoke tests...\n'
"$SCRIPT_DIR/switch-smoke.sh"
}

# A stage left out of every group, or listed twice, would silently skip or
# repeat it in CI.
check_groups() {
local group stage
local -A seen=()

# GROUP_NAMES (the order) and the SMOKE_GROUPS keys (the stages) list the same groups.
[[ "$(printf '%s\n' "${GROUP_NAMES[@]}" | sort)" == "$(printf '%s\n' "${!SMOKE_GROUPS[@]}" | sort)" ]] || {
printf 'ERROR: GROUP_NAMES (%s) and SMOKE_GROUPS (%s) differ\n' "${GROUP_NAMES[*]}" "${!SMOKE_GROUPS[*]}" >&2
return 1
}
for group in "${GROUP_NAMES[@]}"; do
for stage in ${SMOKE_GROUPS[$group]}; do
[[ -z "${seen[$stage]-}" ]] || { printf 'ERROR: stage %s is in groups %s and %s\n' "$stage" "${seen[$stage]}" "$group" >&2; return 1; }
seen[$stage]=$group
done
done
for (( stage = 1; stage <= STAGE_COUNT; stage++ )); do
[[ -n "${seen[$stage]-}" ]] || { printf 'ERROR: stage %s belongs to no group\n' "$stage" >&2; return 1; }
done
(( ${#seen[@]} == STAGE_COUNT )) || { printf 'ERROR: groups name stages beyond %s\n' "$STAGE_COUNT" >&2; return 1; }
}

check_groups

(( $# <= 1 )) || { usage; exit 2; }
group=${1-}
if [[ -z "$group" ]]; then
stages=$(seq 1 "$STAGE_COUNT")
label='base'
elif [[ -n "${SMOKE_GROUPS[$group]-}" ]]; then
stages=${SMOKE_GROUPS[$group]}
label="group $group"
else
printf 'ERROR: unknown smoke test group: %s\n' "$group" >&2
usage
exit 2
fi

printf '==> Running canonical smoke tests for dotfiles (%s)...\n' "$label"
for stage in $stages; do
"stage_$stage"
done

printf '\nOK: all %s dotfiles smoke tests passed successfully\n' "$label"
Loading