Skip to content

feat(assert): add replaceable substring exception expectations - #394

Open
ichinya wants to merge 4 commits into
php-testo:1.xfrom
ichinya:feat/389-exception-expectation-semantics
Open

ichinya wants to merge 4 commits into
php-testo:1.xfrom
ichinya:feat/389-exception-expectation-semantics

Conversation

@ichinya

@ichinya ichinya commented Oct 15, 2026 •

Copy link
Copy Markdown

PHPUnit requires an exactly empty exception message, replaces earlier message expectations, and accepts named exception: arguments. Preserve these contracts during migration, including argument coercion, evaluation order and captured values across assignments.

Add the public ExpectedException::withMessage(string $message, bool $exact = true) comparison mode. exact: false uses str_contains() and replaces the previous message and mode; existing calls remain exact. withMessageContaining() continues to accumulate substrings. This removes the PCRE length limit for dynamic/repeated messages while keeping independent regex constraints intact.

Rector keeps provably nonempty messages simple and removes adjacent overwritten literals when safe. Other arguments are evaluated/coerced once through a typed closure, with the comparison flag passed by name even inside spread arguments. Handler names reserve conditional and later locals across the containing function; dynamic locals from extract() stay manual. The reverse rule leaves repeated withMessage() modes unchanged when PHPUnit cannot preserve their replacement semantics.

Generated-code compatibility: the target project needs the Assert API above before running migrations that emit exact. This API is introduced by this PR and needs an Assert release before consumers can use the new bridge output. Until then, use matching Assert code from the same checkout. An older Assert now reports Unknown named parameter $exact instead of silently ignoring a positional flag. The bridge's development dependencies do not constrain a separately installed target project. The PR is marked feat because it adds public Assert API.

Validation on the final change:

PHP Rector fixtures Harness unit tests Feature tests Assert tests
8.2.34 369 passed 171 passed 3 passed 400 passed
8.3.35 369 passed 171 passed 3 passed 400 passed
8.5.11 369 passed 171 passed 3 passed 400 passed
  • Runtime controls cover variable collisions, weak/strict coercion, overwritten arguments that throw, reverse comparison modes and a 100,000-character message. Each nested result is checked by test name, status and assertion count in the Feature suite.
  • All 24 forward/strict controls agree with real PHPUnit 12.5.39 and 13.4.2: 17 passes, 7 required failures and no errors. The actual Rector CLI with the full PHPUNIT_TO_TESTO set and real PHPUnit reflection preserves all verdicts; auxiliary trace checks use Testo assertions. Testo records 28 assertions and PHPUnit records 56.
  • Generated closure output rejects the old one-argument withMessage() signature explicitly on PHP 8.2, 8.3 and 8.5.
  • Changed PHP files pass CS Fixer under PHP 8.2; git diff --check passes. Full composer cs:diff previously reported a pre-existing difference in unmodified core/Output/Terminal/Renderer/ColorMode.php on Windows.

PHP compatibility: the minimum remains PHP 8.2. Runtime platform checks and the isolated Rector dependency check pass on PHP 8.2.34. The full development dependency check on 8.2 fails because the existing locked infection/abstract-testframework-adapter 0.5.1 requires PHP ^8.3; Composer requirements and lockfiles are unchanged.

Fixes #389.

Handle exact-empty and last-wins messages, normalize named expectation arguments, and keep argument snapshots across assignments. Add transformed runtime controls and document independent dynamic regex limits.

Refs php-testo#389

Assisted-By: GPT-6 <noreply@anthropic.com>
Run converted fixtures through a nested application instead of a subprocess and JSON/JUnit reports. Remove the redundant message flag and restore unsupported runs from the original statement range.

Refs php-testo#389

Assisted-By: GPT-6 <noreply@anthropic.com>
@ichinya
ichinya requested a review from a team as a code owner October 15, 2026 10:50
Add an optional exact comparison mode to withMessage so migrations can replace substring expectations without PCRE length limits. Retain exact matching by default and keep regex constraints independent.

Cover long messages and mode changes through transformed runtime controls, native PHPUnit parity, and the Assert suites. Document the API and handle literal modes in the reverse conversion.

Refs php-testo#389

Assisted-By: GPT-6 <noreply@anthropic.com>
Preserve repeated message modes in reverse migrations and reserve handler names across the containing function, including conditional and later locals. Leave extract-based dynamic locals for manual migration.

Keep provably nonempty messages readable, remove adjacent overwritten literals only when safe, and pass comparison modes by name so older Assert versions reject unsupported output explicitly.

Move runtime controls to the Feature suite, check individual verdicts and assertion counts, and cover weak/strict coercion and assignments that throw. Document generated-code compatibility and the public Assert API requirement.

Refs php-testo#389

Assisted-By: GPT-6 <noreply@anthropic.com>
@ichinya ichinya changed the title fix(rector): preserve PHPUnit exception expectation contracts feat(assert): add replaceable substring exception expectations Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rector: preserve empty, last-wins and named exception expectations

1 participant