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
86 changes: 67 additions & 19 deletions .github/workflows/shell-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ concurrency:
cancel-in-progress: true

jobs:
ubuntu:
name: Ubuntu 24.04 lint and tests
lint:
name: ShellCheck
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
Expand All @@ -37,19 +37,6 @@ jobs:
run: |
command -v shellcheck
shellcheck --version
command -v jq
command -v ip
command -v rsync
command -v ssh

- name: Verify checkout and real Git selection
shell: bash
run: |
# Checkout uses a temporary HOME; trust only this job's workspace.
git config --global --add safe.directory "$GITHUB_WORKSPACE"
test "$(git rev-parse --show-toplevel)" = "$GITHUB_WORKSPACE"
git rev-parse --verify HEAD
bash tests/test-selection-git.sh

- name: Check Bash syntax
run: find . -type f -name '*.sh' -print0 | xargs -0 -n1 bash -n
Expand All @@ -64,17 +51,51 @@ jobs:
git ls-files -z -- "tests/*.sh" | xargs -0 -r shellcheck -x --severity=error -- || status=1
exit "$status"

ubuntu-tests:
name: Ubuntu 24.04 / ${{ matrix.shard }}
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
shard: [motd, push, other]
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false

- name: Verify test dependencies
run: |
command -v jq
command -v ip
command -v rsync
command -v ssh

- name: Verify checkout and real Git selection
shell: bash
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
test "$(git rev-parse --show-toplevel)" = "$GITHUB_WORKSPACE"
git rev-parse --verify HEAD
bash tests/test-selection-git.sh

- name: Run tests
env:
TEST_BASE_SHA: ${{ github.event.pull_request.base.sha }}
TEST_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: bash tests/run.sh
TEST_SHARD: ${{ matrix.shard }}
run: bash tests/run.sh "$TEST_SHARD"

debian-13:
name: Debian 13 tests
debian-tests:
name: Debian 13 / ${{ matrix.shard }}
runs-on: ubuntu-24.04
container: debian:13-slim
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
shard: [motd, push, other]
steps:
- name: Install test dependencies
run: |
Expand Down Expand Up @@ -103,4 +124,31 @@ jobs:
env:
TEST_BASE_SHA: ${{ github.event.pull_request.base.sha }}
TEST_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: bash tests/run.sh
TEST_SHARD: ${{ matrix.shard }}
run: bash tests/run.sh "$TEST_SHARD"

# Preserve existing required-check names; skipped/cancelled dependencies fail closed.
ubuntu:
name: Ubuntu 24.04 lint and tests
if: ${{ always() }}
needs: [lint, ubuntu-tests]
runs-on: ubuntu-24.04
timeout-minutes: 2
steps:
- name: Require lint and every Ubuntu shard
env:
LINT_RESULT: ${{ needs.lint.result }}
TEST_RESULT: ${{ needs.ubuntu-tests.result }}
run: test "$LINT_RESULT" = success && test "$TEST_RESULT" = success

debian-13:
name: Debian 13 tests
if: ${{ always() }}
needs: [debian-tests]
runs-on: ubuntu-24.04
timeout-minutes: 2
steps:
- name: Require every Debian shard
env:
TEST_RESULT: ${{ needs.debian-tests.result }}
run: test "$TEST_RESULT" = success
20 changes: 20 additions & 0 deletions tests/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ set -euo pipefail

readonly ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)

# Positional, not exported: nested selection fixtures must still run all suites.
shard=${1:-all}
if (( $# > 1 )); then
printf 'Usage: tests/run.sh [all|motd|push|other]\n' >&2
exit 2
fi
case "$shard" in
all|motd|push|other) ;;
*) printf 'Invalid test shard: %s\n' "$shard" >&2; exit 2 ;;
esac

declare -A selected=()
full=true
if [[ -n "${TEST_BASE_SHA:-}" && -n "${TEST_HEAD_SHA:-}" ]]; then
Expand Down Expand Up @@ -38,6 +49,15 @@ for test_file in "$ROOT_DIR"/tests/test-*.sh; do
continue
fi
test_name=$(basename "$test_file")
case "$test_name" in
test-motd.sh) suite_shard=motd ;;
test-push.sh|test-push-worker-registration.sh) suite_shard=push ;;
*) suite_shard=other ;;
esac
if [[ "$shard" != all && "$shard" != "$suite_shard" ]]; then
printf 'SKIP: %s (assigned to %s shard)\n' "$test_name" "$suite_shard"
continue
fi
printf '\n==> %s\n' "$test_name"
started=$SECONDS
# Keep the child shell standalone: an if/|| wrapper can change errexit behavior.
Expand Down
65 changes: 65 additions & 0 deletions tests/test-shards.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail
# Inert fixtures only: verify partitioning without running production suites.
ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT
mkdir -p "$TEMP_DIR/tests" "$TEMP_DIR/bin"
cp "$ROOT_DIR/tests/run.sh" "$TEMP_DIR/tests/run.sh"
export CAPTURE="$TEMP_DIR/capture" GITHUB_STEP_SUMMARY="$TEMP_DIR/summary"
unset TEST_BASE_SHA TEST_HEAD_SHA
for name in motd push push-worker-registration new-suite; do
printf '#!/usr/bin/env bash\nprintf "%%s\\n" "%s" >> "$CAPTURE"\n' "test-$name.sh" > "$TEMP_DIR/tests/test-$name.sh"
done
check() {
local expected=$1
shift
: > "$CAPTURE"
bash "$TEMP_DIR/tests/run.sh" "$@" > "$TEMP_DIR/output" 2>&1
[[ "$(cat "$CAPTURE")" == "$expected" ]] || { cat "$TEMP_DIR/output"; exit 1; }
}
all=$'test-motd.sh\ntest-new-suite.sh\ntest-push-worker-registration.sh\ntest-push.sh'
check "$all"
check "$all" all
check test-motd.sh motd
check $'test-push-worker-registration.sh\ntest-push.sh' push
check test-new-suite.sh other
# Every current and future suite belongs to exactly one shard.
: > "$TEMP_DIR/union"
for shard in motd push other; do
: > "$CAPTURE"
bash "$TEMP_DIR/tests/run.sh" "$shard" > "$TEMP_DIR/output" 2>&1
cat "$CAPTURE" >> "$TEMP_DIR/union"
done
[[ "$(sort "$TEMP_DIR/union")" == "$all" ]]
for args in invalid extra; do
: > "$CAPTURE"
rc=0
if [[ "$args" == extra ]]; then
bash "$TEMP_DIR/tests/run.sh" all extra > "$TEMP_DIR/output" 2>&1 || rc=$?
else
bash "$TEMP_DIR/tests/run.sh" invalid > "$TEMP_DIR/output" 2>&1 || rc=$?
fi
[[ "$rc" == 2 && ! -s "$CAPTURE" ]]
done
# PR selection intersects the shard; unavailable history still covers all shards.
cat > "$TEMP_DIR/bin/git" <<'STUB'
#!/usr/bin/env bash
[[ "${DIFF_FAIL:-0}" == 0 ]] || exit 1
printf 'tools/push.sh\0'
STUB
chmod +x "$TEMP_DIR/bin/git"
export PATH="$TEMP_DIR/bin:$PATH" TEST_BASE_SHA=base TEST_HEAD_SHA=head
check '' motd
check '' other
check $'test-push-worker-registration.sh\ntest-push.sh' push
DIFF_FAIL=1 check test-motd.sh motd
DIFF_FAIL=1 check test-new-suite.sh other
# Child errexit and failure propagation must survive sharding.
printf '#!/usr/bin/env bash\nset -e\nfalse\nprintf unexpected >> "$CAPTURE"\n' > "$TEMP_DIR/tests/test-push-worker-registration.sh"
: > "$CAPTURE"
rc=0
bash "$TEMP_DIR/tests/run.sh" push > "$TEMP_DIR/output" 2>&1 || rc=$?
[[ "$rc" == 1 && ! -s "$CAPTURE" ]]
grep -q 'exit=1' "$TEMP_DIR/output"
printf 'All shard fixture tests passed.\n'