forked from RoushTech/docker
Ship pcov alongside Xdebug, disabled until a caller asks for it #37
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/pcov-extension"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds the
pcovextension to the PHP images so that consumers collecting line coverage stop paying Xdebug's price for it. Xdebug stays installed everywhere.Measured in this image at PHP 8.5, on a function-call-heavy workload (best of 3):
mode=debug,profile,coverage, collecting nothingpcov.enabled=0pcov.enabled=1, not collectingIt ships disabled
pcov.enabled=0. Rows 5 and 6 are why: loaded-but-disabled costs nothing measurable, while leaving it enabled taxes every run that is not collecting coverage by ~3.7x — and coverage is the rare case.Opting in is the whole switch, because php-code-coverage's
Selector::forLineCoverage()testshasPCOV()beforehasXdebug(), andhasPCOV()is exactlyextension_loaded('pcov') && ini_get('pcov.enabled'):One caveat worth knowing before you use this:
-ddoes not reach ParaTest's worker processes. Under ParaTest you need--passthru-php="-d pcov.enabled=1", andXDEBUG_MODE=offas an environment variable, which does inherit.Coverage per variant
php8X-pecl-pcovin their branch's community repoedge/testing; not worth the libc skew on a 3.18 basephp7, in any repophp7.4-pcovconf.dis one tree shared by every variant, so51_pcov.iniwould otherwise land in the 8.1 and 7.4 images and make PHP warn about an unloadable library on every invocation.PHP.fragment.Dockerfiledrops it wherever the.sois genuinely absent.That probe uses
findrather than asking PHP for itsextension_dir. This is deliberate and was a real bug first: the step runs beforeFIX_PERMSsymlinks/usr/bin/php, so shelling out tophpthere silently yields nothing, reads as "not packaged", and drops the ini on the very versions that do have the package.Testing
validate.d/php.shgains an assertion that the extension and its ini agree, and that pcov is disabled by default. It is keyed on whether the.sois on disk rather than on the ini — keying it on the ini lets the interesting failure through, since a bad probe leaves both absent and self-consistent on an image whose entire point was to ship pcov. That assertion was added first, missed the bug above, and was then rewritten; it now catches it.Verified on clean
--no-cachebuilds of all six Alpine variants plus Ubuntu 7.4: pcov loaded where expected, ini absent where not,pcov.enabled'0'throughout, Xdebug still present, and no startup warnings anywhere.Unrelated pre-existing issue noticed while testing the Ubuntu side:
ubuntu-php-*fails its ownvalidateonNginx version 1.24 does not match expected version 1.28, which is presumably why those assertions are commented out intest.yml. Not touched here.Measured on a real suite, and one trap found
Ran Grey.ooo/Someones.Computer's suite (1877 tests, 5541 assertions — identical test outcomes across every run) in the CI runner image with pcov installed the way this PR ships it:
-d opcache.file_cache=-d opcache.enable_cli=0pcov silently under-reports coverage when opcache is enabled in CLI. It builds its line table when a file is compiled, and a file served out of opcache is never compiled — so pcov never sees it. There is no warning and no error, just a lower number that reads exactly like a coverage regression. Disabling only the file cache does not help; it has to be
opcache.enable_cli=0, which costs nothing measurable.With that set, the two drivers agree on covered lines exactly.
Verified end to end against Grey.ooo/Someones.Computer's
main, which sits at 100% and so is the case that actually matters:opcache.enable_cli=0Identical numerator and denominator, gate green under both, 2.45x faster.
One caveat worth carrying: the drivers do not always classify the same lines as executable. On a feature branch of that same codebase, where a
throwinside anifwas never executed, pcov counted it as an executable statement and Xdebug did not — which moves the denominator. So diff the totals before trusting a gate on them, rather than assuming parity.Both facts are now written into
51_pcov.iniand the README, since neither is discoverable from a failure message.Under ParaTest the flags are needed in both the parent and the workers — the parent will not start without a driver of its own, and
-ddoes not propagate to the workers that do the collecting:Net effect for the consumer that prompted this: 91s → 35s, on a 16-core workstation. On the contended CI runner where Xdebug was overrunning a 900s Composer timeout, the margin should be considerably larger.
Collecting line coverage with Xdebug is the slowest way to do it, and it is what every consumer of these images currently has to use. Measured in this image at PHP 8.5, on a function-call-heavy workload: no driver 0.009s Xdebug loaded, collecting 0.307s 34x pcov loaded, collecting 0.060s 6.7x pcov loaded, pcov.enabled=0 0.008s free pcov does line coverage only, which is all a --coverage-clover run reads, so for the common case it is a straight 5x on the collection itself. It ships disabled. That last row is the reason: loaded-but-disabled costs nothing measurable, whereas leaving it enabled taxes every run that is *not* collecting coverage by ~3.7x, and coverage is the rare case. Callers opt in per command with -d pcov.enabled=1, which is the whole switch -- php-code-coverage's Selector::forLineCoverage() tests hasPCOV() before hasXdebug(), and hasPCOV() is exactly extension_loaded('pcov') && ini_get('pcov.enabled'). Note that this does not reach paratest's workers on its own; those need --passthru-php. Xdebug stays installed everywhere. It is still wanted for step debugging, and for branch and path coverage, which pcov does not implement. Alpine 8.1 and 7.4 do not get it: 3.18 packages php81-pecl-pcov only in edge/testing, and 3.15 packages none for php7 at all. Since conf.d is one tree shared by every variant, 51_pcov.ini would otherwise land in those two images and make PHP warn about an unloadable library on every invocation, so the fragment drops it wherever the .so is genuinely absent. That probe uses `find` rather than asking PHP for its extension_dir, because it runs before FIX_PERMS symlinks /usr/bin/php and would otherwise silently drop the ini on the very versions that do have the package. The Ubuntu images get pcov on every version, 7.4 included, since ondrej ships php7.4-pcov. validate.d/php.sh asserts the extension and its ini agree, keyed on whether the .so is on disk rather than on the ini -- keying it on the ini lets the interesting failure through, since a bad probe leaves both absent and self-consistent on an image whose whole point was to ship pcov.