Skip to content

[c2h] Make catch macros work on device#8928

Merged
davebayer merged 10 commits into
NVIDIA:mainfrom
davebayer:c2h_test_macros
May 14, 2026
Merged

[c2h] Make catch macros work on device#8928
davebayer merged 10 commits into
NVIDIA:mainfrom
davebayer:c2h_test_macros

Conversation

@davebayer
Copy link
Copy Markdown
Contributor

@davebayer davebayer commented May 12, 2026

Catch 2 test macros are unsupported in device code, so we have other alternatives like CUDAX_REQUIRE or CCCLRT_REQUIRE that should work in both host and device code.

I don't like this, so I found a way to allow us using the original macros directly. If CATCH_CONFIG_PREFIX_ALL macro is defined, catch 2 prepends these macros with CATCH_ and the non-prefixed versions are not defined. We can then define those manually and define our own behaviour in device code.

The output for failed tests looks something like:

/home/dabayer/cccl/cudax/test/group/synchronizer/lane_synchronizer.cu:48:
   CHECK(synchronizer_instance.__lane_mask_ != (this_lane_mask | another_lane_mask)) failed
   block [0, 0, 0], thread [4, 3, 0]

I've also implemented some extensions, such as:

REQUIRE_CUDA(cuSomeDriverCall(...));
CHECK_CUDA(cuSomeDriverCall(...));

REQUIRE_CUDART(cudaSomeCudaRuntimeCall(...));
CHECK_CUDART(cudaSomeCudaRuntimeCall(...));

@github-project-automation github-project-automation Bot moved this to Todo in CCCL May 12, 2026
@cccl-authenticator-app cccl-authenticator-app Bot moved this from Todo to In Progress in CCCL May 12, 2026
@davebayer davebayer force-pushed the c2h_test_macros branch 3 times, most recently from c5fc788 to 7da5c27 Compare May 12, 2026 17:23
Comment on lines +35 to +41
#define CUDAX_REQUIRE(condition) REQUIRE(condition)

#define CUDAX_CHECK(condition) \
NV_IF_ELSE_TARGET(NV_IS_DEVICE, \
(cudax_require_impl(condition, #condition, __FILE__, __LINE__, __PRETTY_FUNCTION__);), \
(CHECK(condition);))
#define CUDAX_CHECK(condition) CHECK(condition)

#define CUDAX_FAIL(message) /* */ \
NV_IF_ELSE_TARGET(NV_IS_DEVICE, /* */ \
(cudax_require_impl(false, message, __FILE__, __LINE__, __PRETTY_FUNCTION__);), \
(FAIL(message);))
#define CUDAX_FAIL(message) FAIL(message)

#define CUDAX_CHECK_FALSE(condition) CUDAX_CHECK(!(condition))
#define CUDAX_CHECK_FALSE(condition) CHECK_FALSE(condition)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I will remove these in a separate PR

Comment on lines 27 to +41
#define CUDART(call) REQUIRE((call) == cudaSuccess)

// There is a problem with clang-cuda and nv/target, but we don't need the device side macros yet,
// disable them for now
#if _CCCL_CUDA_COMPILER(CLANG)
# define CCCLRT_REQUIRE(condition) REQUIRE(condition)
# define CCCLRT_CHECK(condition) CHECK(condition)
# define CCCLRT_FAIL(message) FAIL(message)
# define CCCLRT_CHECK_FALSE(condition) CCCLRT_CHECK(!(condition))

#else // _CCCL_CUDA_COMPILER(CLANG)
# define CCCLRT_REQUIRE(condition) \
NV_IF_ELSE_TARGET(NV_IS_DEVICE, \
(ccclrt_require_impl(condition, #condition, __FILE__, __LINE__, __PRETTY_FUNCTION__);), \
(REQUIRE(condition);))

# define CCCLRT_CHECK(condition) \
NV_IF_ELSE_TARGET(NV_IS_DEVICE, \
(ccclrt_require_impl(condition, #condition, __FILE__, __LINE__, __PRETTY_FUNCTION__);), \
(CHECK(condition);))

# define CCCLRT_FAIL(message) /* */ \
NV_IF_ELSE_TARGET(NV_IS_DEVICE, /* */ \
(ccclrt_require_impl(false, message, __FILE__, __LINE__, __PRETTY_FUNCTION__);), \
(FAIL(message);))

# define CCCLRT_CHECK_FALSE(condition) CCCLRT_CHECK(!(condition))
#endif // _CCCL_CUDA_COMPILER(CLANG)
#define CCCLRT_REQUIRE(condition) REQUIRE(condition)

#define CCCLRT_CHECK(condition) CHECK(condition)

#define CCCLRT_FAIL(message) FAIL(message)

#define CCCLRT_CHECK_FALSE(condition) CHECK_FALSE(condition)

// Explicit device side require macros for clang-cuda
#define CCCLRT_REQUIRE_DEVICE(condition) \
ccclrt_require_impl(condition, #condition, __FILE__, __LINE__, __PRETTY_FUNCTION__);
#define CCCLRT_CHECK_DEVICE(condition) \
ccclrt_require_impl(condition, #condition, __FILE__, __LINE__, __PRETTY_FUNCTION__);
#define CCCLRT_FAIL_DEVICE(message) ccclrt_require_impl(false, message, __FILE__, __LINE__, __PRETTY_FUNCTION__);
#define CCCLRT_CHECK_FALSE_DEVICE(condition) CCCLRT_CHECK_DEVICE(!(condition))
#define CCCLRT_REQUIRE_DEVICE(condition) REQUIRE_DEVICE(condition)
#define CCCLRT_CHECK_DEVICE(condition) CHECK(condition)
#define CCCLRT_FAIL_DEVICE(message) FAIL(message)
#define CCCLRT_CHECK_FALSE_DEVICE(condition) CHECK_FALSE(condition)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ditto

// example-end custom-type

static __host__ std::ostream& operator<<(std::ostream& os, const custom_t& self)
__host__ std::ostream& operator<<(std::ostream& os, const custom_t& self)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I got warnings about these functions being unused 🤷

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IMO then we should remove them if they really are unused.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

They are used, just not during device pass and that makes cicc complain

@davebayer davebayer marked this pull request as ready for review May 12, 2026 20:38
@davebayer davebayer requested review from a team as code owners May 12, 2026 20:38
@cccl-authenticator-app cccl-authenticator-app Bot moved this from In Progress to In Review in CCCL May 12, 2026
@github-actions

This comment has been minimized.

@NVIDIA NVIDIA deleted a comment from copy-pr-bot Bot May 13, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added a unified Catch2-style testing layer with device-side assertion support and expanded test helper macros.
  • Refactor

    • Simplified and consolidated test assertion macros to consistently map host/device behavior.
    • Test integration updated to use the new helper layer and a compile-time definition that affects test builds.
  • Bug Fixes

    • Fixed test linkage and assertion usage to resolve compilation/linkage issues and adjusted several test assertions for correctness.

important:

Walkthrough

Adds a host/device-compatible Catch2 macro header that implements device-side assertion behavior and aliases, exposes REQUIRE/CHECK/FAIL/TEMPLATE/BDD/matcher macros for host and device, and wires the build and helper macros to use prefixed Catch2 forms.

important:

Changes

Catch2 host+device testing infrastructure

Layer / File(s) Summary
Core C2H Catch2 macro foundation
c2h/include/c2h/catch2_test_macros.h
New header providing host/device-aware assertion and test macros (REQUIRE/CHECK/FAIL, TEST_CASE/SECTION, BDD, TEMPLATE/TEMPLATE_LIST, matcher/throws forms), device failure printer that logs file/line and CUDA indices and invokes ::__trap(), plus REQUIRE_DEVICE and CUDA helper macros.
C2H build integration and helper wiring
c2h/CMakeLists.txt, c2h/include/c2h/catch2_test_helper.h
CMake target cccl.c2h adds public compile definition CATCH_CONFIG_PREFIX_ALL. Helper header now includes c2h/catch2_test_macros.h and updates C2H_TEST_*/C2H_TEST_LIST_* macros to use CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(...), wiring ::detail::nvtx_fixture for non-fixture variants and passing FIXTURE for fixture variants.
Assertion macro simplification in test frameworks
cudax/test/common/testing.cuh, libcudacxx/test/libcudacxx/cuda/ccclrt/common/testing.cuh
Replaced prior NV_IF_ELSE_TARGET/ccclrt/cudax dispatch with direct Catch2 macros: CUDAX_* and CCCLRT_* now map to REQUIRE/CHECK/FAIL/CHECK_FALSE (and CCCLRT_REQUIRE_DEVICEREQUIRE_DEVICE); removed earlier conditional/device-implementation indirection.
Test header migrations and small test edits
cub/test/insert_nested_NVTX_range_guard.h, cudax/test/utility/optionally_static.cu, cudax/test/cufile/driver_register.cu, libcudacxx/test/libcudacxx/cuda/containers/buffer/access.cu, libcudacxx/test/libcudacxx/cuda/memory_resource/resources/shared_memory_pools.cu
Tests switched includes to the new C2H helper/macros where applicable. Minor edits: void-cast of expressions inside CHECK_THROWS_AS and added // NOLINT on an intentional copy construction.
Operator linkage adjustments in tests
cub/test/catch2_test_device_radix_sort_custom.cu, cub/test/catch2_test_device_topk_api.cu
Removed static from non-member custom_t operator overloads (operator<<, operator==, operator<, operator>), changing linkage to external within the translation units; implementations unchanged.

Suggested labels

cudax

Suggested reviewers

  • srinivasyadav18

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

Copy link
Copy Markdown

@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: 3

🧹 Nitpick comments (4)
c2h/include/c2h/catch2_test_macros.h (3)

27-37: 💤 Low value

suggestion: The internal macro C2H_INTERNAL_DEVICE_TEST_PRINT should use a reserved identifier pattern (e.g., _C2H_INTERNAL_DEVICE_TEST_PRINT) per libcudacxx coding style, which requires non-public symbols to use reserved identifiers (_ prefix for macros).


64-69: ⚡ Quick win

suggestion: Line 67 concatenates #EXPR #TYPE without spacing, producing output like "buf.at(4)std::out_of_range". Insert a space: `#EXPR " " `#TYPE or #EXPR ", " #TYPE``.


97-101: 💤 Low value

suggestion: Similar to CHECK_THROWS, line 100 concatenates #EXPR #TYPE without spacing. Use `#EXPR " " `#TYPE or #EXPR ", " #TYPE`` for readability.

libcudacxx/test/libcudacxx/cuda/ccclrt/common/testing.cuh (1)

37-40: ⚡ Quick win

suggestion: CCCLRT_CHECK_DEVICE and CCCLRT_FAIL_DEVICE currently forward to CHECK/FAIL instead of explicit device variants. This makes *_DEVICE behavior depend on non-device alias semantics under different toolchain/config combinations. Consider forwarding to CHECK_DEVICE and FAIL_DEVICE for consistency with CCCLRT_REQUIRE_DEVICE.

As per coding guidelines, this path should prioritize host/device availability and portability across supported CUDA toolchains.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 6cfc2c30-30b1-4c59-8b4f-b0653a9160a2

📥 Commits

Reviewing files that changed from the base of the PR and between 5d79bc2 and bd9a3ea.

📒 Files selected for processing (11)
  • c2h/CMakeLists.txt
  • c2h/include/c2h/catch2_test_helper.h
  • c2h/include/c2h/catch2_test_macros.h
  • cub/test/catch2_test_device_radix_sort_custom.cu
  • cub/test/catch2_test_device_topk_api.cu
  • cub/test/insert_nested_NVTX_range_guard.h
  • cudax/test/common/testing.cuh
  • cudax/test/cufile/driver_register.cu
  • cudax/test/utility/optionally_static.cu
  • libcudacxx/test/libcudacxx/cuda/ccclrt/common/testing.cuh
  • libcudacxx/test/libcudacxx/cuda/containers/buffer/access.cu

Comment thread c2h/include/c2h/catch2_test_macros.h Outdated
Comment thread c2h/include/c2h/catch2_test_macros.h Outdated
Comment thread c2h/include/c2h/catch2_test_macros.h Outdated
@github-actions

This comment has been minimized.

Comment thread c2h/include/c2h/catch2_test_macros.h
// <catch2/matchers/catch_matchers.hpp>

#define REQUIRE_THROWS_WITH(...) CATCH_REQUIRE_THROWS_WITH(__VA_ARGS__)
#define REQUIRE_THROWS_MATCHES(...) CATCH_REQUIRE_THROWS_MATCHES(__VA_ARGS__)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In my humble (and correct!) opinion, REQUIRE_THROWS_MATCHES() is the only proper way to check exceptions thrown as it checks both the type and expected message so there is no ambiguity on which exception is being captured.

Perhaps we can take this time to fix up some defaults and just delete the other macros.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We definitely can! Let's move this discussion to slack/new issue. This PR really only aims to provide host + device working alternatives

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would strongly advise to stay with the exact semantics of Catch2. If REQUIRE_THROWS_MATCHES is indeed better, we should just use an agent to rewrite the tests not using it.

If we change the meaning of macros between C2H and Catch2, it will create a lot of confusion for maintainers.

}))
#define REQUIRE_NOTHROW(...) NV_IF_ELSE_TARGET(NV_IS_HOST, (CATCH_REQUIRE_NOTHROW(__VA_ARGS__);), (__VA_ARGS__;))

#define CHECK(...) \
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Similarly, we should consider removing CHECK() and replacing all instances with REQUIRE(). I am struggling to think of a good scenario where you would want test execution to continue after initial failure.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think there could be value in having more information in the log from some external testing like CI or QA, so you might not have to reproduce the issue yourself, while REQUIRE would not give you enough information to figure out the failure. I wouldn't say its common and I can see using CHECK too often lead to a more messy log with multiple lines of repeated same failure for example. So its very case by case, but I think there is space for both of these macros

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed! But maybe someone else would have a valid use case for that

Copy link
Copy Markdown
Contributor

@Jacobfaib Jacobfaib May 14, 2026

Choose a reason for hiding this comment

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

o you might not have to reproduce the issue yourself, while REQUIRE would not give you enough information to figure out the failure.

Ehhh, overall I think this ends up being rare in practice.

I see it similarly to C++ compiler error messages. Yes, once in a blue moon seeing the 30 different overloads of operator<< that GCC failed to match (and Very Helpful(TM) description for each on why they didn't match) might help you debug the failure, but usually all you want to see is that you accidentally typed bool is_less = foo << bar when you meant bool is_less = foo < bar, and that's the first error message in that whole stack.

More often than not, CHECK() leads to red herring errors, e.g. when you have

CHECK(container.size() >= n);
// a lot of other unrelated code...
CHECK(container[n] == foo); // oops

And now you are left trying parse the crash log instead.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I use CHECK() a lot where I want to validate a bunch of values after I ran a test. For example, I do a SortPairs and afterwards validate if the keys and values correspond to two reference arrays. I want to see both failures if they mismatch. Please keep the CHECK() macro.

// example-end custom-type

static __host__ std::ostream& operator<<(std::ostream& os, const custom_t& self)
__host__ std::ostream& operator<<(std::ostream& os, const custom_t& self)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IMO then we should remove them if they really are unused.

// <catch2/catch2_test_macros.hpp>

#define REQUIRE(...) \
NV_IF_ELSE_TARGET(NV_IS_HOST, (CATCH_REQUIRE(__VA_ARGS__);), ({ \
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Don't you need to handle clang-coda here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It seems to be working correctly in most cases, for other problematic cases I added REQUIRE_DEVICE

Copy link
Copy Markdown
Contributor

@alliepiper alliepiper left a comment

Choose a reason for hiding this comment

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

CMake signoff

Copy link
Copy Markdown
Contributor

@bernhardmgruber bernhardmgruber left a comment

Choose a reason for hiding this comment

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

I am fine with this, but I want to point out that this may create additional maintenance work when upgrading Catch2.

Comment thread c2h/include/c2h/catch2_test_macros.h Outdated
Comment thread c2h/include/c2h/catch2_test_macros.h Outdated
}))
#define REQUIRE_NOTHROW(...) NV_IF_ELSE_TARGET(NV_IS_HOST, (CATCH_REQUIRE_NOTHROW(__VA_ARGS__);), (__VA_ARGS__;))

#define CHECK(...) \
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I use CHECK() a lot where I want to validate a bunch of values after I ran a test. For example, I do a SortPairs and afterwards validate if the keys and values correspond to two reference arrays. I want to see both failures if they mismatch. Please keep the CHECK() macro.

// <catch2/matchers/catch_matchers.hpp>

#define REQUIRE_THROWS_WITH(...) CATCH_REQUIRE_THROWS_WITH(__VA_ARGS__)
#define REQUIRE_THROWS_MATCHES(...) CATCH_REQUIRE_THROWS_MATCHES(__VA_ARGS__)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would strongly advise to stay with the exact semantics of Catch2. If REQUIRE_THROWS_MATCHES is indeed better, we should just use an agent to rewrite the tests not using it.

If we change the meaning of macros between C2H and Catch2, it will create a lot of confusion for maintainers.

@davebayer davebayer enabled auto-merge (squash) May 14, 2026 20:40
Copy link
Copy Markdown

@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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 6c5fe465-04e8-41f4-9cc6-a7dccf465957

📥 Commits

Reviewing files that changed from the base of the PR and between 694ebaf and 490c531.

📒 Files selected for processing (1)
  • c2h/include/c2h/catch2_test_macros.h

Comment thread c2h/include/c2h/catch2_test_macros.h
@github-actions
Copy link
Copy Markdown
Contributor

🥳 CI Workflow Results

🟩 Finished in 2h 32m: Pass: 100%/358 | Total: 8d 09h | Max: 2h 31m | Hits: 68%/684690

See results here.

@davebayer davebayer merged commit 16d667a into NVIDIA:main May 14, 2026
380 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants