mirror of
https://github.com/kuhyx/WUT_Computer_Science.git
synced 2026-11-10 19:07:11 +01:00
A run may no longer change the tree it ran in
Executing coursework has silently overwritten a submitted deliverable twice in
this campaign -- TRAK's outputs/output.png and SPD's results_*.png -- and both
times it was found by a later `git status`, not by the run. `course_main` now
snapshots what git sees under the course root before and after `_run`, and
fails with a diff naming the files if anything moved. Gitignored build output
does not show up there, which is why every runner already writes into build/.
Seen failing before being trusted: a stub whose `_run` appends a newline to
TRAK's config.ini exits 1 and names the file; the same stub without the append
exits 0; and a real `./run.sh run Programming/TRAK` still exits 0 after
rendering, because it writes into build/.
The notebook branch was the specific landmine. `nbconvert --execute --inplace`
rewrites the notebook it just ran, and all four notebooks here are tracked with
their outputs. It now executes to a temp --output-dir. That alone is not
enough, though: nbconvert runs the kernel with its cwd set to the notebook's
own directory, so a cell calling plt.savefig('x.png') still writes beside the
notebook. The tree check above is what actually catches that, which is why it
lives in course_main and applies to every course kind rather than to notebooks.
`_have jupyter` was also the wrong probe. jupyter on this machine is a pipx
shim whose interpreter is gone: `command -v` finds it, and running it exits
127. Both the probe and the runner now use `_works`, which runs `--version`.
`./Programming/twm_4/run.sh --probe` reports "needs jupyter" and exit 78
instead of failing confusingly mid-run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
24f800d15a
commit
f780d023be
@ -29,6 +29,12 @@ readonly COURSE_GATES
|
|||||||
|
|
||||||
_have() { command -v "$1" > /dev/null 2>&1; }
|
_have() { command -v "$1" > /dev/null 2>&1; }
|
||||||
|
|
||||||
|
# `command -v` is true for a dead pipx shim: the file is there and executable,
|
||||||
|
# and only running it shows that the interpreter it points at is gone. That is
|
||||||
|
# what `jupyter` is on this machine, and it turned a clean "blocked: needs
|
||||||
|
# jupyter" into a confusing exec failure. Probe by running, not by looking.
|
||||||
|
_works() { _have "$1" && "$1" --version > /dev/null 2>&1; }
|
||||||
|
|
||||||
# Report "blocked" with the reason, which is the whole point of the status
|
# Report "blocked" with the reason, which is the whole point of the status
|
||||||
# table: "needs X" is useful, "failed" is not.
|
# table: "needs X" is useful, "failed" is not.
|
||||||
_blocked() {
|
_blocked() {
|
||||||
@ -43,6 +49,17 @@ _no_code() {
|
|||||||
|
|
||||||
_runnable() { printf '%s\n' "$1"; exit 0; }
|
_runnable() { printf '%s\n' "$1"; exit 0; }
|
||||||
|
|
||||||
|
# What the course tree looks like to git. Compared before and after a run,
|
||||||
|
# because executing coursework has twice silently overwritten a submitted
|
||||||
|
# deliverable -- TRAK's outputs/output.png and SPD's results_*.png -- and
|
||||||
|
# neither was noticed until a much later `git status`. Gitignored build output
|
||||||
|
# does not show up here, which is why every runner writes into build/.
|
||||||
|
# numstat is included so that re-writing an already-modified file still counts.
|
||||||
|
_tree_state() {
|
||||||
|
git -C "$COURSE_ROOT" status --porcelain -- "$COURSE_ROOT" 2>/dev/null || true
|
||||||
|
git -C "$COURSE_ROOT" diff --numstat -- "$COURSE_ROOT" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
# First tool present wins; otherwise blocked naming all the candidates.
|
# First tool present wins; otherwise blocked naming all the candidates.
|
||||||
_require_any() {
|
_require_any() {
|
||||||
local label="$1"; shift
|
local label="$1"; shift
|
||||||
@ -120,7 +137,17 @@ course_main() {
|
|||||||
printf 'usage: %s [--probe]\n' "$(basename "$0")"
|
printf 'usage: %s [--probe]\n' "$(basename "$0")"
|
||||||
printf ' --probe report runnable/blocked/no-code without running\n'
|
printf ' --probe report runnable/blocked/no-code without running\n'
|
||||||
;;
|
;;
|
||||||
"") _run ;;
|
"")
|
||||||
|
local before after
|
||||||
|
before="$(_tree_state)"
|
||||||
|
_run
|
||||||
|
after="$(_tree_state)"
|
||||||
|
if [[ "$before" != "$after" ]]; then
|
||||||
|
printf 'FAILED: the run changed files under %s\n' "$COURSE_ROOT" >&2
|
||||||
|
diff <(printf '%s\n' "$before") <(printf '%s\n' "$after") >&2 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
*) printf 'unknown option: %s\n' "$1" >&2; return 1 ;;
|
*) printf 'unknown option: %s\n' "$1" >&2; return 1 ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,7 @@ _probe() {
|
|||||||
_runnable "compiles each standalone .cpp"
|
_runnable "compiles each standalone .cpp"
|
||||||
;;
|
;;
|
||||||
notebook)
|
notebook)
|
||||||
_have jupyter || _blocked "needs jupyter"
|
_works jupyter || _blocked "needs jupyter"
|
||||||
# Being on PATH is not the same as working: this machine has a
|
# Being on PATH is not the same as working: this machine has a
|
||||||
# jupyter shim whose pipx venv interpreter is gone, so it fails
|
# jupyter shim whose pipx venv interpreter is gone, so it fails
|
||||||
# with "bad interpreter". Probe by actually invoking it.
|
# with "bad interpreter". Probe by actually invoking it.
|
||||||
|
|||||||
@ -181,11 +181,20 @@ _run() {
|
|||||||
printf 'binaries in %s/build\n' "$COURSE_ROOT"
|
printf 'binaries in %s/build\n' "$COURSE_ROOT"
|
||||||
;;
|
;;
|
||||||
notebook)
|
notebook)
|
||||||
_have jupyter || _blocked "needs jupyter"
|
_works jupyter || _blocked "needs jupyter"
|
||||||
local nb
|
# NOT --inplace: all four notebooks in this repo are tracked WITH
|
||||||
|
# their outputs, so executing in place rewrites a deliverable.
|
||||||
|
# --output-dir sends the executed copy to a temp dir instead.
|
||||||
|
# It does NOT redirect what the notebook's own code writes -- the
|
||||||
|
# kernel's cwd is still the notebook's directory -- so the
|
||||||
|
# before/after tree check in course_main is what actually catches
|
||||||
|
# a notebook that saves a figure beside itself.
|
||||||
|
local nb nbtmp
|
||||||
|
nbtmp="$(mktemp -d)"
|
||||||
while IFS= read -r nb; do
|
while IFS= read -r nb; do
|
||||||
jupyter nbconvert --to notebook --execute --inplace "$nb"
|
jupyter nbconvert --to notebook --execute --output-dir "$nbtmp" "$nb"
|
||||||
done < <(find "$COURSE_ROOT" -name '*.ipynb' -not -path '*/.ipynb_checkpoints/*' | sort)
|
done < <(find "$COURSE_ROOT" -name '*.ipynb' -not -path '*/.ipynb_checkpoints/*' | sort)
|
||||||
|
rm -rf "$nbtmp"
|
||||||
;;
|
;;
|
||||||
matlab)
|
matlab)
|
||||||
local tool
|
local tool
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user