Skip to content

Commit a6ab5ac

Browse files
committed
Add support for validating stubs against specific PHP versions
1 parent 1f277d9 commit a6ab5ac

4 files changed

Lines changed: 172 additions & 18 deletions

File tree

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ function curl_copy_handle(#[LanguageLevelTypeAware(['8.0' => 'CurlHandle'], defa
3434
* The easiest way to run the full test suite locally is the bundled script (requires Docker — it installs dependencies, regenerates the stubs and reflection caches, and runs every test suite):
3535
* macOS / Linux: `./runTests.sh`
3636
* Windows: `runTests.bat`
37+
* While iterating on stubs for one PHP version, add `--php-version` (e.g. `./runTests.sh --php-version 5.6`) to validate against that version only — checks that do not apply to it are not generated at all. CI still runs every version.
3738
* To run suites manually instead, see [How to run tests](README.md#how-to-run-tests) in the README.
3839
* If a stub legitimately cannot match reflection (for example a runtime-dependent constant value, or an entity available only on certain PHP versions), register it in `tests/Framework/Validator/KnownProblems/DefaultKnownProblemsProvider.php`.
3940

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@ By default the script validates against the **committed** reflection caches
4545
them. Pass `--refresh-reflection` (e.g. `./runTests.sh --refresh-reflection`) to regenerate them
4646
locally; this is slow and rarely needed, and the result should not be committed (see below).
4747

48+
To validate against a single PHP version instead of all of them, pass `--php-version`:
49+
50+
* macOS / Linux: `./runTests.sh --php-version 5.6`
51+
* Windows: `runTests.bat --php-version 5.6`
52+
53+
Only the checks that apply to that version run — a check declared for PHP 8.2+ produces no test cases
54+
for 5.6, so there is nothing to skip through. The `Unit` and `Structure` suites are not tied to a PHP
55+
version and always run in full, and a suite with nothing to run for the selected version (the
56+
`PhpDoc` checks, for example, are declared for the latest version only) is reported as empty rather
57+
than as a failure. Use this while iterating locally; CI always runs every version.
58+
4859
Both scripts require Docker (they use the `test_runner` image defined in `docker-compose.yml`).
4960

5061
#### Running suites manually

runTests.bat

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,74 @@ rem only by the update-reflection-cache.yml workflow. By default we validate aga
1717
rem caches - exactly what CI does - so a normal run never rewrites them. Pass --refresh-reflection
1818
rem to regenerate them locally (slow; requires the per-version Docker images).
1919
set "REFRESH_REFLECTION=false"
20-
for %%A in (%*) do (
21-
if "%%~A"=="--refresh-reflection" (
20+
rem Empty means "every version from the PhpVersions enum", which is what CI runs.
21+
set "PHP_VERSION_UNDER_TEST="
22+
23+
:parse_args
24+
if "%~1"=="" goto :args_done
25+
if /i "%~1"=="--refresh-reflection" (
2226
set "REFRESH_REFLECTION=true"
23-
) else (
24-
echo Unknown argument: %%~A
25-
echo Usage: %~nx0 [--refresh-reflection]
26-
endlocal
27-
exit /b 1
28-
)
27+
shift
28+
goto :parse_args
29+
)
30+
if /i "%~1"=="--php-version" (
31+
if "%~2"=="" (
32+
echo Missing value for --php-version
33+
goto :usage
34+
)
35+
set "PHP_VERSION_UNDER_TEST=%~2"
36+
shift
37+
shift
38+
goto :parse_args
39+
)
40+
set "CURRENT_ARG=%~1"
41+
if /i "!CURRENT_ARG:~0,14!"=="--php-version=" (
42+
for /f "tokens=1* delims==" %%a in ("!CURRENT_ARG!") do set "PHP_VERSION_UNDER_TEST=%%b"
43+
if "!PHP_VERSION_UNDER_TEST!"=="" (
44+
echo Missing value for --php-version
45+
goto :usage
46+
)
47+
shift
48+
goto :parse_args
49+
)
50+
echo Unknown argument: %~1
51+
goto :usage
52+
53+
:args_done
54+
55+
rem The version the validators run against is not a runner setting - it comes from the data providers,
56+
rem which iterate PhpVersions::cases() and bake the version into every data set name (see
57+
rem ValidatorTestBase::buildTestName(), e.g. checkClassExists_ArrayObject_5.6). Selecting a version
58+
rem therefore means filtering test names on that suffix. PHPUnit renders a named data set as
59+
rem testEntity"<name>", so one closing quote follows the version; the trailing "." in the regex matches
60+
rem it, which keeps the filter free of quotes that cmd would have to escape.
61+
rem Checks that do not apply to the selected version drop out on their own: the providers skip any
62+
rem descriptor whose PhpVersionRange excludes it, so no data set is generated in the first place.
63+
set "VERSION_FILTER_ARGS="
64+
if not "%PHP_VERSION_UNDER_TEST%"=="" (
65+
rem Keep the accepted versions in sync with the canonical enum, like run-all-reflection-parsers.bat does.
66+
set "PHP_ENUM_FILE=%SCRIPT_DIR%tests\Framework\Runner\PhpVersions.php"
67+
if not exist "!PHP_ENUM_FILE!" (
68+
echo Cannot find PhpVersions.php: !PHP_ENUM_FILE!
69+
endlocal
70+
exit /b 1
71+
)
72+
findstr /c:"= '%PHP_VERSION_UNDER_TEST%';" "!PHP_ENUM_FILE!" >nul
73+
if errorlevel 1 (
74+
echo Unknown PHP version: %PHP_VERSION_UNDER_TEST%
75+
rem usebackq (command in backticks) so the single quotes inside the findstr pattern do not
76+
rem terminate the command for cmd's parser.
77+
for /f "usebackq tokens=2 delims='" %%V in (`findstr /r /c:"case PHP_[0-9_]* = '[0-9.]*';" "!PHP_ENUM_FILE!"`) do set "KNOWN_VERSIONS=!KNOWN_VERSIONS! %%V"
78+
echo Valid versions:!KNOWN_VERSIONS!
79+
endlocal
80+
exit /b 1
81+
)
82+
rem Escape the dot for the regex: 5.6 -> 5\.6
83+
set "VERSION_PATTERN=%PHP_VERSION_UNDER_TEST:.=\.%"
84+
rem --do-not-fail-on-empty-test-suite: a suite can legitimately have nothing to run for the selected
85+
rem version (the PhpDoc checks, for instance, are declared LATEST-only), and PHPUnit exits 1 on an
86+
rem empty suite by default, which would abort the script.
87+
set "VERSION_FILTER_ARGS=--filter _!VERSION_PATTERN!.$ --do-not-fail-on-empty-test-suite"
2988
)
3089

3190
echo Installing composer packages...
@@ -38,12 +97,19 @@ if errorlevel 1 goto :fail
3897

3998
if "%REFRESH_REFLECTION%"=="true" (
4099
echo Regenerating reflection caches...
41-
call "%SCRIPT_DIR%tests\run-all-reflection-parsers.bat"
100+
rem With a single version selected, only that version's cache needs rebuilding.
101+
call "%SCRIPT_DIR%tests\run-all-reflection-parsers.bat" %PHP_VERSION_UNDER_TEST%
42102
if errorlevel 1 goto :fail
43103
) else (
44104
echo Using committed reflection caches ^(pass --refresh-reflection to regenerate them^).
45105
)
46106

107+
if not "%PHP_VERSION_UNDER_TEST%"=="" (
108+
echo Validating stubs against PHP %PHP_VERSION_UNDER_TEST% only.
109+
)
110+
111+
rem The Unit and Structure suites are not parameterised by PHP version (they test the framework itself
112+
rem and the layout of the stubs tree), so they always run in full.
47113
echo Running unit tests...
48114
call :dc run --rm test_runner vendor/bin/phpunit --testsuite Unit
49115
if errorlevel 1 goto :fail
@@ -53,11 +119,11 @@ call :dc run --rm test_runner vendor/bin/phpunit --testsuite Structure
53119
if errorlevel 1 goto :fail
54120

55121
echo Running PHPDoc tests...
56-
call :dc run --rm test_runner vendor/bin/phpunit --testsuite PhpDoc
122+
call :dc run --rm test_runner vendor/bin/phpunit --testsuite PhpDoc !VERSION_FILTER_ARGS!
57123
if errorlevel 1 goto :fail
58124

59125
echo Running validator tests...
60-
call :dc run --rm test_runner vendor/bin/phpunit --testsuite General
126+
call :dc run --rm test_runner vendor/bin/phpunit --testsuite General !VERSION_FILTER_ARGS!
61127
if errorlevel 1 goto :fail
62128

63129
endlocal
@@ -67,6 +133,13 @@ exit /b 0
67133
docker compose -f "%COMPOSE_FILE%" %*
68134
exit /b %errorlevel%
69135

136+
:usage
137+
echo Usage: %~nx0 [--refresh-reflection] [--php-version ^<version^>]
138+
echo --refresh-reflection Regenerate the reflection caches instead of using the committed ones.
139+
echo --php-version ^<version^> Validate stubs against a single PHP version (e.g. 5.6) instead of all of them.
140+
endlocal
141+
exit /b 1
142+
70143
:fail
71144
echo.
72145
echo Aborting: a previous step failed.

runTests.sh

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,84 @@ dc() {
1313
docker compose -f "$SCRIPT_DIR/docker-compose.yml" "$@"
1414
}
1515

16+
usage() {
17+
echo "Usage: $(basename "$0") [--refresh-reflection] [--php-version <version>]"
18+
echo " --refresh-reflection Regenerate the reflection caches instead of using the committed ones."
19+
echo " --php-version <version> Validate stubs against a single PHP version (e.g. 5.6) instead of all of them."
20+
}
21+
1622
# Reflection caches (tests/cache/Reflection<version>.json) are committed ground truth, refreshed
1723
# only by the update-reflection-cache.yml workflow. By default we validate against the committed
1824
# caches — exactly what CI does — so a normal run never rewrites them. Pass --refresh-reflection to
1925
# regenerate them locally (slow; requires the per-version Docker images).
2026
REFRESH_REFLECTION=false
21-
for arg in "$@"; do
22-
case "$arg" in
27+
# Empty means "every version from the PhpVersions enum", which is what CI runs.
28+
PHP_VERSION_UNDER_TEST=""
29+
while [ $# -gt 0 ]; do
30+
case "$1" in
2331
--refresh-reflection) REFRESH_REFLECTION=true ;;
32+
--php-version=*)
33+
PHP_VERSION_UNDER_TEST="${1#*=}"
34+
if [ -z "$PHP_VERSION_UNDER_TEST" ]; then
35+
echo "Missing value for --php-version"
36+
usage
37+
exit 1
38+
fi
39+
;;
40+
--php-version)
41+
if [ $# -lt 2 ]; then
42+
echo "Missing value for --php-version"
43+
usage
44+
exit 1
45+
fi
46+
PHP_VERSION_UNDER_TEST="$2"
47+
shift
48+
;;
2449
*)
25-
echo "Unknown argument: $arg"
26-
echo "Usage: $(basename "$0") [--refresh-reflection]"
50+
echo "Unknown argument: $1"
51+
usage
2752
exit 1
2853
;;
2954
esac
55+
shift
3056
done
3157

58+
# The version the validators run against is not a runner setting — it comes from the data providers,
59+
# which iterate PhpVersions::cases() and bake the version into every data set name (see
60+
# ValidatorTestBase::buildTestName(), e.g. checkClassExists_ArrayObject_5.6). Selecting a version
61+
# therefore means filtering test names on that suffix. PHPUnit renders a named data set as
62+
# testEntity"<name>", so one closing quote follows the version; the trailing "." in the regex matches
63+
# it (a literal quote would need per-shell escaping, and runTests.bat has to build the same filter).
64+
# Checks that do not apply to the selected version drop out on their own: the providers skip any
65+
# descriptor whose PhpVersionRange excludes it, so no data set is generated in the first place.
66+
VERSION_FILTER_ARGS=()
67+
if [ -n "$PHP_VERSION_UNDER_TEST" ]; then
68+
# Keep the accepted versions in sync with the canonical enum, like run-all-reflection-parsers.sh does.
69+
PHP_ENUM_FILE="$SCRIPT_DIR/tests/Framework/Runner/PhpVersions.php"
70+
if [ ! -f "$PHP_ENUM_FILE" ]; then
71+
echo "Cannot find PhpVersions.php: $PHP_ENUM_FILE"
72+
exit 1
73+
fi
74+
KNOWN_VERSIONS=($(sed -n "s/.*case PHP_[A-Z0-9_]* = '\([0-9.]*\)'.*/\1/p" "$PHP_ENUM_FILE"))
75+
VERSION_IS_KNOWN=false
76+
for known in "${KNOWN_VERSIONS[@]}"; do
77+
if [ "$known" = "$PHP_VERSION_UNDER_TEST" ]; then
78+
VERSION_IS_KNOWN=true
79+
break
80+
fi
81+
done
82+
if [ "$VERSION_IS_KNOWN" = false ]; then
83+
echo "Unknown PHP version: $PHP_VERSION_UNDER_TEST"
84+
echo "Valid versions: ${KNOWN_VERSIONS[*]}"
85+
exit 1
86+
fi
87+
88+
# --do-not-fail-on-empty-test-suite: a suite can legitimately have nothing to run for the selected
89+
# version (the PhpDoc checks, for instance, are declared LATEST-only), and PHPUnit exits 1 on an
90+
# empty suite by default, which would abort the script.
91+
VERSION_FILTER_ARGS=(--filter "_${PHP_VERSION_UNDER_TEST//./\\.}.\$" --do-not-fail-on-empty-test-suite)
92+
fi
93+
3294
echo "Installing composer packages..."
3395
dc run --rm test_runner composer install --ignore-platform-reqs
3496

@@ -37,19 +99,26 @@ dc run --rm test_runner php tests/run-stubs-parser.php
3799

38100
if [ "$REFRESH_REFLECTION" = true ]; then
39101
echo "Regenerating reflection caches..."
40-
bash "$SCRIPT_DIR/tests/run-all-reflection-parsers.sh"
102+
# With a single version selected, only that version's cache needs rebuilding.
103+
bash "$SCRIPT_DIR/tests/run-all-reflection-parsers.sh" ${PHP_VERSION_UNDER_TEST:+"$PHP_VERSION_UNDER_TEST"}
41104
else
42105
echo "Using committed reflection caches (pass --refresh-reflection to regenerate them)."
43106
fi
44107

108+
if [ -n "$PHP_VERSION_UNDER_TEST" ]; then
109+
echo "Validating stubs against PHP $PHP_VERSION_UNDER_TEST only."
110+
fi
111+
112+
# The Unit and Structure suites are not parameterised by PHP version (they test the framework itself
113+
# and the layout of the stubs tree), so they always run in full.
45114
echo "Running unit tests..."
46115
dc run --rm test_runner vendor/bin/phpunit --testsuite Unit
47116

48117
echo "Running structure tests..."
49118
dc run --rm test_runner vendor/bin/phpunit --testsuite Structure
50119

51120
echo "Running PHPDoc tests..."
52-
dc run --rm test_runner vendor/bin/phpunit --testsuite PhpDoc
121+
dc run --rm test_runner vendor/bin/phpunit --testsuite PhpDoc ${VERSION_FILTER_ARGS[@]+"${VERSION_FILTER_ARGS[@]}"}
53122

54123
echo "Running validator tests..."
55-
dc run --rm test_runner vendor/bin/phpunit --testsuite General
124+
dc run --rm test_runner vendor/bin/phpunit --testsuite General ${VERSION_FILTER_ARGS[@]+"${VERSION_FILTER_ARGS[@]}"}

0 commit comments

Comments
 (0)