Skip to content

fix(hook): use stable Type string key in safe_offset#270

Open
faisalahammad wants to merge 1 commit into
10up:trunkfrom
faisalahammad:fix/268-type-same-method-collision
Open

fix(hook): use stable Type string key in safe_offset#270
faisalahammad wants to merge 1 commit into
10up:trunkfrom
faisalahammad:fix/268-type-same-method-collision

Conversation

@faisalahammad

Copy link
Copy Markdown

Closes: #268

Summary

WP_Mock::expectActionAdded (and the same path through expectFilterAdded and expectHookAdded) failed under strict mode when the test registered two class-typed callbacks that shared a method name, for example:

WP_Mock::expectActionAdded('init', [WP_Mock\Functions::type(SampleClass::class), 'action']);
WP_Mock::expectActionAdded('init', [WP_Mock\Functions::type(SampleSubClass::class), 'action']);

The second expectation silently overwrote the first, producing a Mockery failure: Method intercepted(<Any Arguments>) should be called at least 1 times but called 0 times. Distinct method names worked by accident, and AnyInstance was unaffected.

Details

Functions::type() was storing spl_object_hash($type) as the key in Hook::$objects. The Type object itself was not retained after expect* returned, so PHP could reclaim it. The next Mockery::type() call reused the same object handle, producing the same hash. In Hook::safe_offset() the processor key for an [Type, method] array is the hash concatenated with the method name, so two different classes paired with the same method name collapsed to the same key.

The fix uses Mockery's Type::__toString() output (for example <MyClass>) as the stable key in both Functions::type() and the Type branch of Hook::safe_offset(). WP_Mock::tearDown() now clears Hook::$objects so the mapping does not leak across tests.

Real-instance resolution still routes through Hook::$objects[get_class($instance)], so an actual new SampleClass() continues to match the stable string written by Functions::type(SampleClass::class). Closure and callable Type handling is unchanged.

Introduced by #254 (1.1.0).

Contributor checklist

  • I agree to follow this project's Code of Conduct.
  • I have updated the documentation accordingly.
  • I have added tests to cover changes introduced by this pull request.
  • All new and existing tests pass.

Testing

The reporter's reproduction and adjacent cases are added as unit tests:

  • tests/Unit/WP_MockTest.php::testMultipleActionsTypeSameMethod (the failing case)
  • tests/Unit/WP_MockTest.php::testMultipleActionsTypeDistinctMethod (regression guard)
  • tests/Unit/WP_MockTest.php::testMultipleActionsAnyInstanceSameMethod (any-instance path)
  • tests/Unit/WP_MockTest.php::testMultipleFiltersTypeSameMethod (filter path)
  • tests/Unit/WP_Mock/HookTest.php::testTypeSafeOffsetIsStableAcrossMultipleTypeCalls (key stability)

Run locally with PHP 8.3:

composer install
vendor/bin/phpunit --filter 'MultipleActionsType|MultipleFiltersType|TypeSafeOffsetIsStable'
vendor/bin/phpunit
vendor/bin/phpstan --memory-limit=512M
vendor/bin/phpcs

Results: 195 PHPUnit tests pass (one pre-existing skip in TestCaseTest::testCanRunTests), PHPStan clean, PHPCS clean.

Note: composer test:behat currently fails on trunk with a pre-existing parse error in features/bootstrap/FunctionsContext.php. That is unrelated to this change and is not introduced here.

Reviewer checklist

  • Code changes review
  • Documentation changes review
  • Unit tests pass
  • Static analysis passes

Functions::type() stored spl_object_hash of the Mockery Type as the
processor map key. The Type is dropped after expect* registration, so
PHP can recycle the hash after GC. Two expectActionAdded or
expectFilterAdded calls with the same method name then collapsed to
the same processor key, the second expectation overwriting the first
and producing a 'should be called at least 1 times but called 0 times'
Mockery failure.

Switch the key to Mockery's Type::__toString() output (e.g. <Class>)
in both Functions::type() and Hook::safe_offset(). Clear Hook::$objects
in tearDown so type mappings do not leak across tests.

Fixes 10up#268
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.

expectActionAdded( array( Functions::type( class-string ), 'method' ) ) fails when same method name used twice

1 participant