PROJECT="${2##*/}" STUDENT="${2%/*}" GRADER="$(realpath -m "${GRADER:?}")/$PROJECT" DATABASE="$(realpath -m "${STATE:?}")/db" DIRECTORY="$(mktemp -d /tmp/pigeon.XXXXXXXXXX)" trap 'rm -rf "$DIRECTORY"' EXIT cd "$DIRECTORY" git -c advice.detachedHead=false clone -q --rev "$3" --depth 1 \ "https://oauth2:${TOKEN:?}@gitlab.com/$2.git" student/ rm -rf student/.git mkdir -p project/tests project/build project/args for DIR in "$GRADER"/tests/*/ ; do NAME="$(basename "$DIR")" cp -a "$DIR/input.sh" "project/tests/$NAME" [ -f "$DIR/output.txt" ] || head -c 4096 /dev/urandom | LC_ALL=C tr -dc ' -~' >"project/args/$NAME" done cp -a "$GRADER"/build/. project/build/ mkfs.ext4 -q -F -d student student.img 16M mkfs.ext4 -q -F -d project project.img 16M mkfs.ext4 -q -F results.img 16M if [ -d "$GRADER/canonical" ] ; then mkdir -p canonical cp -a "$GRADER"/canonical/. canonical/ cp -a --remove-destination "$GRADER"/build/. canonical/ ( export PATH=@toolchain@/bin cd canonical && timeout 30 make ) >/dev/null 2>&1 || exit 1 fi cat >vm.json <<'EOF' { "boot-source": { "kernel_image_path": "@kernel@", "boot_args": "reboot=k panic=1 init=/init quiet nomodule i8042.noaux i8042.nomux i8042.dumbkbd swiotlb=noforce console=ttyS0" }, "drives": [ { "drive_id": "rootfs", "path_on_host": "@rootfs@", "is_root_device": true, "is_read_only": true }, { "drive_id": "student", "path_on_host": "student.img", "is_root_device": false, "is_read_only": true }, { "drive_id": "project", "path_on_host": "project.img", "is_root_device": false, "is_read_only": true }, { "drive_id": "results", "path_on_host": "results.img", "is_root_device": false, "is_read_only": false } ], "machine-config": { "vcpu_count": 1, "mem_size_mib": 512 } } EOF timeout 60 firecracker --no-api --level Error --config-file vm.json SCORE=0 PUBLIC=1 for DIR in "$GRADER"/tests/*/ ; do NAME="$(basename "$DIR")" debugfs -R "cat /$NAME" results.img >scratch 2>/dev/null EXIT="$(debugfs -R "cat /$NAME.status" results.img 2>/dev/null)" EXPECTED="$(cat "$DIR/status.txt" 2>/dev/null || true)" case "$EXPECTED" in '' | *[!0-9]*) EXPECTED=0 ;; esac if [ -f "$DIR/output.txt" ] ; then cat "$DIR/output.txt" elif [ -d canonical ] ; then ( export PATH=@toolchain@/bin cd canonical && timeout 2 sh "$DIRECTORY/project/tests/$NAME" ) <"project/args/$NAME" 2>/dev/null || true fi >expected if cmp -s scratch expected && [ "$EXIT" -eq "$EXPECTED" ] ; then RESULT=YES SCORE=$((SCORE + 1)) elif [ "$EXIT" -eq 124 ] || [ "$EXIT" -eq 137 ] ; then RESULT="NO (timed out)" elif [ "$EXIT" -gt 128 ] ; then RESULT="NO (crashed with signal $((EXIT - 128)))" elif [ "$EXIT" -ne "$EXPECTED" ] ; then RESULT="NO (exited with status $EXIT, expected $EXPECTED)" else RESULT="NO (wrong output)" fi case "$NAME" in p*) if [ "$RESULT" != YES ] ; then PUBLIC=0 ; fi echo "$(cat "$DIR/test.txt") $RESULT " >>public ;; r*) echo "$(cat "$DIR/test.txt") $RESULT " >>release ;; esac done q() { printf "'%s'" "${1//\'/\'\'}" } MESSAGE="$(cat public || true)" [ "$(sqlite3 -bail -cmd '.timeout 10000' "$DATABASE" </dev/null || date -u '+%Y-%m-%d %H:%M:%S')"), $SCORE, ($PUBLIC = 1) AND ( SELECT COUNT(*) FROM results WHERE student = $(q "$STUDENT") AND project = $(q "$PROJECT") AND token AND time > datetime('now', '-1 day') ) < 3 RETURNING token; EOF )" = 1 ] && MESSAGE="$(cat public release || true)" if [ "$(debugfs -R "cat /build.status" results.img 2>/dev/null)" -ne 0 ] ; then debugfs -R "cat /build.log" results.img >build.log 2>/dev/null MESSAGE="$(printf '````\n%s\n````' "$(head -c 8192 build.log)")" fi curl -s --show-error --out-null --request PUT \ --header "PRIVATE-TOKEN: ${TOKEN:?}" \ --header 'Content-Type: application/json' \ --data "$(jq -n --arg d "$MESSAGE" '{"description": $d}')" \ --url "https://gitlab.com/api/v4/projects/$1/releases/$(jq -rn --arg t "$4" '$t|@uri')"