Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 69 additions & 3 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,53 @@ jobs:
- '!.cursor-plugin/**'
- '!.claude-plugin/**'
- '!gemini-extension.json'
# Shared infrastructure: triggers ALL component tests when changed.
# Component-specific filters only list paths unique to that component.
test_shared:
- 'conda/**'
- 'cpp/cmake/**'
- 'cpp/CMakeLists.txt'
- 'cpp/include/cuopt/error.hpp'
- 'cpp/include/cuopt/utilities/**'
- 'cpp/src/utilities/**'
- 'cpp/tests/CMakeLists.txt'
- 'cpp/tests/utilities/**'
- 'dependencies.yaml'
- 'python/cuopt/cuopt/*.py'
- 'python/cuopt/cuopt/distance_engine/**'
- 'python/cuopt/cuopt/utils/**'
- 'python/libcuopt/**'
test_routing:
- 'cpp/include/cuopt/routing/**'
- 'cpp/src/distance/**'
- 'cpp/src/routing/**'
- 'cpp/tests/distance_engine/**'
- 'cpp/tests/examples/routing/**'
- 'cpp/tests/routing/**'
- 'python/cuopt/cuopt/routing/**'
- 'python/cuopt/cuopt/tests/routing/**'
- 'regression/routing*'
test_lp:
- 'cpp/include/cuopt/linear_programming/**'
- 'cpp/src/barrier/**'
- 'cpp/src/dual_simplex/**'
- 'cpp/src/math_optimization/**'
- 'cpp/src/pdlp/**'
- 'cpp/tests/dual_simplex/**'
- 'cpp/tests/linear_programming/**'
- 'cpp/tests/qp/**'
- 'python/cuopt/cuopt/tests/linear_programming/**'
- 'python/cuopt/cuopt/tests/quadratic_programming/**'
- 'regression/lp*'
test_mip:
# Note: MIP has no separate Python tests (tested through LP tests).
# If MIP-specific Python tests are added, include their paths here.
- 'cpp/include/cuopt/linear_programming/**'
- 'cpp/src/branch_and_bound/**'
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- 'cpp/src/cuts/**'
- 'cpp/src/mip_heuristics/**'
- 'cpp/tests/mip/**'
- 'regression/mip*'
checks:
secrets: inherit
uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@main
Expand All @@ -296,7 +343,14 @@ jobs:
if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_cpp
with:
build_type: pull-request
script: ci/test_cpp.sh
# Folded (>-): one line for CI; RAPIDS runs $INPUTS_SCRIPT unquoted — see ci/utils/run_with_pr_changed_components.sh.
# test_shared is OR'd into each component flag so shared infra changes trigger all tests.
script: >-
ci/utils/run_with_pr_changed_components.sh
${{ fromJSON(needs.changed-files.outputs.changed_file_groups).test_routing || fromJSON(needs.changed-files.outputs.changed_file_groups).test_shared }}
${{ fromJSON(needs.changed-files.outputs.changed_file_groups).test_lp || fromJSON(needs.changed-files.outputs.changed_file_groups).test_shared }}
${{ fromJSON(needs.changed-files.outputs.changed_file_groups).test_mip || fromJSON(needs.changed-files.outputs.changed_file_groups).test_shared }}
ci/test_cpp.sh
matrix_filter: ${{ needs.compute-matrix-filters.outputs.conda_test_filter }}
secrets:
script-env-secret-1-key: CUOPT_DATASET_S3_URI
Expand All @@ -320,7 +374,13 @@ jobs:
with:
run_codecov: false
build_type: pull-request
script: ci/test_python.sh
# Folded (>-): one line for CI; see ci/utils/run_with_pr_changed_components.sh.
script: >-
ci/utils/run_with_pr_changed_components.sh
${{ fromJSON(needs.changed-files.outputs.changed_file_groups).test_routing || fromJSON(needs.changed-files.outputs.changed_file_groups).test_shared }}
${{ fromJSON(needs.changed-files.outputs.changed_file_groups).test_lp || fromJSON(needs.changed-files.outputs.changed_file_groups).test_shared }}
${{ fromJSON(needs.changed-files.outputs.changed_file_groups).test_mip || fromJSON(needs.changed-files.outputs.changed_file_groups).test_shared }}
ci/test_python.sh
matrix_filter: ${{ needs.compute-matrix-filters.outputs.conda_test_filter }}
secrets:
script-env-secret-1-key: CUOPT_DATASET_S3_URI
Expand Down Expand Up @@ -381,7 +441,13 @@ jobs:
if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python_wheels
with:
build_type: pull-request
script: ci/test_wheel_cuopt.sh
# Folded (>-): one line for CI; see ci/utils/run_with_pr_changed_components.sh.
script: >-
ci/utils/run_with_pr_changed_components.sh
${{ fromJSON(needs.changed-files.outputs.changed_file_groups).test_routing || fromJSON(needs.changed-files.outputs.changed_file_groups).test_shared }}
${{ fromJSON(needs.changed-files.outputs.changed_file_groups).test_lp || fromJSON(needs.changed-files.outputs.changed_file_groups).test_shared }}
${{ fromJSON(needs.changed-files.outputs.changed_file_groups).test_mip || fromJSON(needs.changed-files.outputs.changed_file_groups).test_shared }}
ci/test_wheel_cuopt.sh
matrix_filter: ${{ needs.compute-matrix-filters.outputs.wheel_lean_filter }}
secrets:
script-env-secret-1-key: CUOPT_DATASET_S3_URI
Expand Down
53 changes: 49 additions & 4 deletions ci/run_ctests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,40 @@

set -euo pipefail

# Determine whether a test should run based on the label map generated by CMake.
# The label map file (test_component_labels.txt) contains lines like:
# ROUTING_TEST:routing
# LP_UNIT_TEST:lp
# CLI_TEST:shared
# Tests labelled "shared" always run. Unknown tests (not in the map) also run.
should_run_test() {
local test_name="$1"
local components="${CUOPT_TEST_COMPONENTS:-all}"

if [[ "${components}" == "all" ]]; then
return 0
fi

# Look up the label from the map file
local label=""
if [[ -n "${LABEL_MAP_FILE:-}" && -f "${LABEL_MAP_FILE}" ]]; then
label=$(grep "^${test_name}:" "${LABEL_MAP_FILE}" 2>/dev/null | cut -d: -f2 || true)
fi

# "shared" or unknown tests always run
if [[ -z "${label}" || "${label}" == "shared" ]]; then
return 0
fi

# Check if the test's label is in the requested components
# MIP tests also run when LP is requested (MIP depends on LP infrastructure)
if [[ "${components}" == *"${label}"* ]]; then
return 0
fi

return 1
}

# Support customizing the gtests' install location
# First, try the installed location (CI/conda environments)
installed_test_location="${INSTALL_PREFIX:-${CONDA_PREFIX:-/usr}}/bin/gtests/libcuopt/"
Expand All @@ -21,16 +55,27 @@ else
exit 1
fi

# Load the label map if available
LABEL_MAP_FILE="${GTEST_DIR}/test_component_labels.txt"
if [[ ! -f "${LABEL_MAP_FILE}" ]]; then
echo "Warning: Label map not found at ${LABEL_MAP_FILE}, all tests will run." >&2
LABEL_MAP_FILE=""
fi

for gt in "${GTEST_DIR}"/*_TEST; do
test_name=$(basename "${gt}")
echo "Running gtest ${test_name}"
"${gt}" "$@"
if should_run_test "${test_name}"; then
echo "Running gtest ${test_name}"
"${gt}" "$@"
else
echo "Skipping gtest ${test_name} (not in CUOPT_TEST_COMPONENTS=${CUOPT_TEST_COMPONENTS:-all})"
fi
done

# Run C_API_TEST with CPU memory for local solves (excluding time limit tests)
if [ -x "${GTEST_DIR}/C_API_TEST" ]; then
if [ -x "${GTEST_DIR}/C_API_TEST" ] && should_run_test "C_API_TEST"; then
echo "Running gtest C_API_TEST with CUOPT_USE_CPU_MEM_FOR_LOCAL"
CUOPT_USE_CPU_MEM_FOR_LOCAL=1 "${GTEST_DIR}/C_API_TEST" --gtest_filter=-c_api/TimeLimitTestFixture.* "$@"
else
echo "Skipping C_API_TEST with CUOPT_USE_CPU_MEM_FOR_LOCAL (binary not found)"
echo "Skipping C_API_TEST with CUOPT_USE_CPU_MEM_FOR_LOCAL"
fi
28 changes: 26 additions & 2 deletions ci/run_cuopt_pytests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@ set -euo pipefail
# It is essential to cd into python/cuopt/cuopt as `pytest-xdist` + `coverage` seem to work only at this directory level.

# Support invoking run_cuopt_pytests.sh outside the script directory
cd "$(dirname "$(realpath "${BASH_SOURCE[0]}")")"/../python/cuopt/cuopt/
cd "$(dirname "$(realpath "${BASH_SOURCE[0]}")")/../python/cuopt/cuopt/"

pytest -s --cache-clear "$@" tests
# Build the list of test directories based on CUOPT_TEST_COMPONENTS
COMPONENTS="${CUOPT_TEST_COMPONENTS:-all}"
TEST_DIRS=""

if [[ "${COMPONENTS}" == "all" ]]; then
TEST_DIRS="tests"
else
if [[ "${COMPONENTS}" == *"routing"* ]]; then
TEST_DIRS="${TEST_DIRS} tests/routing"
fi
if [[ "${COMPONENTS}" == *"lp"* ]]; then
TEST_DIRS="${TEST_DIRS} tests/linear_programming tests/quadratic_programming"
fi
# MIP does not have separate Python tests (tested through LP tests)

# If no Python test dirs matched, skip
if [[ -z "${TEST_DIRS}" ]]; then
echo "No Python test directories match CUOPT_TEST_COMPONENTS=${COMPONENTS}, skipping."
exit 0
fi
fi

echo "Running pytest on: ${TEST_DIRS} (CUOPT_TEST_COMPONENTS=${COMPONENTS})"
# shellcheck disable=SC2086
pytest -s --cache-clear "$@" ${TEST_DIRS}
13 changes: 3 additions & 10 deletions ci/test_cpp.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail
Expand Down Expand Up @@ -35,14 +35,7 @@ rapids-logger "Check GPU usage"
nvidia-smi

rapids-logger "Download datasets"
./datasets/linear_programming/download_pdlp_test_dataset.sh
./datasets/mip/download_miplib_test_dataset.sh

RAPIDS_DATASET_ROOT_DIR="$(realpath datasets)"
export RAPIDS_DATASET_ROOT_DIR
pushd "${RAPIDS_DATASET_ROOT_DIR}"
./get_test_data.sh
popd
source ./ci/utils/download_test_datasets.sh

EXITCODE=0
trap "EXITCODE=1" ERR
Expand All @@ -51,7 +44,7 @@ set +e
# Run gtests from libcuopt-tests package
export GTEST_OUTPUT=xml:${RAPIDS_TESTS_DIR}/

rapids-logger "Run gtests"
rapids-logger "Run gtests (components: ${CUOPT_TEST_COMPONENTS})"
timeout 40m ./ci/run_ctests.sh

rapids-logger "Test script exiting with value: $EXITCODE"
Expand Down
10 changes: 2 additions & 8 deletions ci/test_python.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,7 @@ mkdir -p "${RAPIDS_TESTS_DIR}" "${RAPIDS_COVERAGE_DIR}"
rapids-print-env

rapids-logger "Download datasets"
RAPIDS_DATASET_ROOT_DIR="$(realpath datasets)"
export RAPIDS_DATASET_ROOT_DIR
./datasets/linear_programming/download_pdlp_test_dataset.sh
./datasets/mip/download_miplib_test_dataset.sh
pushd "${RAPIDS_DATASET_ROOT_DIR}"
./get_test_data.sh
popd
source ./ci/utils/download_test_datasets.sh

rapids-logger "Check GPU usage"
nvidia-smi
Expand All @@ -57,7 +51,7 @@ export OMP_NUM_THREADS=1
rapids-logger "Test cuopt_cli"
timeout 10m bash ./python/libcuopt/libcuopt/tests/test_cli.sh

rapids-logger "pytest cuopt"
rapids-logger "pytest cuopt (components: ${CUOPT_TEST_COMPONENTS})"
timeout 30m ./ci/run_cuopt_pytests.sh \
--junitxml="${RAPIDS_TESTS_DIR}/junit-cuopt.xml" \
--cov-config=.coveragerc \
Expand Down
13 changes: 4 additions & 9 deletions ci/test_wheel_cuopt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,9 @@ elif command -v dnf &> /dev/null; then
dnf -y install file unzip
fi

./datasets/linear_programming/download_pdlp_test_dataset.sh
./datasets/mip/download_miplib_test_dataset.sh
cd ./datasets
./get_test_data.sh --solomon
./get_test_data.sh --tsp
cd -

RAPIDS_DATASET_ROOT_DIR="$(realpath datasets)"
export RAPIDS_DATASET_ROOT_DIR
CUOPT_ROUTING_DATASET_ARGS="--solomon --tsp"
export CUOPT_ROUTING_DATASET_ARGS
source ./ci/utils/download_test_datasets.sh

# Run CLI tests
timeout 10m bash ./python/libcuopt/libcuopt/tests/test_cli.sh
Expand All @@ -71,6 +65,7 @@ timeout 10m bash ./python/libcuopt/libcuopt/tests/test_cli.sh
# Due to race condition in certain cases UCX might not be able to cleanup properly, so we set the number of threads to 1
export OMP_NUM_THREADS=1

echo "Running cuopt pytests (components: ${CUOPT_TEST_COMPONENTS})"
timeout 30m ./ci/run_cuopt_pytests.sh --verbose --capture=no

# run thirdparty integration tests for only nightly builds
Expand Down
29 changes: 29 additions & 0 deletions ci/utils/derive_test_components.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# Derives CUOPT_TEST_COMPONENTS from changed-files env vars
# (CUOPT_ROUTING_CHANGED, CUOPT_LP_CHANGED, CUOPT_MIP_CHANGED).
#
# When none of these env vars are set (e.g. nightly / non-PR builds),
# defaults to "all" so every test runs.
#
# Usage: source ./ci/utils/derive_test_components.sh

if [[ -z "${CUOPT_ROUTING_CHANGED:-}" && -z "${CUOPT_LP_CHANGED:-}" && -z "${CUOPT_MIP_CHANGED:-}" ]]; then
export CUOPT_TEST_COMPONENTS="all"
else
components=""
[[ "${CUOPT_ROUTING_CHANGED:-}" == "true" ]] && components="${components:+${components},}routing"
[[ "${CUOPT_LP_CHANGED:-}" == "true" ]] && components="${components:+${components},}lp"
[[ "${CUOPT_MIP_CHANGED:-}" == "true" ]] && components="${components:+${components},}mip"
# MIP is validated through LP tests (no separate MIP Python tests),
# so always include LP when MIP changes.
if [[ "${CUOPT_MIP_CHANGED:-}" == "true" && "${components}" != *"lp"* ]]; then
components="${components:+${components},}lp"
fi
# Fallback to "all" if all components are false (defensive — the job-level
# 'if' gate in pr.yaml would normally skip the job entirely in this case).
export CUOPT_TEST_COMPONENTS="${components:-all}"
fi
echo "CUOPT_TEST_COMPONENTS=${CUOPT_TEST_COMPONENTS}"
45 changes: 45 additions & 0 deletions ci/utils/download_test_datasets.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# Downloads test datasets based on CUOPT_TEST_COMPONENTS.
# Sources derive_test_components.sh to set CUOPT_TEST_COMPONENTS if not already set.
#
# Usage: source ./ci/utils/download_test_datasets.sh
#
# Optional env vars:
# CUOPT_ROUTING_DATASET_ARGS — extra args for routing get_test_data.sh (e.g. "--solomon --tsp")
# Defaults to no args (downloads all routing data).

set -euo pipefail

SCRIPT_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"

# Derive CUOPT_TEST_COMPONENTS from changed-files env vars (no-op if already set)
if [[ -z "${CUOPT_TEST_COMPONENTS:-}" ]]; then
source "${SCRIPT_DIR}/derive_test_components.sh"
fi

RAPIDS_DATASET_ROOT_DIR="$(realpath datasets)"
export RAPIDS_DATASET_ROOT_DIR

if [[ "${CUOPT_TEST_COMPONENTS}" == "all" || "${CUOPT_TEST_COMPONENTS}" == *"lp"* ]]; then
./datasets/linear_programming/download_pdlp_test_dataset.sh
else
echo "Skipping LP dataset download (not needed for components: ${CUOPT_TEST_COMPONENTS})"
fi

if [[ "${CUOPT_TEST_COMPONENTS}" == "all" || "${CUOPT_TEST_COMPONENTS}" == *"mip"* ]]; then
./datasets/mip/download_miplib_test_dataset.sh
else
echo "Skipping MIP dataset download (not needed for components: ${CUOPT_TEST_COMPONENTS})"
fi

if [[ "${CUOPT_TEST_COMPONENTS}" == "all" || "${CUOPT_TEST_COMPONENTS}" == *"routing"* ]]; then
pushd "${RAPIDS_DATASET_ROOT_DIR}"
# shellcheck disable=SC2086
./get_test_data.sh ${CUOPT_ROUTING_DATASET_ARGS:-}
popd
else
echo "Skipping routing dataset downloads (not needed for components: ${CUOPT_TEST_COMPONENTS})"
fi
29 changes: 29 additions & 0 deletions ci/utils/run_with_pr_changed_components.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash

# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# Export changed-file group flags from pr.yaml, then run a test script from the repo root.
#
# RAPIDS reusable workflows (conda-*-tests, wheels-test) run the job `script` as unquoted
# $INPUTS_SCRIPT, so multiline shell is word-split and breaks. In pr.yaml use a folded
# block scalar (>-) so this invocation is one physical line to Actions but readable:
#
# script: >-
# ci/utils/run_with_pr_changed_components.sh
# ${{ fromJSON(needs.changed-files.outputs.changed_file_groups).test_routing || fromJSON(needs.changed-files.outputs.changed_file_groups).test_shared }}
# ${{ fromJSON(needs.changed-files.outputs.changed_file_groups).test_lp || fromJSON(needs.changed-files.outputs.changed_file_groups).test_shared }}
# ${{ fromJSON(needs.changed-files.outputs.changed_file_groups).test_mip || fromJSON(needs.changed-files.outputs.changed_file_groups).test_shared }}
# ci/test_example.sh
#
# Optional extra args are forwarded to the test script.

set -euo pipefail

export CUOPT_ROUTING_CHANGED="${1}"
export CUOPT_LP_CHANGED="${2}"
export CUOPT_MIP_CHANGED="${3}"
shift 3

cd "$(dirname "${BASH_SOURCE[0]}")/../.."
exec bash "$@"
Loading
Loading