Skip to content
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

Add PHPUnit tests for Image Loading Optimization #898

Merged
merged 52 commits into from
Jan 12, 2024

Conversation

westonruter
Copy link
Member

@westonruter westonruter commented Dec 6, 2023

Summary

This is part of #869.

This adds full test coverage for the functions in the Image Loading Optimization module.

Relevant technical choices

  • The ILO_HTML_Tag_Processor::open_tags() generator is updated to ensure accurate breadcrumbs/XPath for SVG and MathML elements.
  • The phpdoc and variable names are improved in ilo_construct_preload_links().
  • The integrity attribute is no longer copied from an IMG element to the preload LINK. This attribute doesn't appear on IMG, but rather is only on LINK and SCRIPT.
  • Rework how timestamps are added to URL metrics by adding and examining them in ilo_unshift_url_metrics(). Previously the timestamp was added in ilo_store_url_metric() before passing to ilo_construct_preload_links().
  • Refactor ilo_unshift_url_metrics() to make it easier to test by passing more arguments.
  • Fix JSON Schema definition in ilo_register_endpoint().
  • Discontinue excluding WordPress-Extra sniffs from applying to tests, and add specific sniff exclusions. By allowing WordPress-Extra to apply, PHPCBF can do automatic formatting. PHPCS errors were fixed in other test files. See Update PHPCS formatting in tests #924.
  • Fix REST API endpoint to reject storing a URL metric when the sample is already fully populated for the provided viewport. Previously it would continue to accept requests when any of the breakpoints lacked a full sample size of URL metrics.
  • Remove debug code from being returned in REST API response.

Checklist

  • PR has either [Focus] or Infrastructure label.
  • PR has a [Type] label.
  • PR has a milestone or the no milestone label.

@westonruter westonruter added no milestone PRs that do not have a defined milestone for release [Plugin] Optimization Detective Issues for the Optimization Detective plugin labels Dec 6, 2023
Comment on lines -186 to 192
if ( in_array( $tag_name, self::VOID_TAGS, true ) ) {
if (
in_array( $tag_name, self::VOID_TAGS, true )
||
( $p->has_self_closing_flag() && $this->is_foreign_element() )
) {
array_pop( $this->open_stack_tags );
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dmsnell: FYI, I'm pretty happy with how has_self_closing_flag and the foreign element aspect of the HTML spec simplified things! Now it can properly get breadcrumbs/XPaths for each SVG/MathML element as well.

for ( $i = 0, $len = count( $minimum_viewport_widths ); $i < $len; $i++ ) {
$lcp_element = $lcp_images_by_minimum_viewport_widths[ $minimum_viewport_widths[ $i ] ];
if ( false === $lcp_element || empty( $lcp_element['attributes'] ) ) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty( $lcp_element['attributes'] ) check is removed here because it is always set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another benefit of using a class to represent LCP elements would be that that would be truly enforced :)

Comment on lines +111 to +116
$needs_detection = in_array(
true,
// Each array item is array{int, bool}, with the second item being whether the viewport width is needed.
array_column( $needed_minimum_viewport_widths, 1 ),
true
);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some cool things with PHPStan's static analysis here. If 0 is supplied as the second arg to array_column() it detects an error:

phpstan: Call to function in_array() with arguments true, list and true will always evaluate to false.

This is because from the array shape returned by ilo_get_needed_minimum_viewport_widths() it knows that the first column will all be integers (the minimum viewport widths) and thus the in_array() check will always be false.

Conversely, if I supply a higher index like 2 in array_column(), I get:

phpstan: Call to function in_array() with arguments true, array{} and true will always evaluate to false.

Here PHPStan knows that since there are only two items in each tuple, the return of array_column() will be an empty array, and thus in_array() will also always return false.

@westonruter westonruter mentioned this pull request Jan 9, 2024
3 tasks
@westonruter
Copy link
Member Author

@felixarntz

For now, I left a few comments on the production code changes. They mostly look good to me, although I think the code of some of those functions is extremely complex and could be simplified by separating responsibilities, and using smaller functions, or classes where applicable, for distinct processes. This is by no means a blocker for this PR, but I think it is critical to make this module/plugin long-term maintainable and more accessible to other contributors. [...] For the production code changes and tests, I don't feel strongly that they need to be separated, though I believe that it could help getting the production code changes merged a lot faster. Reviewing the tests takes notably more time.

Agreed. As mentioned above, I wanted to limit further refactoring until tests were written. The code changes not related to test coverage were primarily to fix blatant issues I uncovered while writing the tests. Sorry it grew so large. Since the review cycles have been pretty long, it's hard to keep the PRs very concise.

I think the PHPCS changes that affect the entire plugin make sense too, though I think that would be great to decouple as well, so that it can already be merged. FWIW, this probably shouldn't happen in the feature branch at all, as it likely will lead to more and more merge conflicts, the longer it takes for this feature branch to be merged. Could you open a PR with the PHPCS adjustments that don't affect the module directly against trunk?

Agreed. Done in #924. I've reverted the changes in this PR via 2181d88.

@westonruter
Copy link
Member Author

westonruter commented Jan 10, 2024

Unit tests are failing due to Audit_Enqueued_Assets_Tests, which isn't related to the changes in this PR. Two of the failures are fixed in trunk and the other is fixed in #926 (which for some reason doesn't happen in trunk).

@westonruter
Copy link
Member Author

The PHPStan failures are likewise not related to code changes here. They may be resolved by merging trunk into the feature branch, but I will wait to do that until #926 is merged.

…om/WordPress/performance into add/ilo-tests

* 'feature/image-loading-optimization' of https://github.com/WordPress/performance: (155 commits)
  Remove scheduler from globals since not yet used
  Fix or ignore eslint rules
  Remove modules/images/webp-uploads/fallback.js from ignorePatterns
  Run format-js on JS files
  Cherry pick fixes to JS linting from feature/image-loading-optimization
  Prevent sending header during test
  Manually fix remaining phpcs issues
  Run php-format
  Discontinue excluding all WordPress-Extra from tests so phpcbf can format
  Add @joemcgill to images and measurement focuses.
  Update codeowners, removing inactive contributors.
  Introduce perflab-plugin-management.js
  Load migration script in settings page
  Update function name
  Apply suggestions from code review
  Address slow query feedbacks
  Revise migration pointer un-dismissal to work for all admins
  Rename variable.
  improve code
  Require Node v20
  ...
@westonruter westonruter mentioned this pull request Jan 11, 2024
3 tasks
Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @westonruter, LGTM! I think this is good to merge once the PHPStan problems have been addressed (here or via the other PR).

Let's separately follow up on simplifying some of the logic with classes, and also file structure (see my comment below).

* @covers ::ilo_can_optimize_response
* @dataProvider data_provider_test_ilo_can_optimize_response
*/
public function test_ilo_can_optimize_response( Closure $set_up, bool $expected ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just wondering why this function is in the storage/data.php file. It's only called in optimization.php, and the function name to me suggests it would fit better in there.

I then noticed storage/data.php contains several other functions like this, that to me don't seem storage related. Is that intentional? If so, can you clarify how you see the responsibilities between these two files?

Maybe there's a more intuitive way to break apart the functions here. To be fair, some of it may sort of automatically be addressed when using classes for some of those things as applicable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Function moved in ed305a9.

The other functions are generally storage related in that they relate to how fresh the stored data is, how to determine the slug for the data, manipulating the stored data, etc. But I agree this the functions in this file are much more of a grab bag compared to the functions in other files. I'll work to organize them better with the refactoring.

$slug = ilo_get_url_metrics_slug( array() );
$nonce1 = ilo_get_url_metrics_storage_nonce( $slug );
$this->assertMatchesRegularExpression( '/^[0-9a-f]{10}$/', $nonce1 );
$this->assertSame( 1, ilo_verify_url_metrics_storage_nonce( $nonce1, $slug ) );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit-pick: I find the return of 0, 1, or 2 an odd choice. I realize wp_verify_nonce() returns that, but it's extremely unintuitive, and I don't think many people know, except from reading the docs.

There's no value for us (and probably any other use-cases) to make a difference between when the nonce was generated, so I'd prefer to just return a boolean (i.e. cast the result to a bool). You aren't testing the 2 return value here either as far as I can tell (which makes sense IMO!), so that confirms it's useless to us :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, sounds good. Done in d865bac.

Co-authored-by: Mukesh Panchal <[email protected]>
Copy link
Member

@mukeshpanchal27 mukeshpanchal27 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @westonruter, for the updates. I've left some minor suggestions, but they don't pose any blockers.

  • It would be nice to add @param annotations for the parameters used in the dataProvider for tests.

* @covers ::open_tags
* @covers ::get_xpath
*
* @dataProvider data_provider_sample_documents
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice if we add @param that used in dataProvider.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, somewhat. But it looks like most of the other tests using data providers don't have @param tags in the plugin. Not that this is a good reason.

@westonruter westonruter merged commit 6433bc1 into feature/image-loading-optimization Jan 12, 2024
8 checks passed
@westonruter westonruter deleted the add/ilo-tests branch January 12, 2024 06:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
no milestone PRs that do not have a defined milestone for release [Plugin] Optimization Detective Issues for the Optimization Detective plugin [Type] Enhancement A suggestion for improvement of an existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants