Skip to content

Commit

Permalink
Add script to verify exercises in Docker (#188)
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikSchierboom authored Aug 14, 2024
1 parent 062fda4 commit 24ace04
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 10 deletions.
12 changes: 12 additions & 0 deletions TRACK_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ This command will iterate over all exercises and check to see if their exemplar/

To test a single exercise, run `./bin/verify-exercises <exercise-slug>`.

### Using Docker

If your track has a working [test runner](https://exercism.org/docs/building/tooling/test-runners), the `./bin/verify-exercises-in-docker` script can also be used to test all exercises.
This script pulls (_downloads_) the test runner's [Docker image](https://exercism.org/docs/building/tooling/test-runners/docker) and then uses Docker to run that image to test an exercise.

```exercism/note
The main benefit of this approach is that it best mimics how exercises are tested in production (on the website).
Another benefit is that you don't have to install track-specific dependencies (e.g. an SDK) locally, you just need Docker installed.
```

To test a single exercise, run `./bin/verify-exercises-in-docker <exercise-slug>`.

### Track linting

[`configlet`](https://exercism.org/docs/building/configlet) is an Exercism-wide tool for working with tracks. You can download it by running:
Expand Down
1 change: 1 addition & 0 deletions bin/bootstrap
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ end
"config.json",
"README.md",
"CHECKLIST.md",
File.join("bin", "verify-exercises-in-docker"),
File.join("docs", "config.json")
].each do |name|
f = File.join(dir, name)
Expand Down
27 changes: 17 additions & 10 deletions bin/verify-exercises
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,21 @@ verify_exercise() {
)
}

exercise_slug="${1:-*}"
verify_exercises() {
local exercise_slug

exercise_slug="${1}"

shopt -s nullglob
count=0
for exercise_dir in ./exercises/{concept,practice}/${exercise_slug}/; do
if [[ -d "${exercise_dir}" ]]; then
verify_exercise "${exercise_dir}"
((++count))
fi
done
((count > 0)) || die 'no matching exercises found!'
}

shopt -s nullglob
count=0
for exercise_dir in ./exercises/{concept,practice}/${exercise_slug}/; do
if [[ -d "${exercise_dir}" ]]; then
verify_exercise "${exercise_dir}"
((++count))
fi
done
((count > 0)) || die 'no matching exercises found!'
exercise_slug="${1:-*}"
verify_exercises "${exercise_slug}"
89 changes: 89 additions & 0 deletions bin/verify-exercises-in-docker
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/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

set -eo pipefail

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."
}

required_tool docker

copy_example_or_examplar_to_solution() {
jq -c '[.files.solution, .files.exemplar // .files.example] | transpose | map({src: .[1], dst: .[0]}) | .[]' .meta/config.json \
| while read -r src_and_dst; do
cp "$(jq -r '.src' <<< "${src_and_dst}")" "$(jq -r '.dst' <<< "${src_and_dst}")"
done
}

pull_docker_image() {
docker pull exercism/{{SLUG}}-test-runner
}

run_tests() {
local slug
slug="${1}"

docker run \
--rm \
--network none \
--read-only \
--mount type=bind,src="${PWD}",dst=/solution \
--mount type=bind,src="${PWD}",dst=/output \
--mount type=tmpfs,dst=/tmp \
exercism/{{SLUG}}-test-runner "${slug}" /solution /output
jq -e '.status == "pass"' "${PWD}/results.json" >/dev/null 2>&1
}

verify_exercise() {
local dir
local slug
local tmp_dir
dir=$(realpath "${1}")
slug=$(basename "${dir}")
tmp_dir=$(mktemp -d -t "exercism-verify-${slug}-XXXXX")

echo "Verifying ${slug} exercise..."

(
trap 'rm -rf "$tmp_dir"' EXIT # remove tempdir when subshell ends
cp -r "${dir}/." "${tmp_dir}"
cd "${tmp_dir}"

copy_example_or_examplar_to_solution
run_tests "${slug}"
)
}

verify_exercises() {
local exercise_slug
exercise_slug="${1}"

shopt -s nullglob
count=0
for exercise_dir in ./exercises/{concept,practice}/${exercise_slug}/; do
if [[ -d "${exercise_dir}" ]]; then
verify_exercise "${exercise_dir}"
((++count))
fi
done
((count > 0)) || die 'no matching exercises found!'
}

pull_docker_image

exercise_slug="${1:-*}"
verify_exercises "${exercise_slug}"

0 comments on commit 24ace04

Please sign in to comment.