Skip to content

feat: run only the reduced test selection served by Mergify#448

Open
AlexandreGaubert wants to merge 1 commit into
mainfrom
devs/alexandregaubert/mrgfy-7854-test-selection
Open

feat: run only the reduced test selection served by Mergify#448
AlexandreGaubert wants to merge 1 commit into
mainfrom
devs/alexandregaubert/mrgfy-7854-test-selection

Conversation

@AlexandreGaubert

Copy link
Copy Markdown

When a merge-queue batch fails and Mergify reruns it (bisection or max_checks_retries), the rerun does not need to execute the whole suite: only the tests that failed on the predecessor run are informative. This PR makes pytest-mergify ask the new Mergify test selection endpoint whether the current run is such a reduced rerun, and if so deselects everything but the previously-failing tests.

How it works

  • At session start (same conditions as the trace upload: API token + repo configured), the plugin calls GET /v1/ci/{owner}/repositories/{repo}/test-selection with the run's branch, head SHA, pipeline name and job name — the exact identity keys the uploaded test results are stored under.
  • The endpoint answers selection: subset with a list of test ids, or selection: full. The server owns the whole policy ("is this a merge-queue rerun?", "do I have the predecessor's results?"); the plugin just obeys. Anything other than a clean subset answer — HTTP error, timeout, feature disabled, unknown response — falls back to running the full suite, so the plugin can never lose test coverage.
  • On subset, pytest_collection_modifyitems keeps only the selected node ids (properly reported as deselected), and the terminal summary prints a ✂️ Test selection section stating what was selected and why.

Safety rails

  • Kill switch: MERGIFY_TEST_SELECTION_DISABLE=1 skips the endpoint call entirely.
  • pytest-xdist workers never query the endpoint (only the controller would).
  • If the served subset matches zero collected tests (e.g. test renamed between runs), the plugin runs the full suite instead of running nothing.
  • Server-side, the feature is behind a Mergify feature flag; without it the endpoint always answers full.

Server side: Mergifyio/monorepo#36661.

Validated end-to-end on a real GitHub Actions runner against a live engine: a merge-queue retry draft PR collected 4 tests, deselected 2 and ran only the 2 previously-failing ones (Selection: subset (reason: reduced_rerun)), with the full suite correctly restored on normal runs.

Related to MRGFY-7854

🤖 Generated with Claude Code

On a merge-queue rerun (max_checks_retries attempt or bisection step),
Mergify already knows which tests failed on the previous attempt. At
session start, ask the new test-selection endpoint with the run's own
identity (head branch + head SHA + pipeline + job — the same values
this plugin uploads with every test); when the answer is a subset,
filter the collection down to it (exact nodeid match, standard
pytest_deselected reporting) and say so in the terminal summary.

Full-suite is the only fallback: any HTTP error, timeout, missing
endpoint (404), disabled feature, or a subset matching none of the
collected tests (renamed tests) runs everything. The reduced run can
only remove work, never correctness.

Disable with MERGIFY_TEST_SELECTION_DISABLE=true.

Related to MRGFY-7854

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 9, 2026 13:22
@mergify mergify Bot had a problem deploying to Mergify Merge Protections July 9, 2026 13:22 Failure
@mergify

mergify Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🔴 3 of 6 protections blocking · waiting on 👀 reviews and 🤖 CI

Protection Waiting on
🔴 Approval 👀 reviews
🔴 Continuous Integration 🤖 CI
🔴 🔎 Reviews 👀 reviews
🟢 Enforce conventional commit
🟢 📕 PR description
🟢 🚦 Auto-queue

🔴 Approval

Waiting for

  • #approved-reviews-by >= 2
This rule is failing.
  • #approved-reviews-by >= 2

🔴 Continuous Integration

Waiting for

  • check-success = test (3.10)
  • check-success = test (3.11)
  • check-success = test (3.12)
  • check-success = test (3.13)
  • check-success = test (3.14)
  • check-success = test (3.8)
  • check-success = test (3.9)
This rule is failing.
  • all of:
    • check-success = test (3.10)
    • check-success = test (3.11)
    • check-success = test (3.12)
    • check-success = test (3.13)
    • check-success = test (3.14)
    • check-success = test (3.8)
    • check-success = test (3.9)
    • check-success = codespell

🔴 🔎 Reviews

Waiting for

  • #review-threads-unresolved = 0
This rule is failing.
  • #review-threads-unresolved = 0
  • #changes-requested-reviews-by = 0
  • #review-requested = 0

Show 3 satisfied protections

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert|ui)(?:\(.+\))?:

🟢 📕 PR description

  • body ~= (?ms:.{48,})

🟢 🚦 Auto-queue

When all merge protections are satisfied, this pull request will be queued automatically.

Copilot AI left a comment

Copy link
Copy Markdown

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 introduces support for Mergify “test selection” so that merge-queue reruns (e.g., retries/bisection) can execute only the subset of tests that previously failed, while safely degrading to running the full suite on any error/unknown condition.

Changes:

  • Add a TestSelection client that queries the Mergify test-selection endpoint and filters collected pytest items accordingly.
  • Wire test selection loading into MergifyCIInsights and apply it via pytest_collection_modifyitems (trylast).
  • Add unit tests validating subset application and safe fallbacks for common failure modes.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
pytest_mergify/test_selection.py New TestSelection dataclass to query the API and filter collected items for reduced reruns.
pytest_mergify/ci_insights.py Loads TestSelection at startup (with kill switch + xdist-worker guard) using CI resource attributes.
pytest_mergify/__init__.py Applies selection during collection and reports selection status/errors in terminal summary.
tests/test_test_selection.py Adds tests for subset filtering and fallback behavior on errors/missing endpoint.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +67 to +75
try:
response.raise_for_status()
payload = response.json()
selection = payload["selection"]
reason = payload["reason"]
tests = payload["tests"]
except (requests.HTTPError, requests.exceptions.JSONDecodeError, KeyError) as exc:
self.init_error_msg = f"Error when querying Mergify's API, the full test suite will run. Error: {str(exc)}"
return
Comment on lines +196 to +205
if (
self.token is None
or self.repo_name is None
or utils.strtobool(
os.environ.get("MERGIFY_TEST_SELECTION_DISABLE", "false")
)
# On xdist workers the controller already filtered the collection.
or os.environ.get("PYTEST_XDIST_WORKER") is not None
):
return
Comment on lines +134 to +140
@responses.activate
def test_connection_error_degrades_to_full() -> None:
# No responses registered → ConnectionError raised by the responses lib.
selection = _make_selection()

assert selection.selection == "full"
assert selection.init_error_msg is not None
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants