Skip to content
Open
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ jobs:
- name: Check tracked files use portable line endings
run: bash tests/release/tracked-eol.test.sh

- name: Check ShellCheck covers every tracked shell file
run: bash tests/release/shellcheck-coverage.test.sh

- name: Check systemd directory modes match tmpfiles
run: bash tests/release/systemd-directory-modes.test.sh

Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ on:
- "tests/release/**"
- "scripts/**"
- "assets/demo/**"
- ".githooks/**"
- "apps/sysknife-cli/**"
# scripts-lint builds sysknife-cli, which depends on every crate under
# crates/. Without this the job could not be triggered by a change to the
Expand All @@ -46,6 +47,7 @@ on:
- "tests/release/**"
- "scripts/**"
- "assets/demo/**"
- ".githooks/**"
- "apps/sysknife-cli/**"
# scripts-lint builds sysknife-cli, which depends on every crate under
# crates/. Without this the job could not be triggered by a change to the
Expand Down Expand Up @@ -76,9 +78,11 @@ jobs:

- name: ShellCheck maintained scripts
run: |
find tests/e2e tests/release scripts assets/demo \
-type f -name '*.sh' -print0 \
| xargs -0 shellcheck --severity=warning
file_list="$(mktemp)"
trap 'rm -f "$file_list"' EXIT
scripts/shellcheck-files.sh >"$file_list"
mapfile -d '' files <"$file_list"
shellcheck --severity=warning "${files[@]}"

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
Expand Down
8 changes: 5 additions & 3 deletions scripts/ci-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,11 @@ hygiene_yamllint() (
# since every script this task adds/touches must stay shellcheck-clean.
hygiene_shellcheck() (
cd "$repo_root" || exit 1
find tests/e2e tests/release scripts assets/demo \
-type f -name '*.sh' -print0 \
| xargs -0 shellcheck --severity=warning
file_list="$(mktemp)"
trap 'rm -f "$file_list"' EXIT
scripts/shellcheck-files.sh >"$file_list" || exit 1
mapfile -d '' files <"$file_list"
shellcheck --severity=warning "${files[@]}"
)

run_shell_tests() {
Expand Down
42 changes: 42 additions & 0 deletions scripts/shellcheck-files.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
set -euo pipefail

default_roots=(tests/e2e tests/release scripts assets/demo .githooks)

if [[ "${1:-}" == "--print-roots" ]]; then
printf '%s\0' "${default_roots[@]}"
exit 0
fi

if (($# > 0)); then
roots=("$@")
else
roots=("${default_roots[@]}")
fi

for root in "${roots[@]}"; do
[[ -d "$root" ]] || {
printf 'shellcheck-files: missing search root: %s\n' "$root" >&2
exit 1
}
done

all_files="$(mktemp)"
trap 'rm -f "$all_files"' EXIT
find "${roots[@]}" -type f -print0 >"$all_files"

files=()
while IFS= read -r -d '' file; do
first=''
IFS= read -r first <"$file" || true
if [[ "$first" == '#!'* && ("$first" == *bash* || "$first" == */sh) ]]; then
files+=("$file")
fi
done <"$all_files"

((${#files[@]} > 0)) || {
printf 'shellcheck-files: no shell files found\n' >&2
exit 1
}

printf '%s\0' "${files[@]}"
73 changes: 73 additions & 0 deletions tests/release/shellcheck-coverage.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
set -euo pipefail

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

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

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

scripts/shellcheck-files.sh >"$tmp/scanned"
mapfile -d '' scanned <"$tmp/scanned"
((${#scanned[@]} > 150)) || fail "shellcheck scan covers only ${#scanned[@]} files"

declare -A scanned_set=()
for file in "${scanned[@]}"; do
scanned_set["$file"]=1
done

missing=()
tracked_shell=0
git ls-files -z >"$tmp/tracked"
while IFS= read -r -d '' file; do
[[ -f "$file" ]] || continue
first=''
IFS= read -r first <"$file" || true
is_shell=false
if [[ "$file" == *.sh ]]; then
is_shell=true
elif [[ "$first" == '#!'* && ("$first" == *bash* || "$first" == */sh) ]]; then
is_shell=true
fi
"$is_shell" || continue
tracked_shell=$((tracked_shell + 1))
[[ -n "${scanned_set[$file]:-}" ]] || missing+=("$file")
done <"$tmp/tracked"

((tracked_shell > 150)) || fail "tracked shell-file floor collapsed to $tracked_shell"
((${#missing[@]} == 0)) || fail "tracked shell files missing from ShellCheck scan: ${missing[*]}"
[[ -n "${scanned_set[.githooks/pre-commit]:-}" ]] || fail '.githooks/pre-commit is not ShellCheck-covered'
[[ -n "${scanned_set[.githooks/pre-push]:-}" ]] || fail '.githooks/pre-push is not ShellCheck-covered'

scripts/shellcheck-files.sh --print-roots >"$tmp/roots"
roots_seen=0
while IFS= read -r -d '' root; do
roots_seen=$((roots_seen + 1))
trigger="$root/**"
trigger_count="$(grep -F -c -- "- \"$trigger\"" .github/workflows/e2e.yml || true)"
((trigger_count == 2)) || fail "ShellCheck root $root must trigger both push and pull_request e2e jobs"
done <"$tmp/roots"
((roots_seen > 0)) || fail 'shellcheck-files.sh --print-roots produced no roots'

grep -Fq 'scripts/shellcheck-files.sh >"$file_list"' scripts/ci-local.sh || fail 'ci-local shellcheck must use scripts/shellcheck-files.sh'
if grep -Fq -- "-name '*.sh'" scripts/ci-local.sh; then
fail "ci-local shellcheck must not maintain its own '*.sh' find"
fi

if scripts/shellcheck-files.sh "$tmp/missing-root" >"$tmp/out" 2>"$tmp/err"; then
fail 'missing search root was accepted'
fi
grep -Fq 'missing search root:' "$tmp/err" || fail 'missing-root failure did not name the missing root'

mkdir "$tmp/empty-root"
if scripts/shellcheck-files.sh "$tmp/empty-root" >"$tmp/out" 2>"$tmp/err"; then
fail 'empty search root was accepted'
fi
grep -Fq 'no shell files found' "$tmp/err" || fail 'empty-root failure did not explain the zero-file scan'

printf 'ok: ShellCheck covers %d tracked shell files and refuses missing/empty roots\n' "$tracked_shell"
Loading