Skip to content

Commit a781173

Browse files
author
Lasse Benninga
committed
fix(grader_lib): all check_* functions always return 0 (safe under set -e)
Bare calls to check_no_notimplemented/check_no_relative_imports etc. were causing the script to exit under set -euo pipefail when they returned 1. Removed all 'return 1' from check functions — results go through _grader_details, not exit codes. Verified: scaffold 20/100 fail, solution 100/100 pass.
1 parent fe118bc commit a781173

1 file changed

Lines changed: 22 additions & 34 deletions

File tree

.hyf/grader_lib.sh

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
# Source this at the top of test.sh:
44
# source "$(dirname "$0")/grader_lib.sh"
55
#
6-
# Provides: pass(), fail(), warn(), print_results(), write_score(),
7-
# and a set of common static-analysis checks derived from recurring
8-
# PR review patterns across cohort c55.
6+
# Design rule: every check_* function always returns 0.
7+
# Results are communicated via pass()/fail()/warn() into _grader_details,
8+
# never via exit code. This makes bare calls safe under `set -euo pipefail`.
99

1010
_grader_details=()
1111

@@ -39,8 +39,8 @@ JSON
3939
}
4040

4141
# ── Common static-analysis checks ────────────────────────────────────────────
42-
# Each function: returns 0 on pass, 1 on fail/warn (for caller logic).
43-
# All feedback goes through pass()/fail()/warn() so it appears in print_results.
42+
# All check_* functions always return 0. Findings are recorded via
43+
# pass()/fail()/warn() and printed by print_results().
4444

4545
check_no_print_statements() {
4646
# Usage: check_no_print_statements <dir> [label]
@@ -53,7 +53,6 @@ check_no_print_statements() {
5353
local count
5454
count=$(echo "$found" | wc -l | tr -d ' ')
5555
warn "$label: $count print() call(s) found — use logging.info/warning/error instead (see Week 1 Ch1)"
56-
return 1
5756
fi
5857
return 0
5958
}
@@ -67,7 +66,6 @@ check_no_notimplemented() {
6766
found=$(grep -rn "raise NotImplementedError" "$dir" --include="*.py" 2>/dev/null || true)
6867
if [[ -n "$found" ]]; then
6968
fail "$label: raise NotImplementedError still present — remove stubs before submitting"
70-
return 1
7169
fi
7270
return 0
7371
}
@@ -83,7 +81,6 @@ check_no_relative_imports() {
8381
found=$(grep -rn "^from \." "$dir" --include="*.py" 2>/dev/null || true)
8482
if [[ -n "$found" ]]; then
8583
fail "$label: relative import found (from .module) — use absolute: 'from src.module import x'"
86-
return 1
8784
fi
8885
return 0
8986
}
@@ -95,7 +92,6 @@ check_no_logging_in_utils() {
9592
if [[ ! -f "$file" ]]; then return 0; fi
9693
if grep -qE "logging\.basicConfig|logging\.getLogger" "$file"; then
9794
warn "$file: logging.basicConfig/getLogger found — logging setup belongs in cleaner.py or the entry-point, not in utils"
98-
return 1
9995
fi
10096
return 0
10197
}
@@ -106,7 +102,7 @@ check_gitignore_python() {
106102
local gi="${1:-.gitignore}"
107103
if [[ ! -f "$gi" ]]; then
108104
warn ".gitignore is missing — add one so __pycache__/ and *.pyc are not committed"
109-
return 1
105+
return 0
110106
fi
111107
local ok=true
112108
if ! grep -q "__pycache__" "$gi"; then
@@ -122,36 +118,34 @@ check_gitignore_python() {
122118
ok=false
123119
fi
124120
if [[ "$ok" = true ]]; then pass ".gitignore correctly excludes __pycache__/, *.pyc, and .env"; fi
121+
return 0
125122
}
126123

127124
check_screenshot_is_png() {
128-
# Usage: check_screenshot_is_png <expected_path> [<wrong_ext_glob>]
129-
# Awards full credit for .png, warns (and still credits) for .jpg/.jpeg,
130-
# zero for missing. Matches the pattern flagged in c55 PR reviews.
125+
# Usage: check_screenshot_is_png <expected_path>
126+
# Awards full credit for .png, warns for .jpg/.jpeg, fails if missing.
131127
local expected_png="$1"
132128
local dir
133129
dir="$(dirname "$expected_png")"
134130
local base
135131
base="$(basename "$expected_png" .png)"
136-
137132
if [[ -s "$expected_png" ]]; then
138133
pass "screenshot is $expected_png (.png format ✓)"
139134
return 0
140135
fi
141136
for ext in jpg jpeg; do
142137
if [[ -s "$dir/$base.$ext" ]]; then
143138
warn "screenshot is .$ext but should be .png — rename to $base.png (partial credit still given)"
144-
return 1
139+
return 0
145140
fi
146141
done
147142
fail "screenshot missing: $expected_png not found"
148-
return 2
143+
return 0
149144
}
150145

151146
check_silent_zero_in_except() {
152147
# Usage: check_silent_zero_in_except <file>
153-
# Detects the pattern: try: x = compute() / except: x = 0
154-
# which silently corrupts data instead of skipping or raising.
148+
# Detects: try: x = compute() / except: x = 0 (silent data corruption).
155149
local file="$1"
156150
if [[ ! -f "$file" ]]; then return 0; fi
157151
local found
@@ -170,16 +164,14 @@ for node in ast.walk(tree):
170164
PY
171165
)
172166
if [[ -n "$found" ]]; then
173-
warn "$file: silent 0-assignment in except block — skip the row or raise instead of setting to 0:\n $found"
174-
return 1
167+
warn "$file: silent 0-assignment in except block — skip the row or raise instead:\n $found"
175168
fi
176169
return 0
177170
}
178171

179172
check_exception_logged() {
180173
# Usage: check_exception_logged <dir>
181-
# Warns when except blocks log/print a message but don't include the
182-
# exception variable (e, err, exc), meaning the error type is lost.
174+
# Warns when an except block logs a message but omits the exception variable.
183175
local dir="${1:-.}"
184176
local found
185177
found=$(python3 - "$dir" 2>/dev/null << 'PY'
@@ -197,54 +189,50 @@ for root, _, files in os.walk(sys.argv[1]):
197189
for node in ast.walk(tree):
198190
if not isinstance(node, ast.ExceptHandler):
199191
continue
200-
exc_var = node.name # e.g. "e" in `except ValueError as e`
192+
exc_var = node.name
201193
if not exc_var:
202194
continue
203195
for stmt in node.body:
204196
for call in ast.walk(stmt):
205197
if not isinstance(call, ast.Call):
206198
continue
207-
# Is it a logging.* or print call?
208199
func = call.func
209200
is_log = (isinstance(func, ast.Attribute) and
210201
isinstance(func.value, ast.Name) and
211202
func.value.id == "logging")
212203
is_print = isinstance(func, ast.Name) and func.id == "print"
213204
if not (is_log or is_print):
214205
continue
215-
# Does the call reference the exception variable?
216206
src = ast.unparse(call)
217207
if exc_var not in src:
218-
issues.append(f"{path}:{call.lineno}: log message doesn't include exception variable '{exc_var}' — add it for easier debugging")
208+
issues.append(f"{path}:{call.lineno}: log message doesn't include '{exc_var}' — add it for easier debugging")
219209
if issues:
220-
for i in issues[:3]: # cap at 3 to keep output readable
210+
for i in issues[:3]:
221211
print(i)
222212
PY
223213
)
224214
if [[ -n "$found" ]]; then
225-
warn "exception variable not included in log message (harder to debug):\n $found"
226-
return 1
215+
warn "exception variable missing from log message:\n $found"
227216
fi
228217
return 0
229218
}
230219

231220
check_ruff() {
232221
# Usage: check_ruff <dir> [<select>]
233-
# Runs ruff on <dir> if available; warns on violations.
234-
# Default select: F401 (unused imports), E302 (missing blank lines).
222+
# Runs ruff if available; warns on F401 (unused imports) and E302/E303 (blank lines).
235223
local dir="${1:-.}"
236224
local select="${2:-F401,E302,E303}"
237225
if ! command -v ruff &>/dev/null && ! python3 -m ruff --version &>/dev/null 2>&1; then
238-
return 0 # ruff not installed — skip silently
226+
return 0
239227
fi
240228
local out
241229
out=$(python3 -m ruff check --select="$select" --output-format=text "$dir" 2>/dev/null || true)
242230
if [[ -n "$out" ]]; then
243231
local count
244232
count=$(echo "$out" | grep -c "\.py:" || true)
245233
warn "$dir: ruff found $count style issue(s) (unused imports / missing blank lines) — run 'ruff check $dir' to see details"
246-
return 1
234+
else
235+
pass "$dir: no ruff style issues (F401/E302/E303)"
247236
fi
248-
pass "$dir: no ruff style issues (F401/E302/E303)"
249237
return 0
250238
}

0 commit comments

Comments
 (0)