Skip to content

Commit aec4d35

Browse files
More CI docs (#554)
* More CI docs * Better document setting up CI
1 parent 6ca912f commit aec4d35

File tree

1 file changed

+284
-10
lines changed

1 file changed

+284
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,290 @@
11
# Set up Continuous Integration
22

3-
Setting up Continuous Integration (CI) for your track is very important, as it helps to automatically catch mistakes.
3+
Setting up Continuous Integration (CI) for your track is very important, as it helps automatically catch mistakes.
44

5-
Our tracks all use [GitHub Actions](https://docs.github.com/en/actions) to run their CI.
6-
GitHub actions uses the concept of _workflows_, which are scripts that are run automatically whenever a specific event occurs (e.g. pushing a commit).
5+
## GitHub Actions
76

8-
Each workflow corresponds to a file in `.github/workflows`.
9-
Each new track repository comes pre-loaded with three workflows:
7+
Our tracks (and other repositories) use [GitHub Actions](https://docs.github.com/en/actions) to run their CI.
8+
GitHub Actions uses the concept of _workflows_, which are scripts that run automatically whenever a specific event occurs (e.g. pushing a commit).
109

11-
- `test.yml`: this workflow should run the tests for each exercise the track has implemented
12-
- `configlet.yml`: this workflow runs the [configlet tool](/docs/building/configlet), which checks if a track's (configuration) files are properly structured - both syntactically and semantically.
13-
- `sync-labels.yml`: this workflow automatically syncs the repository's labels from a `labels.yml` file
10+
Each GitHub Actions workflow is defined in a `.yml` file in the `.github/workflows` directory.
11+
For information on workflows, check the following docs:
1412

15-
Of these three workflows, only the first workflow will need some manual work.
16-
To find out what needs to happen, please check the `test.yml` file's contents, which has TODO comments to help you.
13+
- [Workflow syntax](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions)
14+
- [Choosing when your workflow runs](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/triggering-a-workflow)
15+
- [Choosing where your workflow runs](https://docs.github.com/en/actions/writing-workflows/choosing-where-your-workflow-runs)
16+
- [Choose what your workflow does](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does)
17+
- [Writing workflows](https://docs.github.com/en/actions/writing-workflows)
18+
- [Best practices](/docs/building/github/gha-best-practices)
19+
20+
## Pre-defined workflows
21+
22+
A track repository contains several pre-defined workflows:
23+
24+
- `configlet.yml`: runs the [configlet tool](/docs/building/configlet), which checks if a track's (configuration) files are properly structured - both syntactically and semantically
25+
- `no-important-files-changed.yml`: checks if pull requests would cause all existing solutions of one or more changes exercises to be re-run
26+
- `sync-labels.yml`: automatically syncs the repository's labels from a `labels.yml` file
27+
- `test.yml`: verify the track's exercises
28+
29+
Of these workflows, _only_ the `test.yml` workflow requires manual work.
30+
The other workflows should not be changed (we keep them up-to-date automatically).
31+
32+
## Test workflow
33+
34+
The test workflow should verify the track's exercises.
35+
The workflow itself should not do much, except for:
36+
37+
- Checking out the code (already implemented)
38+
- Installing dependencies (e.g. installing an SDK, optional)
39+
- Running the script to verify the exercises (already implemented)
40+
41+
### Verify exercises script
42+
43+
As mentioned, the exercises are verified via a script, namely the `bin/verify-exercises` (bash) script.
44+
This script is _almost_ done, and does the following:
45+
46+
- Loops over all exercise directories
47+
- For each exercise directory, it then:
48+
- Copies the example/exemplar solution to the (stub) solution files (already implemented)
49+
- Calls the `unskip_tests` function in which you can unskip tests in your test files (optional)
50+
- Calls the `run_tests` function in which you should run the tests (required)
51+
52+
The `run_tests` and `unskip_tests` functions are the only things that you need to implement.
53+
54+
### Unskipping tests
55+
56+
If your track supports skipping tests, we must ensure that no tests are skipped when verifying an exercise's example/exemplar solution.
57+
In general, there are two ways in which tracks support "unskipping" tests:
58+
59+
1. Removing annotations/code/text from the test files.
60+
For example, changing `test.skip` to `test`.
61+
2. Providing an environment variable.
62+
For example, setting `SKIP_TESTS=false`.
63+
64+
If skipping tests is file-based (the first option mentioned above), edit the `unskip_tests` function to modify the test files (the existing code already handles the looping over the test files).
65+
66+
```exercism/note
67+
The `unskip_test` function runs on a copy of an exercise directory, so feel free to modify the files as you see fit.
68+
```
69+
70+
If unskipping tests requires an environment variable to be set, make sure that it is set in the `run_tests` function.
71+
72+
### Running tests
73+
74+
The `run_tests` function is responsible for running the tests of an exercise.
75+
When the function is called, the example/exemplar files will already have been copied to (stub) solution files, so you only need to call the right command to run the tests.
76+
77+
The function must return a zero as the exit code if all tests pass, otherwise return a non-zero exit code.
78+
79+
```exercism/note
80+
The `run_tests` function runs on a copy of an exercise directory, so feel free to modify the files as you see fit.
81+
```
82+
83+
### Example: Arturo track
84+
85+
This is what the [`bin/verify-exercises` file](https://github.com/exercism/arturo/blob/79560f853f5cb8e2f3f0a07cbb8fcce8438ee996/bin/verify-exercises) looks file for the Arturo track:
86+
87+
```bash
88+
#!/usr/bin/env bash
89+
90+
# Synopsis:
91+
# Test the track's exercises.
92+
93+
# Example: verify all exercises
94+
# ./bin/verify-exercises
95+
96+
# Example: verify single exercise
97+
# ./bin/verify-exercises two-fer
98+
99+
set -eo pipefail
100+
101+
required_tool() {
102+
command -v "${1}" >/dev/null 2>&1 ||
103+
die "${1} is required but not installed. Please install it and make sure it's in your PATH."
104+
}
105+
106+
required_tool jq
107+
108+
copy_example_or_examplar_to_solution() {
109+
jq -c '[.files.solution, .files.exemplar // .files.example] | transpose | map({src: .[1], dst: .[0]}) | .[]' .meta/config.json | while read -r src_and_dst; do
110+
cp "$(echo "${src_and_dst}" | jq -r '.src')" "$(echo "${src_and_dst}" | jq -r '.dst')"
111+
done
112+
}
113+
114+
unskip_tests() {
115+
jq -r '.files.test[]' .meta/config.json | while read -r test_file; do
116+
sed -i 's/test.skip/test/g' "${test_file}"
117+
done
118+
}
119+
120+
run_tests() {
121+
arturo tester.art
122+
}
123+
124+
verify_exercise() {
125+
local dir
126+
local slug
127+
local tmp_dir
128+
129+
dir=$(realpath "${1}")
130+
slug=$(basename "${dir}")
131+
tmp_dir=$(mktemp -d -t "exercism-verify-${slug}-XXXXX")
132+
133+
echo "Verifying ${slug} exercise..."
134+
135+
(
136+
cp -r "${dir}/." "${tmp_dir}"
137+
cd "${tmp_dir}"
138+
139+
copy_example_or_examplar_to_solution
140+
unskip_tests
141+
run_tests
142+
)
143+
}
144+
145+
exercise_slug="${1:-*}"
146+
147+
shopt -s nullglob
148+
for exercise_dir in ./exercises/{concept,practice}/${exercise_slug}/; do
149+
if [ -d "${exercise_dir}" ]; then
150+
verify_exercise "${exercise_dir}"
151+
fi
152+
done
153+
```
154+
155+
It uses `sed` to unskip tests:
156+
157+
```bash
158+
sed -i 's/test.skip/test/g' "${test_file}"
159+
```
160+
161+
and runs the tests via the `arturo` command:
162+
163+
```bash
164+
arturo tester.art
165+
```
166+
167+
## Implement the test workflow
168+
169+
The goal of the test workflow (defined in `.github/workflows/test.yml`) is to automatically verify that the track's exercises are in proper shape.
170+
The workflow is setup to run automatically (in GitHub Actions terminology: is _triggered_) when a push is made to the `main` branch or to a pull request's branch.
171+
172+
There are three options when implementing this workflow:
173+
174+
### Option 1: install track-specific tooling (e.g. an SDK) in the GitHub Actions runner instance
175+
176+
In this approach, any track-specific tooling (e.g. an SDK) is installed directly in the GitHub Actions runner instance.
177+
Once done, you then run the `bin/verify-exercises` script (which assumes the track tooling is installed).
178+
179+
For an example, see the [Arturo track's `test.yml` workflow](https://github.com/exercism/arturo/blob/79560f853f5cb8e2f3f0a07cbb8fcce8438ee996/.github/workflows/test.yml):
180+
181+
```yml
182+
name: Test
183+
184+
on:
185+
push:
186+
branches: [main]
187+
pull_request:
188+
branches: [main]
189+
workflow_dispatch:
190+
191+
jobs:
192+
ci:
193+
runs-on: ubuntu-22.04
194+
195+
steps:
196+
- name: Checkout repository
197+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
198+
199+
- name: Install dependencies
200+
run: |
201+
sudo apt-get update
202+
sudo apt-get install libgtk-3-dev libwebkit2gtk-4.0-dev libmpfr-dev
203+
204+
- name: Install Arturo
205+
run: bin/install-arturo
206+
env:
207+
GH_TOKEN: ${{ github.token }}
208+
209+
- name: Verify all exercises
210+
run: bin/verify-exercises
211+
```
212+
213+
#### Option 2: running the verify exercises script within test runner Docker image
214+
215+
In this option, we're using the fact that each track must have a test runner which has all dependencies already installed
216+
To enable this option, we need to set the workflow's container to the test runner:
217+
218+
```yml
219+
container:
220+
image: exercism/vimscript-test-runner
221+
```
222+
223+
This will then automatically pull the test runner Docker image when the workflow executes, and run the `verify-exercises` script within that Docker container.
224+
225+
```exercism/note
226+
The main benefit of this approach is that it better mimics how tests are being run in production (on the website).
227+
With the approach, it is less likely that things will fail in production that passed in CI.
228+
The downside of this approach is that it likely is slower, due to having to pull the Docker image and the overhead of Docker.
229+
```
230+
231+
For an example, see the [vimscript track's `test.yml` workflow](https://github.com/exercism/vimscript/blob/e599cd6e02cbcab2c38c5112caed8bef6cdb3c38/.github/workflows/test.yml).
232+
233+
```yml
234+
name: Verify Exercises
235+
236+
on:
237+
push:
238+
branches: [main]
239+
pull_request:
240+
workflow_dispatch:
241+
242+
jobs:
243+
ci:
244+
runs-on: ubuntu-24.04
245+
container:
246+
image: exercism/vimscript-test-runner
247+
248+
steps:
249+
- name: Checkout repository
250+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
251+
252+
- name: Verify all exercises
253+
run: bin/verify-exercises
254+
```
255+
256+
### Option 3: download the test runner Docker image and change verify exercises script
257+
258+
In this option, we're using the fact that each track must have a test runner which already knows how to verify exercises.
259+
To enable this option, we first need to download (pull) the track's test runner Docker image and then run the `bin/verify-exercises` script, which is modified to use the test runner Docker image to run the tests.
260+
261+
```exercism/note
262+
The main benefit of this approach is that it best mimics how tests are being run in production (on the website).
263+
With the approach, it is less likely that things will fail in production that passed in CI.
264+
The downside of this approach is that it likely is slower, due to having to pull the Docker image and the overhead of Docker.
265+
```
266+
267+
For an example, see the [Standard ML track's `test.yml` workflow](https://github.com/exercism/sml/blob/e63e93ee50d8d7f0944ff4b7ad385819b86e1693/.github/workflows/ci.yml).
268+
269+
```yml
270+
name: sml / ci
271+
272+
on:
273+
pull_request:
274+
push:
275+
branches: [main]
276+
workflow_dispatch:
277+
278+
jobs:
279+
ci:
280+
runs-on: ubuntu-22.04
281+
282+
steps:
283+
- name: Checkout code
284+
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
285+
286+
- run: docker pull exercism/sml-test-runner
287+
288+
- name: Run tests for all exercises
289+
run: sh ./bin/test
290+
```

0 commit comments

Comments
 (0)