If pytest reports 0 passed, 0 failed, 0 skipped, treat it as a failure. Something is wrong if no tests ran at all. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
152 lines
4.5 KiB
Bash
Executable File
152 lines
4.5 KiB
Bash
Executable File
#!/bin/bash
|
|
# TSHARPS CI — External Test Runner
|
|
# Runs tests, feature manifests, and package checks against TSHARPS worktrees.
|
|
# READ-ONLY — never writes, commits, or pushes to TSHARPS.
|
|
# Same checks for ALL branches — no branch-specific logic.
|
|
|
|
set -o pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
CONFIG="$SCRIPT_DIR/ci-config.json"
|
|
BRANCH="$1"
|
|
COMMIT="$2"
|
|
ACTOR="${3:-unknown}"
|
|
START_TIME=$(date +%s)
|
|
|
|
# Load config
|
|
TIMEOUT=$(python3 -c "import json; print(json.load(open('$CONFIG')).get('test_timeout', 120))")
|
|
WORKTREE=$(python3 -c "import json; b=json.load(open('$CONFIG'))['branches']; print(b.get('$BRANCH',{}).get('worktree',''))")
|
|
|
|
if [ -z "$WORKTREE" ] || [ ! -d "$WORKTREE" ]; then
|
|
echo "ERROR: Unknown branch '$BRANCH' or worktree not found"
|
|
bash "$SCRIPT_DIR/ci-notify.sh" "$BRANCH" "$COMMIT" "error" "Unknown branch or missing worktree" "" "0s" "$ACTOR"
|
|
exit 1
|
|
fi
|
|
|
|
# Prevent concurrent runs on the same branch
|
|
LOCKFILE="/tmp/tsharps-ci-${BRANCH}.lock"
|
|
exec 200>"$LOCKFILE"
|
|
if ! flock -n 200; then
|
|
echo "SKIP: CI already running for branch $BRANCH"
|
|
exit 0
|
|
fi
|
|
|
|
echo "=== TSHARPS CI Runner ==="
|
|
echo "Branch: $BRANCH"
|
|
echo "Commit: $COMMIT"
|
|
echo "Worktree: $WORKTREE"
|
|
echo "Timeout: ${TIMEOUT}s"
|
|
echo "========================="
|
|
|
|
PASS_COUNT=0
|
|
FAIL_COUNT=0
|
|
SKIP_COUNT=0
|
|
ERROR_COUNT=0
|
|
FEATURES_RESULT=""
|
|
OVERALL="pass"
|
|
|
|
# ─── Step 1: Feature Manifest Check ───
|
|
echo ""
|
|
echo "--- Feature Manifest Check ---"
|
|
if [ -f "$WORKTREE/ops/verify-features.py" ] && [ -d "$WORKTREE/.features" ]; then
|
|
FEATURE_OUTPUT=$(cd "$WORKTREE" && python3 ops/verify-features.py --verbose 2>&1)
|
|
FEATURE_EXIT=$?
|
|
FEATURES_RESULT=$(echo "$FEATURE_OUTPUT" | grep "RESULT:" | tail -1)
|
|
if [ -z "$FEATURES_RESULT" ]; then
|
|
FEATURES_RESULT=$(echo "$FEATURE_OUTPUT" | tail -1)
|
|
fi
|
|
echo "$FEATURE_OUTPUT"
|
|
if [ $FEATURE_EXIT -ne 0 ]; then
|
|
OVERALL="fail"
|
|
echo "FEATURE CHECK FAILED"
|
|
fi
|
|
else
|
|
FEATURES_RESULT="No manifests found"
|
|
echo "No feature manifests — skipping"
|
|
fi
|
|
|
|
# ─── Step 2: Run Test Suite ───
|
|
echo ""
|
|
echo "--- Test Suite ---"
|
|
PYTHON="$WORKTREE/.venv/bin/python3"
|
|
if [ ! -f "$PYTHON" ]; then
|
|
PYTHON="python3"
|
|
fi
|
|
|
|
TIMEOUT_FLAG=""
|
|
if $PYTHON -c "import pytest_timeout" 2>/dev/null; then
|
|
TIMEOUT_FLAG="--timeout=$TIMEOUT"
|
|
fi
|
|
|
|
TEST_OUTPUT=$($PYTHON -m pytest "$WORKTREE/backend/tests/" --tb=line -q $TIMEOUT_FLAG 2>&1)
|
|
TEST_EXIT=$?
|
|
|
|
PASS_COUNT=$(echo "$TEST_OUTPUT" | grep -oP '\d+ passed' | grep -oP '\d+' || echo 0)
|
|
FAIL_COUNT=$(echo "$TEST_OUTPUT" | grep -oP '\d+ failed' | grep -oP '\d+' || echo 0)
|
|
SKIP_COUNT=$(echo "$TEST_OUTPUT" | grep -oP '\d+ skipped' | grep -oP '\d+' || echo 0)
|
|
ERROR_COUNT=$(echo "$TEST_OUTPUT" | grep -oP '\d+ error' | grep -oP '\d+' || echo 0)
|
|
|
|
if [ "$TEST_EXIT" -ne 0 ] && [ "$PASS_COUNT" = "0" ] && [ "$FAIL_COUNT" = "0" ]; then
|
|
ERROR_COUNT=1
|
|
echo "WARNING: pytest exited with code $TEST_EXIT but reported no results — likely a collection error"
|
|
echo "$TEST_OUTPUT" | tail -5
|
|
fi
|
|
|
|
if [ "$PASS_COUNT" = "0" ] && [ "$FAIL_COUNT" = "0" ] && [ "$SKIP_COUNT" = "0" ]; then
|
|
OVERALL="fail"
|
|
echo "FAIL: 0 tests ran — something is wrong"
|
|
fi
|
|
|
|
echo "Tests: $PASS_COUNT passed, $FAIL_COUNT failed, $SKIP_COUNT skipped, $ERROR_COUNT errors"
|
|
|
|
if [ "$FAIL_COUNT" != "0" ] && [ "$FAIL_COUNT" != "" ]; then
|
|
OVERALL="fail"
|
|
echo "TESTS FAILED"
|
|
fi
|
|
if [ "$ERROR_COUNT" != "0" ] && [ "$ERROR_COUNT" != "" ]; then
|
|
OVERALL="fail"
|
|
echo "TEST COLLECTION ERRORS"
|
|
fi
|
|
|
|
# ─── Step 3: Package Check ───
|
|
echo ""
|
|
echo "--- Package Check ---"
|
|
PKG_OUTPUT=$($PYTHON -c "
|
|
import sys
|
|
required = ['fastapi','uvicorn','sqlalchemy','psycopg2','bcrypt','jwt','pandas','openpyxl','pydantic','ortools','astral','requests','dotenv','httpx']
|
|
missing = []
|
|
for mod in required:
|
|
try: __import__(mod)
|
|
except ImportError: missing.append(mod)
|
|
if missing:
|
|
print(f'FAIL: {len(missing)} missing: {missing}')
|
|
sys.exit(1)
|
|
print(f'OK: All {len(required)} packages verified')
|
|
" 2>&1)
|
|
PKG_EXIT=$?
|
|
echo "$PKG_OUTPUT"
|
|
if [ $PKG_EXIT -ne 0 ]; then
|
|
OVERALL="fail"
|
|
fi
|
|
|
|
# ─── Results ───
|
|
END_TIME=$(date +%s)
|
|
DURATION=$((END_TIME - START_TIME))
|
|
SUMMARY="${PASS_COUNT} passed, ${FAIL_COUNT} failed, ${SKIP_COUNT} skipped"
|
|
|
|
echo ""
|
|
echo "========================="
|
|
echo "Result: $OVERALL"
|
|
echo "Summary: $SUMMARY"
|
|
echo "Features: $FEATURES_RESULT"
|
|
echo "Duration: ${DURATION}s"
|
|
echo "========================="
|
|
|
|
# ─── Send Notification ───
|
|
bash "$SCRIPT_DIR/ci-notify.sh" \
|
|
"$BRANCH" "$COMMIT" "$OVERALL" "$SUMMARY" "$FEATURES_RESULT" "${DURATION}s" "$ACTOR"
|
|
|
|
# Release lock
|
|
flock -u 200
|
|
exit 0
|