Quality infrastructure: Infection, PhpBench, PHPStan level 8#53
Merged
Quality infrastructure: Infection, PhpBench, PHPStan level 8#53
Conversation
Three quality/infrastructure items from the ROADMAP, plus a short fork survey that found nothing worth back-porting. Fork survey: - 13 forks, 7 with commits ahead of upstream. All were inspected. - Nothing to back-port. Upstream's v3.x configurable ParseOptions has superseded every fork's patches (accented-char handling via allowUtf8LocalPart, PHP 8 support, PSR-log versions, etc.). None of the forks attempted RFC compliance work — that's genuinely new territory that this library led with. Infection mutation testing: - composer require --dev infection/infection (^0.29.8). - infection.json5 config: @default mutator set, PHPUnit test framework, text + summary logs, .infection-tmp working dir. - composer infect script: XDEBUG_MODE=coverage infection --threads=max. - Current baseline after targeted fixes: 74% MSI / 79% covered MSI / 94% mutation code coverage across 1122 mutants. Thresholds pinned to the baseline (minMsi=74, minCoveredMsi=79) so future regressions fail. - Targeted test strengthening added: * ParseErrorCode::severity() — was only checking "returns a ValidationSeverity"; now asserts each code maps to its specific Warning/Critical (kills 13 MatchArmRemoval mutants in one pass). * LengthLimits defaults — asserts the exact 64/254/63 and 128/512/128 values for createDefault() and createRelaxed() (kills 7 integer mutants). * toJson UNESCAPED_UNICODE + flag pass-through on both ParsedEmailAddress and ParseResult (kills BitwiseOr mutants on the json_encode flag argument). - 85% MSI target remains aspirational and is tracked in ROADMAP as a partial (~) item; raise thresholds as the suite improves. PhpBench baseline: - composer require --dev phpbench/phpbench (^1.4). - phpbench.json config; benchmarks/ParseBench.php with 10 subjects: single ASCII, ASCII via legacy array API, name-addr, UTF-8 local-part, IDN domain, obs-route, 10-address comma batch, 100-address parseStream batch, invalid input, comment extraction. - composer bench script: phpbench run --report=default. - autoload-dev gains Email\Benchmarks\ namespace mapped to benchmarks/. - Smoke run validates all 10 subjects run and produce numbers. PHPStan level 6 → 8: - Four real issues surfaced; all fixed in-place: * Parse::parseMultiple() — narrow the parse() return via a local @phpstan-var to satisfy ParseResult::fromArray()'s shape. * idn_to_ascii() return is string|false — guard before preg_match. * mb_split() return is list<string>|false — return a domain-invalid result when it fails rather than foreach on bogus value. * tests/ParseTest.php — file_get_contents() is string|false; assert not-false before passing to Yaml::parse(). - No new baseline entries needed; phpstan-baseline.neon unchanged. Tooling: - composer.json: new infect and bench scripts; autoload-dev extended for the benchmarks namespace. - .gitignore: infection.log, infection-summary.log, .infection-tmp/. - ROADMAP: Infection marked partial [~], PhpBench and PHPStan 8 marked [x] with implementation notes. Tests: 65 tests / 489 assertions (up from 60 / 472 in v3.3).
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #53 +/- ##
============================================
- Coverage 90.02% 89.95% -0.07%
- Complexity 380 382 +2
============================================
Files 6 6
Lines 982 986 +4
============================================
+ Hits 884 887 +3
- Misses 98 99 +1
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Delivers the three quality-infrastructure items from the ROADMAP (A, B, C), plus a short fork survey that found nothing worth back-porting.
Everything in this PR is non-behavioral — no user-visible changes to the parser or public API. Four internal nullable-return guards were added in
Parse.phpto satisfy PHPStan level 8; they preserve existing behavior for well-formed input and emit a clean error for the previously-silent edge cases.Fork survey (preamble)
13 forks total; 7 had commits ahead of upstream. Every ahead-commit inspected. Nothing to back-port:
allowUtf8LocalPart— same inputs now parse identically in legacy mode and are correctly rejected underrfc5321().isset($commentNestLevel)guard. Unreachable in current code; variable is always initialized at the top ofparse().Interesting finding: no fork attempted RFC 5322 / 5321 / 6531 compliance work before v3.0. The v3.x architecture is genuinely new territory for this library.
Infection (mutation testing)
Baseline: 74% MSI / 79% covered MSI / 94% mutation code coverage across 1122 mutants.
composer require --dev infection/infectioninfection.json5with the@defaultmutator set, text + summary logscomposer infectruns the full suite; CI script includedminMsi=74,minCoveredMsi=79) so future regressions failTargeted test strengthening (wins encoded in the diff):
ParseErrorCode::severity()explicit mapping per codeMatchArmRemovalLengthLimitsexact defaults (64/254/63 and 128/512/128)toJsonJSON_UNESCAPED_UNICODE+ flag pass-through assertionsBitwiseOrmutants85% MSI target remains aspirational; tracked as a partial (
[~]) item in ROADMAP. Raise thresholds as the suite improves.PhpBench (performance baseline)
composer require --dev phpbench/phpbenchphpbench.jsonconfig;benchmarks/ParseBench.phpwith 10 subjectscomposer benchruns the suite with the default PhpBench reportautoload-devgainsEmail\Benchmarks\namespaceSubjects covered (smoke-run numbers for reference; real runs will vary):
benchSimpleAsciiAddressparseSinglehot pathbenchSimpleAsciiAddressArrayApiparse(…, false)comparisonbenchNameAddrbenchUtf8LocalPartbenchIdnDomainbenchObsRoutebenchBatch10CommabenchBatch100StreamCountparseStreamgenerator throughputbenchInvalidAddressbenchCommentExtractionPHPStan level 6 → 8
Four issues surfaced, all fixed in-place with zero behavior change:
Parse::parseMultiple()— narrow theparse()return via a local@phpstan-varsoParseResult::fromArray()'s shape requirement is satisfied.idn_to_ascii()string|false— guard the return before passing topreg_match.mb_split()list<string>|false— return adomain_invalidvalidation failure when it errors rather than iterating a bogus value (edge case that never fired in tests, but was undefined behavior in principle).file_get_contents()intests/ParseTest.php— assert not-false before passing toYaml::parse().No new
phpstan-baseline.neonentries needed.Test plan
composer cipasses: cs:check, PHPStan level 8, 65 tests / 489 assertionscomposer infectpasses at new thresholds (74% MSI / 79% covered MSI)composer benchruns without errors, produces numbers for all 10 subjectsFiles changed
infection.json5,phpbench.json,benchmarks/ParseBench.phpcomposer.json(2 new scripts + benchmarks namespace),phpstan.neon(level 6→8),.gitignore(Infection artifacts),src/Parse.php(3 nullable guards + 1 docblock shape),tests/ParseTest.php(strengthened assertions + 1 level-8 fix),ROADMAP.md(items flipped)What's next (from ROADMAP)
All of A, B, C now done. Remaining quality items that aren't in this PR: Psalm cross-check, property-based tests, Parse.php line coverage 86.69% → ≥95%, PHP 8.5 CI. Then the v4.0 breaking work (trimmed to DNS/MX callback + RFC 6854 group syntax after canonical() and normalizer moved to v3.3).