Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ tests/
pre-compiled/.spago
pre-compiled/node_modules
pre-compiled/output
pre-compiled/spago.lock
.appends
.gitignore
.gitattributes
36 changes: 21 additions & 15 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
# Use the latest LTS version of Node.js
FROM node:20-bullseye-slim
# Spago 1.0.3 requires Node.js >= 22.5.0 (uses node:sqlite built-in module)
FROM node:22-bookworm-slim

# Update package lists and install required dependencies
# Install system dependencies:
# - ca-certificates: HTTPS support for downloading packages from the registry
# - git: required by Spago for registry index management
# - jq: used by run.sh to generate results.json output
# - libncurses5: runtime dependency for the PureScript compiler (purs)
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
git \
jq \
libncurses5 \
&& rm -rf /var/lib/apt/lists/*

# Set up working directory
WORKDIR /opt/test-runner/pre-compiled
# Use a single WORKDIR for the test runner root
WORKDIR /opt/test-runner

# Copy and install dependencies
COPY pre-compiled .
RUN npm install && npx spago install && npx spago build --deps-only
# Install PureScript dependencies and pre-compile them.
# The output/ and .spago/ directories are reused at runtime to avoid
# recompiling 280+ modules for every student submission.
COPY pre-compiled pre-compiled/
RUN cd pre-compiled \
&& npm install \
&& npx spago install \
&& npm cache clean --force \
&& rm -rf /root/.npm
Comment on lines +19 to +27
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The image build no longer runs a Spago build step to generate compiled artifacts, but bin/run.sh expects pre-compiled/output to exist and be populated (it copies it to preserve purs cache timestamps). If npx spago install doesn’t produce output/, the runner will either fail the copy step or lose the intended precompilation speedup (and may time out). Consider adding an explicit npx spago build --deps-only (or the Spago 1 equivalent) after install to ensure output/ is created during docker build.

Copilot uses AI. Check for mistakes.

# Set up bin directory
WORKDIR /opt/test-runner/bin
COPY bin/run.sh bin/run-tests.sh ./
# Copy runner scripts
COPY bin/run.sh bin/run-tests.sh bin/
RUN chmod +x bin/*.sh

# Ensure scripts have execution permissions
RUN chmod +x /opt/test-runner/bin/*.sh

# Set the entry point
ENTRYPOINT ["/opt/test-runner/bin/run.sh"]
4 changes: 2 additions & 2 deletions bin/run-tests.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bash

# Synopsis:
# Test the test runner by running it against a predefined set of solutions
# Test the test runner by running it against a predefined set of solutions
# with an expected output.

# Output:
Expand All @@ -19,7 +19,7 @@ exit_code=0
base_dir=$(builtin cd "${BASH_SOURCE%/*}/.." || exit; pwd)

# Iterate over all test Spago projects
for config in "${base_dir}"/tests/*/spago.dhall; do
for config in "${base_dir}"/tests/*/spago.yaml; do
exercise_dir=$(dirname "${config}")
slug=$(basename "${exercise_dir}")
expected_results_file="${exercise_dir}/expected_results.json"
Expand Down
51 changes: 32 additions & 19 deletions bin/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ results_file="${output_dir}/results.json"
# - We can work with a write-able file-system
# - We avoid copying files between the docker host and client giving a nice speedup.
build_dir=/tmp/build
cache_dir=${build_dir}/cache

if [ ! -d "${input_dir}" ]; then
echo "No such directory: ${input_dir}"
Expand All @@ -55,17 +54,12 @@ fi
mkdir -p ${build_dir}
pushd "${build_dir}" > /dev/null || exit

# Put the basic spago project in place
cp "${input_dir}"/*.dhall .
# Put the spago project in place: copy and rewrite the package name to match
# the pre-compiled lockfile so we can use --pure mode (no registry access).
sed 's/^ name: .*/ name: pre-compiled/' "${input_dir}/spago.yaml" > spago.yaml
ln -s "${input_dir}"/src .
ln -s "${input_dir}"/test .

# Setup cache directory. We require a writable dhall cache because dhall will
# attempt to fetch the upstream package-set definition.
mkdir ${cache_dir}
cp -R "${HOME}"/.cache/dhall ${cache_dir}
cp -R "${HOME}"/.cache/dhall-haskell ${cache_dir}

# Setup our prepared node setup.
ln -s "${base_dir}/pre-compiled/node_modules" .

Expand All @@ -75,17 +69,15 @@ ln -s "${base_dir}/pre-compiled/node_modules" .
# flag).
cp -R -p "${base_dir}/pre-compiled/output" .
cp -R "${base_dir}/pre-compiled/.spago" .
cp "${base_dir}/pre-compiled/spago.lock" .

echo "Build and test ${slug} in ${build_dir}..."

# Run the tests for the provided implementation file and redirect stdout and
# stderr to capture it. We do our best to minimize the output to emit and
# compiler errors or unit test output as this scrubbed and presented to the
# student. In addition spago will try to write to ~/cache/.spago and will fail
# on a read-only mount and thus we skip the global cache and request to not
# install packages.
export XDG_CACHE_HOME=${cache_dir}
spago_output=$(npx spago --global-cache skip --no-psa test --no-install 2>&1)
# stderr to capture it.
# --offline --pure: use cached packages and lockfile, no registry/network access
# HOME=/tmp: Spago's SQLite cache needs a writable directory (Docker runs --read-only)
spago_output=$(HOME=/tmp npx spago test --offline --pure 2>&1)
exit_code=$?

popd > /dev/null || exit
Expand All @@ -95,9 +87,30 @@ popd > /dev/null || exit
if [ $exit_code -eq 0 ]; then
jq -n '{version: 1, status: "pass"}' > "${results_file}"
else
sanitized_spago_output=$(echo "${spago_output}" | sed -E \
-e '/^Compiling/d' \
-e '/at.*:[[:digit:]]+:[[:digit:]]+\)?/d')
sanitized_spago_output=$(printf '%s\n' "${spago_output}" | awk '
BEGIN { blanks = 2 }
/^Reading Spago workspace/ || \
/^✓ Selecting package/ || \
/^Checking dependencies/ || \
/^Downloading dependencies/ || \
/^No lockfile found/ || \
/^Lockfile written/ || \
/^Building\.\.\./ || \
/^\[[[:space:]]*[0-9]+ of [0-9]+\] Compiling / || \
/^✓ Build succeeded/ || \
/^Running tests for package/ || \
/^✘ Tests failed/ || \
/^✘ Failed to build/ || \
/^[[:space:]]+Src[[:space:]]+Lib[[:space:]]+All/ || \
/^Warnings[[:space:]]+[0-9]/ || \
/^Errors[[:space:]]+[0-9]/ || \
/^[[:space:]]+at .*(\.js|\.mjs|node:internal).*:[0-9]/ { next }
/^\[WARNING / { warn = 1; next }
warn && /^$/ { warn = 0; next }
warn { next }
NF { blanks = 0; print; next }
blanks < 2 { blanks++; print }
')

jq --null-input --arg output "${sanitized_spago_output}" '{version: 1, status: "fail", message: $output}' > "${results_file}"
fi
Expand Down
18 changes: 11 additions & 7 deletions bin/update-tests.sh
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
#!/usr/bin/env bash

# This script will update spago.dhall and package.dhall of all exercises
# using the master files from the project template (pre-compiled/).
# This script will update the workspace section of all test examples'
# spago.yaml to match the pre-compiled project's workspace configuration
# (package set version, extra packages).

set -o pipefail
set -u

base_dir=$(builtin cd "${BASH_SOURCE%/*}/.." || exit; pwd)
project_dir="${base_dir}/pre-compiled"

for config in ./tests/*/spago.dhall; do
# Extract the workspace section from pre-compiled/spago.yaml
workspace_section=$(sed -n '/^workspace:/,$p' "${base_dir}/pre-compiled/spago.yaml")

for config in "${base_dir}"/tests/*/spago.yaml; do
exercise_dir=$(dirname "${config}")
# slug=$(basename "${exercise_dir}")
slug=$(basename "${exercise_dir}")

echo "Working in ${exercise_dir}..."

# sed -e "s/pre-compiled/${slug}/" < "${project_dir}/spago.dhall" > "${exercise_dir}/spago.dhall"
cp "${project_dir}/packages.dhall" "${exercise_dir}/packages.dhall"
# Replace the workspace section (everything from "workspace:" to EOF)
package_section=$(sed -n '1,/^workspace:/{ /^workspace:/!p; }' "${config}")
printf '%s\n%s\n' "${package_section}" "${workspace_section}" > "${config}"
done
Loading