Skip to content

Commit 24ace04

Browse files
Add script to verify exercises in Docker (#188)
1 parent 062fda4 commit 24ace04

File tree

4 files changed

+119
-10
lines changed

4 files changed

+119
-10
lines changed

TRACK_README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ This command will iterate over all exercises and check to see if their exemplar/
1111

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

14+
### Using Docker
15+
16+
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.
17+
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.
18+
19+
```exercism/note
20+
The main benefit of this approach is that it best mimics how exercises are tested in production (on the website).
21+
Another benefit is that you don't have to install track-specific dependencies (e.g. an SDK) locally, you just need Docker installed.
22+
```
23+
24+
To test a single exercise, run `./bin/verify-exercises-in-docker <exercise-slug>`.
25+
1426
### Track linting
1527

1628
[`configlet`](https://exercism.org/docs/building/configlet) is an Exercism-wide tool for working with tracks. You can download it by running:

bin/bootstrap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ end
6666
"config.json",
6767
"README.md",
6868
"CHECKLIST.md",
69+
File.join("bin", "verify-exercises-in-docker"),
6970
File.join("docs", "config.json")
7071
].each do |name|
7172
f = File.join(dir, name)

bin/verify-exercises

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,21 @@ verify_exercise() {
7373
)
7474
}
7575

76-
exercise_slug="${1:-*}"
76+
verify_exercises() {
77+
local exercise_slug
78+
79+
exercise_slug="${1}"
80+
81+
shopt -s nullglob
82+
count=0
83+
for exercise_dir in ./exercises/{concept,practice}/${exercise_slug}/; do
84+
if [[ -d "${exercise_dir}" ]]; then
85+
verify_exercise "${exercise_dir}"
86+
((++count))
87+
fi
88+
done
89+
((count > 0)) || die 'no matching exercises found!'
90+
}
7791

78-
shopt -s nullglob
79-
count=0
80-
for exercise_dir in ./exercises/{concept,practice}/${exercise_slug}/; do
81-
if [[ -d "${exercise_dir}" ]]; then
82-
verify_exercise "${exercise_dir}"
83-
((++count))
84-
fi
85-
done
86-
((count > 0)) || die 'no matching exercises found!'
92+
exercise_slug="${1:-*}"
93+
verify_exercises "${exercise_slug}"

bin/verify-exercises-in-docker

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env bash
2+
3+
# Synopsis:
4+
# Verify that each exercise's example/exemplar solution passes the tests
5+
# using the track's test runner Docker image.
6+
# You can either verify all exercises or a single exercise.
7+
8+
# Example: verify all exercises in Docker
9+
# bin/verify-exercises-in-docker
10+
11+
# Example: verify single exercise in Docker
12+
# bin/verify-exercises-in-docker two-fer
13+
14+
set -eo pipefail
15+
16+
die() { echo "$*" >&2; exit 1; }
17+
18+
required_tool() {
19+
command -v "${1}" >/dev/null 2>&1 ||
20+
die "${1} is required but not installed. Please install it and make sure it's in your PATH."
21+
}
22+
23+
required_tool docker
24+
25+
copy_example_or_examplar_to_solution() {
26+
jq -c '[.files.solution, .files.exemplar // .files.example] | transpose | map({src: .[1], dst: .[0]}) | .[]' .meta/config.json \
27+
| while read -r src_and_dst; do
28+
cp "$(jq -r '.src' <<< "${src_and_dst}")" "$(jq -r '.dst' <<< "${src_and_dst}")"
29+
done
30+
}
31+
32+
pull_docker_image() {
33+
docker pull exercism/{{SLUG}}-test-runner
34+
}
35+
36+
run_tests() {
37+
local slug
38+
slug="${1}"
39+
40+
docker run \
41+
--rm \
42+
--network none \
43+
--read-only \
44+
--mount type=bind,src="${PWD}",dst=/solution \
45+
--mount type=bind,src="${PWD}",dst=/output \
46+
--mount type=tmpfs,dst=/tmp \
47+
exercism/{{SLUG}}-test-runner "${slug}" /solution /output
48+
jq -e '.status == "pass"' "${PWD}/results.json" >/dev/null 2>&1
49+
}
50+
51+
verify_exercise() {
52+
local dir
53+
local slug
54+
local tmp_dir
55+
dir=$(realpath "${1}")
56+
slug=$(basename "${dir}")
57+
tmp_dir=$(mktemp -d -t "exercism-verify-${slug}-XXXXX")
58+
59+
echo "Verifying ${slug} exercise..."
60+
61+
(
62+
trap 'rm -rf "$tmp_dir"' EXIT # remove tempdir when subshell ends
63+
cp -r "${dir}/." "${tmp_dir}"
64+
cd "${tmp_dir}"
65+
66+
copy_example_or_examplar_to_solution
67+
run_tests "${slug}"
68+
)
69+
}
70+
71+
verify_exercises() {
72+
local exercise_slug
73+
exercise_slug="${1}"
74+
75+
shopt -s nullglob
76+
count=0
77+
for exercise_dir in ./exercises/{concept,practice}/${exercise_slug}/; do
78+
if [[ -d "${exercise_dir}" ]]; then
79+
verify_exercise "${exercise_dir}"
80+
((++count))
81+
fi
82+
done
83+
((count > 0)) || die 'no matching exercises found!'
84+
}
85+
86+
pull_docker_image
87+
88+
exercise_slug="${1:-*}"
89+
verify_exercises "${exercise_slug}"

0 commit comments

Comments
 (0)