From 8c9d700b16db5c99783da06dd84c1f5bea6bec92 Mon Sep 17 00:00:00 2001 From: Josh Wegener Date: Mon, 30 Mar 2026 11:43:25 -0600 Subject: [PATCH] Add script regression coverage --- .github/workflows/ci.yml | 3 + download-model.sh | 10 +-- tests/run.sh | 10 +++ tests/test-download-model.sh | 128 +++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 4 deletions(-) create mode 100755 tests/run.sh create mode 100755 tests/test-download-model.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a688531..e4d9d74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,9 @@ jobs: - name: Install CMake run: command -v cmake >/dev/null || brew install cmake + - name: Run script regressions + run: ./tests/run.sh + - name: Download bundled Whisper model run: ./download-model.sh ggml-tiny.en.bin diff --git a/download-model.sh b/download-model.sh index 2e186bc..06855f9 100755 --- a/download-model.sh +++ b/download-model.sh @@ -2,10 +2,12 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$SCRIPT_DIR/model-checksums.sh" +MODEL_CHECKSUMS_FILE="${MODEL_CHECKSUMS_FILE:-$SCRIPT_DIR/model-checksums.sh}" +source "$MODEL_CHECKSUMS_FILE" -MODEL_DIR="$HOME/.yell/models" -BASE_URL="https://huggingface.co/ggerganov/whisper.cpp/resolve/main" +MODEL_DIR="${MODEL_DIR:-$HOME/.yell/models}" +BASE_URL="${BASE_URL:-https://huggingface.co/ggerganov/whisper.cpp/resolve/main}" +CURL_BIN="${CURL_BIN:-curl}" DEFAULT_MODELS=( "ggml-tiny.en.bin" "ggml-base.en.bin" @@ -46,7 +48,7 @@ download_if_missing() { if [ -t 2 ]; then curl_progress="--progress-bar" fi - curl -fL --retry 3 "$curl_progress" -o "$tmp_path" "$BASE_URL/$name" + "$CURL_BIN" -fL --retry 3 "$curl_progress" -o "$tmp_path" "$BASE_URL/$name" if ! model_checksum_matches "$tmp_path" "$name"; then rm -f "$tmp_path" echo "$name failed checksum validation after download (expected $expected_checksum)." >&2 diff --git a/tests/run.sh b/tests/run.sh new file mode 100755 index 0000000..23d245d --- /dev/null +++ b/tests/run.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +for test_script in "$SCRIPT_DIR"/test-*.sh; do + [ -f "$test_script" ] || continue + echo "Running $(basename "$test_script")..." + "$test_script" +done diff --git a/tests/test-download-model.sh b/tests/test-download-model.sh new file mode 100755 index 0000000..3bcd8c4 --- /dev/null +++ b/tests/test-download-model.sh @@ -0,0 +1,128 @@ +#!/bin/bash +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +fail() { + echo "FAIL: $*" >&2 + exit 1 +} + +assert_file_matches() { + local actual="$1" + local expected="$2" + cmp -s "$actual" "$expected" || fail "$actual did not match $expected" +} + +assert_equals() { + local expected="$1" + local actual="$2" + [ "$expected" = "$actual" ] || fail "expected '$expected', got '$actual'" +} + +FIXTURE_PATH="$TMP_DIR/fixture.bin" +printf 'fixture whisper model\n' > "$FIXTURE_PATH" +FIXTURE_SHA="$(shasum -a 256 "$FIXTURE_PATH" | awk '{print $1}')" + +cat > "$TMP_DIR/model-checksums.sh" < "$TMP_DIR/fake-curl.sh" <<'EOF' +#!/bin/bash +set -euo pipefail + +out="" +url="" +while [ "$#" -gt 0 ]; do + case "$1" in + -fL|-Lf) + shift + ;; + -o|--output) + out="$2" + shift 2 + ;; + --retry) + shift 2 + ;; + -f|-L|--no-progress-meter|--progress-bar) + shift + ;; + *) + url="$1" + shift + ;; + esac +done + +[ -n "$out" ] || exit 1 +count=0 +if [ -f "$TEST_CURL_COUNT_FILE" ]; then + count="$(cat "$TEST_CURL_COUNT_FILE")" +fi +printf '%s' "$((count + 1))" > "$TEST_CURL_COUNT_FILE" +printf '%s\n' "$url" > "$TEST_CURL_URL_FILE" +cp "$TEST_FIXTURE_PATH" "$out" +EOF +chmod +x "$TMP_DIR/fake-curl.sh" + +MODEL_DIR="$TMP_DIR/models" +COMMON_ENV=( + "MODEL_DIR=$MODEL_DIR" + "MODEL_CHECKSUMS_FILE=$TMP_DIR/model-checksums.sh" + "CURL_BIN=$TMP_DIR/fake-curl.sh" + "BASE_URL=https://example.invalid/models" + "TEST_FIXTURE_PATH=$FIXTURE_PATH" + "TEST_CURL_COUNT_FILE=$CURL_COUNT_FILE" + "TEST_CURL_URL_FILE=$TMP_DIR/curl-url.txt" +) + +env "${COMMON_ENV[@]}" "$REPO_DIR/download-model.sh" ggml-tiny.en.bin +assert_file_matches "$MODEL_DIR/ggml-tiny.en.bin" "$FIXTURE_PATH" +assert_equals "1" "$(cat "$CURL_COUNT_FILE")" +assert_equals "https://example.invalid/models/ggml-tiny.en.bin" "$(cat "$TMP_DIR/curl-url.txt")" + +env "${COMMON_ENV[@]}" "$REPO_DIR/download-model.sh" ggml-tiny.en.bin +assert_equals "1" "$(cat "$CURL_COUNT_FILE")" + +printf 'corrupt cache\n' > "$MODEL_DIR/ggml-tiny.en.bin" +env "${COMMON_ENV[@]}" "$REPO_DIR/download-model.sh" ggml-tiny.en.bin +assert_file_matches "$MODEL_DIR/ggml-tiny.en.bin" "$FIXTURE_PATH" +assert_equals "2" "$(cat "$CURL_COUNT_FILE")" + +UNKNOWN_PATH="$MODEL_DIR/ggml-large.bin" +printf 'keep me\n' > "$UNKNOWN_PATH" +env "${COMMON_ENV[@]}" "$REPO_DIR/download-model.sh" ggml-large.bin +assert_file_matches "$UNKNOWN_PATH" <(printf 'keep me\n') + +set +e +unknown_output="$(env "${COMMON_ENV[@]}" "$REPO_DIR/download-model.sh" ggml-large-v3.bin 2>&1)" +unknown_status=$? +set -e +assert_equals "1" "$unknown_status" +printf '%s' "$unknown_output" | grep -q "No checksum is defined for ggml-large-v3.bin" || fail "missing checksum error for unknown model"