From 09e17de0ddfc822d7c3e7fb4670faf970c537518 Mon Sep 17 00:00:00 2001 From: Eric Willigers Date: Sun, 17 May 2026 21:24:56 +1000 Subject: [PATCH 1/3] bin/verify-exercises-in-docker This script is useful for testing exercises against a test runner. --- bin/verify-exercises-in-docker | 107 +++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100755 bin/verify-exercises-in-docker diff --git a/bin/verify-exercises-in-docker b/bin/verify-exercises-in-docker new file mode 100755 index 0000000..f2f078f --- /dev/null +++ b/bin/verify-exercises-in-docker @@ -0,0 +1,107 @@ +#!/usr/bin/env bash + +# Synopsis: +# Verify that each exercise's example/exemplar solution passes the tests +# using the track's test runner Docker image. +# You can either verify all exercises or a single exercise. + +# Example: verify all exercises in Docker +# bin/verify-exercises-in-docker + +# Example: verify single exercise in Docker +# bin/verify-exercises-in-docker two-fer + +# Example: verify all exercises against specified test runner +# bin/verify-exercises-in-docker -i my-local-image + +set -e +shopt -s nullglob + +die() { + echo "$*" >&2 + exit 1 +} + +required_tool() { + command -v "${1}" >/dev/null 2>&1 || die "${1} is required but not installed. Please install it and make sure it's in your PATH." +} + +copy_example_or_exemplar_to_solution() { + local dir="${1}" + jq -r '[.files.solution, .files.exemplar // .files.example] | transpose | map(select(.[0] and .[1]))[][]' "${dir}/.meta/config.json" \ + | while read -r dst; read -r src; do + cp "${dir}/${src}" "${dir}/${dst}" + done +} + +run_tests() { + local slug="${1}" dir="${2}" + local -a docker_args + + docker_args+=( --rm --network none ) + docker_args+=( --mount "type=bind,src=${dir},dst=/solution" ) + # /tmp needs to be a proper volume to run the compiled executable; tmpfs is not executable. + docker_args+=( --mount "type=volume,dst=/tmp" ) + + # /solution is used both as the location to read the code from and as a destination for the results.json file. + docker run "${docker_args[@]}" "${image}" "${slug}" /solution /solution + jq -e '.status == "pass"' "${dir}/results.json" >/dev/null 2>&1 +} + +verify_exercise() { + local dir slug tmpdir + dir="${1%/}" + slug="${dir##*/}" + tmpdir="$(mktemp -d -t "exercism-verify-${slug}-XXXXX")" + + echo "Verifying ${slug} exercise..." + ( + trap 'rm -rf "${tmpdir}"' EXIT # remove tempdir when subshell ends + cp -r "${dir}/." "${tmpdir}" || exit + copy_example_or_exemplar_to_solution "${tmpdir}" + run_tests "${slug}" "${tmpdir}" || { cat "${tmpdir}/results.json"; exit 1; } + ) +} + +verify_exercises() { + local -a exercises + local parent path + if (( $# )); then + for slug; do + for parent in concept practice; do + path="./exercises/${parent}/${slug}" + [[ -d "${path}" ]] && exercises+=( "${path}" ) + done + done + else + exercises=( ./exercises/{concept,practice}/*/ ) + fi + (( ${#exercises[@]} )) || die "No matching exercises found" + + rc=0 + for exercise_dir in "${exercises[@]}"; do + verify_exercise "${exercise_dir}" || rc=$? + done + return "$rc" +} + + +required_tool docker +required_tool jq + +image='' +while getopts i: opt; do + case "${opt}" in + i) image="${OPTARG}" ;; + ?) die "Unknown option: -$OPTARG" ;; + esac +done +shift "$((OPTIND - 1))" + +if [[ -z "${image}" ]]; then + image="exercism/gdscript-test-runner" + docker pull "${image}" || + die "docker pull ${image} failed. Check the test runner docs at https://exercism.org/docs/building/tooling/test-runners for more information." +fi + +verify_exercises "$@" From 6167b1021d4be2764502b3b0c84596c328f96a31 Mon Sep 17 00:00:00 2001 From: Eric Willigers Date: Tue, 19 May 2026 06:02:58 +1000 Subject: [PATCH 2/3] avoid need for jq --- bin/verify-exercises-in-docker | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/bin/verify-exercises-in-docker b/bin/verify-exercises-in-docker index f2f078f..6086d1e 100755 --- a/bin/verify-exercises-in-docker +++ b/bin/verify-exercises-in-docker @@ -27,11 +27,13 @@ required_tool() { } copy_example_or_exemplar_to_solution() { - local dir="${1}" - jq -r '[.files.solution, .files.exemplar // .files.example] | transpose | map(select(.[0] and .[1]))[][]' "${dir}/.meta/config.json" \ - | while read -r dst; read -r src; do - cp "${dir}/${src}" "${dir}/${dst}" - done + local dir="${1}" slug="${2}" src + if [[ -f "${dir}/.meta/exemplar.gd" ]]; then + src=".meta/exemplar.gd" + else + src=".meta/example.gd" + fi + cp "${dir}/${src}" "${dir}/${slug//-/_}.gd" } run_tests() { @@ -40,12 +42,11 @@ run_tests() { docker_args+=( --rm --network none ) docker_args+=( --mount "type=bind,src=${dir},dst=/solution" ) - # /tmp needs to be a proper volume to run the compiled executable; tmpfs is not executable. - docker_args+=( --mount "type=volume,dst=/tmp" ) + docker_args+=( --mount "type=tmpfs,dst=/tmp" ) # /solution is used both as the location to read the code from and as a destination for the results.json file. - docker run "${docker_args[@]}" "${image}" "${slug}" /solution /solution - jq -e '.status == "pass"' "${dir}/results.json" >/dev/null 2>&1 + docker run "${docker_args[@]}" "${image}" "${slug}" /solution /solution && + grep -q '^ "status": "pass"' "${dir}/results.json" } verify_exercise() { @@ -58,7 +59,7 @@ verify_exercise() { ( trap 'rm -rf "${tmpdir}"' EXIT # remove tempdir when subshell ends cp -r "${dir}/." "${tmpdir}" || exit - copy_example_or_exemplar_to_solution "${tmpdir}" + copy_example_or_exemplar_to_solution "${tmpdir}" "${slug}" run_tests "${slug}" "${tmpdir}" || { cat "${tmpdir}/results.json"; exit 1; } ) } @@ -87,7 +88,6 @@ verify_exercises() { required_tool docker -required_tool jq image='' while getopts i: opt; do From b41e31193725daca7e88ebb4bbc9777c66c4769e Mon Sep 17 00:00:00 2001 From: Eric Willigers Date: Sun, 31 May 2026 07:48:43 +1000 Subject: [PATCH 3/3] check that image exists --- bin/verify-exercises-in-docker | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/verify-exercises-in-docker b/bin/verify-exercises-in-docker index 6086d1e..89d1e33 100755 --- a/bin/verify-exercises-in-docker +++ b/bin/verify-exercises-in-docker @@ -104,4 +104,7 @@ if [[ -z "${image}" ]]; then die "docker pull ${image} failed. Check the test runner docs at https://exercism.org/docs/building/tooling/test-runners for more information." fi +docker image inspect "${image}" >/dev/null 2>&1 || + die "Docker image '${image}' not found" + verify_exercises "$@"