Skip to content

Fix Runtime::getCurrentSettings() forwarding spurious empty overrides for php.ini-only extensions#100

Merged
sebastianbergmann merged 1 commit into
8.1from
issue-99
May 25, 2026
Merged

Fix Runtime::getCurrentSettings() forwarding spurious empty overrides for php.ini-only extensions#100
sebastianbergmann merged 1 commit into
8.1from
issue-99

Conversation

@sebastianbergmann
Copy link
Copy Markdown
Owner

getCurrentSettings() now skips a setting when all of the following hold:

  • its runtime value is the empty string, and
  • it is absent from the loaded ini files, and
  • it is absent from the compiled-in defaults.
if ($set === '' &&
    !isset($iniFileValues[$value]) &&
    !array_key_exists($value, $compiledDefaults)) {
    continue;
}

The existing compiled-default check used ($compiledDefaults[$value] ?? null) === $set, which cannot tell "absent" apart from "present and empty"; the new guard uses array_key_exists() precisely for that distinction.

This is deliberately narrower than the old blanket $set === '' skip, so the empty/"off" override detection that 616034c added is preserved: an empty or Off value is still reported whenever there is evidence for it (the setting appears in an ini file, or in the compiled-in defaults).

The guard only bows out when there is no evidence at all that an override happened, which is exactly the apc.preload_path case, and more generally any ini setting of an extension that is invisible to the php -n probe.

This is complementary to #98, not a replacement for it. #98 reads builtin_default_value from ini_get_all() in-process, but that relies on php/php-src#22134, which is still open against master. Until that ships and users upgrade to a PHP version that has it, #98 falls back to the same php -n child-process probe.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 24, 2026

Codecov Report

❌ Patch coverage is 0% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.14%. Comparing base (a5cce2c) to head (3b18157).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/Runtime.php 0.00% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##                8.1     #100      +/-   ##
============================================
- Coverage     55.36%   54.14%   -1.23%     
- Complexity       92       95       +3     
============================================
  Files             2        2              
  Lines           177      181       +4     
============================================
  Hits             98       98              
- Misses           79       83       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@sebastianbergmann
Copy link
Copy Markdown
Owner Author

These changes also fix the root cause of sebastianbergmann/phpunit#6673, where PCOV-based code coverage was reported as 0%. I was able to reproduce the bug and confirm that this branch resolves it.

Runtime::getCurrentSettings() forwards a setting as an override when its current value differs from what is configured in the loaded INI files. For an extension that is loaded only via php.ini (rather than being compiled in), this misfires: the extension is not present under php -n, so the compiled-in defaults probe cannot see its settings, and a setting that is merely sitting at its default value is mistaken for a deliberate override.

PCOV is a textbook case. On the affected machine PCOV is loaded by a conf.d file that only loads the extension:

[pcov]
extension="…/pcov.so"

It sets neither pcov.enabled nor pcov.directory, so ini_get('pcov.directory') returns an empty string. Because PCOV is invisible to the php -n probe, that empty value has no INI-file entry and no visible compiled-in default to compare against and getCurrentSettings() treats it as an override and forwards it.

With 8.1.1, getCurrentSettings(['pcov.enabled', 'pcov.directory', 'pcov.exclude']) returns:

pcov.enabled   => "pcov.enabled=1"
pcov.directory => "pcov.directory="   ← spurious empty override
pcov.exclude   => "pcov.exclude="     ← spurious empty override

PHPUnit forwards these settings as -d flags to the child processes it spawns for PHPT tests and for tests run in process isolation. The spurious -d pcov.directory= overrides PCOV's directory in the child with an empty string, so PCOV instruments nothing and coverage comes back as 0%, which is exactly sebastianbergmann/phpunit#6673. The same class of spurious empty/"off" override is behind the report in #99.

The new logic implemented in this pull request only forwards a value when there is actual evidence that it was overridden: an empty value that is absent from both the loaded INI files and the compiled-in defaults is left alone, because there is nothing to indicate it was set deliberately.

Running the identical probe with this branch's src/ in place returns:

pcov.enabled => "pcov.enabled=1"
(pcov.directory and pcov.exclude are no longer forwarded)

The spurious empty overrides are gone, while the legitimate, non-default pcov.enabled=1 is still forwarded. With the empty pcov.directory= no longer clobbering the child process, PCOV keeps its directory and collects coverage as expected.

I copied src/Runtime.php from this branch into a PHPUnit checkout's vendor/sebastian/environment/ and confirmed:

  1. getCurrentSettings() no longer emits the empty pcov.directory= / pcov.exclude= entries (output above), while still emitting pcov.enabled=1.
  2. The PHPUnit code-coverage end-to-end tests that exercise PCOV collect coverage correctly.

This is the proper fix for the forwarding behavior at its source.

@sebastianbergmann sebastianbergmann merged commit 3b18157 into 8.1 May 25, 2026
30 of 32 checks passed
@sebastianbergmann sebastianbergmann deleted the issue-99 branch May 25, 2026 13:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Runtime::getCurrentSettings() forwards empty-string overrides for php.ini-only extensions

1 participant