-
Notifications
You must be signed in to change notification settings - Fork 140
Run unit tests only for plugins with changes #1838
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?
Run unit tests only for plugins with changes #1838
Conversation
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## trunk #1838 +/- ##
=======================================
Coverage 69.17% 69.17%
=======================================
Files 90 90
Lines 7708 7708
=======================================
Hits 5332 5332
Misses 2376 2376
☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| uses: tj-actions/changed-files@v45 | ||
| with: | ||
| dir_names: true # Output unique changed directories. | ||
| dir_names_max_depth: 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.
This limits the directory output to a maximum depth of 2. For example, plugins/<plugin-name>/tests will be returned as plugins/<plugin-name>.
Since dir_names: true ensures only unique directories are listed, combining it with dir_names_max_depth: 2 allows us to extract a clean list of changed plugin names. This avoids additional logic in the next step for determining modified plugins.
| # Define and add plugin dependencies (e.g., optimization-detective triggers others). | ||
| declare -A PLUGIN_DEPENDENCIES=( | ||
| ["optimization-detective"]="embed-optimizer image-prioritizer" | ||
| ) | ||
| for PLUGIN in "${ALL_CHANGED_PLUGINS[@]}"; do | ||
| if [[ -n "${PLUGIN_DEPENDENCIES[$PLUGIN]}" ]]; then | ||
| for DEP in ${PLUGIN_DEPENDENCIES[$PLUGIN]}; do | ||
| if [[ ! " ${ALL_CHANGED_PLUGINS[@]} " =~ " ${DEP} " ]]; then | ||
| ALL_CHANGED_PLUGINS+=("$DEP") | ||
| fi | ||
| done | ||
| fi | ||
| done |
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.
Currently, optimization-detective is the only plugin with dependencies, so we could simplify this by checking for it explicitly instead of maintaining a general dependency structure. However, I think this approach keeps things flexible if more dependencies are introduced in the future.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
| for PLUGIN in "${ALL_CHANGED_PLUGINS[@]}"; do | ||
| if [[ -n "${PLUGIN_DEPENDENCIES[$PLUGIN]}" ]]; then | ||
| for DEP in ${PLUGIN_DEPENDENCIES[$PLUGIN]}; do | ||
| if [[ ! " ${ALL_CHANGED_PLUGINS[@]} " =~ " ${DEP} " ]]; then |
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.
PhpStorm (via Shellcheck) is flagging this line:
Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @).
See SC2199.
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.
Fixed it in 11949ac.
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
felixarntz
left a comment
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.
@ShyamGadde This looks great to me so far, just one minor comment.
Before we merge this, I would like to discuss though under which conditions we should do this. To me, it makes sense to only run selected unit tests for PRs, to speed up the process. But running the whole test suite also has value, e.g. to warn us early in case something breaks somewhere else where we didn't change any files - which can happen e.g. based on WordPress Core updates.
How about we implement this change so that it only applies to pull requests, but for merges to our main branches it would still run all test suites? IMO this would be the best of both worlds:
- Running tests against PRs would be more efficient, faster and cost less resources.
- Running tests against code merged into
trunkand release branches would still be ensured to be more comprehensively tested.
It probably wouldn't be a huge lift to change that, we could make the step that determines the changed files and plugins conditional to pull_request events and otherwise return the list of all plugins.
WDYT? cc @westonruter @thelovekesh
| done | ||
| # Define and add plugin dependencies (e.g., optimization-detective triggers others). | ||
| declare -A PLUGIN_DEPENDENCIES=( |
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.
Maybe call this PLUGIN_DEPENDENTS to clarify? In this case, Optimization Detective is a dependency of the other two, but the other two are dependents of Optimization Detective.
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.
That makes sense. Updated it in 90a5ab7.
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
codecov.yml
Outdated
| patch: | ||
| default: | ||
| threshold: 80% |
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.
I noticed that the threshold for patch coverage is set to 80%, which (based on Codecov’s docs) means that coverage can drop by up to 80% while still passing. I'm wondering if this was intentional—perhaps it was meant to ensure a minimum of 80% coverage rather than allowing such a large drop?
Additionally, since target isn't explicitly set, it defaults to the project coverage from the pull request base. That results in PR checks displaying something like:
Would it make sense to set target: 100% for patch coverage, with a lower threshold (maybe 10-20%), to ensure new changes are reasonably covered? Just wanted to check if this aligns with the intended behavior.
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.
I'm wondering if this was intentional—perhaps it was meant to ensure a minimum of
80%coverage rather than allowing such a large drop?
I think so, yes. The naming is a bit confusing.
So maybe threshold: 20% or target: 80%?
|
@swissspidy Since you raised some valid points in #1792 (comment), it would be great to get your feedback whether this solution would address them. |
|
@ShyamGadde Appreciate the thorough comparison here 💪 👍 While individual flags per plugin are tempting, I think we can start with the simpler option 2 for now and then see how well it works. |
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
af943f8 to
c1c5b11
Compare
| comment: | ||
| hide_comment_details: true | ||
| hide_project_coverage: false | ||
| show_carryforward_flags: true |
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.
@westonruter Would you prefer that we display the carried forward flags in the PR comment, as shown below, or would you rather exclude them?
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.
I suppose it doesn't hurt to include?
This comment was marked as spam.
This comment was marked as spam.
|
@phanduynam No it's not, please stop spamming the repo with these comments. |
| - uses: actions/checkout@v4 | ||
| - name: Get changed files | ||
| id: changed-files | ||
| uses: tj-actions/changed-files@v45 |
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.
This action was recently compromised. My recommendation would be to pin it to a specific SHA for better security.
Additionally, reviewing the workflow, I noticed there's quite a bit of scripting involved, which might not be the easiest to follow for all developers.
To improve clarity and flexibility, I'd suggest using a JavaScript or PHP script instead. This approach would make the code more accessible to a wider range of developers and allow for easier management of edge cases.
You can find a similar composite action here - https://github.com/ampproject/amp-wp/tree/develop/.github/actions/determine-changed-files
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.
What if we pin it to a vulnerable version?
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.
But it seems like based on the latest supply chain attack with qix today, pinning is a way to avoid being exploited. Perhaps Dependabot includes some checks for the dependencies it is proposing updates for which would address the concern of pinning to a vulnerable version? It doesn't seem this is the case right now, but this would sure be a great use of AI.
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.
The best way to avoid these issues is to use your own solution rather than relying on third-party actions. It's generally a good idea to either use @actions/* or create your own composite actions.
Also, in core, it's very rare for maintainers to allow third-party actions, and when they do, they always pin to a specific SHA to mitigate potential security risks.
What if we pin it to a vulnerable version?
By default, pin to the SHA of a version that was published at least a week ago. This provides ample time to detect if the action has been compromised.
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.
Thank you for the advice. I've opened a PR to implement this: #2171
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.
I just saw a mention of this action being compromised back in March: https://semgrep.dev/blog/2025/popular-github-action-tj-actionschanged-files-is-compromised/
This was around when the commits were being pushed to this branch. I suppose this means we should rotate the secrets? Granted, it has been 9 months.
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.
| uses: tj-actions/changed-files@v45 | |
| uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # v47.0.1 |
codecov.yml
Outdated
| individual_flags: | ||
| - name: auto-sizes | ||
| paths: | ||
| - plugins/auto-sizes/ |
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.
the indenting on this line doesn't match the other plugins
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.
Yeah. There is also a merge conflict due to #1605. So that merge conflict needs to be fixed, and then npm run format-js should fix up this YAML file as well.
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.
Fixed it in bc5cfe9.
Signed-off-by: Shyamsundar Gadde <shyamsundar.gadde@rtcamp.com>
Co-authored-by: Weston Ruter <weston@ruter.net>
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.
Pull request overview
This PR optimizes CI/CD by running unit tests only for plugins affected by changes in pull requests, while maintaining full test coverage for commits to the trunk branch. It introduces Codecov flag management to track coverage per plugin and implements plugin dependency detection.
Changes:
- Added Codecov flag management to enable per-plugin coverage tracking with carryforward support
- Modified the test workflow to detect changed plugins and run tests selectively
- Implemented dependency-aware testing (e.g., changes to optimization-detective trigger tests for embed-optimizer and image-prioritizer)
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| codecov.yml | Configured per-plugin coverage flags, carryforward rules, and adjusted coverage thresholds for selective testing |
| .github/workflows/php-test-plugins.yml | Added changed file detection, plugin dependency logic, selective test execution, and direct Codecov CLI integration |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| patch: | ||
| default: | ||
| threshold: 80% | ||
| threshold: 20% |
Copilot
AI
Jan 13, 2026
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.
The patch threshold was lowered from 80% to 20%, which is a significant decrease that could allow code with poor test coverage to be merged. This change should be documented in the PR description or reconsidered, as it represents a substantial reduction in code quality standards.
| threshold: 20% | |
| threshold: 80% |
| done | ||
| fi | ||
| done | ||
Copilot
AI
Jan 13, 2026
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.
The shell script doesn't handle the case where no plugins have changed. If ALL_CHANGED_PLUGINS is empty, the subsequent for loops in the test execution steps will fail silently or produce errors. Add a check to ensure at least one plugin needs testing, or exit early with a message if no plugins changed.
| if [[ ${#ALL_CHANGED_PLUGINS[@]} -eq 0 ]]; then | |
| echo "No changed plugins detected; skipping plugin tests." | |
| echo "all_changed_plugins=" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi |
| - name: Download Codecov CLI | ||
| if: ${{ matrix.coverage == true }} | ||
| uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 | ||
| with: | ||
| token: ${{ secrets.CODECOV_TOKEN }} | ||
| directory: ./single-site-reports | ||
| flags: single | ||
| name: ${{ matrix.php }}-single-site-coverage | ||
| fail_ci_if_error: true | ||
| - name: Upload multisite coverage reports to Codecov | ||
| run: | | ||
| # Create a temporary directory for GPG operations | ||
| mkdir -p .gpg | ||
| curl https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --homedir .gpg --no-default-keyring --keyring trustedkeys.gpg --import | ||
| curl -Os https://cli.codecov.io/latest/linux/codecov | ||
| curl -Os https://cli.codecov.io/latest/linux/codecov.SHA256SUM | ||
| curl -Os https://cli.codecov.io/latest/linux/codecov.SHA256SUM.sig | ||
| gpg --homedir .gpg --no-default-keyring --keyring trustedkeys.gpg --verify codecov.SHA256SUM.sig codecov.SHA256SUM | ||
| shasum -a 256 -c codecov.SHA256SUM | ||
| chmod +x codecov |
Copilot
AI
Jan 13, 2026
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.
The codecov CLI verification steps create a .gpg directory but don't clean it up afterward. This could leave sensitive keyring files on the runner. Add a cleanup step or use the system's default GPG directory instead.
| ./codecov upload-coverage "${cc_args[@]}" \ | ||
| --file ./single-site-reports/coverage-$PLUGIN.xml \ | ||
| --file ./multisite-reports/coverage-multisite-$PLUGIN.xml \ | ||
| --flag $PLUGIN \ | ||
| --name $PLUGIN-coverage |
Copilot
AI
Jan 13, 2026
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.
The codecov upload command assumes that coverage files exist for all changed plugins, but if a test execution fails for any plugin, the corresponding coverage file won't be generated. This will cause the upload to fail with --fail-on-error. Consider checking if files exist before attempting to upload them, or handle missing files gracefully.
| ./codecov upload-coverage "${cc_args[@]}" \ | |
| --file ./single-site-reports/coverage-$PLUGIN.xml \ | |
| --file ./multisite-reports/coverage-multisite-$PLUGIN.xml \ | |
| --flag $PLUGIN \ | |
| --name $PLUGIN-coverage | |
| single_site_report="./single-site-reports/coverage-$PLUGIN.xml" | |
| multisite_report="./multisite-reports/coverage-multisite-$PLUGIN.xml" | |
| files=() | |
| if [ -f "$single_site_report" ]; then | |
| files+=(--file "$single_site_report") | |
| else | |
| echo "::warning::Single-site coverage report not found for plugin '$PLUGIN' at '$single_site_report'." | |
| fi | |
| if [ -f "$multisite_report" ]; then | |
| files+=(--file "$multisite_report") | |
| else | |
| echo "::warning::Multisite coverage report not found for plugin '$PLUGIN' at '$multisite_report'." | |
| fi | |
| if [ ${#files[@]} -eq 0 ]; then | |
| echo "::warning::No coverage reports found for plugin '$PLUGIN'. Skipping Codecov upload for this plugin." | |
| echo "::endgroup::" | |
| continue | |
| fi | |
| ./codecov upload-coverage "${cc_args[@]}" "${files[@]}" \ | |
| --flag "$PLUGIN" \ | |
| --name "$PLUGIN-coverage" |
| for PLUGIN in ${{ steps.changed-plugins.outputs.all_changed_plugins }}; do | ||
| npm run test-php:$PLUGIN -- -- -- --coverage-clover=./single-site-reports/coverage-$PLUGIN.xml | ||
| done |
Copilot
AI
Jan 13, 2026
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.
The script uses unquoted variable expansion in the for loops (${{ steps.changed-plugins.outputs.all_changed_plugins }}), which could cause issues if plugin names contain spaces or special characters. While plugin names in this repository don't have spaces, it's a best practice to quote these expansions for robustness.
| if [[ "${{ github.event_name }}" == "push" || "${{ steps.changed-files.outputs.config_any_changed }}" == "true" ]]; then | ||
| ALL_CHANGED_PLUGINS=($(ls plugins)) | ||
| echo "all_changed_plugins=${ALL_CHANGED_PLUGINS[*]}" >> $GITHUB_OUTPUT | ||
| exit 0 | ||
| fi |
Copilot
AI
Jan 13, 2026
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.
The condition checks if github.event_name equals "push" but doesn't verify which branch was pushed to. According to the workflow trigger configuration, pushes can occur on both "trunk" and "release/**" branches. The description states "All tests will run for all plugins when commits are added to the trunk branch", but this condition will run all tests for any push, including release branches. Consider adding a branch check to ensure this behavior only applies to trunk pushes if that's the intended behavior.


Summary
Fixes #1792
Relevant technical choices
trunkbranch.