Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New test runner design #130

Merged
merged 45 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
90f8915
Make project with PHPUnit as no-dev requirement
mk-mxp Aug 4, 2024
0a965e4
Configure PHPUnit to load our extension stub
mk-mxp Aug 4, 2024
8775bd5
Produce a valid success result JSON
mk-mxp Aug 4, 2024
c66dc5c
Adjust success test case to match output produced
mk-mxp Aug 4, 2024
6a2c2b5
Re-make Docker image and test scripts
mk-mxp Aug 4, 2024
8d0b3ab
Capture user output on success into result JSON
mk-mxp Aug 4, 2024
536ebf4
Process partial fail into result JSON
mk-mxp Aug 4, 2024
c576b0e
Move reflection method finding to code extraction
mk-mxp Aug 5, 2024
2d57974
Remove unused method name from Result
mk-mxp Aug 5, 2024
013e8da
Remove PHP extension `ds` from README
mk-mxp Aug 5, 2024
6793415
Add run-locally.sh to run PHPUnit + diff directly
mk-mxp Aug 5, 2024
4b1a0e7
Process fail-with-user-output into result JSON
mk-mxp Aug 5, 2024
fccb20d
Normalize object id locally as in run-tests.sh
mk-mxp Aug 5, 2024
fef631e
Missing test case update from previous commit
mk-mxp Aug 5, 2024
673d80e
Replace all path occurences in output
mk-mxp Aug 8, 2024
c06052d
Remove dead code
mk-mxp Aug 8, 2024
dc811c1
Process all-error into result JSON
mk-mxp Aug 8, 2024
f4d43ae
Process error-with-user-output into result JSON
mk-mxp Aug 8, 2024
95fad6c
Process empty-file into result JSON
mk-mxp Aug 8, 2024
dc103ca
Process runtime-error-in-global-code
mk-mxp Aug 9, 2024
172e4d6
Process dataProvider-success-with-user-output
mk-mxp Aug 9, 2024
9bc8c86
Process dataProvider-partial-fail-with-user-output
mk-mxp Aug 9, 2024
be037d3
Process dataProvider-fail-with-user-output
mk-mxp Aug 9, 2024
ca8e7b4
Process dataProvider-error
mk-mxp Aug 10, 2024
b2a299e
Process dataProvider-error-with-user-output
mk-mxp Aug 10, 2024
15974db
Finalize the Docker image and scripts
mk-mxp Aug 10, 2024
270727e
Copy only run.sh into Docker image
mk-mxp Aug 10, 2024
fbc644c
Remove old junit-handler
mk-mxp Aug 10, 2024
d6f4d6a
Use non-deprecated env var to get no build summary
mk-mxp Aug 10, 2024
455b17a
Prevent composer super user warning
mk-mxp Aug 10, 2024
576b6b5
Show bin directory on building, too
mk-mxp Aug 10, 2024
2e27408
Prevent composer super user warning
mk-mxp Aug 10, 2024
f89c6da
Do not list Docker image content anymore
mk-mxp Aug 10, 2024
b083af8
Update code ownership
mk-mxp Aug 10, 2024
c09ff92
Process success-with-task-id
mk-mxp Aug 10, 2024
15b4845
Use allow list for .dockerignore
mk-mxp Aug 11, 2024
ead7784
Add ds extension, drop `apk update`, missing comma
mk-mxp Aug 11, 2024
2c47b05
Slice instead of filter the code line array
mk-mxp Aug 11, 2024
fb4f930
Make Result->task_id readonly
mk-mxp Aug 11, 2024
560768f
Use return of preg_match for value detection
mk-mxp Aug 11, 2024
5e59544
Add type hint to foreach(), where it may matter
mk-mxp Aug 11, 2024
6627642
Document array format and string options
mk-mxp Aug 11, 2024
dccd6ce
Add debug flags
mk-mxp Aug 11, 2024
e483d0c
Add version limit to ds extension
mk-mxp Aug 11, 2024
a237cf9
Update README
mk-mxp Aug 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 10 additions & 21 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
# Ignore git details
.git
.gitignore
.github
# Disallow everything
*

# Ignore run artifacts
error_log
metadata.json
output
output.json
results.xml
results.json

# Ignore smoke test files
test/

junit-to-json/node_modules/
junit-to-json/**/*.js
.appends
.gitattributes
.dockerignore
Dockerfile
# Allow list
!bin/run.sh
!CODE_OF_CONDUCT.md
!composer.json
!LICENSE
!phpunit.xml
!README.md
!src
4 changes: 2 additions & 2 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
* @neenjaw
.github/CODEOWNERS @exercism/maintainers-admin
* @mk-mxp
.github/CODEOWNERS @exercism/maintainers-admin
2 changes: 1 addition & 1 deletion .github/workflows/smoketest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Build Docker image and store in cache
uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445
env:
DOCKER_BUILD_NO_SUMMARY: true
DOCKER_BUILD_SUMMARY: false
with:
context: .
push: false
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ results.xml
results.json

*.scratch

/vendor/
composer.lock
homersimpsons marked this conversation as resolved.
Show resolved Hide resolved
46 changes: 24 additions & 22 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
FROM php:8.3.4-cli-alpine3.19 AS build
FROM php:8.3.10-cli-alpine3.20 AS build

RUN apk update && \
apk add --no-cache ca-certificates curl jo zip unzip
RUN apk add --no-cache ca-certificates curl jo zip unzip

WORKDIR /usr/local/bin

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

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

WORKDIR /usr/local/bin/junit-handler/
COPY --from=composer:2.7.2 /usr/bin/composer /usr/local/bin/composer
COPY junit-handler/ .
# We need PHPUnit from junit-handler/ to run test-runner tests in CI / locally
# composer warns about missing a "root version" to resolve dependencies. Fake to stop warning
RUN COMPOSER_ROOT_VERSION=1.0.0 composer install --no-interaction

FROM php:8.3.4-cli-alpine3.19 AS runtime
WORKDIR /opt/test-runner
COPY . .
# composer warns about missing a "root version" to resolve dependencies. Fake to stop warning.
# composer warns about running as root. Silence it, we know what we are doing.
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
&& php --modules \
&& COMPOSER_ALLOW_SUPERUSER=1 \
composer --version \
&& COMPOSER_ROOT_VERSION=1.0.0 \
COMPOSER_ALLOW_SUPERUSER=1 \
composer install --no-cache --no-dev --no-interaction --no-progress

FROM php:8.3.10-cli-alpine3.20 AS runtime

COPY --from=build /usr/bin/jo /usr/bin/jo
COPY --from=build /usr/local/lib/php/extensions /usr/local/lib/php/extensions
COPY --from=build /usr/local/bin/phpunit-10.phar /opt/test-runner/bin/phpunit-10.phar
COPY --from=build /usr/local/bin/junit-handler /opt/test-runner/junit-handler
COPY --from=build /opt/test-runner /opt/test-runner

# Use the default production configuration
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
RUN apk add --no-cache bash
RUN adduser -Ds /bin/bash appuser
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
&& apk add --no-cache bash \
&& adduser -Ds /bin/bash appuser

WORKDIR /opt/test-runner
COPY . .

USER appuser

Expand Down
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ It meets the complete spec for testing all exercises.
### Docker image

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

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

In `./tests/` are golden tests to verify that the test runner behaves as expected.
The CI uses `bin/run-tests.sh` to execute them.
The CI uses `bin/run-tests.sh` to execute them in the Docker image.

### Running tests locally

Recommended to easily test new test cases during development.
Use `bin/run-locally.sh <test-slug>` to run PHPUnit in your current shell and compare the resulting JSON to `expected_results.json`.
mk-mxp marked this conversation as resolved.
Show resolved Hide resolved
Make sure you have at least PHP 8.1 installed.

### Running tests in Docker locally

This is the recommended way to use this locally.
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.
This is the recommended way to run all tests locally as they would run in production.
Use `bin/run-tests-in-docker.sh` to locally build and run all tests in the Docker image.
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.

### JUnit to JSON
### PHPUnit extension

PHPUnit can natively output tests run to JUnit XML format, but Exercism requires output in JSON format.
A PHP-based app is located in the `junit-handler` folder.
It provides a translation layer from one format to the other incorporating `task_id` identification and test code inclusion.
PHPUnit can natively output test results to various formats, but Exercism requires output in a special JSON format.
An extension to PHPUnit is located in `src` folder and registered in `phpunit.xml`.
It provides a tracer for PHPUnit events to produce the required JSON result.
The tracer incorporates `task_id` identification, test code inclusion, and user output dumping.
11 changes: 11 additions & 0 deletions bin/run-locally.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash

EXERCISM_RESULT_FILE="${PWD%/}/results.json" \
EXERCISM_EXERCISE_DIR="${PWD%/}/tests/${1}" \
vendor/bin/phpunit --do-not-cache-result tests/"${1}"/*Test.php

# Sync'ed from run-tests.sh - Normalize the object ID of `var_dump(new stdClass())`
sed -i -E \
-e 's/(object\(stdClass\))(#[[:digit:]]+)/\1#79/g' \
results.json
diff results.json tests/"${1}"/expected_results.json
2 changes: 1 addition & 1 deletion bin/run-tests-in-docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ docker run \
--rm \
--network none \
--read-only \
--mount type=bind,src="${PWD}/tests",dst=/opt/test-runner/tests \
--mount type=tmpfs,dst=/tmp \
--volume "${PWD}/tests:/opt/test-runner/tests" \
--volume "${PWD}/bin/run-tests.sh:/opt/test-runner/bin/run-tests.sh" \
--workdir /opt/test-runner \
--entrypoint /opt/test-runner/bin/run-tests.sh \
Expand Down
41 changes: 11 additions & 30 deletions bin/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

set -euo pipefail

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

# JUnit results contain the unit test failures only. But they contain `@testdox` - readable test names.
# Teamcity results contain user output in addition to failures as "failed risky tests"
# (command line arguments --disallow-test-output and --fail-on-risky).
# At the moment we require both logs to provide all information to the website.
output=$("${PHPUNIT_BIN}" \
-d memory_limit=300M \
--log-junit "${output_dir%/}/${JUNIT_RESULTS}" \
--log-teamcity "${output_dir%/}/${TEAMCITY_RESULTS}" \
--no-configuration \
--do-not-cache-result \
--disallow-test-output \
--fail-on-risky \
"${test_files%%*( )}" 2>&1)
# Our PHPUnit extension writes directly to ${EXERCISM_RESULT_FILE}
# Our PHPUnit extension requires ${EXERCISM_EXERCISE_DIR} before PHPUnit provides it
output=$( \
EXERCISM_RESULT_FILE="${output_dir%/}/${EXERCISM_RESULTS}" \
EXERCISM_EXERCISE_DIR="${solution_dir%/}" \
"${PHPUNIT_BIN}" \
-d memory_limit=300M \
mk-mxp marked this conversation as resolved.
Show resolved Hide resolved
--do-not-cache-result \
"${test_files%%*( )}" 2>&1 \
)
phpunit_exit_code=$?
set -e

Expand All @@ -48,20 +43,6 @@ function main {
jo version=3 status=error message="${output//"$solution_dir/"/""}" tests="[]" > "${output_dir%/}/${EXERCISM_RESULTS}"
return 0;
fi

# This catches runtime errors in "global student code" (during `require_once`)
if [[ "${phpunit_exit_code}" -eq 2 ]]; then
if ! grep -q '<testcase' "${output_dir%/}/${JUNIT_RESULTS}"; then
output="${output#*" MB"}"
jo version=3 status=error message="${output//"$solution_dir/"/""}" tests="[]" > "${output_dir%/}/${EXERCISM_RESULTS}"
return 0;
fi
fi

php junit-handler/run.php \
"${output_dir%/}/${EXERCISM_RESULTS}" \
"${output_dir%/}/${JUNIT_RESULTS}" \
"${output_dir%/}/${TEAMCITY_RESULTS}"
}

function installed {
Expand Down
13 changes: 13 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "exercism/php-test-runner",
"type": "project",
"license": "AGPL-3.0-only",
mk-mxp marked this conversation as resolved.
Show resolved Hide resolved
"autoload": {
"psr-4": {
"Exercism\\PhpTestRunner\\": "src/"
}
},
"require": {
"phpunit/phpunit": "^10.5.29"
}
}
1 change: 0 additions & 1 deletion junit-handler/.gitignore

This file was deleted.

21 changes: 0 additions & 21 deletions junit-handler/composer.json

This file was deleted.

Loading