From cc9c22cbbe213de522cd95d35d411bfcaae7519b Mon Sep 17 00:00:00 2001 From: "Michael A. Smith" Date: Wed, 20 May 2026 13:00:11 -0400 Subject: [PATCH 1/4] feat(php-tests): emit text summary and Clover XML coverage outputs Default coverage-command now writes coverage.txt and clover.xml into coverage-report/ alongside the HTML report, so consumers (PR comments, agents, CI tools) can read coverage without scraping HTML. Assisted-by: Claude Code --- .github/workflows/php-tests.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php-tests.yml b/.github/workflows/php-tests.yml index 64a8366..f15b9a0 100644 --- a/.github/workflows/php-tests.yml +++ b/.github/workflows/php-tests.yml @@ -63,9 +63,13 @@ on: type: string default: '' coverage-command: - description: Command run on the coverage matrix entry. Used only when coverage-php-version is non-empty. + description: | + Command run on the coverage matrix entry. Used only when coverage-php-version is non-empty. + The default writes three outputs into coverage-report/: the HTML report, + coverage.txt (PHPUnit text report — totals plus per-file lines), and clover.xml + (machine-readable line coverage for agents and CI tools). type: string - default: 'vendor/bin/phpunit --coverage-text --coverage-html coverage-report --colors=never' + default: 'mkdir -p coverage-report && vendor/bin/phpunit --coverage-text=coverage-report/coverage.txt --coverage-clover=coverage-report/clover.xml --coverage-html=coverage-report --colors=never' coverage-artifact-name: description: Artifact name for the uploaded coverage report type: string From b6eb90e46e87ab9d77ce3811ea67b0bfd65f2539 Mon Sep 17 00:00:00 2001 From: "Michael A. Smith" Date: Wed, 20 May 2026 13:55:49 -0400 Subject: [PATCH 2/4] feat(php-tests): gate coverage outputs behind per-format boolean flags Add coverage-html, coverage-text, coverage-clover (all default true) so callers can opt out of any format. The default coverage-command becomes empty and is composed from the booleans; setting coverage-command still overrides everything as an escape hatch. Assisted-by: Claude Code --- .github/workflows/php-tests.yml | 48 ++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/.github/workflows/php-tests.yml b/.github/workflows/php-tests.yml index f15b9a0..8a5f15a 100644 --- a/.github/workflows/php-tests.yml +++ b/.github/workflows/php-tests.yml @@ -62,14 +62,25 @@ on: description: If non-empty, run coverage-command on the matrix entry matching this PHP version (instead of test-script) and upload an HTML coverage artifact. Leave empty to skip coverage collection entirely. type: string default: '' + coverage-html: + description: Generate the PHPUnit HTML coverage report under coverage-report/. Ignored when coverage-command is non-empty. + type: boolean + default: true + coverage-text: + description: Generate the PHPUnit text coverage report at coverage-report/coverage.txt. Ignored when coverage-command is non-empty. + type: boolean + default: true + coverage-clover: + description: Generate the Clover XML coverage report at coverage-report/clover.xml. Ignored when coverage-command is non-empty. + type: boolean + default: true coverage-command: description: | - Command run on the coverage matrix entry. Used only when coverage-php-version is non-empty. - The default writes three outputs into coverage-report/: the HTML report, - coverage.txt (PHPUnit text report — totals plus per-file lines), and clover.xml - (machine-readable line coverage for agents and CI tools). + Explicit override for the PHPUnit coverage invocation, used only when coverage-php-version is non-empty. + Leave empty (default) to let coverage-html / coverage-text / coverage-clover compose the command. + When set, the booleans are ignored and this string is run as-is. type: string - default: 'mkdir -p coverage-report && vendor/bin/phpunit --coverage-text=coverage-report/coverage.txt --coverage-clover=coverage-report/clover.xml --coverage-html=coverage-report --colors=never' + default: '' coverage-artifact-name: description: Artifact name for the uploaded coverage report type: string @@ -158,9 +169,34 @@ jobs: - name: Run tests with coverage if: ${{ inputs.coverage-php-version != '' && matrix.php-version == inputs.coverage-php-version }} + env: + COVERAGE_COMMAND: ${{ inputs.coverage-command }} + COVERAGE_HTML: ${{ inputs.coverage-html }} + COVERAGE_TEXT: ${{ inputs.coverage-text }} + COVERAGE_CLOVER: ${{ inputs.coverage-clover }} run: | + set -euo pipefail + mkdir -p coverage-report echo "::group::Running PHPUnit with Coverage" - ${{ inputs.coverage-command }} + if [[ -n "$COVERAGE_COMMAND" ]]; then + eval "$COVERAGE_COMMAND" + else + args=() + if [[ "$COVERAGE_HTML" == 'true' ]]; then + args+=(--coverage-html=coverage-report) + fi + if [[ "$COVERAGE_TEXT" == 'true' ]]; then + args+=(--coverage-text=coverage-report/coverage.txt) + fi + if [[ "$COVERAGE_CLOVER" == 'true' ]]; then + args+=(--coverage-clover=coverage-report/clover.xml) + fi + if (( ${#args[@]} == 0 )); then + echo '::error::At least one of coverage-html, coverage-text, coverage-clover must be true (or set coverage-command).' + exit 1 + fi + vendor/bin/phpunit "${args[@]}" --colors=never + fi echo "::endgroup::" - name: Upload coverage report From 219b1e9d7cb2622703d4a21d0be6fe0cb1876360 Mon Sep 17 00:00:00 2001 From: "Michael A. Smith" Date: Wed, 20 May 2026 16:30:58 -0400 Subject: [PATCH 3/4] feat(php-tests)!: drop coverage-command escape hatch The booleans (coverage-html, coverage-text, coverage-clover) are now the entire API for choosing which coverage outputs to emit. Removing the arbitrary-shell-eval escape hatch eliminates a foot-gun: callers can no longer accidentally interpolate untrusted GitHub context into a shell string, and the workflow no longer eval's a free-form input. Callers that need to customize PHPUnit (config path, suites, listeners, bootstrap, coverage exclusions, etc.) configure those in phpunit.xml, which PHPUnit auto-discovers. BREAKING CHANGE: The coverage-command input has been removed. Callers that set it must remove the input and move any custom PHPUnit configuration into phpunit.xml. Assisted-by: Claude Code --- .github/workflows/php-tests.yml | 50 +++++++++++++-------------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/.github/workflows/php-tests.yml b/.github/workflows/php-tests.yml index 8a5f15a..ed7f9da 100644 --- a/.github/workflows/php-tests.yml +++ b/.github/workflows/php-tests.yml @@ -59,28 +59,21 @@ on: type: string default: 'pcov' coverage-php-version: - description: If non-empty, run coverage-command on the matrix entry matching this PHP version (instead of test-script) and upload an HTML coverage artifact. Leave empty to skip coverage collection entirely. + description: If non-empty, collect coverage on the matrix entry matching this PHP version (instead of running test-script) and upload the artifact. Leave empty to skip coverage collection entirely. type: string default: '' coverage-html: - description: Generate the PHPUnit HTML coverage report under coverage-report/. Ignored when coverage-command is non-empty. + description: Generate the PHPUnit HTML coverage report under coverage-report/. type: boolean default: true coverage-text: - description: Generate the PHPUnit text coverage report at coverage-report/coverage.txt. Ignored when coverage-command is non-empty. + description: Generate the PHPUnit text coverage report at coverage-report/coverage.txt. type: boolean default: true coverage-clover: - description: Generate the Clover XML coverage report at coverage-report/clover.xml. Ignored when coverage-command is non-empty. + description: Generate the Clover XML coverage report at coverage-report/clover.xml. type: boolean default: true - coverage-command: - description: | - Explicit override for the PHPUnit coverage invocation, used only when coverage-php-version is non-empty. - Leave empty (default) to let coverage-html / coverage-text / coverage-clover compose the command. - When set, the booleans are ignored and this string is run as-is. - type: string - default: '' coverage-artifact-name: description: Artifact name for the uploaded coverage report type: string @@ -170,33 +163,28 @@ jobs: - name: Run tests with coverage if: ${{ inputs.coverage-php-version != '' && matrix.php-version == inputs.coverage-php-version }} env: - COVERAGE_COMMAND: ${{ inputs.coverage-command }} COVERAGE_HTML: ${{ inputs.coverage-html }} COVERAGE_TEXT: ${{ inputs.coverage-text }} COVERAGE_CLOVER: ${{ inputs.coverage-clover }} run: | set -euo pipefail mkdir -p coverage-report - echo "::group::Running PHPUnit with Coverage" - if [[ -n "$COVERAGE_COMMAND" ]]; then - eval "$COVERAGE_COMMAND" - else - args=() - if [[ "$COVERAGE_HTML" == 'true' ]]; then - args+=(--coverage-html=coverage-report) - fi - if [[ "$COVERAGE_TEXT" == 'true' ]]; then - args+=(--coverage-text=coverage-report/coverage.txt) - fi - if [[ "$COVERAGE_CLOVER" == 'true' ]]; then - args+=(--coverage-clover=coverage-report/clover.xml) - fi - if (( ${#args[@]} == 0 )); then - echo '::error::At least one of coverage-html, coverage-text, coverage-clover must be true (or set coverage-command).' - exit 1 - fi - vendor/bin/phpunit "${args[@]}" --colors=never + args=() + if [[ "$COVERAGE_HTML" == 'true' ]]; then + args+=(--coverage-html=coverage-report) + fi + if [[ "$COVERAGE_TEXT" == 'true' ]]; then + args+=(--coverage-text=coverage-report/coverage.txt) fi + if [[ "$COVERAGE_CLOVER" == 'true' ]]; then + args+=(--coverage-clover=coverage-report/clover.xml) + fi + if (( ${#args[@]} == 0 )); then + echo '::error::At least one of coverage-html, coverage-text, coverage-clover must be true.' + exit 1 + fi + echo "::group::Running PHPUnit with Coverage" + vendor/bin/phpunit "${args[@]}" --colors=never echo "::endgroup::" - name: Upload coverage report From e4d9a5b16a8ddd99c31070e4317dcbf270051dd5 Mon Sep 17 00:00:00 2001 From: "Michael A. Smith" Date: Wed, 20 May 2026 16:31:59 -0400 Subject: [PATCH 4/4] style(php-tests): use = for bash string comparison POSIX-canonical form in [[ ]] tests; reserve == for arithmetic inside (( )). Assisted-by: Claude Code --- .github/workflows/php-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/php-tests.yml b/.github/workflows/php-tests.yml index ed7f9da..d48e3b3 100644 --- a/.github/workflows/php-tests.yml +++ b/.github/workflows/php-tests.yml @@ -170,13 +170,13 @@ jobs: set -euo pipefail mkdir -p coverage-report args=() - if [[ "$COVERAGE_HTML" == 'true' ]]; then + if [[ "$COVERAGE_HTML" = 'true' ]]; then args+=(--coverage-html=coverage-report) fi - if [[ "$COVERAGE_TEXT" == 'true' ]]; then + if [[ "$COVERAGE_TEXT" = 'true' ]]; then args+=(--coverage-text=coverage-report/coverage.txt) fi - if [[ "$COVERAGE_CLOVER" == 'true' ]]; then + if [[ "$COVERAGE_CLOVER" = 'true' ]]; then args+=(--coverage-clover=coverage-report/clover.xml) fi if (( ${#args[@]} == 0 )); then