Skip to content

[FEA] Add selective test execution for PR builds#931

Open
bala-bhargav wants to merge 1 commit intoNVIDIA:mainfrom
bala-bhargav:feat/ci-selective-test-execution-861
Open

[FEA] Add selective test execution for PR builds#931
bala-bhargav wants to merge 1 commit intoNVIDIA:mainfrom
bala-bhargav:feat/ci-selective-test-execution-861

Conversation

@bala-bhargav
Copy link

@bala-bhargav bala-bhargav commented Mar 4, 2026

Implements component-level selective test execution for pull request CI builds. When a PR only touches routing code, only routing tests and datasets are downloaded/run. Same for LP and MIP. Nightly builds continue running everything unchanged.
#861

Summary by CodeRabbit

  • Chores
    • CI now detects which test components changed and runs only those tests.
    • Test runners selectively run or skip C++ and Python tests and print which components are being executed or skipped.
    • Dataset downloads/preparation are conditional—only performed for needed components, with clear skip messages.
    • Pytest invocation targets component-specific test directories for faster, targeted runs.

@bala-bhargav bala-bhargav requested a review from a team as a code owner March 4, 2026 17:41
@bala-bhargav bala-bhargav requested a review from jameslamb March 4, 2026 17:41
@copy-pr-bot
Copy link

copy-pr-bot bot commented Mar 4, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

📝 Walkthrough

Walkthrough

Adds a detector script that sets CUOPT_TEST_COMPONENTS (routing, lp, mip, or all) from diffs or defaults, and updates CI test scripts to conditionally prepare datasets and run only the relevant C++ gtests and Python pytest directories.

Changes

Cohort / File(s) Summary
Test detection script
ci/detect_test_components.sh
New script exporting CUOPT_TEST_COMPONENTS; derives components from PR diffs with fallbacks for non‑PR builds and error cases; prints the exported value.
C/C++ gtest orchestration
ci/run_ctests.sh
Adds should_run_test(test_name) to map gtest binaries to components (routing, lp, mip) and skip tests not included in CUOPT_TEST_COMPONENTS; preserves run/exit semantics for selected tests.
Python pytest selection
ci/run_cuopt_pytests.sh
Selects pytest target directories based on CUOPT_TEST_COMPONENTS (all → tests, routing → tests/routing, lp → tests/linear_programming & tests/quadratic_programming); skips if no matching dirs.
CI dataset & test runners
ci/test_cpp.sh, ci/test_python.sh, ci/test_wheel_cuopt.sh
Source the detector and conditionally download/prepare LP, MIP, and routing datasets; update log messages to include components and skip dataset steps when not required.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[FEA] Add selective test execution for PR builds' accurately summarizes the primary change: implementing selective/component-level test execution for PR builds rather than running the full test suite.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ci/run_ctests.sh`:
- Around line 28-34: The UNIT_TEST and PRESOLVE_TEST blocks currently only allow
running when components contains "mip", which skips them for LP-only runs;
update the conditional in both the UNIT_TEST and PRESOLVE_TEST cases (refer to
the UNIT_TEST and PRESOLVE_TEST labels and the components variable) to also
accept "lp" (e.g., check for "mip" or "lp" inside components) so those tests run
for LP-selected PRs as well.
- Line 25: The case pattern contains a redundant subpattern "MIP_TERMINATION_*"
because the earlier "MIP_*" already matches it; update the case arm that
currently reads 'MIP_*|PROBLEM_*|...|MIP_TERMINATION_*|...' by removing the
"MIP_TERMINATION_*" token so the pattern list no longer contains the unreachable
subpattern (keep all other alternatives unchanged).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9450e103-1ed3-4907-9d70-8f2b748e6975

📥 Commits

Reviewing files that changed from the base of the PR and between 0aa1b31 and f9cf547.

📒 Files selected for processing (6)
  • ci/detect_test_components.sh
  • ci/run_ctests.sh
  • ci/run_cuopt_pytests.sh
  • ci/test_cpp.sh
  • ci/test_python.sh
  • ci/test_wheel_cuopt.sh

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ci/detect_test_components.sh`:
- Around line 17-19: The script's early-return branch checks
CUOPT_TEST_COMPONENTS but doesn't export it, so child processes may not see a
non-exported shell variable; update the conditional in detect_test_components.sh
to export the variable before returning (e.g., call export CUOPT_TEST_COMPONENTS
inside the if [[ -n "${CUOPT_TEST_COMPONENTS:-}" ]]; then block) so the existing
value is preserved for child scripts without changing the branch logic.

In `@ci/test_python.sh`:
- Around line 45-50: The current conditional uses a combined check that causes
both ./datasets/linear_programming/download_pdlp_test_dataset.sh and
./datasets/mip/download_miplib_test_dataset.sh to run when CUOPT_TEST_COMPONENTS
contains either "lp" or "mip"; change the logic to perform separate checks so
each dataset script runs only when its component is requested. Replace the
single if-block that references CUOPT_TEST_COMPONENTS with two conditional
checks: one that runs download_pdlp_test_dataset.sh only when
CUOPT_TEST_COMPONENTS equals "all" or contains "lp", and another that runs
download_miplib_test_dataset.sh only when CUOPT_TEST_COMPONENTS equals "all" or
contains "mip"; keep the rapids-logger fallback message for when neither dataset
is needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 34c2f55c-352a-4220-9b3d-41df53cfc3f1

📥 Commits

Reviewing files that changed from the base of the PR and between f9cf547 and e7be4f5.

📒 Files selected for processing (6)
  • ci/detect_test_components.sh
  • ci/run_ctests.sh
  • ci/run_cuopt_pytests.sh
  • ci/test_cpp.sh
  • ci/test_python.sh
  • ci/test_wheel_cuopt.sh
🚧 Files skipped from review as they are similar to previous changes (3)
  • ci/test_cpp.sh
  • ci/run_ctests.sh
  • ci/test_wheel_cuopt.sh

@anandhkb anandhkb added this to the 26.04 milestone Mar 4, 2026
Add component-level test filtering to CI scripts so that pull requests
only run tests relevant to the changed files (routing, LP, or MIP).
Nightly builds continue to run all tests.

- New: ci/detect_test_components.sh — detects changed components from
  the PR diff and exports CUOPT_TEST_COMPONENTS
- Modified: run_ctests.sh — added should_run_test() to filter gtests
  by component
- Modified: run_cuopt_pytests.sh — select pytest directories by component
- Modified: test_cpp.sh, test_python.sh, test_wheel_cuopt.sh — conditional
  dataset downloads based on detected components

Closes NVIDIA#861
@bala-bhargav bala-bhargav force-pushed the feat/ci-selective-test-execution-861 branch from e7be4f5 to b93bd54 Compare March 5, 2026 01:06
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ci/detect_test_components.sh`:
- Around line 28-37: The script currently only verifies HEAD~1 exists before
using git diff HEAD~1..HEAD, which can under-select tests on non-merge commits;
update the pre-check to also verify that HEAD is a merge commit (has at least
two parents) before using HEAD~1..HEAD. In the block that checks git rev-parse
--verify HEAD~1 and before running git diff, add a check (e.g., test for HEAD^2
via git rev-parse --verify HEAD^2 or inspect parents) and if that check fails
set CUOPT_TEST_COMPONENTS="all" and return; keep the existing changed_files
variable and subsequent git diff HEAD~1..HEAD logic unchanged otherwise.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 334b8f94-22a6-4673-8238-9c35f0b7294f

📥 Commits

Reviewing files that changed from the base of the PR and between e7be4f5 and b93bd54.

📒 Files selected for processing (6)
  • ci/detect_test_components.sh
  • ci/run_ctests.sh
  • ci/run_cuopt_pytests.sh
  • ci/test_cpp.sh
  • ci/test_python.sh
  • ci/test_wheel_cuopt.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • ci/test_wheel_cuopt.sh

Comment on lines +28 to +37
# In RAPIDS CI pull-request builds, the branch is a merge commit so
# HEAD~1..HEAD gives the full PR diff. If the parent isn't reachable
# (e.g. shallow clone or non-merge workflow), fall back to running all.
local changed_files
if ! git rev-parse --verify HEAD~1 &>/dev/null; then
export CUOPT_TEST_COMPONENTS="all"
return
fi
if ! changed_files=$(git diff --name-only HEAD~1..HEAD 2>/dev/null); then
# If git diff fails, run all tests to be safe
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the full script context to understand the code better
cd /tmp && git ls-files | head -20

Repository: NVIDIA/cuopt

Length of output: 124


🏁 Script executed:

# Find and examine the script
find . -name "detect_test_components.sh" -type f 2>/dev/null

Repository: NVIDIA/cuopt

Length of output: 86


🏁 Script executed:

# Check GitHub Actions workflows
find .github/workflows -name "*.yml" -o -name "*.yaml" 2>/dev/null | head -20

Repository: NVIDIA/cuopt

Length of output: 505


🏁 Script executed:

# Read the full detect_test_components.sh script
cat -n ci/detect_test_components.sh

Repository: NVIDIA/cuopt

Length of output: 6237


🏁 Script executed:

# Check the PR workflow configuration
cat -n .github/workflows/pr.yaml

Repository: NVIDIA/cuopt

Length of output: 20632


🏁 Script executed:

# Also check test.yaml
cat -n .github/workflows/test.yaml

Repository: NVIDIA/cuopt

Length of output: 4785


🏁 Script executed:

# Look for any documentation about how RAPIDS CI handles PR checkouts
find . -name "*.md" -o -name "*.txt" | xargs grep -l "RAPIDS_BUILD_TYPE\|pull-request" 2>/dev/null | head -5

Repository: NVIDIA/cuopt

Length of output: 73


🏁 Script executed:

# Check if there are any comments/docs in the script about the merge commit assumption
grep -n "merge\|parent\|HEAD~1" ci/detect_test_components.sh

Repository: NVIDIA/cuopt

Length of output: 427


🏁 Script executed:

# Search for any references to the merge commit handling in other CI scripts
grep -r "HEAD~1\|merge.*commit" ci/ --include="*.sh" 2>/dev/null | head -10

Repository: NVIDIA/cuopt

Length of output: 452


Add a merge-commit check before using HEAD~1..HEAD for PR diff.

The current check at line 32 only verifies that HEAD~1 exists, but doesn't guarantee HEAD is a merge commit. On a single-parent commit, HEAD~1..HEAD shows only that commit, not the full PR diff—potentially under-selecting tests. The code comment already acknowledges non-merge workflows as a fallback case, so add the verification:

Proposed fix
    if ! git rev-parse --verify HEAD~1 &>/dev/null; then
        export CUOPT_TEST_COMPONENTS="all"
        return
    fi
+   # Require merge-commit context for HEAD~1..HEAD PR diff assumption.
+   # `git rev-list --parents -n 1 HEAD` returns: <sha> <parent1> [parent2...]
+   if [[ $(git rev-list --parents -n 1 HEAD | awk '{print NF}') -lt 3 ]]; then
+       export CUOPT_TEST_COMPONENTS="all"
+       return
+   fi
    if ! changed_files=$(git diff --name-only HEAD~1..HEAD 2>/dev/null); then
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ci/detect_test_components.sh` around lines 28 - 37, The script currently only
verifies HEAD~1 exists before using git diff HEAD~1..HEAD, which can
under-select tests on non-merge commits; update the pre-check to also verify
that HEAD is a merge commit (has at least two parents) before using
HEAD~1..HEAD. In the block that checks git rev-parse --verify HEAD~1 and before
running git diff, add a check (e.g., test for HEAD^2 via git rev-parse --verify
HEAD^2 or inspect parents) and if that check fails set
CUOPT_TEST_COMPONENTS="all" and return; keep the existing changed_files variable
and subsequent git diff HEAD~1..HEAD logic unchanged otherwise.

Copy link
Member

@jameslamb jameslamb left a comment

Choose a reason for hiding this comment

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

Why is this being done in shell scripts?

We already have a GitHub Action that runs on PR CI to do exactly this, and would prefer to not have multiple competing implementations of the same behavior.

changed-files:
secrets: inherit
uses: rapidsai/shared-workflows/.github/workflows/changed-files.yaml@main

@rgsl888prabhu
Copy link
Collaborator

Why is this being done in shell scripts?

We already have a GitHub Action that runs on PR CI to do exactly this, and would prefer to not have multiple competing implementations of the same behavior.

changed-files:
secrets: inherit
uses: rapidsai/shared-workflows/.github/workflows/changed-files.yaml@main

Hi @jameslamb,

The issue stems from multiple solvers under same library, currently routing is in good state and we don't change anything related to it often, so we wanted to reduce routing test overhead. Currently we are are using your approach a whole for the complete test, but here we need to selectively not run part of the tests.

Can you please suggest a better approach where we can use existing method to disable the tests? Should we separate these tests into different workflow or is there a way we can pass env variable to enable or disable these tests based on the files ?

Copy link
Member

@jameslamb jameslamb left a comment

Choose a reason for hiding this comment

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

Can you please suggest a better approach where we can use existing method to disable the tests? Should we separate these tests into different workflow or is there a way we can pass env variable to enable or disable these tests based on the files ?

Yes, definitely!

Looking at this more closely and with the benefit of your description, I do understand how this is a bit different than the top-level "run / don't run an entire workflow" changed-files gate.

I think you could re-use the output of the changed-files workflow for this purpose though. Each of the changed-files groups holds a boolean saying whether or not a group of file patterns was matched.

if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_cpp

I think you could re-use those to pass environment variables to the code (doing what ci/detect_test_components.sh is attempting to do).

Something like this:

conda-python-tests:
  needs: [conda-python-build, changed-files, compute-matrix-filters]
  uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@main
  if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python_conda
  with:
    script: |
        CUOPT_ROUTING_FILES_CHANGED="${{ fromJSON(needs.changed-files.outputs.changed_file_groups).test_routing }}"
        export CUOPT_ROUTING_FILES_CHANGED
        ci/test_python.sh

And then handle those variables in test scripts however you want.

Untested, but in theory I think multi-line scripts like this should work: https://github.com/rapidsai/shared-workflows/blob/52b43724e7c9edba0acf675aaf6f526a22b3111f/.github/workflows/conda-python-tests.yaml#L304

If not, you can combine everything together with ;.

Benefits of this approach:

  • no need to re-implement the "figure out which files changed in the PR" logic (it is not trivial!)
  • less shell code to maintain
  • allows the groups to be re-used and combined for other jobs
  • puts all of the "which files affect which workflows" logic in one place (changed-files: configuration in pr.yaml) instead

And whether or not you do that, please do consider how these things will work when the tests scripts are run in workflow NOT triggered by a PR.

Comment on lines +28 to +36
# In RAPIDS CI pull-request builds, the branch is a merge commit so
# HEAD~1..HEAD gives the full PR diff. If the parent isn't reachable
# (e.g. shallow clone or non-merge workflow), fall back to running all.
local changed_files
if ! git rev-parse --verify HEAD~1 &>/dev/null; then
export CUOPT_TEST_COMPONENTS="all"
return
fi
if ! changed_files=$(git diff --name-only HEAD~1..HEAD 2>/dev/null); then
Copy link
Member

Choose a reason for hiding this comment

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

This is not true. copy-pr-bot copies commits from the PR branch to a branch on the repo called pull-request/{pr-number} and there HEAD is exactly HEAD of the PR branch.

It isn't like in other open-source projects on GitHub where HEAD is a merge commit created by GitHub that merges the PR branch into the target branch.

That is why in the RAPIDS changed-files action, we do some work to figure out what the target branch of the PR was:

https://github.com/rapidsai/shared-actions/blob/f722a09c99e883ccca580bbb7135945d8efb90e0/changed-files/action.yml#L40-L45

I think this check HEAD~1..HEAD is only going to compare between the difference between the latest and previous-to-that commit on the PR branch, which is not what you want.

You may want to look into something like step-security/changed-files, which the existing RAPIDS changed-files action uses:

It produces an output listing all the files that changed: https://github.com/step-security/changed-files?tab=readme-ov-file#outputs-

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.

4 participants