Files
Pigeon/pigeon.sh
T

139 lines
4.4 KiB
Bash
Raw Normal View History

2026-10-23 18:16:37 -04:00
PROJECT="${2##*/}"
STUDENT="${2%/*}"
GRADER="$(realpath -m "${GRADER:?}")/$PROJECT"
DATABASE="$(realpath -m "${STATE:?}")/db"
2026-10-15 20:33:59 -04:00
DIRECTORY="$(mktemp -d /tmp/pigeon.XXXXXXXXXX)"
trap 'rm -rf "$DIRECTORY"' EXIT
cd "$DIRECTORY"
2026-10-23 18:16:37 -04:00
git -c advice.detachedHead=false clone -q --rev "$3" --depth 1 \
"https://oauth2:${TOKEN:?}@gitlab.com/$2.git" student/
2026-10-15 20:33:59 -04:00
rm -rf student/.git
2026-10-23 18:16:37 -04:00
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"
2026-10-23 12:36:36 -04:00
done
2026-10-23 18:16:37 -04:00
cp -a "$GRADER"/build/. project/build/
2026-10-23 11:25:43 -04:00
2026-10-15 20:33:59 -04:00
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
2026-10-23 18:16:37 -04:00
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
2026-10-15 20:33:59 -04:00
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
2026-10-23 18:16:37 -04:00
timeout 60 firecracker --no-api --level Error --config-file vm.json
2026-10-15 20:33:59 -04:00
2026-10-23 12:36:36 -04:00
SCORE=0
2026-10-23 18:16:37 -04:00
PUBLIC=1
for DIR in "$GRADER"/tests/*/ ; do
2026-10-23 12:36:36 -04:00
NAME="$(basename "$DIR")"
2026-10-23 11:25:43 -04:00
debugfs -R "cat /$NAME" results.img >scratch 2>/dev/null
EXIT="$(debugfs -R "cat /$NAME.status" results.img 2>/dev/null)"
2026-10-23 18:16:37 -04:00
EXPECTED="$(cat "$DIR/status.txt" 2>/dev/null || true)"
case "$EXPECTED" in '' | *[!0-9]*) EXPECTED=0 ;; esac
ARGS="project/args/$NAME"
[ -f "$ARGS" ] || ARGS=/dev/null
2026-10-23 11:25:43 -04:00
2026-10-23 12:36:36 -04:00
if [ -f "$DIR/output.txt" ] ; then
2026-10-23 18:16:37 -04:00
cat "$DIR/output.txt"
elif [ -d canonical ] ; then
(
export PATH=@toolchain@/bin
cd canonical && timeout 2 sh "$DIRECTORY/project/tests/$NAME"
) <"$ARGS" 2>/dev/null || true
fi >expected
2026-10-23 11:25:43 -04:00
2026-10-23 18:16:37 -04:00
if cmp -s scratch expected && [ "$EXIT" -eq "$EXPECTED" ] ; then
RESULT=YES
2026-10-23 12:36:36 -04:00
SCORE=$((SCORE + 1))
elif [ "$EXIT" -eq 124 ] || [ "$EXIT" -eq 137 ] ; then
2026-10-23 18:16:37 -04:00
RESULT="NO (timed out)"
2026-10-23 12:36:36 -04:00
elif [ "$EXIT" -gt 128 ] ; then
2026-10-23 18:16:37 -04:00
RESULT="NO (crashed with signal $((EXIT - 128)))"
elif [ "$EXIT" -ne "$EXPECTED" ] ; then
RESULT="NO (exited with status $EXIT, expected $EXPECTED)"
2026-10-23 11:25:43 -04:00
else
2026-10-23 18:16:37 -04:00
RESULT="NO (wrong output)"
2026-10-23 11:25:43 -04:00
fi
2026-10-23 12:36:36 -04:00
case "$NAME" in
2026-10-23 18:16:37 -04:00
p*)
if [ "$RESULT" != YES ] ; then PUBLIC=0 ; fi
echo "$(cat "$DIR/test.txt") $RESULT " >>public ;;
r*)
echo "$(cat "$DIR/test.txt") $RESULT " >>release ;;
2026-10-23 11:25:43 -04:00
esac
2026-10-15 20:33:59 -04:00
done
2026-10-23 18:16:37 -04:00
q() {
printf "'%s'" "${1//\'/\'\'}"
}
MESSAGE="$(cat public || true)"
[ "$(sqlite3 -bail -cmd '.timeout 10000' "$DATABASE" <<EOF
CREATE TABLE IF NOT EXISTS results (
id INTEGER PRIMARY KEY,
student TEXT NOT NULL,
project TEXT NOT NULL,
hash TEXT NOT NULL,
time TEXT NOT NULL,
score INTEGER NOT NULL,
token BOOLEAN NOT NULL
);
2026-10-23 11:25:43 -04:00
INSERT INTO results (student, project, hash, time, score, token)
2026-10-23 18:16:37 -04:00
SELECT
$(q "$STUDENT"),
$(q "$PROJECT"),
$(q "$3"),
$(q "$(date -u -d "$5" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date -u '+%Y-%m-%d %H:%M:%S')"),
2026-10-23 11:25:43 -04:00
$SCORE,
2026-10-23 18:16:37 -04:00
($PUBLIC = 1) AND (
2026-10-23 11:25:43 -04:00
SELECT COUNT(*) FROM results
2026-10-23 18:16:37 -04:00
WHERE student = $(q "$STUDENT") AND project = $(q "$PROJECT")
AND token AND time > datetime('now', '-1 day')
) < 3
2026-10-23 11:25:43 -04:00
RETURNING token;
EOF
2026-10-23 18:16:37 -04:00
)" = 1 ] && MESSAGE="$(cat public release || true)"
2026-10-23 11:25:43 -04:00
2026-10-23 18:16:37 -04:00
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)")"
2026-10-23 11:25:43 -04:00
fi
2026-10-23 12:36:36 -04:00
curl -s --show-error --out-null --request PUT \
2026-10-23 18:16:37 -04:00
--header "PRIVATE-TOKEN: ${TOKEN:?}" \
2026-10-23 12:36:36 -04:00
--header 'Content-Type: application/json' \
--data "$(jq -n --arg d "$MESSAGE" '{"description": $d}')" \
2026-10-23 18:16:37 -04:00
--url "https://gitlab.com/api/v4/projects/$1/releases/$4"