Skip to content

Commit 225bbb4

Browse files
Add Docker smoke tests
1 parent 2cc8f5d commit 225bbb4

File tree

14 files changed

+451
-0
lines changed

14 files changed

+451
-0
lines changed

.dockerignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ dev.Dockerfile
99
.gitignore
1010
.gitattributes
1111
.dockerignore
12+
/bin/
13+
!/bin/run.sh
14+
!/bin/prepare.sh
15+
test/

bin/run-in-docker.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env sh
2+
3+
# Synopsis:
4+
# Run the test runner on a solution using the test runner Docker image.
5+
# The test runner Docker image is built automatically.
6+
7+
# Arguments:
8+
# $1: exercise slug
9+
# $2: path to solution folder
10+
# $3: path to output directory
11+
12+
# Output:
13+
# Writes the test results to a results.json file in the passed-in output directory.
14+
# The test results are formatted according to the specifications at https://github.com/exercism/docs/blob/main/building/tooling/test-runners/interface.md
15+
16+
# Example:
17+
# ./bin/run-in-docker.sh two-fer path/to/solution/folder/ path/to/output/directory/
18+
19+
# Stop executing when a command returns a non-zero return code
20+
set -e
21+
22+
# If any required arguments is missing, print the usage and exit
23+
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
24+
echo "usage: ./bin/run-in-docker.sh exercise-slug path/to/solution/folder/ path/to/output/directory/"
25+
exit 1
26+
fi
27+
28+
slug="$1"
29+
solution_dir=$(realpath "${2%/}")
30+
output_dir=$(realpath "${3%/}")
31+
32+
# Create the output directory if it doesn't exist
33+
mkdir -p "${output_dir}"
34+
35+
# Build the Docker image
36+
docker build --rm -t exercism/javascript-test-runner .
37+
38+
# Run the Docker image using the settings mimicking the production environment
39+
docker run \
40+
--rm \
41+
--network none \
42+
--read-only \
43+
--mount type=bind,src="${solution_dir}",dst=/solution \
44+
--mount type=bind,src="${output_dir}",dst=/output \
45+
--mount type=tmpfs,dst=/tmp \
46+
exercism/javascript-test-runner "${slug}" /solution /output

bin/run-tests-in-docker.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env sh
2+
3+
# Synopsis:
4+
# Test the test runner Docker image by running it against a predefined set of
5+
# solutions with an expected output.
6+
# The test runner Docker image is built automatically.
7+
8+
# Output:
9+
# Outputs the diff of the expected test results against the actual test results
10+
# generated by the test runner Docker image.
11+
12+
# Example:
13+
# ./bin/run-tests-in-docker.sh
14+
15+
# Stop executing when a command returns a non-zero return code
16+
set -e
17+
18+
# Build the Docker image
19+
docker build --rm -t exercism/javascript-test-runner .
20+
21+
# Run the Docker image using the settings mimicking the production environment
22+
docker run \
23+
--rm \
24+
--network none \
25+
--read-only \
26+
--mount type=bind,src="${PWD}/test/fixtures",dst=/opt/test-runner/test/fixtures \
27+
--mount type=tmpfs,dst=/tmp \
28+
--volume "${PWD}/bin/run-tests.sh:/opt/test-runner/bin/run-tests.sh" \
29+
--workdir /opt/test-runner \
30+
--entrypoint /opt/test-runner/bin/run-tests.sh \
31+
exercism/javascript-test-runner

bin/run-tests.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env sh
2+
3+
# Synopsis:
4+
# Test the test runner by running it against a predefined set of solutions
5+
# with an expected output.
6+
7+
# Output:
8+
# Outputs the diff of the expected test results against the actual test results
9+
# generated by the test runner.
10+
11+
# Example:
12+
# ./bin/run-tests.sh
13+
14+
exit_code=0
15+
16+
# We need to copy the fixtures to a temp directory as the user
17+
# running within the Docker container does not have permissions
18+
# to run the sed command on the fixtures directory
19+
fixtures_dir="test/fixtures"
20+
tmp_fixtures_dir="/tmp/test/fixtures"
21+
rm -rf "${tmp_fixtures_dir}"
22+
mkdir -p "${tmp_fixtures_dir}"
23+
cp -R ${fixtures_dir}/* "${tmp_fixtures_dir}"
24+
25+
# Iterate over all test directories
26+
for test_file in $(find "${tmp_fixtures_dir}" -name '*.spec.js'); do
27+
test_dir=$(dirname "${test_file}")
28+
test_dir_name=$(basename "${test_dir}")
29+
test_dir_path=$(realpath "${test_dir}")
30+
results_file_path="${test_dir_path}/results.json"
31+
expected_results_file_path="${test_dir_path}/expected_results.json"
32+
33+
bin/run.sh "${test_dir_name}" "${test_dir_path}" "${test_dir_path}"
34+
35+
echo "${test_dir_name}: comparing results.json to expected_results.json"
36+
diff "${results_file_path}" "${expected_results_file_path}"
37+
38+
if [ $? -ne 0 ]; then
39+
exit_code=1
40+
fi
41+
done
42+
43+
exit ${exit_code}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"status": "pass",
3+
"tests": [
4+
{
5+
"name": "Clock > Creating a new clock with an initial time > on the hour",
6+
"status": "pass",
7+
"message": "",
8+
"output": null,
9+
"test_code": "expect(new Clock(8).toString()).toEqual('08:00');"
10+
}
11+
],
12+
"version": 3
13+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"status": "pass",
3+
"tests": [
4+
{
5+
"name": "EXPECTED_MINUTES_IN_OVEN > constant is defined correctly",
6+
"status": "pass",
7+
"message": "",
8+
"output": null,
9+
"test_code": "expect(EXPECTED_MINUTES_IN_OVEN).toBe(40);",
10+
"task_id": 1
11+
},
12+
{
13+
"name": "remainingMinutesInOven > calculates the remaining time",
14+
"status": "pass",
15+
"message": "",
16+
"output": null,
17+
"test_code": "expect(remainingMinutesInOven(25)).toBe(15);\nexpect(remainingMinutesInOven(5)).toBe(35);\nexpect(remainingMinutesInOven(39)).toBe(1);",
18+
"task_id": 2
19+
},
20+
{
21+
"name": "remainingMinutesInOven > works correctly for the edge cases",
22+
"status": "pass",
23+
"message": "",
24+
"output": null,
25+
"test_code": "expect(remainingMinutesInOven(40)).toBe(0);\nexpect(remainingMinutesInOven(0)).toBe(40);",
26+
"task_id": 2
27+
},
28+
{
29+
"name": "preparationTimeInMinutes > calculates the preparation time",
30+
"status": "pass",
31+
"message": "",
32+
"output": null,
33+
"test_code": "expect(preparationTimeInMinutes(1)).toBe(2);\nexpect(preparationTimeInMinutes(2)).toBe(4);\nexpect(preparationTimeInMinutes(8)).toBe(16);",
34+
"task_id": 3
35+
},
36+
{
37+
"name": "totalTimeInMinutes > calculates the total cooking time",
38+
"status": "pass",
39+
"message": "",
40+
"output": null,
41+
"test_code": "expect(totalTimeInMinutes(1, 5)).toBe(7);\nexpect(totalTimeInMinutes(4, 15)).toBe(23);\nexpect(totalTimeInMinutes(1, 35)).toBe(37);",
42+
"task_id": 4
43+
}
44+
],
45+
"version": 3
46+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"status": "pass",
3+
"tests": [
4+
{
5+
"name": "strings > front door > it outputs a character per line",
6+
"status": "pass",
7+
"message": "",
8+
"output": null,
9+
"test_code": "const key = ShireGuard.recite();\nexpect(key.length).toBe(SHIRE_HORSE.length);"
10+
},
11+
{
12+
"name": "strings > front door > it outputs takes the first characters",
13+
"status": "pass",
14+
"message": "",
15+
"output": null,
16+
"test_code": "const key = ShireGuard.recite();\nexpect(key.toUpperCase()).toBe(SHIRE_HORSE.acrostic.toUpperCase());"
17+
},
18+
{
19+
"name": "strings > front door > it generates the correct password",
20+
"status": "pass",
21+
"message": "",
22+
"output": null,
23+
"test_code": "ShireGuard.assert();"
24+
},
25+
{
26+
"name": "strings > front door > frontDoorPassword(SUMMER)",
27+
"status": "pass",
28+
"message": "",
29+
"output": null,
30+
"test_code": null
31+
},
32+
{
33+
"name": "strings > front door > frontDoorPassword(SOPHIA)",
34+
"status": "pass",
35+
"message": "",
36+
"output": null,
37+
"test_code": null
38+
},
39+
{
40+
"name": "strings > front door > frontDoorPassword(CODE)",
41+
"status": "pass",
42+
"message": "",
43+
"output": null,
44+
"test_code": null
45+
},
46+
{
47+
"name": "strings > back door > it outputs a character per line",
48+
"status": "pass",
49+
"message": "",
50+
"output": null,
51+
"test_code": "const key = ShireGuard.recite();\nexpect(key.length).toBe(SHIRE_HORSE.length);"
52+
},
53+
{
54+
"name": "strings > back door > it outputs takes the first characters",
55+
"status": "pass",
56+
"message": "",
57+
"output": null,
58+
"test_code": "const key = ShireGuard.recite();\nexpect(key.toUpperCase()).toBe(SHIRE_HORSE.telestich.toUpperCase());"
59+
},
60+
{
61+
"name": "strings > back door > it generates the correct password",
62+
"status": "pass",
63+
"message": "",
64+
"output": null,
65+
"test_code": "ShireGuard.assert();"
66+
},
67+
{
68+
"name": "strings > back door > backDoorGuard(WORK)",
69+
"status": "pass",
70+
"message": "",
71+
"output": null,
72+
"test_code": null
73+
}
74+
],
75+
"version": 3
76+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"status": "pass",
3+
"tests": [
4+
{
5+
"name": "Triplet > triplets whose sum is 12",
6+
"status": "pass",
7+
"message": "",
8+
"output": null,
9+
"test_code": "expect(tripletsWithSum(12)).toEqual([[3, 4, 5]]);"
10+
},
11+
{
12+
"name": "Triplet > triplets whose sum is 108",
13+
"status": "pass",
14+
"message": "",
15+
"output": null,
16+
"test_code": "expect(tripletsWithSum(108)).toEqual([[27, 36, 45]]);"
17+
},
18+
{
19+
"name": "Triplet > triplets whose sum is 1000",
20+
"status": "pass",
21+
"message": "",
22+
"output": null,
23+
"test_code": "expect(tripletsWithSum(1000)).toEqual([[200, 375, 425]]);"
24+
},
25+
{
26+
"name": "Triplet > no matching triplets for 1001",
27+
"status": "pass",
28+
"message": "",
29+
"output": null,
30+
"test_code": "expect(tripletsWithSum(1001)).toEqual([]);"
31+
},
32+
{
33+
"name": "Triplet > returns all matching triplets",
34+
"status": "pass",
35+
"message": "",
36+
"output": null,
37+
"test_code": "expect(tripletsWithSum(90)).toEqual([\n [9, 40, 41],\n [15, 36, 39],\n ]);"
38+
},
39+
{
40+
"name": "Triplet > several matching triplets",
41+
"status": "pass",
42+
"message": "",
43+
"output": null,
44+
"test_code": "expect(tripletsWithSum(840)).toEqual([\n [40, 399, 401],\n [56, 390, 394],\n [105, 360, 375],\n [120, 350, 370],\n [140, 336, 364],\n [168, 315, 357],\n [210, 280, 350],\n [240, 252, 348],\n ]);"
45+
},
46+
{
47+
"name": "Triplet > returns triplets with no factor smaller than minimum factor",
48+
"status": "pass",
49+
"message": "",
50+
"output": null,
51+
"test_code": "expect(tripletsWithSum(90, { minFactor: 10 })).toEqual([[15, 36, 39]]);"
52+
},
53+
{
54+
"name": "Triplet > returns triplets with no factor larger than maximum factor",
55+
"status": "pass",
56+
"message": "",
57+
"output": null,
58+
"test_code": "expect(tripletsWithSum(840, { maxFactor: 349 })).toEqual([[240, 252, 348]]);"
59+
},
60+
{
61+
"name": "Triplet > returns triplets with factors in range",
62+
"status": "pass",
63+
"message": "",
64+
"output": null,
65+
"test_code": "expect(tripletsWithSum(840, { maxFactor: 352, minFactor: 150 })).toEqual([\n [210, 280, 350],\n [240, 252, 348],\n ]);"
66+
}
67+
],
68+
"version": 3
69+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"status": "error",
3+
"message": "SyntaxError: <solution>/two-fer.spec.js: Unexpected token (2:0)\n\n \u001b[0m \u001b[90m 1 |\u001b[39m describe(\u001b[32m'twoFer()'\u001b[39m\u001b[33m,\u001b[39m t(\u001b[32m'another name given'\u001b[39m\u001b[33m,\u001b[39m () \u001b[33m=>\u001b[39m {\u001b[0m\n \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 2 |\u001b[39m\u001b[0m\n \u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n",
4+
"tests": [],
5+
"version": 3
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"status": "error",
3+
"message": "SyntaxError: <solution>/two-fer.js: Unexpected keyword 'const'. (1:13)\n\n \u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 1 |\u001b[39m \u001b[36mexport\u001b[39m \u001b[36mconst\u001b[39m \u001b[36mconst\u001b[39m () \u001b[33m=>\u001b[39m { (\u001b[0m\n \u001b[0m \u001b[90m |\u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n \u001b[0m \u001b[90m 2 |\u001b[39m\u001b[0m\n",
4+
"tests": [],
5+
"version": 3
6+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"status": "fail",
3+
"tests": [
4+
{
5+
"name": "twoFer() > no name given",
6+
"status": "fail",
7+
"message": "TypeError: (0 , _twoFer.twoFer) is not a function",
8+
"output": null,
9+
"test_code": "expect(twoFer()).toEqual('One for you, one for me.');"
10+
},
11+
{
12+
"name": "twoFer() > a name given",
13+
"status": "fail",
14+
"message": "TypeError: (0 , _twoFer.twoFer) is not a function",
15+
"output": null,
16+
"test_code": "const name = 'Alice';\nexpect(twoFer(name)).toEqual('One for Alice, one for me.');"
17+
},
18+
{
19+
"name": "twoFer() > another name given",
20+
"status": "fail",
21+
"message": "TypeError: (0 , _twoFer.twoFer) is not a function",
22+
"output": null,
23+
"test_code": "const name = 'Bob';\nexpect(twoFer(name)).toEqual('One for Bob, one for me.');"
24+
}
25+
],
26+
"version": 3
27+
}

0 commit comments

Comments
 (0)