Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 49635d38e6 | |||
| 0312dd9871 | |||
| 6f6ead4ffc | |||
| 66f6eeb4e4 | |||
| 835d796781 | |||
| 51490707f5 | |||
| ba6e49ffb7 | |||
| e0ab86b8f8 | |||
| fd13778dd8 | |||
| 8ea3c7d1ae | |||
| d3acd4989a | |||
| 82c944728e | |||
| ad74c8a39e | |||
| 0e35d8d978 | |||
| 3da86dbf8a | |||
| cef655acc6 | |||
| 9a0ea96d22 | |||
| ec0502f829 | |||
| 7d04aef94d | |||
| 172c100dec | |||
| 94bd45c3b5 |
@@ -1,37 +1,42 @@
|
||||
# Task 06G4M8WHGQ4P3GQMPEEH0RJRHM
|
||||
# Task 06G4WDJ06G7FK00QF73Z9DDBT4
|
||||
|
||||
Give the healthcheck a --list-checks flag
|
||||
Sort the check names in --list-checks
|
||||
|
||||
- Project: test-e2e
|
||||
- Source: burnin/run14-c
|
||||
- Source: gitea:test-e2e/27
|
||||
- Priority: 0
|
||||
|
||||
## Instructions
|
||||
|
||||
Give `scripts/orchestra_e2e_healthcheck.sh` a `--list-checks` flag that prints the name of each check it performs, one per line, and exits 0.
|
||||
`--list-checks` prints the ten check names in the order they run. Add a `--sorted` modifier that prints them in alphabetical order instead, leaving run order untouched.
|
||||
|
||||
This is deliberately two pieces of work, and the plan should carry two phases:
|
||||
Plan this as THREE phases:
|
||||
|
||||
1. Name the checks. The script performs its checks inline. Introduce one list of check names and drive the existing run from that list, with no change to any output or exit status.
|
||||
2. Add the flag. `--list-checks` prints the names from that list and exits 0. Add one line to the usage text describing it.
|
||||
1. A helper that returns the check names in alphabetical order, with no caller yet.
|
||||
2. `--sorted` accepted alongside `--list-checks`, using that helper. Alone, `--sorted` changes nothing.
|
||||
3. The usage text in `--help`, and assertions in scripts/test_healthcheck.sh for both orders.
|
||||
|
||||
## Work one phase at a time
|
||||
|
||||
Implement phase N, request its verification, and only then begin phase N+1.
|
||||
|
||||
## Verification policy for this project
|
||||
|
||||
Only two commands may appear on a `- run:` line, exactly:
|
||||
Only two commands may appear on a run: line, exactly:
|
||||
|
||||
- `["bash", "-n", "<path>"]`
|
||||
- `["bash", "scripts/orchestra_e2e_healthcheck.sh"]`
|
||||
- ["bash", "-n", "<path>"]
|
||||
- ["bash", "scripts/orchestra_e2e_healthcheck.sh"]
|
||||
|
||||
Anything else is refused when you seal the plan. `scripts/test_healthcheck.sh` is NOT permitted as an automated command. Update it as a manual step so its expected `--help` text matches, and list that as a manual check.
|
||||
Anything else is refused when you seal the plan. Give every phase at least one automated check from that list and at least one manual check.
|
||||
|
||||
## Research first
|
||||
|
||||
Establish at least three findings with distinct confidence: at least one `fact`, one `inference`, one `assumption`.
|
||||
Establish at least three findings with distinct confidence: at least one fact, one inference, one assumption.
|
||||
|
||||
|
||||
## Quality gate
|
||||
|
||||
bash -n scripts/*.sh && bash scripts/orchestra_e2e_healthcheck.sh
|
||||
Acceptance:
|
||||
- `--list-checks --sorted` prints the ten names alphabetically.
|
||||
- `--list-checks` alone keeps run order.
|
||||
- Every other flag is unchanged.
|
||||
|
||||
## Completion
|
||||
|
||||
|
||||
@@ -1,7 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
readonly VERSION=1.0.0
|
||||
readonly CHECKS=(script-runs)
|
||||
readonly SUCCESS_MESSAGE='OK - all healthchecks passed'
|
||||
readonly ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
||||
|
||||
# Space-separated check names; each has a check_<name> function.
|
||||
CHECKS="repo_root_readable scripts_dir_present healthcheck_executable_bit
|
||||
selftest_present test_healthcheck_present readme_present
|
||||
no_crlf_line_endings no_tabs_in_scripts
|
||||
shebang_is_bash usage_text_mentions_every_flag"
|
||||
|
||||
# name=ok / name=fail pairs, in run order.
|
||||
RESULTS=
|
||||
FAILED=0
|
||||
TOTAL=0
|
||||
|
||||
# record <name> <status>: append one outcome to the results state.
|
||||
record() {
|
||||
RESULTS="$RESULTS $1=$2"
|
||||
TOTAL=$((TOTAL + 1))
|
||||
[ "$2" = ok ] || FAILED=$((FAILED + 1))
|
||||
}
|
||||
|
||||
check_repo_root_readable() { [ -d "$ROOT" ] && [ -r "$ROOT" ]; }
|
||||
check_scripts_dir_present() { [ -d "$ROOT/scripts" ]; }
|
||||
check_healthcheck_executable_bit() { [ -x "$ROOT/scripts/orchestra_e2e_healthcheck.sh" ]; }
|
||||
check_selftest_present() { [ -f "$ROOT/scripts/orchestra_e2e_selftest.sh" ]; }
|
||||
check_test_healthcheck_present() { [ -f "$ROOT/scripts/test_healthcheck.sh" ]; }
|
||||
check_readme_present() { [ -f "$ROOT/README.md" ]; }
|
||||
|
||||
check_no_crlf_line_endings() {
|
||||
[ -d "$ROOT/scripts" ] || return 1
|
||||
! grep -qU $'\r' "$ROOT"/scripts/*.sh "$ROOT/README.md" 2>/dev/null
|
||||
}
|
||||
|
||||
check_no_tabs_in_scripts() {
|
||||
[ -d "$ROOT/scripts" ] || return 1
|
||||
! grep -q "$(printf '\t')" "$ROOT"/scripts/*.sh 2>/dev/null
|
||||
}
|
||||
|
||||
check_shebang_is_bash() {
|
||||
[ -d "$ROOT/scripts" ] || return 1
|
||||
local f
|
||||
for f in "$ROOT"/scripts/*.sh; do
|
||||
[ "$(head -n 1 "$f")" = '#!/usr/bin/env bash' ] || return 1
|
||||
done
|
||||
}
|
||||
|
||||
check_usage_text_mentions_every_flag() {
|
||||
local text flag
|
||||
text=$(usage text)
|
||||
for flag in --quiet --json --help; do
|
||||
case "$text" in
|
||||
*"$flag"*) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# now_us: current time in microseconds. Strips any locale decimal separator.
|
||||
now_us() { printf '%s' "${EPOCHREALTIME//[!0-9]/}"; }
|
||||
|
||||
# elapsed_ms <start-us>: whole milliseconds since <start-us>.
|
||||
elapsed_ms() { printf '%s' "$(( ($(now_us) - $1) / 1000 ))"; }
|
||||
|
||||
# emit <format> <template> [args...]: the single output path.
|
||||
# format: text or json print; none suppresses (--quiet). Explicit argument,
|
||||
@@ -12,27 +73,88 @@ emit() {
|
||||
[ "$format" = none ] || printf "$@"
|
||||
}
|
||||
|
||||
# usage <format>: the --help body. Also the source check_usage_text_mentions_every_flag
|
||||
# reads, so the flag list has exactly one home.
|
||||
usage() {
|
||||
emit "$1" 'Usage: %s [--quiet] [--json] [--summary] [--timing] [--strict] [--list-checks] [--sorted]\n\n' "${0##*/}"
|
||||
emit "$1" 'Healthcheck script for Orchestra E2E tests.\n'
|
||||
emit "$1" 'Exits 0 with a success message if all checks pass.\n'
|
||||
emit "$1" ' --quiet Suppress the success message; still exits 0.\n'
|
||||
emit "$1" ' --json Print a single-line JSON result; still exits 0.\n'
|
||||
emit "$1" ' --help Print this usage text and exit 0.\n'
|
||||
emit "$1" ' --summary Print one line per check before the success message.\n'
|
||||
emit "$1" ' --timing Print elapsed milliseconds for the run.\n'
|
||||
emit "$1" ' --strict Reject an unrecognised flag: exits 2.\n'
|
||||
emit "$1" ' --list-checks Print the name of each check; exits 0.\n'
|
||||
emit "$1" ' --sorted With --list-checks, list the names alphabetically.\n'
|
||||
emit "$1" '\nChecks:\n'
|
||||
local name
|
||||
for name in $CHECKS; do
|
||||
emit "$1" ' %s\n' "$name"
|
||||
done
|
||||
emit "$1" '\nVersion: %s\n' "$VERSION"
|
||||
}
|
||||
|
||||
# sorted_checks: the check names, one per line, in alphabetical order.
|
||||
# LC_ALL=C keeps the order fixed if a name with a digit or capital is added.
|
||||
sorted_checks() {
|
||||
printf '%s\n' $CHECKS | LC_ALL=C sort
|
||||
}
|
||||
|
||||
main() {
|
||||
local start_us
|
||||
start_us=$(now_us)
|
||||
local quiet=
|
||||
local json=
|
||||
local summary=
|
||||
local timing=
|
||||
local strict=
|
||||
local unknown=
|
||||
local list=
|
||||
local sorted=
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = --help ]; then
|
||||
emit text 'Usage: %s [--quiet]\n\n' "${0##*/}"
|
||||
emit text 'Healthcheck script for Orchestra E2E tests.\n'
|
||||
emit text 'Exits 0 with a success message if all checks pass.\n'
|
||||
emit text ' --quiet Suppress the success message; still exits 0.\n'
|
||||
emit text ' --list-checks Print the name of each check; exits 0.\n'
|
||||
emit text 'Version: %s\n' "$VERSION"
|
||||
usage text
|
||||
exit 0
|
||||
elif [ "$arg" = --list-checks ]; then
|
||||
for name in "${CHECKS[@]}"; do
|
||||
emit text '%s\n' "$name"
|
||||
done
|
||||
exit 0
|
||||
list=1
|
||||
elif [ "$arg" = --sorted ]; then
|
||||
sorted=1
|
||||
elif [ "$arg" = --quiet ]; then
|
||||
quiet=1
|
||||
elif [ "$arg" = --json ]; then
|
||||
json=1
|
||||
elif [ "$arg" = --summary ]; then
|
||||
summary=1
|
||||
elif [ "$arg" = --timing ]; then
|
||||
timing=1
|
||||
elif [ "$arg" = --strict ]; then
|
||||
strict=1
|
||||
else
|
||||
[ -n "$unknown" ] || unknown=$arg
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$list" ]; then
|
||||
if [ -n "$sorted" ]; then
|
||||
sorted_checks
|
||||
else
|
||||
printf '%s\n' $CHECKS
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -n "$strict" ] && [ -n "$unknown" ]; then
|
||||
printf 'unknown flag: %s\n' "$unknown" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
local name
|
||||
for name in $CHECKS; do
|
||||
if "check_$name"; then
|
||||
record "$name" ok
|
||||
else
|
||||
record "$name" fail
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -40,12 +162,36 @@ main() {
|
||||
[ -n "$quiet" ] && format=none
|
||||
[ -n "$json" ] && format=json
|
||||
|
||||
if [ "$FAILED" -gt 0 ]; then
|
||||
local entry
|
||||
for entry in $RESULTS; do
|
||||
[ "${entry#*=}" = fail ] && emit "$format" 'FAIL - %s\n' "${entry%=*}"
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local elapsed
|
||||
elapsed=$(elapsed_ms "$start_us")
|
||||
|
||||
if [ "$format" = json ]; then
|
||||
emit "$format" '{"status":"ok","checks":%d}\n' "${#CHECKS[@]}"
|
||||
local timings=
|
||||
[ -n "$timing" ] && timings=",\"timings\":{\"healthchecks\":$elapsed}"
|
||||
emit "$format" '{"status":"ok","checks":%d%s}\n' "$TOTAL" "$timings"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
emit "$format" 'OK - all healthchecks passed\n'
|
||||
if [ -n "$summary" ]; then
|
||||
local entry
|
||||
for entry in $RESULTS; do
|
||||
emit "$format" '%s: %s\n' "${entry%=*}" "${entry#*=}"
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -n "$timing" ]; then
|
||||
emit "$format" 'healthchecks: %sms\n' "$elapsed"
|
||||
fi
|
||||
|
||||
emit "$format" '%s\n' "$SUCCESS_MESSAGE"
|
||||
exit 0
|
||||
}
|
||||
|
||||
|
||||
@@ -34,4 +34,16 @@ case "$jout" in
|
||||
*) fail '--json run stdout prefix: want {"status":' "$jout" ;;
|
||||
esac
|
||||
|
||||
# --strict rejects an unknown flag
|
||||
sout=$(bash "$healthcheck" --strict --nonsense 2>&1)
|
||||
sstatus=$?
|
||||
|
||||
[ "$sstatus" -eq 2 ] || fail "--strict --nonsense exit status: want 2, got $sstatus" "$sout"
|
||||
|
||||
# --strict alone is accepted
|
||||
gout=$(bash "$healthcheck" --strict)
|
||||
gstatus=$?
|
||||
|
||||
[ "$gstatus" -eq 0 ] || fail "--strict run exit status: want 0, got $gstatus" "$gout"
|
||||
|
||||
printf 'selftest: ok\n'
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
|
||||
HC="$(dirname "$0")/orchestra_e2e_healthcheck.sh"
|
||||
OK='OK - all healthchecks passed'
|
||||
USAGE="$(printf 'Usage: orchestra_e2e_healthcheck.sh [--quiet]\n\nHealthcheck script for Orchestra E2E tests.\nExits 0 with a success message if all checks pass.\n --quiet Suppress the success message; still exits 0.\n --list-checks Print the name of each check; exits 0.\nVersion: 1.0.0')"
|
||||
NAMES='repo_root_readable scripts_dir_present healthcheck_executable_bit
|
||||
selftest_present test_healthcheck_present readme_present
|
||||
no_crlf_line_endings no_tabs_in_scripts
|
||||
shebang_is_bash usage_text_mentions_every_flag'
|
||||
USAGE="$(printf 'Usage: orchestra_e2e_healthcheck.sh [--quiet] [--json] [--summary] [--timing] [--strict] [--list-checks] [--sorted]\n\nHealthcheck script for Orchestra E2E tests.\nExits 0 with a success message if all checks pass.\n --quiet Suppress the success message; still exits 0.\n --json Print a single-line JSON result; still exits 0.\n --help Print this usage text and exit 0.\n --summary Print one line per check before the success message.\n --timing Print elapsed milliseconds for the run.\n --strict Reject an unrecognised flag: exits 2.\n --list-checks Print the name of each check; exits 0.\n --sorted With --list-checks, list the names alphabetically.\n\nChecks:\n repo_root_readable\n scripts_dir_present\n healthcheck_executable_bit\n selftest_present\n test_healthcheck_present\n readme_present\n no_crlf_line_endings\n no_tabs_in_scripts\n shebang_is_bash\n usage_text_mentions_every_flag\n\nVersion: 1.0.0')"
|
||||
fails=0
|
||||
|
||||
check() { # check <expected-stdout> <label> [args...]
|
||||
@@ -17,6 +21,18 @@ check() { # check <expected-stdout> <label> [args...]
|
||||
fi
|
||||
}
|
||||
|
||||
contains() { # contains <needle> <label> [args...]
|
||||
local want=$1 label=$2 got status
|
||||
shift 2
|
||||
got=$(bash "$HC" "$@" 2>/dev/null)
|
||||
status=$?
|
||||
if [ "$status" -ne 0 ] || [ "${got#*"$want"}" = "$got" ]; then
|
||||
printf 'FAIL: %s (exit=%s)\n---missing---\n%s\n---in---\n%s\n' \
|
||||
"$label" "$status" "$want" "$got"
|
||||
fails=$((fails + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
check "$OK" 'no args'
|
||||
check '' '--quiet' --quiet
|
||||
check "$USAGE" '--help' --help
|
||||
@@ -24,5 +40,65 @@ check "$USAGE" '--help --quiet' --help --quiet
|
||||
check "$USAGE" '--quiet --help' --quiet --help
|
||||
check "$OK" 'unknown flag ignored' --bogus
|
||||
|
||||
for name in $NAMES; do
|
||||
contains "$name" "--help names $name" --help
|
||||
done
|
||||
|
||||
# All ten names in one run's output.
|
||||
help_out=$(bash "$HC" --help 2>/dev/null)
|
||||
missing=
|
||||
for name in $NAMES; do
|
||||
[ "${help_out#*"$name"}" = "$help_out" ] && missing="$missing $name"
|
||||
done
|
||||
if [ -n "$missing" ]; then
|
||||
printf 'FAIL: --help lists all ten check names\n---missing---\n%s\n' "$missing"
|
||||
fails=$((fails + 1))
|
||||
fi
|
||||
|
||||
# --list-checks prints one name per line and nothing else.
|
||||
listed=$(bash "$HC" --list-checks 2>/dev/null)
|
||||
want_listed=$(printf '%s\n' $NAMES)
|
||||
if [ "$listed" != "$want_listed" ]; then
|
||||
printf 'FAIL: --list-checks\n---got---\n%s\n---want---\n%s\n' "$listed" "$want_listed"
|
||||
fails=$((fails + 1))
|
||||
fi
|
||||
|
||||
# --sorted lists the names alphabetically, in either argument order.
|
||||
want_sorted=$(printf '%s\n' $NAMES | LC_ALL=C sort)
|
||||
for args in '--list-checks --sorted' '--sorted --list-checks'; do
|
||||
sorted_out=$(bash "$HC" $args 2>/dev/null)
|
||||
if [ "$sorted_out" != "$want_sorted" ]; then
|
||||
printf 'FAIL: %s\n---got---\n%s\n---want---\n%s\n' "$args" "$sorted_out" "$want_sorted"
|
||||
fails=$((fails + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# --summary prints one name: outcome line per check, then the success line.
|
||||
summary_out=$(bash "$HC" --summary 2>/dev/null)
|
||||
for name in $NAMES; do
|
||||
case "$summary_out" in
|
||||
*"$name: ok"*) ;;
|
||||
*) printf 'FAIL: --summary omits %s\n' "$name"; fails=$((fails + 1)) ;;
|
||||
esac
|
||||
done
|
||||
case "$summary_out" in
|
||||
*"$OK") ;;
|
||||
*) printf 'FAIL: --summary does not end with the success line\n'; fails=$((fails + 1)) ;;
|
||||
esac
|
||||
|
||||
# --timing prints an elapsed line, and composes with --json and --quiet.
|
||||
contains 'healthchecks:' '--timing' --timing
|
||||
contains '"timings":{"healthchecks":' '--timing --json' --timing --json
|
||||
check '' '--timing --quiet' --timing --quiet
|
||||
|
||||
# --strict rejects an unrecognised flag; on its own it changes nothing.
|
||||
strict_out=$(bash "$HC" --strict --nonsense 2>&1)
|
||||
strict_status=$?
|
||||
if [ "$strict_status" -ne 2 ] || [ "${strict_out#*unknown flag}" = "$strict_out" ]; then
|
||||
printf 'FAIL: --strict --nonsense (exit=%s)\n---got---\n%s\n' "$strict_status" "$strict_out"
|
||||
fails=$((fails + 1))
|
||||
fi
|
||||
check "$OK" '--strict alone' --strict
|
||||
|
||||
[ "$fails" -eq 0 ] || exit 1
|
||||
printf 'all checks passed\n'
|
||||
|
||||
Reference in New Issue
Block a user