Overview
A tiny Rust CLI that bootstraps GitHub Classroom autograding for Rust projects.
π Currently deployed in Boston Universityβs Intro to Rust course (130+ students, 1000+ student repos).
Key Features
- β‘ Fast setup β go from repo β Classroom-ready assignment in under 60 seconds.
- π Flexible outputs β grading tables copied to clipboard or written directly to your README.
- ποΈ Optimized CI β precompiled YAMLs (no runtime parsing) for faster, cheaper runs.
- π§ Instructor-friendly CLI β
init
,build
,table
,reset
cover the full workflow.
How it Works
init
β scans for Rust tests and builds.autograder/autograder.json
.build
β converts that config into a ready-to-run GitHub Actions workflow at.github/workflows/classroom.yaml
.table
β generates a Markdown grading table for READMEs, keeping grading criteria transparent.reset
β cleans up generated files for a fresh start.
Keeps autograding setup simple for instructors while making grading criteria clear for students.
Releases
- Latest: https://github.com/JoeyRussoniello/rust-autograder-setup/releases/latest
- All releases: https://github.com/JoeyRussoniello/rust-autograder-setup/releases
Prebuilt binaries
OS / Target | Download |
---|---|
macOS (x86_64-apple-darwin) | See Assets on the latest release |
Windows (x86_64-pc-windows-gnu) | See Assets on the latest release |
Assets are named:
autograder-setup-vX.Y.Z-<target>.tar.gz
(macOS) or.zip
(Windows).
Installation
Option A β Install via Cargo (recommended)
If you already have Rust installed:
cargo install autograder-setup
Check installation:
autograder-setup --version
Option B β Install from release
macOS
# 1) Download the macOS asset from the latest release
# 2) Extract and install:
tar -xzf autograder-setup-vX.Y.Z-x86_64-apple-darwin.tar.gz
sudo install -m 0755 autograder-setup-vX.Y.Z-x86_64-apple-darwin/autograder-setup /usr/local/bin/autograder-setup
# 3) Remove the Quarantine Attribute to disable macOS Gatekeeper and code signing requirement.
sudo xattr -r -d com.apple.quarantine /usr/local/bin/autograder-setup
# 4) Check that you can run it
autograder-setup --version
Windows (PowerShell)
# 1) Download the Windows .zip from the latest release
# 2) Extract and install:
Expand-Archive autograder-setup-vX.Y.Z-x86_64-pc-windows-gnu.zip -DestinationPath .
$dir = Get-ChildItem -Directory "autograder-setup-v*-x86_64-pc-windows-gnu" | Select-Object -First 1
$exe = Join-Path $dir.FullName "autograder-setup.exe"
$UserBin = "$env:USERPROFILE\.local\bin"
New-Item -ItemType Directory -Force -Path $UserBin | Out-Null
Move-Item $exe "$UserBin\autograder-setup.exe" -Force
# Add to PATH for current session (optionally add permanently in System settings)
$env:PATH = "$UserBin;$env:PATH"
autograder-setup --version
Option C β Build from source
git clone https://github.com/JoeyRussoniello/rust-autograder-setup
cd rust-autograder-setup
cargo build --release
# binary at target/release/autograder-setup.
# Add to PATH, or move the binary to the assignment repo you're configuring
Quickstart
The goal of autograder-setup
is to take a plain Rust project template and make it GitHub Classroomβready in under a minute.
This quickstart shows the minimal workflow instructors need to get up and running.
Step 0: Explore the CLI
autograder-setup --help
This prints top-level usage and shows all available subcommands. Use --help
after any command to see its options.
Step 1: Initialize an autograder configuration
autograder-setup init
- Scans your project recursively for all
#[test]
functions. - Builds a JSON config at
.autograder/autograder.json
. - Automatically adds optional checks (Clippy linting, commit count) unless disabled.
Why itβs useful: this JSON file acts as a single source of truth for grading. You can tweak points, timeouts, or thresholds before generating CI.
Step 2: Review and adjust points
Open .autograder/autograder.json
in your editor:
[
{
"meta": { "name": "add_two", "description": "check add_two works", "points": 2, "timeout": 10 },
"type": "cargo_test",
"manifest_path": "Cargo.toml"
}
]
You can increase/decrease point values, set timeouts, or change descriptions here. This makes grading customizable.
Step 3: Build the GitHub Actions workflow
autograder-setup build
- Reads
.autograder/autograder.json
. - Emits
.github/workflows/classroom.yaml
with one job per test/check. - Uses the official
classroom-resources/autograding-*
actions.
Why itβs useful: this workflow is what Classroom actually runs when grading. It ensures consistency between local tests and CI.
(Optional) Step 4: Generate a grading table
autograder-setup table --to-readme
- Produces a Markdown table of test names, descriptions, and points.
- Appends to your
README.md
, or copy to clipboard by default.
Why itβs useful: students can see exactly how theyβll be graded up front.
Step 5: Reset if needed
autograder-setup reset
Deletes .autograder/
and the generated workflow, so you can start fresh.
Summary
init
= discover tests and set up configbuild
= turn config into a ready-to-run workflowtable
= generate a transparent grading table for studentsreset
= undo everything
Together, these steps let you go from zero β reproducible autograder in under a minute, while keeping grading criteria explicit for both instructors and students.
Command: init
Scans the project (recursively), finds test functions, and writes .autograder/autograder.json
. Supports nested Rust directories.
Options
-r, --root <ROOT>
Root of the Rust project (defaults to current directory)
[default: .]
-t, --tests-dir <TESTS_DIR>
Location of all test cases (defaults to <root>)
[default: .]
--default-points <DEFAULT_POINTS>
Default number of points per test
[default: 1]
--no-style-check
Disable the Clippy style check (enabled by default)
--require-commits <REQUIRE_COMMITS>...
Require specific commit thresholds (e.g. --require-commits 5 10 15 20)
[default: 1]
--require-branches <REQUIRE_BRANCHES>...
Require specific branch tresholds (e.g --require-branches 2 4 6)
--require-tests <REQUIRE_TESTS>...
Require specific student-written test thresholds (e.g --require-tests 2 4 6)
-h, --help
Print help (see a summary with '-h')
Examples
# Initialize for a sibling repo
autograder-setup init --root ../student-assignment
# Only search ./tests recursively
autograder-setup init --tests-dir tests
# Default 5 points per test
autograder-setup init --default-points 5
# Omit style or commit checks
autograder-setup init --no-style-check
autograder-setup init --no-commit-count
# Require at least 5 tests
autograder-setup init --require-tests 5
# Award 1 point for reaching 5, 10, and 20 commits
autograder-setup init --require-commits 5 10 20
# Award points for 10 and 20 commits, and for having 2 and 3 branches
autograder-setup init --require-commits 10 20 --require-branches 2 3
Counting checks (commits, branches, tests)
The init
command can emit simple threshold checks that award 1 point each when a submission meets a given threshold. The three related flags behave the same way: each value supplied becomes an independent 1βpoint check.
-
--require-commits <N>...
- Each value produces a check that the submission has at least N commits.
- Example:
--require-commits 5 10 20
β three checks (5, 10, 20 commits).
-
--require-branches <N>...
- Each value produces a check that the repository has at least N distinct branches.
- Example:
--require-branches 2 4
β two checks (2 branches, 4 branches).
-
--require-tests <N>...
- Each value produces a check that the student-written test count for a crate reaches at least N tests.
- IMPORTANT:
--require-tests
applies per manifest path. For a workspace, a threshold value produces a separate check for the root crate and for each member crate (i.e., each manifest path gets its own check). - Example: in a workspace with
member/
and a root crate,--require-tests 3
yields aTEST_COUNT
check for the root (if present) and aTEST_COUNT
check formember
(each requiring 3 tests).- This behavior can be refined by changing/removing the entries in
.autograder.json
- This behavior can be refined by changing/removing the entries in
Examples
# Award 1 point for reaching 5, 10, and 20 commits
autograder-setup init --require-commits 5 10 20
# Award 1 point for having 2 and 4 branches
autograder-setup init --require-branches 2 4
# Require at least 3 tests for each manifest (root and each workspace member)
autograder-setup init --require-tests 3
Notes
- Each supplied value becomes an independent 1βpoint check (not cumulative).
- Deprecated:
--num-commit-checks N
expands to thresholds1..=N
(e.g.,--num-commit-checks 3
β1 2 3
). Prefer--require-commits
for explicit thresholds.
Command: build
Generates .github/workflows/classroom.yaml
from .autograder/autograder.json
(and a commit-count script if needed).
Options
-r, --root <ROOT>
Root directory of the Rust project (defaults to current directory)
[default: .]
--triggers <TRIGGERS>...
Add one or more event triggers (always includes `repository_dispatch`)
Examples:
--triggers push pull-request
Possible values:
- workflow-dispatch: Manual run via the Actions UI
- push: Run on git push
- pull-request: Run on PR events
--preset <PRESET>
Quick config for common classroom setups
Possible values:
- instructor: Instructor-driven grading; manual-friendly
- student-push: Students get feedback on push
- student-pr: Students get feedback on PRs
--branches <BRANCHES>...
Restrict pushes and PRs to these branches
Example:
--branches main feature
--paths <PATHS>...
Only grade when changes affect these files or paths
Example:
--paths src/** Cargo.toml
--paths-ignore <PATHS_IGNORE>...
Ignore pushes and PRs that *only* affect these files or paths
Example:
--paths-ignore README.md docs/**
-h, --help
Print help (see a summary with '-h')
Examples
autograder-setup build
autograder-setup build --root ../student-assignment
# Run on every push and pull request
autograder-setup build --triggers push pull-request
# Restrict to main branch only
autograder-setup build --triggers push pull-request --branches main
# Ignore documentation-only changes
autograder-setup build --triggers push --paths-ignore README.md docs/**
Workflow details
- Fixed preamble (permissions, checkout, Rust toolchain).
- One autograding step per entry in
autograder.json
. - Final reporter step wiring
${{ steps.<id>.outputs.result }}
into the report.
Name/ID rules
- Step
name
/test-name
: verbatim forcargo test
entries; ALL_CAPS for other steps (e.g.,CLIPPY_STYLE_CHECK
). - Step
id
: slugifiedname
(lowercase; spaces & non-alnum β-
). - Command:
cargo test <name>
.
Workflow triggers (on:
)
By default, the generated workflow uses:
on: [repository_dispatch]
. This allows instructors to trigger grading manually from the Classroom UI.
Running with flags modifies the triggers:
--triggers push
β run the autograder on every push.--triggers push pull-request
β run on every push and pull request.--branches
β restrict which branches are eligible for push/PR grading.--paths
and--paths-ignore
β limit workflow runs to specific file changes.
Notes about choosing triggers:
repository_dispatch
is the safe default for instructor-initiated grading (avoids CI load on every push).push
orpull-request
triggers give students instant feedback but increase compute usage.- Combine
--branches
,--paths
, and--paths-ignore
to control when grading runs.
Example Workflow YAML
name: Autograding Tests
on: [repository_dispatch, push]
permissions:
checks: write
actions: read
contents: read
jobs:
run-autograding-tests:
runs-on: ubuntu-latest
if: github.actor != 'github-classroom[bot]'
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: clippy,rustfmt
- name: basic_add_small_numbers
id: basic-add-small-numbers
uses: classroom-resources/autograding-command-grader@v1
with:
test-name: "basic_add_small_numbers"
setup-command: ""
command: "cargo test basic_add_small_numbers"
timeout: 10
max-score: 1
- name: CLIPPY_STYLE_CHECK
id: clippy-style-check
uses: classroom-resources/autograding-command-grader@v1
with:
test-name: "CLIPPY_STYLE_CHECK"
setup-command: ""
command: "cargo clippy -- -D warnings"
timeout: 10
max-score: 1
- name: Autograding Reporter
uses: classroom-resources/autograding-grading-reporter@v1
env:
BASIC-ADD-SMALL-NUMBERS_RESULTS: "${{steps.basic-add-small-numbers.outputs.result}}"
CLIPPY-STYLE-CHECK_RESULTS: "${{steps.clippy-style-check.outputs.result}}"
with:
runners: basic-add-small-numbers,clippy-style-check
build
usage matrix
This matrix maps common scenarios to the flags you pass, what triggers resolve to,
and what the generated on:
block looks like.
Scenario | CLI flags | Filters applied | Resulting on: block |
---|---|---|---|
Instructor-only (manual) | (none) | β | on: [repository_dispatch] |
Instructor preset | --preset instructor | β | on: [repository_dispatch, workflow_dispatch] |
Push only | --triggers push | β | on: [repository_dispatch, push] |
Pull request only | --triggers pull-request | β | on: [repository_dispatch, pull_request] |
Push + PR (student feedback) | --triggers push pull-request | β | on: [repository_dispatch, push, pull_request] |
Student push preset | --preset student-push | β | on: [repository_dispatch, push] |
Student PR preset | --preset student-pr | β | on: [repository_dispatch, pull_request] |
Branch-limited (main only) | --triggers push pull-request --branches main | branches: [main] | See Example A |
Branch-limited (main & feature) | --triggers push pull-request --branches main feature | branches: [main, feature] | See Example B |
Run only when certain files change | --triggers push --paths src/** Cargo.toml | paths: [src/**, Cargo.toml] | See Example C |
Ignore doc-only changes | --triggers push --paths-ignore README.md docs/** | paths-ignore: [README.md, docs/**] | See Example D |
Combine paths + branches | --triggers pull-request --branches main --paths src/** | branches: [main], paths: [src/**] | See Example E |
Everything together | --triggers push pull-request --branches main release --paths src/** Cargo.toml --paths-ignore README.md | branches, paths, paths-ignore | See Example F |
Advanced Build YAML Examples
Example A
autograder-setup build --triggers push pull-request --branches main
Runs the autograder on both pushes and pull requests, but only for the main
branch.
on:
repository_dispatch:
push:
branches: [main]
pull_request:
branches: [main]
Example B
autograder-setup build --triggers push pull-request --branches main feature
Same as Example A, but allow grading on multiple branches β here, both main
and feature
.
on:
repository_dispatch:
push:
branches: [main, feature]
pull_request:
branches: [main, feature]
Example C
autograder-setup build --triggers push --paths src/** Cargo.toml
Run the autograder only when commits modify source files or the manifest (Cargo.toml
).
on:
repository_dispatch:
push:
paths: [src/**, Cargo.toml]
Example D
autograder-setup build --triggers push --paths-ignore README.md docs/**
Ignore pushes that only change documentation or the README.
on:
repository_dispatch:
push:
paths-ignore: [README.md, docs/**]
Example E
autograder-setup build --triggers pull-request --branches main --paths src/**`
Run the autograder for pull requests into main
, but only when code files are modified.
on:
repository_dispatch:
pull_request:
branches: [main]
paths: [src/**]
Example F: Putting It All Together
Using the command
autograder-setup build --triggers push pull-request \
--branches main release --paths src/** Cargo.toml \
--paths-ignore README.md
We can combine all filters for fine-grained control: grade only on code changes to main
or release
branches, ignoring documentation-only updates.
on:
repository_dispatch:
push:
branches: [main, release]
paths: [src/**, Cargo.toml]
paths-ignore: [README.md]
pull_request:
branches: [main, release]
paths: [src/**, Cargo.toml]
paths-ignore: [README.md]
All options can be further tweaked in the YAML for additional refinement, such as grading different branches for pushes and pull requests
Do these calls feel verbose? Feel free to open an issue for commonly used build patterns to improve our presets options!
Command: table
Reads .autograder/autograder.json
and generates a Markdown table of test names, descriptions, and points.
Options
-r, --root <ROOT> Root of the Rust project [default: .]
--no-clipboard Print to stdout instead of copying to clipboard
--to-readme Append the table to README.md
-h, --help Print help
Examples
autograder-setup table # copy to clipboard
autograder-setup table --no-clipboard
autograder-setup table --root ../student-assignment --to-readme
Example Output
Test name | Description | Points |
---|---|---|
add_core | Add function works in the core case | 10 |
add_small_numbers | Add function works with small numbers | 5 |
add_with_negatives | Add function handles negative inputs | 3 |
clippy_style_check | Clippy linting check | 2 |
Command: reset
Deletes generated files: the .autograder/
directory and .github/workflows/classroom.yml
.
Options
-r, --root <ROOT> Root of the Rust project [default: .]
-h, --help Print help
Example
autograder-setup reset
.autograder/autograder.json
Schema
Field | Type | Req | Description |
---|---|---|---|
meta.name | string | yes | Display name in the workflow and test filter |
meta.description | string | yes | Student-facing description (supports ## placeholder for counts) |
meta.points | number | yes | Max score for this test (default 1) |
meta.timeout | number | yes | Seconds for the autograder step (default 10) |
type | string | yes | One of: cargo_test , clippy , commit_count , test_count |
manifest_path | string | no | Path to Cargo.toml (for cargo_test , clippy , test_count ) |
min_commits | number | no | Required commits (only for commit_count ) |
min_tests | number | no | Required tests (only for test_count ) |
Example
[
{
"meta": { "name": "test_func_1", "description": "a test function", "points": 1, "timeout": 10 },
"type": "cargo_test",
"manifest_path": "Cargo.toml"
},
{
"meta": { "name": "COMMIT_COUNT_1", "description": "Ensure at least ## commits.", "points": 1, "timeout": 10 },
"type": "commit_count",
"min_commits": 5
},
{
"meta": { "name": "TEST_COUNT", "description": "Ensure at least ## tests exist.", "points": 1, "timeout": 10 },
"type": "test_count",
"min_tests": 3
}
]
Repository Structure
.
βββ Cargo.lock # Cargo dependency lockfile (generated; checked in for reproducible builds)
βββ Cargo.toml # Crate metadata and dependencies
βββ LICENSE # Project license
βββ README.md # Top-level overview (link to docs)
βββ src
βββ cli # CLI subcommands and orchestration
β βββ build # `autograder-setup build` β render workflow YAML from autograder.json
β β βββ build_functions.rs # Preamble, YAML helpers, commit-count script writer, small utilities
β β βββ mod.rs # Subcommand entry + YAMLAutograder builder (ties everything together)
β β βββ steps.rs # Hand-assembled YAML step emitters (CommandStep / ReporterStep)
β β βββ tests.rs # Unit tests for YAML rendering and build behavior
β βββ init # `autograder-setup init` β scan tests and write `.autograder/autograder.json`
β β βββ functions.rs # High-level constructors for AutoTests (clippy/commit count/test count)
β β βββ mod.rs # Subcommand entry and pipeline glue
β β βββ scan.rs # Rust source scanner (finds #[test]/#[..::test], docs, manifests)
β β βββ tests.rs # Parser/scan tests and manifest-path logic tests
β βββ mod.rs # Top-level CLI wiring (arg parsing, subcommand dispatch)
β βββ reset # `autograder-setup reset` β remove generated files
β β βββ mod.rs # Subcommand entry
β β βββ tests.rs # Safety checks for deleting generated artifacts
β βββ table # `autograder-setup table` β generate student-facing Markdown table
β β βββ mod.rs # Subcommand entry and table rendering
β βββ tests.rs # Cross-subcommand/integration-style tests for the CLI layer
βββ main.rs # Binary entrypoint; delegates to `cli`
βββ types # Core data model for the autograder
β βββ command_makers.rs # Per-variant command builders (cargo test/clippy/test-count/commit-count)
β βββ mod.rs # `AutoTest { meta, kind }`, `TestMeta`, `TestKind` + Markdown row impl
βββ utils
βββ mod.rs # Shared helpers: path walking, slug/id, yaml_quote, replace_double_hashtag, etc.
βββ tests.rs # Unit tests for utilities
FAQ
Does it run on every push?
Only if you use --grade-on-push
. The default trigger is repository_dispatch
to keep CI usage low.
How do I show a grading table in the README?
Run autograder-setup table
(copy to clipboard) or autograder-setup table --to-readme
.
Can I award partial credit for commit frequency?
Yes β use multiple thresholds with --require-commits
, e.g., 5 10 20
.
How do I require students to write additional tests?
Use --require-tests N
. The autograder expects N
+ the number of tests generated from cargo test
entries.