[INFRA] Import NVIDIA/CCCL upstream as optimization reference library
CCCL (CUDA C++ Core Libraries) provides: - CUB: device/block/warp-level GPU primitives (reduce, scan, sort, topk) - Thrust: high-level parallel algorithms (transform_reduce, sort, scan) - libcudacxx: CUDA C++ standard library (atomics, barriers, memory) - cudax: experimental features (memory resources, allocators) - Tuning policies: per-SM hardware-specific algorithm parameters Competition optimization vectors mapped to CCCL: - Output TPS (83% weight): warp_reduce, block_reduce, device_topk - Input TPS (14% weight): device_scan, block_load, prefetch - Cache TPS (3% weight): prefix caching strategy patterns - Memory (0.9 util): pooled/cached/buddy allocators Source: https://github.com/NVIDIA/cccl (shallow clone, HEAD only) License: Apache-2.0
This commit is contained in:
202
cccl_upstream/libcudacxx/test/debugging/CMakeLists.txt
Normal file
202
cccl_upstream/libcudacxx/test/debugging/CMakeLists.txt
Normal file
@@ -0,0 +1,202 @@
|
||||
set(LIBCUDACXX_SUPPORTED_DEBUGGERS lldb gdb)
|
||||
set(LIBCUDACXX_DEBUGGING_BASE_TARGET libcudacxx.test.debugging)
|
||||
|
||||
function(libcudacxx_init_debugger_testing enabled_var)
|
||||
# Unconditionally create the umbrella target to make CMakePresets easier to setup. If we
|
||||
# don't have the debuggers available, this target doesn't do anything
|
||||
add_custom_target(${LIBCUDACXX_DEBUGGING_BASE_TARGET})
|
||||
|
||||
# Windows lldb is broken sometimes (see
|
||||
# https://github.com/llvm/llvm-project/issues/74073) so merely finding it does not mean
|
||||
# it is functional. In any case, it is good to validate the binary since even a found
|
||||
# lldb/gdb on other platforms that ends up not working is annoying to work around
|
||||
function(validator result_var item)
|
||||
execute_process(
|
||||
COMMAND ${item} --version
|
||||
OUTPUT_QUIET
|
||||
ERROR_QUIET
|
||||
RESULT_VARIABLE result
|
||||
TIMEOUT 10
|
||||
)
|
||||
|
||||
if (result EQUAL 0)
|
||||
set(${result_var} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${result_var} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set(enabled FALSE)
|
||||
foreach (debugger IN LISTS LIBCUDACXX_SUPPORTED_DEBUGGERS)
|
||||
string(TOUPPER "${debugger}" DEBUGGER_UPPER)
|
||||
|
||||
find_program(
|
||||
LIBCUDACXX_${DEBUGGER_UPPER}
|
||||
NAMES "${debugger}"
|
||||
VALIDATOR validator
|
||||
)
|
||||
|
||||
if (LIBCUDACXX_${DEBUGGER_UPPER})
|
||||
set(debugger_exe "${LIBCUDACXX_${DEBUGGER_UPPER}}")
|
||||
message(STATUS "Found ${debugger}: ${debugger_exe}")
|
||||
execute_process(
|
||||
COMMAND ${debugger_exe} --version
|
||||
OUTPUT_VARIABLE version
|
||||
ERROR_VARIABLE version
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_STRIP_TRAILING_WHITESPACE
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
message(STATUS "${debugger_exe} --version: ${version}")
|
||||
endif()
|
||||
|
||||
set(default OFF)
|
||||
if (LIBCUDACXX_${DEBUGGER_UPPER})
|
||||
set(default ON)
|
||||
endif()
|
||||
|
||||
option(
|
||||
LIBCUDACXX_ENABLE_${DEBUGGER_UPPER}_TESTING
|
||||
"Run libcudacxx pretty-printer tests with ${debugger}"
|
||||
${default}
|
||||
)
|
||||
|
||||
if (
|
||||
LIBCUDACXX_ENABLE_${DEBUGGER_UPPER}_TESTING
|
||||
AND NOT LIBCUDACXX_${DEBUGGER_UPPER}
|
||||
)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"LIBCUDACXX_ENABLE_${DEBUGGER_UPPER}_TESTING is enabled, but ${debugger} was not found"
|
||||
)
|
||||
endif()
|
||||
|
||||
set(
|
||||
LIBCUDACXX_${DEBUGGER_UPPER}
|
||||
"${LIBCUDACXX_${DEBUGGER_UPPER}}"
|
||||
PARENT_SCOPE
|
||||
)
|
||||
set(
|
||||
LIBCUDACXX_ENABLE_${DEBUGGER_UPPER}_TESTING
|
||||
"${LIBCUDACXX_ENABLE_${DEBUGGER_UPPER}_TESTING}"
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
if (LIBCUDACXX_ENABLE_${DEBUGGER_UPPER}_TESTING)
|
||||
set(enabled TRUE)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(${enabled_var} ${enabled} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
libcudacxx_init_debugger_testing(testing_enabled)
|
||||
|
||||
if (NOT testing_enabled)
|
||||
return()
|
||||
endif()
|
||||
|
||||
#[=======================================================================[.rst:
|
||||
libcudacxx_add_pretty_printer_test
|
||||
---------------------------------
|
||||
|
||||
Build a CUDA pretty-printer scenario and register its enabled debugger tests.
|
||||
|
||||
The function creates the executable target
|
||||
``libcudacxx.test.debugging.<NAME>`` with host debug information and optimization
|
||||
disabled. For each enabled debugger, it registers a serial CTest named
|
||||
``libcudacxx.test.debugging.<debugger>.<NAME>``. The test uses the debugger's
|
||||
formatter entry point and the ``<debugger>.expected`` file in the caller's source
|
||||
directory.
|
||||
|
||||
Arguments
|
||||
^^^^^^^^^
|
||||
|
||||
``NAME``
|
||||
Scenario name used in the executable target, CTest names, and diagnostic output.
|
||||
|
||||
``SOURCES``
|
||||
Source files used to build the CUDA scenario executable. Relative paths are resolved
|
||||
against the caller's source directory.
|
||||
|
||||
``CASES``
|
||||
Ordered runner arguments describing the debugger stops and expressions. Each case
|
||||
has the form ``--case <breakpoint> <caller-frame-index> <section-name>
|
||||
<expression>``.
|
||||
|
||||
#]=======================================================================]
|
||||
function(libcudacxx_add_pretty_printer_test)
|
||||
set(options)
|
||||
set(one_value_arguments NAME)
|
||||
set(multi_value_arguments SOURCES CASES)
|
||||
cmake_parse_arguments(
|
||||
pretty_printer
|
||||
"${options}"
|
||||
"${one_value_arguments}"
|
||||
"${multi_value_arguments}"
|
||||
${ARGN}
|
||||
)
|
||||
|
||||
if (pretty_printer_UNPARSED_ARGUMENTS)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Unrecognized arguments: ${pretty_printer_UNPARSED_ARGUMENTS}"
|
||||
)
|
||||
endif()
|
||||
if (NOT pretty_printer_NAME)
|
||||
message(FATAL_ERROR "libcudacxx_add_pretty_printer_test requires NAME")
|
||||
endif()
|
||||
if (NOT pretty_printer_SOURCES)
|
||||
message(FATAL_ERROR "libcudacxx_add_pretty_printer_test requires SOURCES")
|
||||
endif()
|
||||
if (NOT pretty_printer_CASES)
|
||||
message(FATAL_ERROR "libcudacxx_add_pretty_printer_test requires CASES")
|
||||
endif()
|
||||
|
||||
set(target_name "${LIBCUDACXX_DEBUGGING_BASE_TARGET}.${pretty_printer_NAME}")
|
||||
cccl_add_executable(
|
||||
${target_name}
|
||||
DIALECT 17
|
||||
NO_CLANG_TIDY
|
||||
SOURCES ${pretty_printer_SOURCES}
|
||||
)
|
||||
target_compile_options(
|
||||
${target_name}
|
||||
PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:-g> $<$<COMPILE_LANGUAGE:CUDA>:-O0>
|
||||
)
|
||||
target_link_libraries(${target_name} PRIVATE libcudacxx.compiler_interface)
|
||||
|
||||
foreach (debugger IN LISTS LIBCUDACXX_SUPPORTED_DEBUGGERS)
|
||||
string(TOUPPER "${debugger}" DEBUGGER_UPPER)
|
||||
|
||||
if (NOT LIBCUDACXX_ENABLE_${DEBUGGER_UPPER}_TESTING)
|
||||
continue()
|
||||
endif()
|
||||
|
||||
set(executable "${LIBCUDACXX_${DEBUGGER_UPPER}}")
|
||||
set(
|
||||
formatter
|
||||
"${libcudacxx_SOURCE_DIR}/share/libcudacxx/${debugger}/__init__.py"
|
||||
)
|
||||
set(test_name "${target_name}.${debugger}")
|
||||
add_test(
|
||||
NAME ${test_name}
|
||||
COMMAND
|
||||
# gersemi: off
|
||||
"${Python_EXECUTABLE}" "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/run_pretty_printer_test.py"
|
||||
--debugger "${debugger}"
|
||||
--debugger-executable "${executable}"
|
||||
--program "$<TARGET_FILE:${target_name}>"
|
||||
--formatter-init "${formatter}"
|
||||
--expected "${CMAKE_CURRENT_SOURCE_DIR}/${debugger}.expected"
|
||||
--output-log "${CMAKE_CURRENT_BINARY_DIR}/${debugger}.log"
|
||||
${pretty_printer_CASES}
|
||||
# gersemi: on
|
||||
)
|
||||
set_tests_properties(${test_name} PROPERTIES TIMEOUT 60)
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
add_subdirectory(array)
|
||||
add_subdirectory(buffer)
|
||||
add_subdirectory(memory_resource)
|
||||
13
cccl_upstream/libcudacxx/test/debugging/array/CMakeLists.txt
Normal file
13
cccl_upstream/libcudacxx/test/debugging/array/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
libcudacxx_add_pretty_printer_test(
|
||||
NAME array
|
||||
SOURCES source.cu
|
||||
CASES
|
||||
# gersemi: off
|
||||
--case inspect_normal 1 array.normal normal
|
||||
--case inspect_empty 1 array.empty empty
|
||||
--case inspect_nested 1 array.nested nested
|
||||
--case inspect_alias 1 array.alias alias
|
||||
--case inspect_before_update 1 array.update.before updated_values
|
||||
--case inspect_after_update 1 array.update.after updated_values
|
||||
# gersemi: on
|
||||
)
|
||||
44
cccl_upstream/libcudacxx/test/debugging/array/gdb.expected
Normal file
44
cccl_upstream/libcudacxx/test/debugging/array/gdb.expected
Normal file
@@ -0,0 +1,44 @@
|
||||
=============== array.normal begin ===============
|
||||
cuda::std::array<int, 3> = {
|
||||
[0] = -7,
|
||||
[1] = 0,
|
||||
[2] = 42
|
||||
}
|
||||
=============== array.normal end ===============
|
||||
=============== array.empty begin ===============
|
||||
cuda::std::array<int, 0>
|
||||
=============== array.empty end ===============
|
||||
=============== array.nested begin ===============
|
||||
cuda::std::array<cuda::std::array<int, 2>, 2> = {
|
||||
[0] = cuda::std::array<int, 2> = {
|
||||
[0] = 13,
|
||||
[1] = -5
|
||||
},
|
||||
[1] = cuda::std::array<int, 2> = {
|
||||
[0] = 0,
|
||||
[1] = 88
|
||||
}
|
||||
}
|
||||
=============== array.nested end ===============
|
||||
=============== array.alias begin ===============
|
||||
cuda::std::array<int, 4> = {
|
||||
[0] = -31,
|
||||
[1] = 17,
|
||||
[2] = 8,
|
||||
[3] = -64
|
||||
}
|
||||
=============== array.alias end ===============
|
||||
=============== array.update.before begin ===============
|
||||
cuda::std::array<int, 3> = {
|
||||
[0] = 6,
|
||||
[1] = -91,
|
||||
[2] = 52
|
||||
}
|
||||
=============== array.update.before end ===============
|
||||
=============== array.update.after begin ===============
|
||||
cuda::std::array<int, 3> = {
|
||||
[0] = 3,
|
||||
[1] = 85,
|
||||
[2] = -12
|
||||
}
|
||||
=============== array.update.after end ===============
|
||||
21
cccl_upstream/libcudacxx/test/debugging/array/lldb.expected
Normal file
21
cccl_upstream/libcudacxx/test/debugging/array/lldb.expected
Normal file
@@ -0,0 +1,21 @@
|
||||
=============== array.normal begin ===============
|
||||
(cuda::std::array<int, 3>) ([0] = -7, [1] = 0, [2] = 42)
|
||||
=============== array.normal end ===============
|
||||
=============== array.empty begin ===============
|
||||
(cuda::std::array<int, 0>)
|
||||
=============== array.empty end ===============
|
||||
=============== array.nested begin ===============
|
||||
(cuda::std::array<cuda::std::array<int, 2>, 2>) {
|
||||
[0] = ([0] = 13, [1] = -5)
|
||||
[1] = ([0] = 0, [1] = 88)
|
||||
}
|
||||
=============== array.nested end ===============
|
||||
=============== array.alias begin ===============
|
||||
(cuda::std::array<int, 4>) ([0] = -31, [1] = 17, [2] = 8, [3] = -64)
|
||||
=============== array.alias end ===============
|
||||
=============== array.update.before begin ===============
|
||||
(cuda::std::array<int, 3>) ([0] = 6, [1] = -91, [2] = 52)
|
||||
=============== array.update.before end ===============
|
||||
=============== array.update.after begin ===============
|
||||
(cuda::std::array<int, 3>) ([0] = 3, [1] = 85, [2] = -12)
|
||||
=============== array.update.after end ===============
|
||||
60
cccl_upstream/libcudacxx/test/debugging/array/source.cu
Normal file
60
cccl_upstream/libcudacxx/test/debugging/array/source.cu
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include <cuda/std/array>
|
||||
|
||||
template <class T>
|
||||
[[gnu::noinline]] void keep_for_debugger(const T& value)
|
||||
{
|
||||
asm volatile("" : : "g"(&value) : "memory");
|
||||
}
|
||||
|
||||
using array_alias = cuda::std::array<int, 4>;
|
||||
|
||||
[[gnu::noinline]] void inspect_normal(const cuda::std::array<int, 3>& values)
|
||||
{
|
||||
keep_for_debugger(values);
|
||||
}
|
||||
|
||||
[[gnu::noinline]] void inspect_empty(const cuda::std::array<int, 0>& values)
|
||||
{
|
||||
keep_for_debugger(values);
|
||||
}
|
||||
|
||||
[[gnu::noinline]] void inspect_nested(const cuda::std::array<cuda::std::array<int, 2>, 2>& values)
|
||||
{
|
||||
keep_for_debugger(values);
|
||||
}
|
||||
|
||||
[[gnu::noinline]] void inspect_alias(const array_alias& values)
|
||||
{
|
||||
keep_for_debugger(values);
|
||||
}
|
||||
|
||||
[[gnu::noinline]] void inspect_before_update(const cuda::std::array<int, 3>& values)
|
||||
{
|
||||
keep_for_debugger(values);
|
||||
}
|
||||
|
||||
[[gnu::noinline]] void inspect_after_update(const cuda::std::array<int, 3>& values)
|
||||
{
|
||||
keep_for_debugger(values);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
const cuda::std::array<int, 3> normal = {-7, 0, 42};
|
||||
const cuda::std::array<int, 0> empty = {};
|
||||
const cuda::std::array<cuda::std::array<int, 2>, 2> nested = {{{13, -5}, {0, 88}}};
|
||||
const array_alias alias = {-31, 17, 8, -64};
|
||||
cuda::std::array<int, 3> updated_values = {6, -91, 52};
|
||||
|
||||
inspect_normal(normal);
|
||||
inspect_empty(empty);
|
||||
inspect_nested(nested);
|
||||
inspect_alias(alias);
|
||||
inspect_before_update(updated_values);
|
||||
updated_values = {3, 85, -12};
|
||||
inspect_after_update(updated_values);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
libcudacxx_add_pretty_printer_test(
|
||||
NAME buffer
|
||||
SOURCES source.cu
|
||||
CASES
|
||||
# gersemi: off
|
||||
--case inspect_normal 1 buffer.normal normal_values
|
||||
--case inspect_alias 1 buffer.alias aliased_values
|
||||
--case inspect_vector 1 buffer.vector.0 "buffer_vector[0]"
|
||||
--case inspect_vector 1 buffer.vector.1 "buffer_vector[1]"
|
||||
--case inspect_host_device 1 buffer.host_device host_device_values
|
||||
--case inspect_empty 1 buffer.empty empty_values
|
||||
--case inspect_before_update 1 buffer.update.before updated_values
|
||||
--case inspect_after_update 1 buffer.update.after updated_values
|
||||
# gersemi: on
|
||||
)
|
||||
63
cccl_upstream/libcudacxx/test/debugging/buffer/gdb.expected
Normal file
63
cccl_upstream/libcudacxx/test/debugging/buffer/gdb.expected
Normal file
@@ -0,0 +1,63 @@
|
||||
=============== buffer.normal begin ===============
|
||||
cuda::buffer<int, cuda::mr::device_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=10, align=4, data=<address> (device) = {
|
||||
[0] = -56,
|
||||
[1] = 22,
|
||||
[2] = 94,
|
||||
[3] = -13,
|
||||
[4] = 7,
|
||||
[5] = 41,
|
||||
[6] = -82,
|
||||
[7] = 0,
|
||||
[8] = 63,
|
||||
[9] = -5
|
||||
}
|
||||
=============== buffer.normal end ===============
|
||||
=============== buffer.alias begin ===============
|
||||
cuda::buffer<int, cuda::mr::device_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (device) = {
|
||||
[0] = 17,
|
||||
[1] = -31,
|
||||
[2] = 8,
|
||||
[3] = 55
|
||||
}
|
||||
=============== buffer.alias end ===============
|
||||
=============== buffer.vector.0 begin ===============
|
||||
cuda::buffer<int, cuda::mr::device_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=3, align=4, data=<address> (device) = {
|
||||
[0] = -2,
|
||||
[1] = 4,
|
||||
[2] = 6
|
||||
}
|
||||
=============== buffer.vector.0 end ===============
|
||||
=============== buffer.vector.1 begin ===============
|
||||
cuda::buffer<int, cuda::mr::device_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=3, align=4, data=<address> (device) = {
|
||||
[0] = 11,
|
||||
[1] = -9,
|
||||
[2] = 27
|
||||
}
|
||||
=============== buffer.vector.1 end ===============
|
||||
=============== buffer.host_device begin ===============
|
||||
cuda::buffer<int, cuda::mr::device_accessible, cuda::mr::host_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible, cuda::mr::host_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (host/device) = {
|
||||
[0] = 3,
|
||||
[1] = 14,
|
||||
[2] = -15,
|
||||
[3] = 92
|
||||
}
|
||||
=============== buffer.host_device end ===============
|
||||
=============== buffer.empty begin ===============
|
||||
cuda::buffer<int, cuda::mr::device_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=0, align=4, data=0x0 (device)
|
||||
=============== buffer.empty end ===============
|
||||
=============== buffer.update.before begin ===============
|
||||
cuda::buffer<int, cuda::mr::device_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (device) = {
|
||||
[0] = 1,
|
||||
[1] = 2,
|
||||
[2] = 3,
|
||||
[3] = 4
|
||||
}
|
||||
=============== buffer.update.before end ===============
|
||||
=============== buffer.update.after begin ===============
|
||||
cuda::buffer<int, cuda::mr::device_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (device) = {
|
||||
[0] = -8,
|
||||
[1] = 13,
|
||||
[2] = 21,
|
||||
[3] = -34
|
||||
}
|
||||
=============== buffer.update.after end ===============
|
||||
63
cccl_upstream/libcudacxx/test/debugging/buffer/lldb.expected
Normal file
63
cccl_upstream/libcudacxx/test/debugging/buffer/lldb.expected
Normal file
@@ -0,0 +1,63 @@
|
||||
=============== buffer.normal begin ===============
|
||||
(cuda::buffer<int, cuda::mr::device_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=10, align=4, data=<address> (device) {
|
||||
[0] = -56
|
||||
[1] = 22
|
||||
[2] = 94
|
||||
[3] = -13
|
||||
[4] = 7
|
||||
[5] = 41
|
||||
[6] = -82
|
||||
[7] = 0
|
||||
[8] = 63
|
||||
[9] = -5
|
||||
}
|
||||
=============== buffer.normal end ===============
|
||||
=============== buffer.alias begin ===============
|
||||
(cuda::buffer<int, cuda::mr::device_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (device) {
|
||||
[0] = 17
|
||||
[1] = -31
|
||||
[2] = 8
|
||||
[3] = 55
|
||||
}
|
||||
=============== buffer.alias end ===============
|
||||
=============== buffer.vector.0 begin ===============
|
||||
(cuda::buffer<int, cuda::mr::device_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=3, align=4, data=<address> (device) {
|
||||
[0] = -2
|
||||
[1] = 4
|
||||
[2] = 6
|
||||
}
|
||||
=============== buffer.vector.0 end ===============
|
||||
=============== buffer.vector.1 begin ===============
|
||||
(cuda::buffer<int, cuda::mr::device_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=3, align=4, data=<address> (device) {
|
||||
[0] = 11
|
||||
[1] = -9
|
||||
[2] = 27
|
||||
}
|
||||
=============== buffer.vector.1 end ===============
|
||||
=============== buffer.host_device begin ===============
|
||||
(cuda::buffer<int, cuda::mr::device_accessible, cuda::mr::host_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible, cuda::mr::host_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (host/device) {
|
||||
[0] = 3
|
||||
[1] = 14
|
||||
[2] = -15
|
||||
[3] = 92
|
||||
}
|
||||
=============== buffer.host_device end ===============
|
||||
=============== buffer.empty begin ===============
|
||||
(cuda::buffer<int, cuda::mr::device_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=0, align=4, data=0x0 (device)
|
||||
=============== buffer.empty end ===============
|
||||
=============== buffer.update.before begin ===============
|
||||
(cuda::buffer<int, cuda::mr::device_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (device) {
|
||||
[0] = 1
|
||||
[1] = 2
|
||||
[2] = 3
|
||||
[3] = 4
|
||||
}
|
||||
=============== buffer.update.before end ===============
|
||||
=============== buffer.update.after begin ===============
|
||||
(cuda::buffer<int, cuda::mr::device_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (device) {
|
||||
[0] = -8
|
||||
[1] = 13
|
||||
[2] = 21
|
||||
[3] = -34
|
||||
}
|
||||
=============== buffer.update.after end ===============
|
||||
102
cccl_upstream/libcudacxx/test/debugging/buffer/source.cu
Normal file
102
cccl_upstream/libcudacxx/test/debugging/buffer/source.cu
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include <cuda/buffer>
|
||||
#include <cuda/memory_resource>
|
||||
#include <cuda/std/array>
|
||||
#include <cuda/stream>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <cuda_runtime_api.h>
|
||||
|
||||
template <class T>
|
||||
[[gnu::noinline]] void keep_for_debugger(const T& value)
|
||||
{
|
||||
asm volatile("" : : "g"(&value) : "memory");
|
||||
}
|
||||
|
||||
[[gnu::noinline]] void inspect_normal(const cuda::device_buffer<int>& values)
|
||||
{
|
||||
keep_for_debugger(values);
|
||||
}
|
||||
|
||||
using device_buffer_alias = cuda::buffer<int, cuda::mr::device_accessible>;
|
||||
|
||||
[[gnu::noinline]] void inspect_alias(const device_buffer_alias& values)
|
||||
{
|
||||
keep_for_debugger(values);
|
||||
}
|
||||
|
||||
[[gnu::noinline]] void inspect_vector(const std::vector<cuda::device_buffer<int>>& values)
|
||||
{
|
||||
keep_for_debugger(values[0]);
|
||||
keep_for_debugger(values[1]);
|
||||
}
|
||||
|
||||
template <class Buffer>
|
||||
[[gnu::noinline]] void inspect_host_device(const Buffer& values)
|
||||
{
|
||||
keep_for_debugger(values);
|
||||
}
|
||||
|
||||
[[gnu::noinline]] void inspect_empty(const cuda::device_buffer<int>& values)
|
||||
{
|
||||
keep_for_debugger(values);
|
||||
}
|
||||
|
||||
[[gnu::noinline]] void inspect_before_update(const cuda::device_buffer<int>& values)
|
||||
{
|
||||
keep_for_debugger(values);
|
||||
}
|
||||
|
||||
[[gnu::noinline]] void inspect_after_update(const cuda::device_buffer<int>& values)
|
||||
{
|
||||
keep_for_debugger(values);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
constexpr cuda::device_ref device{0};
|
||||
cuda::stream stream{device};
|
||||
|
||||
const cuda::std::array normal_host_values{-56, 22, 94, -13, 7, 41, -82, 0, 63, -5};
|
||||
const auto normal_values = cuda::make_device_buffer<int>(stream, device, normal_host_values);
|
||||
|
||||
const cuda::std::array alias_host_values{17, -31, 8, 55};
|
||||
const device_buffer_alias aliased_values = cuda::make_device_buffer<int>(stream, device, alias_host_values);
|
||||
|
||||
std::vector<cuda::device_buffer<int>> buffer_vector;
|
||||
buffer_vector.emplace_back(cuda::make_device_buffer<int>(stream, device, cuda::std::array{-2, 4, 6}));
|
||||
buffer_vector.emplace_back(cuda::make_device_buffer<int>(stream, device, cuda::std::array{11, -9, 27}));
|
||||
|
||||
cuda::mr::legacy_managed_memory_resource managed_resource;
|
||||
const cuda::std::array host_device_host_values{3, 14, -15, 92};
|
||||
const auto host_device_values = cuda::make_buffer<int>(stream, managed_resource, host_device_host_values);
|
||||
const auto empty_values = cuda::make_device_buffer<int>(stream, device);
|
||||
|
||||
const cuda::std::array initial_updated_host_values{1, 2, 3, 4};
|
||||
auto updated_values = cuda::make_device_buffer<int>(stream, device, initial_updated_host_values);
|
||||
|
||||
stream.sync();
|
||||
inspect_normal(normal_values);
|
||||
inspect_alias(aliased_values);
|
||||
inspect_vector(buffer_vector);
|
||||
inspect_host_device(host_device_values);
|
||||
inspect_empty(empty_values);
|
||||
inspect_before_update(updated_values);
|
||||
|
||||
const cuda::std::array replacement_host_values{-8, 13, 21, -34};
|
||||
if (cudaMemcpyAsync(updated_values.data(),
|
||||
replacement_host_values.data(),
|
||||
replacement_host_values.size() * sizeof(*updated_values.data()),
|
||||
cudaMemcpyDefault,
|
||||
stream.get())
|
||||
!= cudaSuccess)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
stream.sync();
|
||||
inspect_after_update(updated_values);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
libcudacxx_add_pretty_printer_test(
|
||||
NAME memory_resource
|
||||
SOURCES source.cu
|
||||
CASES
|
||||
# gersemi: off
|
||||
--case inspect_device 1 memory_resource.device device_resource
|
||||
--case inspect_host_device 1 memory_resource.host_device host_device_resource
|
||||
--case inspect_alias 1 memory_resource.alias aliased_resource
|
||||
# gersemi: on
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
=============== memory_resource.device begin ===============
|
||||
cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>
|
||||
=============== memory_resource.device end ===============
|
||||
=============== memory_resource.host_device begin ===============
|
||||
cuda::mr::any_resource<cuda::mr::device_accessible, cuda::mr::host_accessible> @ <address>
|
||||
=============== memory_resource.host_device end ===============
|
||||
=============== memory_resource.alias begin ===============
|
||||
cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>
|
||||
=============== memory_resource.alias end ===============
|
||||
@@ -0,0 +1,9 @@
|
||||
=============== memory_resource.device begin ===============
|
||||
(const device_resource_type) cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>
|
||||
=============== memory_resource.device end ===============
|
||||
=============== memory_resource.host_device begin ===============
|
||||
(const host_device_resource_type) cuda::mr::any_resource<cuda::mr::device_accessible, cuda::mr::host_accessible> @ <address>
|
||||
=============== memory_resource.host_device end ===============
|
||||
=============== memory_resource.alias begin ===============
|
||||
(const resource_alias) cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>
|
||||
=============== memory_resource.alias end ===============
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include <cuda/memory_resource>
|
||||
|
||||
template <class T>
|
||||
[[gnu::noinline]] void keep_for_debugger(const T& value)
|
||||
{
|
||||
asm volatile("" : : "g"(&value) : "memory");
|
||||
}
|
||||
|
||||
using device_resource_type = cuda::mr::any_resource<cuda::mr::device_accessible>;
|
||||
using host_device_resource_type = cuda::mr::any_resource<cuda::mr::device_accessible, cuda::mr::host_accessible>;
|
||||
using resource_alias = device_resource_type;
|
||||
|
||||
[[gnu::noinline]] void inspect_device(const device_resource_type& resource)
|
||||
{
|
||||
keep_for_debugger(resource);
|
||||
}
|
||||
|
||||
[[gnu::noinline]] void inspect_host_device(const host_device_resource_type& resource)
|
||||
{
|
||||
keep_for_debugger(resource);
|
||||
}
|
||||
|
||||
[[gnu::noinline]] void inspect_alias(const resource_alias& resource)
|
||||
{
|
||||
keep_for_debugger(resource);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
using adapted_resource = cuda::mr::synchronous_resource_adapter<cuda::mr::legacy_managed_memory_resource>;
|
||||
const adapted_resource managed_resource{cuda::mr::legacy_managed_memory_resource{}};
|
||||
const device_resource_type device_resource{managed_resource};
|
||||
const host_device_resource_type host_device_resource{managed_resource};
|
||||
const resource_alias aliased_resource{managed_resource};
|
||||
|
||||
inspect_device(device_resource);
|
||||
inspect_host_device(host_device_resource);
|
||||
inspect_alias(aliased_resource);
|
||||
}
|
||||
@@ -0,0 +1,790 @@
|
||||
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
"""Run a libcudacxx pretty-printer scenario under LLDB or GDB."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import difflib
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
from pathlib import Path
|
||||
|
||||
_MARKER_EDGE = "=" * 15
|
||||
_MARKER_PATTERN = re.compile(
|
||||
rf"^{re.escape(_MARKER_EDGE)} (?P<section>.+) (?P<kind>begin|end) {re.escape(_MARKER_EDGE)}$"
|
||||
)
|
||||
_LLDB_ECHO_PATTERN = re.compile(r"^\s*\(lldb\)\s")
|
||||
_GDB_VALUE_PREFIX_PATTERN = re.compile(r"^\s*\$\d+ = ")
|
||||
_NONZERO_HEX_PATTERN = re.compile(r"\b0x(?!0+\b)[0-9a-fA-F]+\b")
|
||||
|
||||
|
||||
class HarnessError(RuntimeError):
|
||||
"""Report invalid test input or debugger output."""
|
||||
|
||||
|
||||
class DebuggerError(RuntimeError):
|
||||
"""Report a debugger launch, timeout, or exit failure."""
|
||||
|
||||
|
||||
class Debugger(StrEnum):
|
||||
LLDB = "lldb"
|
||||
GDB = "gdb"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Case:
|
||||
breakpoint: str
|
||||
frame: int
|
||||
section: str
|
||||
expression: str
|
||||
|
||||
|
||||
class CaseAction(argparse.Action):
|
||||
"""Parse and validate one four-part ``--case`` option."""
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
parser: argparse.ArgumentParser,
|
||||
namespace: argparse.Namespace,
|
||||
values: Sequence[str],
|
||||
option_string: str | None = None,
|
||||
) -> None:
|
||||
"""Append one parsed case to the argument namespace.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
parser : argparse.ArgumentParser
|
||||
Parser handling the command line.
|
||||
namespace : argparse.Namespace
|
||||
Namespace receiving parsed cases.
|
||||
values : Sequence[str]
|
||||
Breakpoint, frame, section, and expression values.
|
||||
option_string : str or None
|
||||
Option spelling that supplied the values.
|
||||
|
||||
Raises
|
||||
------
|
||||
SystemExit
|
||||
If the frame, breakpoint, section, or expression is invalid, or if
|
||||
the section name is duplicated.
|
||||
"""
|
||||
breakpoint, raw_frame, section, expression = values
|
||||
try:
|
||||
frame = int(raw_frame)
|
||||
except ValueError:
|
||||
parser.error(f"invalid caller frame index {raw_frame!r}")
|
||||
if frame < 0:
|
||||
parser.error(f"caller frame index must be nonnegative: {frame}")
|
||||
for label, value in (
|
||||
("breakpoint", breakpoint),
|
||||
("section", section),
|
||||
("expression", expression),
|
||||
):
|
||||
if not value or "\n" in value or "\r" in value:
|
||||
parser.error(f"{label} must be a nonempty single line")
|
||||
|
||||
cases: list[Case] = getattr(namespace, self.dest) or []
|
||||
if any(case.section == section for case in cases):
|
||||
parser.error(f"duplicate section name: {section}")
|
||||
cases.append(Case(breakpoint, frame, section, expression))
|
||||
setattr(namespace, self.dest, cases)
|
||||
|
||||
|
||||
def marker(section: str, kind: str) -> str:
|
||||
"""Build an exact marker for a captured section.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
section : str
|
||||
Unique name of the output section.
|
||||
kind : str
|
||||
Marker kind, either ``begin`` or ``end``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Complete marker line expected in debugger output.
|
||||
"""
|
||||
return f"{_MARKER_EDGE} {section} {kind} {_MARKER_EDGE}"
|
||||
|
||||
|
||||
class DebuggerAdapter(ABC):
|
||||
"""Provide debugger-specific command and transcript hooks.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
executable : Path
|
||||
Debugger executable path.
|
||||
formatter_init : Path
|
||||
Pretty-printer entry-point path.
|
||||
program : Path
|
||||
Scenario executable path.
|
||||
"""
|
||||
|
||||
kind: Debugger
|
||||
|
||||
def __init__(self, executable: Path, formatter_init: Path, program: Path) -> None:
|
||||
"""Store paths shared by debugger-specific operations.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
executable : Path
|
||||
Debugger executable path.
|
||||
formatter_init : Path
|
||||
Pretty-printer entry-point path.
|
||||
program : Path
|
||||
Scenario executable path.
|
||||
"""
|
||||
self.executable = executable
|
||||
self.formatter_init = formatter_init
|
||||
self.program = program
|
||||
|
||||
def generate_commands(self, cases: Sequence[Case]) -> str:
|
||||
"""Generate commands for an ordered list of cases.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cases : Sequence[Case]
|
||||
Cases to execute in order.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Complete debugger command-file contents.
|
||||
|
||||
Raises
|
||||
------
|
||||
HarnessError
|
||||
If a completed breakpoint group is reopened later.
|
||||
"""
|
||||
closed_stops: set[tuple[str, int]] = set()
|
||||
previous_stop: tuple[str, int] | None = None
|
||||
for case in cases:
|
||||
stop = (case.breakpoint, case.frame)
|
||||
if stop == previous_stop:
|
||||
continue
|
||||
if stop in closed_stops:
|
||||
raise HarnessError(
|
||||
f"breakpoint group {case.breakpoint!r} at frame {case.frame} was reopened"
|
||||
)
|
||||
if previous_stop is not None:
|
||||
closed_stops.add(previous_stop)
|
||||
previous_stop = stop
|
||||
return self._generate_commands(cases)
|
||||
|
||||
@abstractmethod
|
||||
def _generate_commands(self, cases: Sequence[Case]) -> str:
|
||||
"""Generate commands for validated cases.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cases : Sequence[Case]
|
||||
Cases to execute in order.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Complete debugger command-file contents.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def command(self, command_file: Path) -> list[str]:
|
||||
"""Build the debugger subprocess argument list.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
command_file : Path
|
||||
Generated debugger command-file path.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[str]
|
||||
Subprocess arguments for this debugger.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def include_transcript_line(self, line: str) -> bool:
|
||||
"""Return whether a line inside a marked section should be retained.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
line : str
|
||||
Transcript line inside a marked section.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
``True`` when the line belongs in normalized output.
|
||||
"""
|
||||
return True
|
||||
|
||||
def normalize_line(self, line: str) -> str:
|
||||
"""Apply debugger-specific normalization to one output line.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
line : str
|
||||
Extracted debugger output line.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Line after debugger-specific normalization.
|
||||
"""
|
||||
return line
|
||||
|
||||
|
||||
class GDB(DebuggerAdapter):
|
||||
"""Provide GDB-specific pretty-printer test behavior."""
|
||||
|
||||
kind = Debugger.GDB
|
||||
|
||||
def _generate_commands(self, cases: Sequence[Case]) -> str:
|
||||
"""Generate a GDB command file for ordered cases.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cases : Sequence[Case]
|
||||
Cases to execute in order.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Complete GDB command-file contents.
|
||||
"""
|
||||
lines = [
|
||||
"set pagination off",
|
||||
"set print pretty on",
|
||||
"set print array-indexes on",
|
||||
"set debuginfod enabled off",
|
||||
f"source {self.formatter_init}",
|
||||
]
|
||||
seen_breakpoints: set[str] = set()
|
||||
for case in cases:
|
||||
if case.breakpoint in seen_breakpoints:
|
||||
continue
|
||||
lines.append(f"break {case.breakpoint}")
|
||||
seen_breakpoints.add(case.breakpoint)
|
||||
lines.append("run")
|
||||
previous_stop: tuple[str, int] | None = None
|
||||
for case in cases:
|
||||
stop = (case.breakpoint, case.frame)
|
||||
if previous_stop is not None and stop != previous_stop:
|
||||
lines.append("continue")
|
||||
if stop != previous_stop:
|
||||
lines.append(f"frame {case.frame}")
|
||||
previous_stop = stop
|
||||
begin = marker(case.section, "begin")
|
||||
end = marker(case.section, "end")
|
||||
expression = repr(case.expression)
|
||||
lines.extend(
|
||||
[
|
||||
f"python print({begin!r})",
|
||||
"python",
|
||||
"try:",
|
||||
f" print(gdb.execute('print ' + {expression}, from_tty=True, to_string=True), end='')",
|
||||
"except Exception as error:",
|
||||
" print(error)",
|
||||
"end",
|
||||
f"python print({end!r})",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
def command(self, command_file: Path) -> list[str]:
|
||||
"""Build the GDB subprocess argument list.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
command_file : Path
|
||||
Generated GDB command-file path.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[str]
|
||||
GDB subprocess arguments.
|
||||
"""
|
||||
return [
|
||||
str(self.executable),
|
||||
"--quiet",
|
||||
"--batch",
|
||||
"--nx",
|
||||
"--command",
|
||||
str(command_file),
|
||||
str(self.program),
|
||||
]
|
||||
|
||||
def normalize_line(self, line: str) -> str:
|
||||
"""Remove GDB value-history prefixes from an output line.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
line : str
|
||||
Extracted GDB output line.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Line without a leading ``$N =`` prefix.
|
||||
"""
|
||||
return _GDB_VALUE_PREFIX_PATTERN.sub("", line)
|
||||
|
||||
|
||||
class LLDB(DebuggerAdapter):
|
||||
"""Provide LLDB-specific pretty-printer test behavior."""
|
||||
|
||||
kind = Debugger.LLDB
|
||||
|
||||
def _generate_commands(self, cases: Sequence[Case]) -> str:
|
||||
"""Generate an LLDB command file for ordered cases.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cases : Sequence[Case]
|
||||
Cases to execute in order.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Complete LLDB command-file contents.
|
||||
"""
|
||||
lines = [f'command script import "{self.formatter_init}"']
|
||||
seen_breakpoints: set[str] = set()
|
||||
for case in cases:
|
||||
if case.breakpoint in seen_breakpoints:
|
||||
continue
|
||||
lines.append(f"breakpoint set --name {case.breakpoint}")
|
||||
seen_breakpoints.add(case.breakpoint)
|
||||
lines.append("run")
|
||||
previous_stop: tuple[str, int] | None = None
|
||||
for case in cases:
|
||||
stop = (case.breakpoint, case.frame)
|
||||
if previous_stop is not None and stop != previous_stop:
|
||||
lines.append("continue")
|
||||
if stop != previous_stop:
|
||||
lines.append(f"frame select {case.frame}")
|
||||
previous_stop = stop
|
||||
begin = marker(case.section, "begin")
|
||||
end = marker(case.section, "end")
|
||||
debugger_command = f"dwim-print -- {case.expression}"
|
||||
lines.extend(
|
||||
[
|
||||
f"script print({begin!r})",
|
||||
"script result = lldb.SBCommandReturnObject(); "
|
||||
f"status = lldb.debugger.GetCommandInterpreter().HandleCommand({debugger_command!r}, result); "
|
||||
"print(result.GetOutput(), end=''); print(result.GetError(), end='')",
|
||||
f"script print({end!r})",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
def command(self, command_file: Path) -> list[str]:
|
||||
"""Build the LLDB subprocess argument list.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
command_file : Path
|
||||
Generated LLDB command-file path.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[str]
|
||||
LLDB subprocess arguments.
|
||||
"""
|
||||
return [
|
||||
str(self.executable),
|
||||
"--batch",
|
||||
"--no-lldbinit",
|
||||
"--source",
|
||||
str(command_file),
|
||||
str(self.program),
|
||||
]
|
||||
|
||||
def include_transcript_line(self, line: str) -> bool:
|
||||
"""Exclude LLDB prompt and command-echo lines from marked output.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
line : str
|
||||
Transcript line inside a marked section.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
``False`` for LLDB prompt or command-echo lines.
|
||||
"""
|
||||
return _LLDB_ECHO_PATTERN.match(line) is None
|
||||
|
||||
|
||||
def extract_sections(
|
||||
transcript: str, section_order: Sequence[str], debugger: DebuggerAdapter
|
||||
) -> str:
|
||||
"""Extract and validate marked sections from a debugger transcript.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
transcript : str
|
||||
Complete combined debugger output.
|
||||
section_order : Sequence[str]
|
||||
Expected section names in manifest order.
|
||||
debugger : DebuggerAdapter
|
||||
Adapter for the debugger that produced the transcript.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Marked sections concatenated in manifest order.
|
||||
|
||||
Raises
|
||||
------
|
||||
HarnessError
|
||||
If markers are unexpected, missing, duplicated, nested, mismatched, or
|
||||
unterminated.
|
||||
"""
|
||||
expected_sections = set(section_order)
|
||||
captured: dict[str, list[str]] = {}
|
||||
active_section: str | None = None
|
||||
|
||||
for line in transcript.splitlines():
|
||||
match = _MARKER_PATTERN.fullmatch(line)
|
||||
if not match:
|
||||
if active_section is None:
|
||||
continue
|
||||
if not debugger.include_transcript_line(line):
|
||||
continue
|
||||
captured[active_section].append(line)
|
||||
continue
|
||||
|
||||
section = match.group("section")
|
||||
if section not in expected_sections:
|
||||
raise HarnessError(f"unexpected marked section: {section}")
|
||||
|
||||
kind = match.group("kind")
|
||||
|
||||
match kind:
|
||||
case "begin":
|
||||
if active_section is not None:
|
||||
raise HarnessError(
|
||||
f"nested section {section!r} inside {active_section!r}"
|
||||
)
|
||||
if section in captured:
|
||||
raise HarnessError(f"duplicate marked section: {section}")
|
||||
captured[section] = [line]
|
||||
active_section = section
|
||||
case "end":
|
||||
if active_section is None:
|
||||
raise HarnessError(f"end marker without begin marker: {section}")
|
||||
if active_section != section:
|
||||
raise HarnessError(
|
||||
f"mismatched end marker for {section!r}; expected {active_section!r}"
|
||||
)
|
||||
captured[section].append(line)
|
||||
active_section = None
|
||||
case _:
|
||||
raise HarnessError(f"invalid marker kind: {kind}")
|
||||
|
||||
if active_section is not None:
|
||||
raise HarnessError(f"unterminated marked section: {active_section}")
|
||||
missing = [section for section in section_order if section not in captured]
|
||||
if missing:
|
||||
raise HarnessError(f"missing marked sections: {', '.join(missing)}")
|
||||
|
||||
lines: list[str] = []
|
||||
for section in section_order:
|
||||
lines.extend(captured[section])
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def normalize_output(output: str, debugger: DebuggerAdapter) -> str:
|
||||
"""Normalize unstable values while preserving output structure.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
output : str
|
||||
Extracted marked output.
|
||||
debugger : DebuggerAdapter
|
||||
Adapter that applies debugger-specific line normalization.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Output with unstable addresses and debugger prefixes normalized.
|
||||
"""
|
||||
normalized_lines: list[str] = []
|
||||
for line in output.splitlines():
|
||||
line = debugger.normalize_line(line.rstrip())
|
||||
# Some debuggers may print C++98 style > > for multiple templates.
|
||||
line = re.sub(r">\s+>", ">>", line)
|
||||
line = _NONZERO_HEX_PATTERN.sub("<address>", line)
|
||||
normalized_lines.append(line)
|
||||
return "\n".join(normalized_lines) + "\n"
|
||||
|
||||
|
||||
def compare_expected(
|
||||
actual: str, expected: str, debugger: DebuggerAdapter, scenario: str
|
||||
) -> None:
|
||||
"""Compare normalized output with its checked-in golden text.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
actual : str
|
||||
Normalized debugger output.
|
||||
expected : str
|
||||
Checked-in golden output.
|
||||
debugger : DebuggerAdapter
|
||||
Adapter for the debugger that produced the output.
|
||||
scenario : str
|
||||
Scenario name used in diagnostics.
|
||||
|
||||
Raises
|
||||
------
|
||||
HarnessError
|
||||
If actual and expected output differ.
|
||||
"""
|
||||
if actual == expected:
|
||||
return
|
||||
difference = "".join(
|
||||
difflib.unified_diff(
|
||||
expected.splitlines(keepends=True),
|
||||
actual.splitlines(keepends=True),
|
||||
fromfile=f"{scenario}/{debugger.kind}.expected",
|
||||
tofile=f"{scenario}/{debugger.kind}.actual",
|
||||
)
|
||||
)
|
||||
raise HarnessError(
|
||||
f"{debugger.kind} pretty-printer output mismatch for {scenario}:\n{difference}"
|
||||
)
|
||||
|
||||
|
||||
def _parse_arguments(arguments: Sequence[str] | None) -> argparse.Namespace:
|
||||
"""Parse debugger configuration and ordered case definitions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
arguments : Sequence[str] or None
|
||||
Command-line arguments, or ``None`` to use ``sys.argv``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
argparse.Namespace
|
||||
Parsed command-line namespace.
|
||||
"""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--debugger", type=Debugger, choices=Debugger, required=True)
|
||||
parser.add_argument("--debugger-executable", type=Path, required=True)
|
||||
parser.add_argument("--program", type=Path, required=True)
|
||||
parser.add_argument("--formatter-init", type=Path, required=True)
|
||||
parser.add_argument("--expected", type=Path, required=True)
|
||||
parser.add_argument("--output-log", type=Path, required=True)
|
||||
parser.add_argument("--timeout", type=float, default=90.0)
|
||||
parser.add_argument("--update-expected", action="store_true")
|
||||
parser.add_argument(
|
||||
"--case",
|
||||
dest="cases",
|
||||
nargs=4,
|
||||
action=CaseAction,
|
||||
default=None,
|
||||
required=True,
|
||||
)
|
||||
return parser.parse_args(arguments)
|
||||
|
||||
|
||||
def _create_debugger(args: argparse.Namespace) -> DebuggerAdapter:
|
||||
"""Create the debugger adapter selected by command-line arguments.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args : argparse.Namespace
|
||||
Parsed runner arguments.
|
||||
|
||||
Returns
|
||||
-------
|
||||
DebuggerAdapter
|
||||
Configured adapter for the selected debugger.
|
||||
|
||||
Raises
|
||||
------
|
||||
HarnessError
|
||||
If the selected debugger is unsupported.
|
||||
"""
|
||||
match args.debugger:
|
||||
case Debugger.LLDB:
|
||||
return LLDB(args.debugger_executable, args.formatter_init, args.program)
|
||||
case Debugger.GDB:
|
||||
return GDB(args.debugger_executable, args.formatter_init, args.program)
|
||||
case _:
|
||||
raise HarnessError(f"unsupported debugger: {args.debugger}")
|
||||
|
||||
|
||||
def _run_debugger(
|
||||
args: argparse.Namespace, debugger: DebuggerAdapter, command_file: Path
|
||||
) -> str:
|
||||
"""Run the debugger and persist its complete transcript.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args : argparse.Namespace
|
||||
Parsed runner arguments.
|
||||
debugger : DebuggerAdapter
|
||||
Configured debugger adapter.
|
||||
command_file : Path
|
||||
Generated debugger command-file path.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Complete combined debugger output.
|
||||
|
||||
Raises
|
||||
------
|
||||
DebuggerError
|
||||
If the debugger cannot launch, times out, or exits with a nonzero status.
|
||||
OSError
|
||||
If the transcript cannot be written.
|
||||
"""
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
debugger.command(command_file),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
timeout=args.timeout,
|
||||
)
|
||||
except subprocess.TimeoutExpired as error:
|
||||
if isinstance(error.stdout, str):
|
||||
args.output_log.write_text(error.stdout)
|
||||
raise DebuggerError(
|
||||
f"{debugger.kind} timed out after {args.timeout:g} seconds"
|
||||
) from error
|
||||
except OSError as error:
|
||||
raise DebuggerError(f"failed to launch {debugger.kind}: {error}") from error
|
||||
|
||||
args.output_log.write_text(completed.stdout)
|
||||
if completed.returncode != 0:
|
||||
raise DebuggerError(
|
||||
f"{debugger.kind} exited with status {completed.returncode}"
|
||||
)
|
||||
return completed.stdout
|
||||
|
||||
|
||||
def _match_output(
|
||||
args: argparse.Namespace,
|
||||
debugger: DebuggerAdapter,
|
||||
cases: Sequence[Case],
|
||||
transcript: str,
|
||||
) -> None:
|
||||
"""Extract, normalize, and compare or update debugger output.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args : argparse.Namespace
|
||||
Parsed runner arguments.
|
||||
debugger : DebuggerAdapter
|
||||
Configured debugger adapter.
|
||||
cases : Sequence[Case]
|
||||
Validated cases in manifest order.
|
||||
transcript : str
|
||||
Complete combined debugger output.
|
||||
|
||||
Raises
|
||||
------
|
||||
HarnessError
|
||||
If marked output is invalid or differs from the golden.
|
||||
OSError
|
||||
If the golden file cannot be read or updated.
|
||||
"""
|
||||
extracted = extract_sections(transcript, [case.section for case in cases], debugger)
|
||||
actual = normalize_output(extracted, debugger)
|
||||
if args.update_expected:
|
||||
args.expected.write_text(actual)
|
||||
return
|
||||
compare_expected(
|
||||
actual,
|
||||
args.expected.read_text(),
|
||||
debugger,
|
||||
args.expected.parent.name,
|
||||
)
|
||||
|
||||
|
||||
def _report_error(
|
||||
args: argparse.Namespace,
|
||||
debugger: DebuggerAdapter,
|
||||
command_file: Path,
|
||||
error: Exception,
|
||||
) -> None:
|
||||
"""Report a debugger or output-matching failure with artifact paths.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
args : argparse.Namespace
|
||||
Parsed runner arguments.
|
||||
debugger : DebuggerAdapter
|
||||
Configured debugger adapter.
|
||||
command_file : Path
|
||||
Generated debugger command-file path.
|
||||
error : Exception
|
||||
Failure being reported.
|
||||
"""
|
||||
scenario = args.expected.parent.name
|
||||
print(
|
||||
f"error: {debugger.kind} pretty-printer test for {scenario}: {error}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
print(f"debugger commands: {command_file}", file=sys.stderr)
|
||||
if args.output_log.exists():
|
||||
print(f"complete transcript: {args.output_log}", file=sys.stderr)
|
||||
|
||||
|
||||
def main(arguments: Sequence[str] | None = None) -> int:
|
||||
"""Run one debugger pretty-printer test from command-line arguments.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
arguments : Sequence[str] or None
|
||||
Command-line arguments, or ``None`` to use ``sys.argv``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
Zero on success and one for handled debugger or matching failures.
|
||||
|
||||
Raises
|
||||
------
|
||||
HarnessError
|
||||
If case setup is invalid.
|
||||
OSError
|
||||
If setup artifacts or golden files cannot be accessed.
|
||||
"""
|
||||
args = _parse_arguments(arguments)
|
||||
debugger = _create_debugger(args)
|
||||
commands = debugger.generate_commands(args.cases)
|
||||
command_file = args.output_log.with_suffix(".commands")
|
||||
args.output_log.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output_log.unlink(missing_ok=True)
|
||||
command_file.write_text(commands)
|
||||
|
||||
try:
|
||||
transcript = _run_debugger(args, debugger, command_file)
|
||||
except DebuggerError as error:
|
||||
_report_error(args, debugger, command_file, error)
|
||||
return 1
|
||||
|
||||
try:
|
||||
_match_output(args, debugger, args.cases, transcript)
|
||||
except HarnessError as error:
|
||||
_report_error(args, debugger, command_file, error)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user