diff --git a/ci/matrix.yaml b/ci/matrix.yaml index 21e8d7cc94d..44786e3b19b 100644 --- a/ci/matrix.yaml +++ b/ci/matrix.yaml @@ -8,6 +8,7 @@ workflows: # - {jobs: ['test'], project: 'thrust', std: 17, ctk: 'curr', cxx: ['gcc12', 'llvm16']} # override: + - {jobs: ['compute-sanitizer'], project: 'cub', std: 17, ctk: 'curr', cxx: ['gcc12'], cmake_options: '-DCMAKE_CUDA_FLAGS=-lineinfo'} pull_request: # Old CTK @@ -178,6 +179,11 @@ jobs: # General: build: { gpu: false } test: { gpu: true, needs: 'build' } + compute-sanitizer: + gpu: true + name: 'ComputeSanitizer' + needs: 'build' + invoke: { prefix: 'test', args: '-compute-sanitizer' } # CCCL: infra: { gpu: true } # example project launches a kernel diff --git a/ci/test_cub.sh b/ci/test_cub.sh index 9e036bd06f3..1566883d588 100755 --- a/ci/test_cub.sh +++ b/ci/test_cub.sh @@ -6,10 +6,11 @@ NO_LID=false LID0=false LID1=false LID2=false +COMPUTE_SANITIZER=false ci_dir=$(dirname "$0") -new_args=$("${ci_dir}/util/extract_switches.sh" -no-lid -lid0 -lid1 -lid2 -- "$@") +new_args=$("${ci_dir}/util/extract_switches.sh" -no-lid -lid0 -lid1 -lid2 -compute-sanitizer -- "$@") eval set -- ${new_args} while true; do case "$1" in @@ -29,6 +30,10 @@ while true; do LID2=true shift ;; + -compute-sanitizer) + COMPUTE_SANITIZER=true + shift + ;; --) shift break @@ -58,6 +63,11 @@ else PRESETS=("cub-cpp$CXX_STANDARD") fi +if $COMPUTE_SANITIZER; then + echo "Setting CCCL_TEST_MODE=compute-sanitizer" + export CCCL_TEST_MODE=compute-sanitizer +fi + for PRESET in ${PRESETS[@]}; do test_preset "CUB (${PRESET})" ${PRESET} done diff --git a/cub/test/CMakeLists.txt b/cub/test/CMakeLists.txt index 7d7f3e3db7b..ff1bc3a9295 100644 --- a/cub/test/CMakeLists.txt +++ b/cub/test/CMakeLists.txt @@ -219,7 +219,11 @@ function(cub_add_test target_name_var test_name test_src cub_target launcher_id) target_link_libraries(${test_target} PRIVATE ${catch2_main_objects}) add_dependencies(${config_meta_target} ${test_target}) - add_test(NAME ${test_target} COMMAND "$") + add_test(NAME ${test_target} COMMAND + "${CMAKE_COMMAND}" + "-DTEST=$" + -P "${CUB_SOURCE_DIR}/test/run_test.cmake" + ) else() # Not CUB_SEPARATE_CATCH2 # Per config catch2 runner set(config_c2run_target ${config_prefix}.catch2_test.lid_${launcher_id}) @@ -244,8 +248,10 @@ function(cub_add_test target_name_var test_name test_src cub_target launcher_id) thrust_fix_clang_nvcc_build_for(${config_c2run_target}) endif() - add_test(NAME ${config_c2run_target} - COMMAND "$" + add_test(NAME ${config_c2run_target} COMMAND + "${CMAKE_COMMAND}" + "-DTEST=$" + -P "${CUB_SOURCE_DIR}/test/run_test.cmake" ) endif() # per config catch2 runner @@ -335,7 +341,11 @@ function(cub_add_test target_name_var test_name test_src cub_target launcher_id) endif() add_dependencies(${test_meta_target} ${test_target}) - add_test(NAME ${test_target} COMMAND "$") + add_test(NAME ${test_target} COMMAND + "${CMAKE_COMMAND}" + "-DTEST=$" + -P "${CUB_SOURCE_DIR}/test/run_test.cmake" + ) endif() endif() # Not catch2 test endfunction() diff --git a/cub/test/run_test.cmake b/cub/test/run_test.cmake new file mode 100644 index 00000000000..2ef9818ec84 --- /dev/null +++ b/cub/test/run_test.cmake @@ -0,0 +1,87 @@ +# +# Launch a test, optionally enabling runtime sanitizers, etc. +# + +function(usage) + message("Usage:") + message(" cmake -D TEST=bin/test.exe \\") + message(" -D ARGS=\"arg1 arg2\" \\") + message(" -D MODE=compute-sanitizer \\") + message(" -P cccl/cub/test/run_test.cmake") + message("") + message(" - TEST: Required. Path to the test executable.") + message(" - ARGS: Optional. Arguments to pass to the test executable.") + message(" - MODE: Optional.") + message(" - May be set through CCCL_TEST_MODE env var.") + message(" - Must be one of the following:") + message(" - \"none\" (default)") + message(" - \"compute-sanitizer\"") +endfunction() + +# Usage: +# run_command(COMMAND [ARGS...]) +# +# The command is printed before it is executed. +# The new process's stdout, sterr are redirected to the current process. +# The current process will exit with an error if the new process exits with a non-zero status. +function(run_command command) + list(APPEND command ${ARGN}) + list(JOIN command " " command_str) + message(STATUS ">> Running:\n\t${command_str}") + execute_process(COMMAND ${command} RESULT_VARIABLE result) + if (NOT result EQUAL 0) + message(FATAL_ERROR ">> Exit Status: ${result}") + else() + message(STATUS ">>Exit Status: ${result}") + endif() +endfunction() + +# Run compute-sanitizer with the default project config with a specific sanitizer tool. +function(run_compute_sanitizer tool command) + run_command(compute-sanitizer + --tool ${tool} + # TODO Figure out what the min version needed is for this: + # --check-bulk-copy yes + --check-device-heap yes + --check-exit-code yes + --check-warpgroup-mma yes + --error-exitcode 1 + --nvtx true + ${command} ${ARGN} + ) +endfunction() + +###################################################################### + +# Parse arguments +if(NOT DEFINED TEST) + usage() + message(FATAL_ERROR "TEST must be defined") +endif() + +if(NOT DEFINED ARGS) + set(ARGS "") +endif() + +if(NOT DEFINED MODE) + if(DEFINED ENV{CCCL_TEST_MODE}) + message(STATUS "Using CCCL_TEST_MODE from env: $ENV{CCCL_TEST_MODE}") + set(MODE $ENV{CCCL_TEST_MODE}) + else() + set(MODE "none") + endif() +elseif(NOT MODE) + set(MODE "none") +endif() + +if (MODE STREQUAL "none") + run_command(${TEST} ${ARGS}) +elseif (MODE STREQUAL "compute-sanitizer") + run_compute_sanitizer("memcheck" ${TEST} ${ARGS}) + run_compute_sanitizer("racecheck" ${TEST} ${ARGS}) + run_compute_sanitizer("initcheck" ${TEST} ${ARGS}) + run_compute_sanitizer("synccheck" ${TEST} ${ARGS}) +else() + usage() + message(FATAL_ERROR "Invalid MODE: ${MODE}") +endif()