From a22b6d51f612b5a9abbc5c701196d23205ad26fc Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Tue, 14 Apr 2026 15:35:40 -0500 Subject: [PATCH 1/6] feat(tests): add CTest labels and CI label filtering for selective C++ test execution Add domain and tier labels to all 31 C++ test executables so that CI and developers can run only the tests relevant to their changes. Labels: - Domain: routing, solver (LP+MIP+QP umbrella), lp, mip, qp, grpc, parser, cli - Tier: tier1 (fast unit tests), tier2 (slow integration/benchmarks) Changes: - Extend ConfigureTest() CMake macro with optional LABELS keyword argument - Add register_test_labels() helper and CUOPT_TEST_LABEL_ENTRIES global property to generate an installed test_labels.txt manifest - Add labels to every ConfigureTest call and manual add_test call - Update ci/run_ctests.sh to support CUOPT_TEST_LABELS env var for filtering (e.g. CUOPT_TEST_LABELS=routing runs only routing tests) - Fully backward compatible: no labels set means all tests run --- ci/run_ctests.sh | 70 +++++++++++++++++-- cpp/libmps_parser/tests/CMakeLists.txt | 1 + cpp/tests/CMakeLists.txt | 39 ++++++++++- cpp/tests/distance_engine/CMakeLists.txt | 2 +- cpp/tests/dual_simplex/CMakeLists.txt | 2 +- cpp/tests/linear_programming/CMakeLists.txt | 7 +- .../linear_programming/grpc/CMakeLists.txt | 6 ++ cpp/tests/mip/CMakeLists.txt | 28 ++++---- cpp/tests/qp/CMakeLists.txt | 2 +- cpp/tests/routing/CMakeLists.txt | 23 +++--- cpp/tests/utilities/CMakeLists.txt | 3 +- 11 files changed, 150 insertions(+), 33 deletions(-) diff --git a/ci/run_ctests.sh b/ci/run_ctests.sh index fc1de8e1b4..2a4ae3d2ed 100755 --- a/ci/run_ctests.sh +++ b/ci/run_ctests.sh @@ -21,16 +21,78 @@ else exit 1 fi +# --------------------------------------------------------------------------- +# Label-based filtering +# +# Set CUOPT_TEST_LABELS to a comma-separated list of labels to run only tests +# matching ANY of those labels. For example: +# CUOPT_TEST_LABELS=routing — run only routing tests +# CUOPT_TEST_LABELS=solver — run all LP/MIP/QP tests +# CUOPT_TEST_LABELS=tier1 — run only fast unit tests +# CUOPT_TEST_LABELS=routing,solver — run routing + solver tests +# +# When unset or empty, all tests run (backward-compatible default). +# --------------------------------------------------------------------------- +LABEL_MANIFEST="${GTEST_DIR}/test_labels.txt" + +# should_run_test +# Returns 0 (true) if the test should run given the current label filter. +should_run_test() { + local test_name="$1" + + # No filter requested — run everything. + if [[ -z "${CUOPT_TEST_LABELS:-}" ]]; then + return 0 + fi + + # No manifest available — cannot filter, run everything with a warning. + if [[ ! -f "${LABEL_MANIFEST}" ]]; then + if [[ -z "${_label_warn_printed:-}" ]]; then + echo "Warning: CUOPT_TEST_LABELS set but ${LABEL_MANIFEST} not found; running all tests." >&2 + _label_warn_printed=1 + fi + return 0 + fi + + # Look up the test's labels in the manifest (format: TEST_NAME:label1,label2). + local entry + entry=$(grep "^${test_name}:" "${LABEL_MANIFEST}" 2>/dev/null || true) + if [[ -z "${entry}" ]]; then + # Test not in manifest — run it to be safe. + return 0 + fi + + local test_labels="${entry#*:}" + + # Check if any requested label matches any of the test's labels. + IFS=',' read -ra requested <<< "${CUOPT_TEST_LABELS}" + IFS=',' read -ra actual <<< "${test_labels}" + for req in "${requested[@]}"; do + for act in "${actual[@]}"; do + if [[ "${req}" == "${act}" ]]; then + return 0 + fi + done + done + + # No label match — skip. + return 1 +} + 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} (labels do not match CUOPT_TEST_LABELS=${CUOPT_TEST_LABELS})" + 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 (binary not found or filtered out)" fi diff --git a/cpp/libmps_parser/tests/CMakeLists.txt b/cpp/libmps_parser/tests/CMakeLists.txt index 2d86a1da18..58642c1585 100644 --- a/cpp/libmps_parser/tests/CMakeLists.txt +++ b/cpp/libmps_parser/tests/CMakeLists.txt @@ -53,4 +53,5 @@ endfunction() ConfigureTest(MPS_PARSER_TEST mps_parser_test.cpp ) + set_tests_properties(MPS_PARSER_TEST PROPERTIES LABELS "parser;tier1") ################################################################################################### diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index a73a3361ce..82cfe88b63 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -28,14 +28,30 @@ if(BUILD_TESTS) if(NOT DEFINED INSTALL_TARGET OR "${INSTALL_TARGET}" STREQUAL "") target_link_options(cuopttestutils PRIVATE -Wl,--enable-new-dtags) endif() + + # Global property to accumulate test-name:label mappings for the installed manifest. + define_property(GLOBAL PROPERTY CUOPT_TEST_LABEL_ENTRIES + BRIEF_DOCS "Lines for test_labels.txt (NAME:label1,label2)") + set_property(GLOBAL PROPERTY CUOPT_TEST_LABEL_ENTRIES "") endif() set(CUOPT_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +# Helper: append a test's labels to the global manifest property. +function(register_test_labels TEST_NAME) + list(JOIN ARGN "," _labels) + set_property(GLOBAL APPEND PROPERTY CUOPT_TEST_LABEL_ENTRIES "${TEST_NAME}:${_labels}") +endfunction() + # ################################################################ ------------------------------------------------------------------ +# ConfigureTest(NAME source1.cu source2.cu [LABELS label1 label2 ...]) +# +# LABELS sets CTest labels (for local `ctest -L