Skip to content

Load data.json in the grader images and add plot-serializer - #28

Merged
eliotwrobson merged 6 commits into
mainfrom
claude/data-json-plot-serializer
Nov 5, 2026
Merged

eliotwrobson merged 6 commits into
mainfrom
claude/data-json-plot-serializer

Conversation

@eliotwrobson

@eliotwrobson eliotwrobson commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Two grader image fixes, split out of #27. The query_setup work stays there.

Changes

Load PrairieLearn's data.json. The data_json fixture only looked next to the test file, so it never found /grade/data/data.json. Tests using data_json["params"] failed, and correct submissions scored 0.

  • run.sh now passes --data-json-path=/grade/data/data.json. Without the option, or if that file doesn't exist, the fixture reads data.json next to the test file.
  • data.json can contain correct_answers, so run.sh makes it root-owned with owner-only modes before student code runs. If that fails, grading stops with a failure result.
  • A data.json that can't be parsed or read now gives a clear error instead of being treated as missing.
  • run.sh no longer exports JOB_DIR or MERGE_DIR.

Add plot-serializer==0.2.3 to the full image. Plot grading needs it, and it was only a dev dependency. The docs now say plots must be built with MatplotlibSerializer.

Release together: this run.sh fails on package 1.1.0 because it doesn't know the new option.

Testing

  • 70 tests pass, mypy is clean, and ruff check shows nothing new. test_matplotlib_plots can fail on the first run in a fresh environment, as it does on main.
  • Lite image with this branch's source mounted over the package: a correct submission scores 1.0, a wrong one scores 0.6, and student code running as ag gets Permission denied on data.json. With a read-only data directory, student code never starts.
  • Not tested: a Linux host bind mount, or the full image with this run.sh. Docker Desktop's macOS bind mount doesn't enforce the modes. Automated tests don't cover --data-json-path or run.sh.

Known remaining issues

  • ag can still read the test files, and can reach params in sandbox memory.
  • Student code keeps root's supplementary groups, because drop_privileges doesn't call setgroups.
  • If job files are owned by uid 1000 on the host, ag could likely modify the moved test files, including setup_code.py. This wasn't tried.

🤖 Generated with Claude Code

eliotwrobson and others added 6 commits November 5, 2026 11:24
Plot grading depends on plot_serializer, which was only a dev dependency, so
it failed in the full image. Also correct the docs: plots must be built with
MatplotlibSerializer, because a plain Figure isn't serialized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PrairieLearn writes /grade/data/data.json, but the data_json fixture only read
data.json next to the test file and nothing put it there, so tests using
data_json failed with a NoneType error and correct submissions scored 0.

run.sh now passes the path with --data-json-path. The option uses the
--data-json-path=VALUE form because pytest's first pass over the arguments would
treat a separate value as a test path, which moves rootdir and loses the results
file. Without the option, or if the file doesn't exist, the fixture reads
data.json next to the test file.

data.json can hold correct_answers, so run.sh makes it root-owned with owner-only
modes before student code runs, and stops grading if that fails. Root ownership
matters because the student uid can match the host owner of mounted files, and
owner-only modes matter because student code keeps root's supplementary groups.

The fixture now raises a clear error for a data.json it can't parse or read,
instead of treating it like a missing file. run.sh takes the job directory as an
optional argument so tests can run it, and no longer exports JOB_DIR or MERGE_DIR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
run.sh isn't Python, so it doesn't get its own test file. The optional job
directory argument only existed for that test, so /grade is fixed in the script
again, and protect_data_json no longer has to handle running as non-root.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moving the lookup out of the try block meant a test module without a file
raised an AssertionError instead of getting None, as it did before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
run.sh still moves a data.json from /grade/tests to that location, and the
fixture still falls back to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pytest's first pass over the arguments treats a separate value as a possible
test path, so use the same form as --data-json-path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@eliotwrobson
eliotwrobson marked this pull request as ready for review November 5, 2026 16:36
@eliotwrobson
eliotwrobson merged commit 8494b95 into main Nov 5, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant