Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 32 additions & 32 deletions .hyf/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,24 @@ warn() { _grader_details+=("WARN: $1"); }
l1=0
for f in Dockerfile "src/pipeline.py" "tests/test_pipeline.py" "AI_ASSIST.md"; do
if [[ -f "$REPO_ROOT/$f" ]]; then
((l1 += 3))
l1=$((l1 + 3))
else
fail "missing $f"
fi
done
# ci.yml
if ls "$REPO_ROOT/.github/workflows/"*.yml 2>/dev/null | grep -q .; then
((l1 += 2))
l1=$((l1 + 2))
else
fail "missing .github/workflows/*.yml"
fi
# requirements.txt or pyproject.toml
if [[ -f "$REPO_ROOT/requirements.txt" ]] || [[ -f "$REPO_ROOT/pyproject.toml" ]]; then
((l1 += 1))
l1=$((l1 + 1))
else
fail "missing requirements.txt or pyproject.toml"
fi
((score += l1))
score=$((score + l1))
pass "Level 1: required files ($l1/15 pts)"


Expand All @@ -46,7 +46,7 @@ l2=0
df="$REPO_ROOT/Dockerfile"
if [[ -f "$df" ]]; then
if grep -qE "^FROM\s+python:3\.11-slim" "$df"; then
((l2 += 5)); pass "Dockerfile uses python:3.11-slim base image"
l2=$((l2 + 5)); pass "Dockerfile uses python:3.11-slim base image"
else
fail "Dockerfile does not use python:3.11-slim base image"
fi
Expand All @@ -55,18 +55,18 @@ if [[ -f "$df" ]]; then
req_line=$(grep -n "COPY.*requirements" "$df" | head -1 | cut -d: -f1 || echo 0)
src_line=$(grep -n "COPY.*src" "$df" | head -1 | cut -d: -f1 || echo 9999)
if [[ "$req_line" -gt 0 && "$src_line" -lt 9999 && "$req_line" -lt "$src_line" ]]; then
((l2 += 7)); pass "Dockerfile copies requirements before source (cache-friendly)"
l2=$((l2 + 7)); pass "Dockerfile copies requirements before source (cache-friendly)"
else
fail "Dockerfile does not copy requirements before source code"
fi

if grep -qE "^CMD" "$df"; then
((l2 += 3)); pass "Dockerfile has a CMD instruction"
l2=$((l2 + 3)); pass "Dockerfile has a CMD instruction"
else
fail "Dockerfile missing CMD instruction"
fi
fi
((score += l2))
score=$((score + l2))
pass "Level 2: Dockerfile ($l2/15 pts)"

# ── Level 3 (10 pts): unit tests ─────────────────────────────────────────────
Expand All @@ -75,67 +75,67 @@ test_file="$REPO_ROOT/tests/test_pipeline.py"
if [[ -f "$test_file" ]]; then
test_count=$(grep -cE "^[[:space:]]*def test_" "$test_file" || true)
if [[ "$test_count" -ge 2 ]]; then
((l3 += 7)); pass "tests/test_pipeline.py has $test_count test functions (≥2 required)"
l3=$((l3 + 7)); pass "tests/test_pipeline.py has $test_count test functions (≥2 required)"
else
fail "tests/test_pipeline.py has only $test_count test function(s) — at least 2 required"
fi
if ! grep -q "NotImplementedError" "$test_file"; then
((l3 += 3)); pass "tests/test_pipeline.py has no NotImplementedError stubs remaining"
l3=$((l3 + 3)); pass "tests/test_pipeline.py has no NotImplementedError stubs remaining"
else
fail "tests/test_pipeline.py still contains NotImplementedError stubs"
fi
fi
((score += l3))
score=$((score + l3))
pass "Level 3: unit tests ($l3/10 pts)"

# ── Level 4 (10 pts): pinned dependencies ───────────────────────────────────
l4=0
if [[ -f "$REPO_ROOT/requirements.txt" ]]; then
pinned=$(grep -cE "^[a-zA-Z].*==" "$REPO_ROOT/requirements.txt" || true)
if [[ "$pinned" -ge 1 ]]; then
((l4 += 7)); pass "requirements.txt has $pinned pinned package(s)"
l4=$((l4 + 7)); pass "requirements.txt has $pinned pinned package(s)"
else
fail "requirements.txt has no pinned packages (use package==version)"
fi
fi
if [[ -f "$REPO_ROOT/uv.lock" ]]; then
((l4 += 3)); pass "uv.lock present (full dependency tree pinned)"
l4=$((l4 + 3)); pass "uv.lock present (full dependency tree pinned)"
elif [[ "$l4" -ge 7 ]]; then
((l4 += 3)); pass "requirements.txt pins satisfied (no uv.lock needed)"
l4=$((l4 + 3)); pass "requirements.txt pins satisfied (no uv.lock needed)"
fi
((score += l4))
score=$((score + l4))
pass "Level 4: pinned dependencies ($l4/10 pts)"

# ── Level 5 (20 pts): CI workflow ────────────────────────────────────────────
l5=0
ci_file=$(ls "$REPO_ROOT/.github/workflows/"*.yml 2>/dev/null | head -1 || true)
if [[ -n "$ci_file" ]]; then
grep -q "pull_request" "$ci_file" && { ((l5 += 4)); pass "ci.yml triggers on pull_request"; } || fail "ci.yml missing pull_request trigger"
grep -qE '\bmain\b' "$ci_file" && { ((l5 += 4)); pass "ci.yml triggers on push to main"; } || fail "ci.yml missing push to main trigger"
grep -q "ruff check" "$ci_file" && { ((l5 += 3)); pass "ci.yml runs ruff check (lint)"; } || fail "ci.yml missing ruff check step"
grep -q "ruff format" "$ci_file" && { ((l5 += 3)); pass "ci.yml runs ruff format (format check)"; } || fail "ci.yml missing ruff format step"
grep -q "pytest" "$ci_file" && { ((l5 += 3)); pass "ci.yml runs pytest"; } || fail "ci.yml missing pytest step"
grep -q "docker build" "$ci_file" && { ((l5 += 3)); pass "ci.yml runs docker build"; } || fail "ci.yml missing docker build step"
grep -q "pull_request" "$ci_file" && { l5=$((l5 + 4)); pass "ci.yml triggers on pull_request"; } || fail "ci.yml missing pull_request trigger"
grep -qE '\bmain\b' "$ci_file" && { l5=$((l5 + 4)); pass "ci.yml triggers on push to main"; } || fail "ci.yml missing push to main trigger"
grep -q "ruff check" "$ci_file" && { l5=$((l5 + 3)); pass "ci.yml runs ruff check (lint)"; } || fail "ci.yml missing ruff check step"
grep -q "ruff format" "$ci_file" && { l5=$((l5 + 3)); pass "ci.yml runs ruff format (format check)"; } || fail "ci.yml missing ruff format step"
grep -q "pytest" "$ci_file" && { l5=$((l5 + 3)); pass "ci.yml runs pytest"; } || fail "ci.yml missing pytest step"
grep -q "docker build" "$ci_file" && { l5=$((l5 + 3)); pass "ci.yml runs docker build"; } || fail "ci.yml missing docker build step"
fi
((score += l5))
score=$((score + l5))
pass "Level 5: CI workflow ($l5/20 pts)"

# ── Level 6 (15 pts): env-var configuration ──────────────────────────────────
l6=0
py="$REPO_ROOT/src/pipeline.py"
if [[ -f "$py" ]]; then
if grep -qE "os\.(environ|getenv)|from os import (environ|getenv)" "$py"; then
((l6 += 10)); pass "pipeline.py reads config from os.environ/os.getenv"
l6=$((l6 + 10)); pass "pipeline.py reads config from os.environ/os.getenv"
else
fail "pipeline.py does not read from os.environ or os.getenv"
fi
if ! grep -q "NotImplementedError" "$py"; then
((l6 += 5)); pass "pipeline.py has no NotImplementedError stubs remaining"
l6=$((l6 + 5)); pass "pipeline.py has no NotImplementedError stubs remaining"
else
fail "pipeline.py still contains NotImplementedError"
fi
fi
((score += l6))
score=$((score + l6))
pass "Level 6: env-var config ($l6/15 pts)"

# ── Level 7 (10 pts): ACR screenshot ────────────────────────────────────────
Expand All @@ -144,14 +144,14 @@ screenshot="$REPO_ROOT/assets/acr_push_week5.png"
if [[ -f "$screenshot" ]]; then
size=$(wc -c < "$screenshot")
if [[ "$size" -gt 1024 ]]; then
((l7 += 10)); pass "assets/acr_push_week5.png present and non-trivial (${size} bytes)"
l7=$((l7 + 10)); pass "assets/acr_push_week5.png present and non-trivial (${size} bytes)"
else
fail "assets/acr_push_week5.png exists but looks empty (${size} bytes)"
fi
else
fail "assets/acr_push_week5.png missing (Task 7 deliverable)"
fi
((score += l7))
score=$((score + l7))
pass "Level 7: ACR screenshot ($l7/10 pts)"

# ── Level 8 (5 pts): AI_ASSIST.md content ───────────────────────────────────
Expand All @@ -165,22 +165,22 @@ if [[ -f "$ai" ]]; then
has_todo=$(grep -c "^TODO:" "$ai" || true)

if [[ "$has_prompt" -ge 1 && "$has_code" -ge 1 && "$has_changed" -ge 1 ]]; then
((l8 += 3)); pass "AI_ASSIST.md has all three required sections"
l8=$((l8 + 3)); pass "AI_ASSIST.md has all three required sections"
else
fail "AI_ASSIST.md missing one or more required sections"
fi
if [[ "$chars" -gt 500 && "$has_todo" -eq 0 ]]; then
((l8 += 2)); pass "AI_ASSIST.md is filled in (${chars} chars, no TODO placeholders)"
l8=$((l8 + 2)); pass "AI_ASSIST.md is filled in (${chars} chars, no TODO placeholders)"
else
fail "AI_ASSIST.md still contains TODO placeholders or is too short (${chars} chars)"
fi
fi
((score += l8))
score=$((score + l8))
pass "Level 8: AI report ($l8/5 pts)"

# ── Code hygiene (warnings from grader_lib) ──────────────────────────────────
check_no_print_statements "$REPO_ROOT/src" "src/"
check_gitignore_python "$REPO_ROOT/.gitignore"
check_no_print_statements "$REPO_ROOT/src" "src/" || true
check_gitignore_python "$REPO_ROOT/.gitignore" || true

# ── Final result ─────────────────────────────────────────────────────────────
passing_score=60
Expand Down
Loading