-
Notifications
You must be signed in to change notification settings - Fork 3.3k
memoize wp_normalize_path #10770
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
memoize wp_normalize_path #10770
Changes from all commits
12fde07
8b21f23
0f68954
b16350b
9369fd0
9664f3b
9c85c8b
0d12856
3e306c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -222,6 +222,145 @@ | |
| array( 'php://input', 'php://input' ), | ||
| array( 'http://example.com//path.ext', 'http://example.com/path.ext' ), | ||
| array( 'file://c:\\www\\path\\', 'file://C:/www/path/' ), | ||
|
|
||
| // Edge cases. | ||
| array( '', '' ), // Empty string should return empty string. | ||
| array( 123, '123' ), // Integer should be cast to string. | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests that wp_normalize_path() works with objects that have __toString(). | ||
| * | ||
| * This is important because the function uses a static cache, and the input | ||
| * must be cast to string before being used as an array key. | ||
| * | ||
| * @ticket 64538 | ||
| */ | ||
| public function test_wp_normalize_path_with_stringable_object() { | ||
| $stringable = new class() { | ||
| public function __toString() { | ||
| return '/var/www/html\\test'; | ||
| } | ||
| }; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a more representative test might be an SplFileInfo instance, since that is actually used in Core. as discovered in #10781 we can fix that currently-improper call in another patch. |
||
|
|
||
| $this->assertSame( '/var/www/html/test', wp_normalize_path( $stringable ) ); | ||
| } | ||
|
|
||
| /** | ||
| * Tests that wp_normalize_path() returns consistent results on repeated calls. | ||
| * | ||
| * The function uses a static cache, so this verifies cache behavior. | ||
| * | ||
| * @ticket 64538 | ||
| */ | ||
| public function test_wp_normalize_path_returns_consistent_results() { | ||
| $path = 'C:\\www\\path\\'; | ||
|
|
||
| $first_call = wp_normalize_path( $path ); | ||
| $second_call = wp_normalize_path( $path ); | ||
| $third_call = wp_normalize_path( $path ); | ||
|
|
||
| $this->assertSame( $first_call, $second_call, 'Second call should return same result as first.' ); | ||
| $this->assertSame( $second_call, $third_call, 'Third call should return same result as second.' ); | ||
| $this->assertSame( 'C:/www/path/', $first_call, 'Normalized path should match expected value.' ); | ||
| } | ||
josephscott marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Tests that wp_normalize_path() cache rotation works correctly. | ||
| * | ||
| * The function uses a two-tier cache (hot/warm) with max 100 entries in hot. | ||
| * This verifies that after exceeding the cache size, both old and new paths | ||
| * still return correct results. | ||
| * | ||
| * @ticket 64538 | ||
| */ | ||
| public function test_wp_normalize_path_cache_rotation() { | ||
| $paths = array(); | ||
| $expected = array(); | ||
|
|
||
| // Generate 150 unique paths to exceed the 100-entry hot cache limit. | ||
| for ( $i = 0; $i < 150; $i++ ) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. while I wouldn’t normally want to suggest poking internally into a function like this, I think this is an appropriate test due to the fragile nature of the cache. to that end, hard-coding 150 here and leaving a comment decouples the test from the code. what do you think about reading the max and basing the test off of it? I would hate for someone to come in and retune the max in a way that makes the tests pass when they should fail. $function_reflector = new ReflectionFunction( '\wp_normalize_path' );
$max_cache_size = $function_reflector->getStaticVariables()['max'] ?? null;
$this->assertIsInt( $max_cache_size, 'Should have read max cache size from static variable "$max" inside "wp_normalize_path()" but found none: Check test setup.' );
for ( $i = 0; $i < 2 * $max_cache_size; $i++ ) {
…
}
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ha! I see that you used this in the next test case but not here. LLMs being a little flight-of-mind on this? |
||
| $paths[ $i ] = "/var/www/test-{$i}\\subdir\\"; | ||
| $expected[ $i ] = "/var/www/test-{$i}/subdir/"; | ||
| } | ||
|
|
||
| // First pass: normalize all paths (fills hot, triggers rotation to warm). | ||
| $results_first_pass = array(); | ||
| foreach ( $paths as $i => $path ) { | ||
| $results_first_pass[ $i ] = wp_normalize_path( $path ); | ||
| } | ||
|
|
||
| // Verify all first pass results are correct. | ||
| foreach ( $results_first_pass as $i => $result ) { | ||
| $this->assertSame( | ||
| $expected[ $i ], | ||
| $result, | ||
| "First pass: Path {$i} should be normalized correctly." | ||
| ); | ||
| } | ||
|
|
||
| // Second pass: verify old paths (now in warm) still return correct results. | ||
| // Access early paths which should have rotated to warm cache. | ||
| for ( $i = 0; $i < 50; $i++ ) { | ||
| $this->assertSame( | ||
| $expected[ $i ], | ||
| wp_normalize_path( $paths[ $i ] ), | ||
| "Second pass: Early path {$i} should still return correct result from warm cache." | ||
| ); | ||
| } | ||
|
|
||
| // Verify recent paths (should be in hot cache) also work. | ||
| for ( $i = 100; $i < 150; $i++ ) { | ||
| $this->assertSame( | ||
| $expected[ $i ], | ||
| wp_normalize_path( $paths[ $i ] ), | ||
| "Second pass: Recent path {$i} should return correct result from hot cache." | ||
| ); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same note about basing our test on the actual configured max cache sizes in these two loop vs. hard-coding one. |
||
| } | ||
|
|
||
| /** | ||
| * Tests that wp_normalize_path() cache segments do not exceed the maximum size. | ||
| * | ||
| * The function uses a two-tier cache with a max of 100 entries per segment. | ||
| * This verifies the cache remains bounded after processing many unique paths. | ||
| * | ||
| * @ticket 64538 | ||
| */ | ||
| public function test_wp_normalize_path_cache_segment_limits() { | ||
| // Generate 250 unique paths to ensure multiple cache rotations. | ||
| for ( $i = 0; $i < 250; $i++ ) { | ||
| wp_normalize_path( "/unique/path/number-{$i}\\file.php" ); | ||
| } | ||
|
|
||
| // Use reflection to inspect the static cache variables. | ||
| $reflection = new ReflectionFunction( 'wp_normalize_path' ); | ||
| $static_vars = $reflection->getStaticVariables(); | ||
| $hot_count = count( $static_vars['hot'] ); | ||
| $warm_count = count( $static_vars['warm'] ); | ||
| $max = $static_vars['max']; | ||
|
|
||
| // Verify hot cache does not exceed max. | ||
| $this->assertLessThanOrEqual( | ||
| $max, | ||
| $hot_count, | ||
| "Hot cache ({$hot_count} entries) should not exceed max ({$max})." | ||
| ); | ||
|
|
||
| // Verify warm cache does not exceed max. | ||
| $this->assertLessThanOrEqual( | ||
| $max, | ||
| $warm_count, | ||
| "Warm cache ({$warm_count} entries) should not exceed max ({$max})." | ||
| ); | ||
|
|
||
| // Verify total cached entries is bounded (at most 2x max). | ||
| $total = $hot_count + $warm_count; | ||
| $this->assertLessThanOrEqual( | ||
| $max * 2, | ||
| $total, | ||
| "Total cache ({$total} entries) should not exceed 2x max (" . ( $max * 2 ) . ').' | ||
| ); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these could use explanation. happy to add or propose some if you prefer and are willing to wait a few days.