Skip to content

Commit ddd15b3

Browse files
authored
New test runner design (#130)
Closes #110
1 parent b919716 commit ddd15b3

File tree

39 files changed

+454
-2426
lines changed

39 files changed

+454
-2426
lines changed

.dockerignore

+10-21
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,11 @@
1-
# Ignore git details
2-
.git
3-
.gitignore
4-
.github
1+
# Disallow everything
2+
*
53

6-
# Ignore run artifacts
7-
error_log
8-
metadata.json
9-
output
10-
output.json
11-
results.xml
12-
results.json
13-
14-
# Ignore smoke test files
15-
test/
16-
17-
junit-to-json/node_modules/
18-
junit-to-json/**/*.js
19-
.appends
20-
.gitattributes
21-
.dockerignore
22-
Dockerfile
4+
# Allow list
5+
!bin/run.sh
6+
!CODE_OF_CONDUCT.md
7+
!composer.json
8+
!LICENSE
9+
!phpunit.xml
10+
!README.md
11+
!src

.github/CODEOWNERS

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
* @neenjaw
2-
.github/CODEOWNERS @exercism/maintainers-admin
1+
* @mk-mxp
2+
.github/CODEOWNERS @exercism/maintainers-admin

.github/workflows/smoketest.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- name: Build Docker image and store in cache
1919
uses: docker/build-push-action@16ebe778df0e7752d2cfcbd924afdbbd89c1a755
2020
env:
21-
DOCKER_BUILD_NO_SUMMARY: true
21+
DOCKER_BUILD_SUMMARY: false
2222
with:
2323
context: .
2424
push: false

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ results.xml
1111
results.json
1212

1313
*.scratch
14+
15+
/vendor/
16+
composer.lock

Dockerfile

+24-22
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,40 @@
1-
FROM php:8.3.4-cli-alpine3.19 AS build
1+
FROM php:8.3.10-cli-alpine3.20 AS build
22

3-
RUN apk update && \
4-
apk add --no-cache ca-certificates curl jo zip unzip
3+
RUN apk add --no-cache ca-certificates curl jo zip unzip
54

65
WORKDIR /usr/local/bin
76

8-
RUN curl -L -o install-php-extensions https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions && \
9-
chmod +x install-php-extensions && \
10-
install-php-extensions ds-1.5.0 intl
7+
RUN curl -L -o install-php-extensions \
8+
https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions \
9+
&& chmod +x install-php-extensions \
10+
&& install-php-extensions ds-^1@stable intl
1111

12-
RUN curl -L -o phpunit-10.phar https://phar.phpunit.de/phpunit-10.phar && \
13-
chmod +x phpunit-10.phar
12+
COPY --from=composer:2.7.7 /usr/bin/composer /usr/local/bin/composer
1413

15-
WORKDIR /usr/local/bin/junit-handler/
16-
COPY --from=composer:2.7.2 /usr/bin/composer /usr/local/bin/composer
17-
COPY junit-handler/ .
18-
# We need PHPUnit from junit-handler/ to run test-runner tests in CI / locally
19-
# composer warns about missing a "root version" to resolve dependencies. Fake to stop warning
20-
RUN COMPOSER_ROOT_VERSION=1.0.0 composer install --no-interaction
21-
22-
FROM php:8.3.4-cli-alpine3.19 AS runtime
14+
WORKDIR /opt/test-runner
15+
COPY . .
16+
# composer warns about missing a "root version" to resolve dependencies. Fake to stop warning.
17+
# composer warns about running as root. Silence it, we know what we are doing.
18+
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
19+
&& php --modules \
20+
&& COMPOSER_ALLOW_SUPERUSER=1 \
21+
composer --version \
22+
&& COMPOSER_ROOT_VERSION=1.0.0 \
23+
COMPOSER_ALLOW_SUPERUSER=1 \
24+
composer install --no-cache --no-dev --no-interaction --no-progress
25+
26+
FROM php:8.3.10-cli-alpine3.20 AS runtime
2327

2428
COPY --from=build /usr/bin/jo /usr/bin/jo
2529
COPY --from=build /usr/local/lib/php/extensions /usr/local/lib/php/extensions
26-
COPY --from=build /usr/local/bin/phpunit-10.phar /opt/test-runner/bin/phpunit-10.phar
27-
COPY --from=build /usr/local/bin/junit-handler /opt/test-runner/junit-handler
30+
COPY --from=build /opt/test-runner /opt/test-runner
2831

2932
# Use the default production configuration
30-
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
31-
RUN apk add --no-cache bash
32-
RUN adduser -Ds /bin/bash appuser
33+
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
34+
&& apk add --no-cache bash \
35+
&& adduser -Ds /bin/bash appuser
3336

3437
WORKDIR /opt/test-runner
35-
COPY . .
3638

3739
USER appuser
3840

README.md

+16-8
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ It meets the complete spec for testing all exercises.
1010
### Docker image
1111

1212
The website uses isolated Docker images to run untrusted code in a sandbox.
13-
The image provided by this repository consists of PHP 8.3.4 (PHPUnit 10).
13+
The image provided by this repository consists of PHP 8.3.10 (PHPUnit 10).
1414
All final assets are built into the image, because the image does not have network access once in use.
1515

1616
Includes PHP extensions: ds, intl
@@ -28,15 +28,23 @@ This is what runs inside the production Docker image when students submit their
2828
### Testing the test runner
2929

3030
In `./tests/` are golden tests to verify that the test runner behaves as expected.
31-
The CI uses `bin/run-tests.sh` to execute them.
31+
The CI uses `bin/run-tests.sh` to execute them in the Docker image.
32+
33+
### Running tests locally
34+
35+
Recommended to easily test new test cases during development.
36+
Use `bin/run-locally.sh <test-slug>` to run PHPUnit in your current shell and compare the resulting JSON to `expected_results.json`.
37+
Make sure you have at least PHP 8.1 installed.
3238

3339
### Running tests in Docker locally
3440

35-
This is the recommended way to use this locally.
36-
Use `bin/run-in-docker.sh <test-slug> <directory path to solution> <directory path for output>` and `bin/run-tests-in-docker.sh` to locally build and run the Docker image.
41+
This is the recommended way to run all tests locally as they would run in production.
42+
Use `bin/run-tests-in-docker.sh` to locally build and run all tests in the Docker image.
43+
Use `bin/run-in-docker.sh <test-slug> <directory path to solution> <directory path for output>` for a single test run in the Docker image.
3744

38-
### JUnit to JSON
45+
### PHPUnit extension
3946

40-
PHPUnit can natively output tests run to JUnit XML format, but Exercism requires output in JSON format.
41-
A PHP-based app is located in the `junit-handler` folder.
42-
It provides a translation layer from one format to the other incorporating `task_id` identification and test code inclusion.
47+
PHPUnit can natively output test results to various formats, but Exercism requires output in a special JSON format.
48+
An extension to PHPUnit is located in `src` folder and registered in `phpunit.xml`.
49+
It provides a tracer for PHPUnit events to produce the required JSON result.
50+
The tracer incorporates `task_id` identification, test code inclusion, and user output dumping.

bin/run-locally.sh

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env bash
2+
3+
EXERCISM_RESULT_FILE="${PWD%/}/results.json" \
4+
EXERCISM_EXERCISE_DIR="${PWD%/}/tests/${1}" \
5+
vendor/bin/phpunit --do-not-cache-result tests/"${1}"/*Test.php
6+
7+
# Sync'ed from run-tests.sh - Normalize the object ID of `var_dump(new stdClass())`
8+
sed -i -E \
9+
-e 's/(object\(stdClass\))(#[[:digit:]]+)/\1#79/g' \
10+
results.json
11+
diff results.json tests/"${1}"/expected_results.json

bin/run-tests-in-docker.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ docker run \
2323
--rm \
2424
--network none \
2525
--read-only \
26-
--mount type=bind,src="${PWD}/tests",dst=/opt/test-runner/tests \
2726
--mount type=tmpfs,dst=/tmp \
27+
--volume "${PWD}/tests:/opt/test-runner/tests" \
2828
--volume "${PWD}/bin/run-tests.sh:/opt/test-runner/bin/run-tests.sh" \
2929
--workdir /opt/test-runner \
3030
--entrypoint /opt/test-runner/bin/run-tests.sh \

bin/run.sh

+11-30
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
set -euo pipefail
44

5-
PHPUNIT_BIN="./bin/phpunit-10.phar"
6-
JUNIT_RESULTS='results.xml'
7-
TEAMCITY_RESULTS='teamcity.txt'
5+
PHPUNIT_BIN="./vendor/bin/phpunit"
86
EXERCISM_RESULTS='results.json'
97
# shellcheck disable=SC2034 # Modifies XDebug behaviour when invoking PHP
108
XDEBUG_MODE='off'
@@ -25,19 +23,16 @@ function main {
2523
return 0;
2624
fi
2725

28-
# JUnit results contain the unit test failures only. But they contain `@testdox` - readable test names.
29-
# Teamcity results contain user output in addition to failures as "failed risky tests"
30-
# (command line arguments --disallow-test-output and --fail-on-risky).
31-
# At the moment we require both logs to provide all information to the website.
32-
output=$("${PHPUNIT_BIN}" \
33-
-d memory_limit=300M \
34-
--log-junit "${output_dir%/}/${JUNIT_RESULTS}" \
35-
--log-teamcity "${output_dir%/}/${TEAMCITY_RESULTS}" \
36-
--no-configuration \
37-
--do-not-cache-result \
38-
--disallow-test-output \
39-
--fail-on-risky \
40-
"${test_files%%*( )}" 2>&1)
26+
# Our PHPUnit extension writes directly to ${EXERCISM_RESULT_FILE}
27+
# Our PHPUnit extension requires ${EXERCISM_EXERCISE_DIR} before PHPUnit provides it
28+
output=$( \
29+
EXERCISM_RESULT_FILE="${output_dir%/}/${EXERCISM_RESULTS}" \
30+
EXERCISM_EXERCISE_DIR="${solution_dir%/}" \
31+
"${PHPUNIT_BIN}" \
32+
-d memory_limit=300M \
33+
--do-not-cache-result \
34+
"${test_files%%*( )}" 2>&1 \
35+
)
4136
phpunit_exit_code=$?
4237
set -e
4338

@@ -48,20 +43,6 @@ function main {
4843
jo version=3 status=error message="${output//"$solution_dir/"/""}" tests="[]" > "${output_dir%/}/${EXERCISM_RESULTS}"
4944
return 0;
5045
fi
51-
52-
# This catches runtime errors in "global student code" (during `require_once`)
53-
if [[ "${phpunit_exit_code}" -eq 2 ]]; then
54-
if ! grep -q '<testcase' "${output_dir%/}/${JUNIT_RESULTS}"; then
55-
output="${output#*" MB"}"
56-
jo version=3 status=error message="${output//"$solution_dir/"/""}" tests="[]" > "${output_dir%/}/${EXERCISM_RESULTS}"
57-
return 0;
58-
fi
59-
fi
60-
61-
php junit-handler/run.php \
62-
"${output_dir%/}/${EXERCISM_RESULTS}" \
63-
"${output_dir%/}/${JUNIT_RESULTS}" \
64-
"${output_dir%/}/${TEAMCITY_RESULTS}"
6546
}
6647

6748
function installed {

composer.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "exercism/php-test-runner",
3+
"type": "project",
4+
"license": "AGPL-3.0-only",
5+
"autoload": {
6+
"psr-4": {
7+
"Exercism\\PhpTestRunner\\": "src/"
8+
}
9+
},
10+
"require": {
11+
"phpunit/phpunit": "^10.5.29"
12+
}
13+
}

junit-handler/.gitignore

-1
This file was deleted.

junit-handler/composer.json

-21
This file was deleted.

0 commit comments

Comments
 (0)