Add git-status-report: recursive git repo scanner with status reporting
Scans a directory tree for git repos and produces a concise report showing uncommitted changes (with +/- character counts) and ahead/behind status per remote. Supports --dryrun to preview discovered repos without running checks. Includes OpenSpec, implementation, and 26-assertion test suite. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
241
tests/test-git-status-report.sh
Executable file
241
tests/test-git-status-report.sh
Executable file
@@ -0,0 +1,241 @@
|
||||
#!/usr/bin/env bash
|
||||
# Test script for git-status-report using --dryrun and live checks
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
SCRIPT="$REPO_ROOT/scripts/git-status-report"
|
||||
PASS=0
|
||||
FAIL=0
|
||||
TMPDIR=""
|
||||
|
||||
# --- Colours ---
|
||||
RED='\033[31m'
|
||||
GREEN='\033[32m'
|
||||
RESET='\033[0m'
|
||||
|
||||
cleanup() {
|
||||
[[ -n "$TMPDIR" && -d "$TMPDIR" ]] && rm -rf "$TMPDIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
setup_tmpdir() {
|
||||
TMPDIR=$(mktemp -d)
|
||||
}
|
||||
|
||||
pass() {
|
||||
printf " ${GREEN}✓${RESET} %s\n" "$1"
|
||||
PASS=$((PASS + 1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf " ${RED}✗${RESET} %s\n" "$1"
|
||||
[[ -n "${2:-}" ]] && printf " %s\n" "$2"
|
||||
FAIL=$((FAIL + 1))
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
local output="$1" expected="$2" label="$3"
|
||||
if echo "$output" | grep -qF -- "$expected"; then
|
||||
pass "$label"
|
||||
else
|
||||
fail "$label" "Expected to find: $expected"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_contains() {
|
||||
local output="$1" unexpected="$2" label="$3"
|
||||
if echo "$output" | grep -qF -- "$unexpected"; then
|
||||
fail "$label" "Did not expect to find: $unexpected"
|
||||
else
|
||||
pass "$label"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_exit_code() {
|
||||
local actual="$1" expected="$2" label="$3"
|
||||
if [[ "$actual" -eq "$expected" ]]; then
|
||||
pass "$label"
|
||||
else
|
||||
fail "$label" "Expected exit $expected, got $actual"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
echo "=== git-status-report tests ==="
|
||||
echo ""
|
||||
|
||||
# --- Test: --help flag ---
|
||||
echo "-- help flag --"
|
||||
output=$(bash "$SCRIPT" --help 2>&1) || true
|
||||
assert_contains "$output" "Usage:" "--help shows usage"
|
||||
assert_contains "$output" "--dryrun" "--help mentions dryrun"
|
||||
|
||||
# --- Test: no repos found ---
|
||||
echo "-- no repos found --"
|
||||
setup_tmpdir
|
||||
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) || true
|
||||
assert_contains "$output" "No git repositories found" "empty dir reports no repos"
|
||||
|
||||
# --- Test: dryrun discovers repos ---
|
||||
echo "-- dryrun discovery --"
|
||||
setup_tmpdir
|
||||
mkdir -p "$TMPDIR/repo-a"
|
||||
git -C "$TMPDIR/repo-a" init -q
|
||||
mkdir -p "$TMPDIR/subdir/repo-b"
|
||||
git -C "$TMPDIR/subdir/repo-b" init -q
|
||||
|
||||
output=$(bash "$SCRIPT" --dryrun "$TMPDIR" 2>&1)
|
||||
rc=$?
|
||||
assert_exit_code "$rc" 0 "dryrun exits 0"
|
||||
assert_contains "$output" "[dryrun] Would check 2 repositories:" "dryrun header with count"
|
||||
assert_contains "$output" "repo-a" "dryrun lists repo-a"
|
||||
assert_contains "$output" "repo-b" "dryrun lists repo-b"
|
||||
|
||||
# --- Test: dryrun with -n short flag ---
|
||||
echo "-- dryrun short flag --"
|
||||
output=$(bash "$SCRIPT" -n "$TMPDIR" 2>&1)
|
||||
assert_contains "$output" "[dryrun]" "-n flag works as dryrun"
|
||||
|
||||
# --- Test: clean repo ---
|
||||
echo "-- clean repo --"
|
||||
setup_tmpdir
|
||||
mkdir -p "$TMPDIR/clean-repo"
|
||||
git -C "$TMPDIR/clean-repo" init -q
|
||||
git -C "$TMPDIR/clean-repo" commit --allow-empty -m "init" -q
|
||||
|
||||
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1)
|
||||
rc=$?
|
||||
assert_exit_code "$rc" 0 "clean repo exits 0"
|
||||
assert_contains "$output" "clean and in sync" "clean repo shows clean message"
|
||||
|
||||
# --- Test: dirty repo with uncommitted changes ---
|
||||
echo "-- dirty repo --"
|
||||
setup_tmpdir
|
||||
mkdir -p "$TMPDIR/dirty-repo"
|
||||
git -C "$TMPDIR/dirty-repo" init -q
|
||||
echo "hello world" > "$TMPDIR/dirty-repo/file.txt"
|
||||
git -C "$TMPDIR/dirty-repo" add file.txt
|
||||
git -C "$TMPDIR/dirty-repo" commit -m "init" -q
|
||||
echo "hello world modified with extra content" > "$TMPDIR/dirty-repo/file.txt"
|
||||
|
||||
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) && rc=$? || rc=$?
|
||||
assert_exit_code "$rc" 1 "dirty repo exits 1"
|
||||
assert_contains "$output" "dirty: dirty-repo" "dirty repo header shown"
|
||||
assert_contains "$output" "file.txt" "changed file listed"
|
||||
|
||||
# --- Test: untracked files ---
|
||||
echo "-- untracked files --"
|
||||
setup_tmpdir
|
||||
mkdir -p "$TMPDIR/untracked-repo"
|
||||
git -C "$TMPDIR/untracked-repo" init -q
|
||||
git -C "$TMPDIR/untracked-repo" commit --allow-empty -m "init" -q
|
||||
echo "new file content" > "$TMPDIR/untracked-repo/newfile.txt"
|
||||
|
||||
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) && rc=$? || rc=$?
|
||||
assert_exit_code "$rc" 1 "untracked file makes repo dirty"
|
||||
assert_contains "$output" "newfile.txt" "untracked file listed"
|
||||
assert_contains "$output" "??" "untracked status shown"
|
||||
|
||||
# --- Test: deleted files ---
|
||||
echo "-- deleted files --"
|
||||
setup_tmpdir
|
||||
mkdir -p "$TMPDIR/del-repo"
|
||||
git -C "$TMPDIR/del-repo" init -q
|
||||
echo "content to delete" > "$TMPDIR/del-repo/gone.txt"
|
||||
git -C "$TMPDIR/del-repo" add gone.txt
|
||||
git -C "$TMPDIR/del-repo" commit -m "init" -q
|
||||
rm "$TMPDIR/del-repo/gone.txt"
|
||||
|
||||
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) || true
|
||||
assert_contains "$output" "gone.txt" "deleted file listed"
|
||||
|
||||
# --- Test: repo with no remotes ---
|
||||
echo "-- no remotes --"
|
||||
setup_tmpdir
|
||||
mkdir -p "$TMPDIR/no-remote"
|
||||
git -C "$TMPDIR/no-remote" init -q
|
||||
echo "change" > "$TMPDIR/no-remote/file.txt"
|
||||
git -C "$TMPDIR/no-remote" add file.txt
|
||||
git -C "$TMPDIR/no-remote" commit -m "init" -q
|
||||
echo "modified" > "$TMPDIR/no-remote/file.txt"
|
||||
|
||||
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) || true
|
||||
assert_contains "$output" "no remotes" "no remotes noted on dirty repo"
|
||||
|
||||
# --- Test: ahead of remote ---
|
||||
echo "-- ahead of remote --"
|
||||
setup_tmpdir
|
||||
# Create a bare "remote"
|
||||
git init -q --bare "$TMPDIR/remote.git"
|
||||
mkdir -p "$TMPDIR/ahead-repo"
|
||||
git -C "$TMPDIR/ahead-repo" init -q
|
||||
git -C "$TMPDIR/ahead-repo" remote add origin "$TMPDIR/remote.git"
|
||||
echo "first" > "$TMPDIR/ahead-repo/file.txt"
|
||||
git -C "$TMPDIR/ahead-repo" add file.txt
|
||||
git -C "$TMPDIR/ahead-repo" commit -m "first" -q
|
||||
git -C "$TMPDIR/ahead-repo" push -u origin main -q 2>/dev/null || git -C "$TMPDIR/ahead-repo" push -u origin master -q 2>/dev/null
|
||||
# Make a local-only commit
|
||||
echo "second" > "$TMPDIR/ahead-repo/file.txt"
|
||||
git -C "$TMPDIR/ahead-repo" add file.txt
|
||||
git -C "$TMPDIR/ahead-repo" commit -m "second" -q
|
||||
|
||||
output=$(bash "$SCRIPT" "$TMPDIR/ahead-repo" 2>&1) || true
|
||||
assert_contains "$output" "1 ahead" "ahead count shown"
|
||||
|
||||
# --- Test: mixed clean and dirty ---
|
||||
echo "-- mixed repos --"
|
||||
setup_tmpdir
|
||||
mkdir -p "$TMPDIR/clean"
|
||||
git -C "$TMPDIR/clean" init -q
|
||||
git -C "$TMPDIR/clean" commit --allow-empty -m "init" -q
|
||||
|
||||
mkdir -p "$TMPDIR/dirty"
|
||||
git -C "$TMPDIR/dirty" init -q
|
||||
echo "content" > "$TMPDIR/dirty/file.txt"
|
||||
git -C "$TMPDIR/dirty" add file.txt
|
||||
git -C "$TMPDIR/dirty" commit -m "init" -q
|
||||
echo "changed" > "$TMPDIR/dirty/file.txt"
|
||||
|
||||
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1) || true
|
||||
assert_contains "$output" "Scanned 2 repositories" "scanned count correct"
|
||||
assert_contains "$output" "dirty: dirty" "dirty repo listed"
|
||||
assert_contains "$output" "1 repositories are clean" "clean count shown"
|
||||
|
||||
# --- Test: nested repos not double-counted ---
|
||||
echo "-- nested repos --"
|
||||
setup_tmpdir
|
||||
mkdir -p "$TMPDIR/outer"
|
||||
git -C "$TMPDIR/outer" init -q
|
||||
git -C "$TMPDIR/outer" commit --allow-empty -m "init" -q
|
||||
mkdir -p "$TMPDIR/outer/inner"
|
||||
git -C "$TMPDIR/outer/inner" init -q
|
||||
git -C "$TMPDIR/outer/inner" commit --allow-empty -m "init" -q
|
||||
|
||||
output=$(bash "$SCRIPT" --dryrun "$TMPDIR" 2>&1)
|
||||
assert_contains "$output" "Would check 1 repositories:" "nested repo filtered out"
|
||||
assert_contains "$output" "outer" "outer repo found"
|
||||
assert_not_contains "$output" "inner" "inner repo excluded"
|
||||
|
||||
# --- Test: permission denied ---
|
||||
echo "-- permission denied --"
|
||||
setup_tmpdir
|
||||
mkdir -p "$TMPDIR/accessible"
|
||||
git -C "$TMPDIR/accessible" init -q
|
||||
git -C "$TMPDIR/accessible" commit --allow-empty -m "init" -q
|
||||
mkdir -p "$TMPDIR/blocked"
|
||||
chmod 000 "$TMPDIR/blocked"
|
||||
|
||||
output=$(bash "$SCRIPT" "$TMPDIR" 2>&1)
|
||||
rc=$?
|
||||
# Should still find the accessible repo
|
||||
assert_contains "$output" "Scanned 1 repositories" "survives permission denied"
|
||||
chmod 755 "$TMPDIR/blocked" # Cleanup
|
||||
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "Results: $PASS passed, $FAIL failed"
|
||||
if [[ $FAIL -gt 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
printf "${GREEN}All tests passed.${RESET}\n"
|
||||
Reference in New Issue
Block a user