Skip to content

Coverage ratchet is unclosable when the merge base predates the app's PHPUnit setup (dev→main PRs) #352

Description

@rubenvdlinde

Summary

On a pull request whose merge base predates the app's PHPUnit setup, the
coverage ratchet fails with no action available to the author. This is the
unclosable-gate shape. It bites development → main / development → beta
release-promotion PRs, where the merge base can be months old.

Confirmed on zaakafhandelapp PR #294 (development → main), run
31478794540,
job PHPUnit (PHP 8.3, NC stable32) (93739014068).

What the log shows

The tests are fine — Run PHPUnit tests is a green check:

OK (242 tests, 772 assertions)

The failing step is Measure merge-base coverage:

head=4fa6d02… base=b233af99… merge-base=b233af99…
HEAD is now at b233af9 chore(ci): add missing canonical workflows (#244)
No composer.lock file present. Updating dependencies to latest instead.
…
/home/runner/work/_temp/….sh: line 9: ./vendor/bin/phpunit: No such file or directory
##[error]Could not measure coverage at merge base b233af99… — refusing to report a ratchet that did not run.

Verified against the repo rather than inferred:

  • git cat-file -e b233af99…:composer.lockfatal: Not a valid object name
  • git show b233af99…:composer.json has no phpunit/phpunit in require-dev

Today's development has both. So at the merge base the test tooling simply did
not exist yet, composer install resolves without phpunit, no base-clover.xml
is produced, and the test -s guard fires.

Why the single-cell shape is a red herring

quality.yml:1829 restricts the step to one matrix cell:

if: ${{ inputs.enable-coverage-guard && github.event_name == 'pull_request'
        && matrix.php-version == inputs.php-version
        && matrix.nextcloud-ref == fromJSON(inputs.nextcloud-test-refs)[0] }}

so 8.3/stable32 is the only cell that runs it. The other three "passes" never
executed the step. This shape is being misread across the fleet as an
environment-dependent test failure (class_exists() autoload) or a wiped
vendor/. Control pair that rules both out: the same commit 8a790664 passed
this exact cell on the push run 31478791392
and failed on the PR run — the only
difference is github.event_name.

The guard itself is correct — please do not soften it

The step's own comment explains why a missing/zero clover must be fatal: a base
measured as 0% sets the floor to zero and passes every possible drop. So all of
these are wrong fixes:

  • || true on the clover check, or deleting the test -s guard
  • treating an unmeasurable base as 0%
  • skipping the ratchet whenever it is inconvenient

Suggested direction

The defect is that an unmeasurable base is reported as a failed base. A
merge base predating the suite has undefined coverage, not zero.

The trigger is cleanly detectable and distinguishable from "tests ran and
produced nothing": ./vendor/bin/phpunit absent after composer install at the
merge base
. In that case the defensible fallback is the absolute check this
same workflow already runs on every push —

php scripts/coverage-guard.php coverage/clover.xml

against the committed .coverage-baseline. That is a real check rather than a
skip, so the ratchet is never silently demoted to nothing, and the PR author has
something they can actually satisfy.

Also affected

openconnector is reported to hit the same failure mode. Worth sweeping every repo
that sets enable-coverage-guard and has a long-lived main/beta divergence.

Not fixed in this issue, deliberately

I have authority to change .github, but this is a behaviour change to a workflow
every app inherits, and #347 is currently being held back specifically to avoid
reddening ten repos mid-flight. Filing the diagnosis rather than shipping the
change seemed the right trade; happy to implement it once the fleet is quiescent.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions