90 lines
2.7 KiB
Bash
Executable File
90 lines
2.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# validate-tdd.sh
|
|
# Guard that production code changes are accompanied by tests.
|
|
set -euo pipefail
|
|
|
|
BASE_REF="${1:-origin/main}"
|
|
|
|
if ! git rev-parse --verify "$BASE_REF" >/dev/null 2>&1; then
|
|
BASE_REF="HEAD~1"
|
|
fi
|
|
|
|
CHANGED_FILES=()
|
|
while IFS= read -r line; do
|
|
[[ -n "$line" ]] && CHANGED_FILES+=("$line")
|
|
done < <(git diff --name-only "$BASE_REF"...HEAD)
|
|
|
|
if [[ ${#CHANGED_FILES[@]} -eq 0 ]]; then
|
|
echo "[validate-tdd] No changed files."
|
|
exit 0
|
|
fi
|
|
|
|
is_production_file() {
|
|
local f="$1"
|
|
[[ "$f" == shared/src/commonMain/* ]] && return 0
|
|
[[ "$f" == androidApp/src/main/* ]] && return 0
|
|
[[ "$f" == iosApp/iosApp/* ]] && [[ "$f" != iosApp/iosAppUITests/* ]] && [[ "$f" != iosApp/iosAppTests/* ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
is_test_file() {
|
|
local f="$1"
|
|
[[ "$f" == shared/src/commonTest/* ]] && return 0
|
|
[[ "$f" == shared/src/jvmTest/* ]] && return 0
|
|
[[ "$f" == androidApp/src/androidTest/* ]] && return 0
|
|
[[ "$f" == androidApp/src/test/* ]] && return 0
|
|
[[ "$f" == iosApp/iosAppUITests/* ]] && return 0
|
|
[[ "$f" == iosApp/iosAppTests/* ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
PROD_COUNT=0
|
|
TEST_COUNT=0
|
|
for file in "${CHANGED_FILES[@]}"; do
|
|
if is_production_file "$file"; then
|
|
PROD_COUNT=$((PROD_COUNT + 1))
|
|
fi
|
|
if is_test_file "$file"; then
|
|
TEST_COUNT=$((TEST_COUNT + 1))
|
|
fi
|
|
done
|
|
|
|
if [[ "$PROD_COUNT" -gt 0 && "$TEST_COUNT" -eq 0 ]]; then
|
|
echo "[validate-tdd] Failing: production code changed without matching test updates."
|
|
echo "[validate-tdd] Production files changed: $PROD_COUNT"
|
|
exit 1
|
|
fi
|
|
|
|
CHANGED_TEST_FILES=()
|
|
TEST_PATH_REGEX='^(shared/src/(commonTest|jvmTest)/|androidApp/src/(androidTest|test)/|iosApp/iosApp(UI)?Tests/)'
|
|
while IFS= read -r line; do
|
|
[[ -n "$line" ]] && CHANGED_TEST_FILES+=("$line")
|
|
done < <(
|
|
if command -v rg >/dev/null 2>&1; then
|
|
printf '%s\n' "${CHANGED_FILES[@]}" | rg "$TEST_PATH_REGEX" || true
|
|
else
|
|
printf '%s\n' "${CHANGED_FILES[@]}" | grep -E "$TEST_PATH_REGEX" || true
|
|
fi
|
|
)
|
|
for test_file in "${CHANGED_TEST_FILES[@]:-}"; do
|
|
if [[ -f "$test_file" ]]; then
|
|
if command -v rg >/dev/null 2>&1; then
|
|
if rg -q 'catch[[:space:]]*\([[:space:]]*AssertionError|XCTExpectFailure|@Ignore|@Disabled' "$test_file"; then
|
|
echo "[validate-tdd] Failing: potential weak assertion/skip anti-pattern in $test_file"
|
|
exit 1
|
|
fi
|
|
else
|
|
if grep -Eq 'catch[[:space:]]*\([[:space:]]*AssertionError|XCTExpectFailure|@Ignore|@Disabled' "$test_file"; then
|
|
echo "[validate-tdd] Failing: potential weak assertion/skip anti-pattern in $test_file"
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [[ "${FORCE_AUDIT_GATES:-0}" == "1" ]]; then
|
|
echo "[validate-tdd] FORCE_AUDIT_GATES enabled."
|
|
fi
|
|
|
|
echo "[validate-tdd] PASS ($BASE_REF...HEAD)"
|