[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:
EngineX CI
2026-07-30 09:35:51 +00:00
parent b4d01f481e
commit 56fd68e7dd
8871 changed files with 1454674 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
foreach (thrust_target IN LISTS THRUST_TARGETS)
thrust_get_target_property(config_device ${thrust_target} DEVICE)
thrust_get_target_property(config_prefix ${thrust_target} PREFIX)
set(framework_target ${config_prefix}.test.framework)
if ("CUDA" STREQUAL "${config_device}")
set(
framework_srcs #
testframework.cu
cuda/testframework.cu
)
else()
# Wrap the cu file inside a .cpp file for non-CUDA builds
thrust_wrap_cu_in_cpp(framework_srcs testframework.cu ${thrust_target})
endif()
add_library(${framework_target} STATIC ${framework_srcs})
cccl_configure_target(${framework_target})
cccl_ensure_metatargets(${framework_target})
target_link_libraries(${framework_target} PUBLIC ${thrust_target})
target_include_directories(
${framework_target}
PRIVATE "${Thrust_SOURCE_DIR}/testing"
)
if ("CUDA" STREQUAL "${config_device}")
thrust_configure_cuda_target(${framework_target} RDC ${THRUST_FORCE_RDC})
endif()
endforeach()

View File

@@ -0,0 +1,767 @@
#pragma once
#include <thrust/complex.h>
#include <thrust/detail/type_traits.h>
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>
#include <thrust/iterator/iterator_traits.h>
#include <thrust/universal_vector.h>
#include <cuda/std/utility>
#include <unittest/exceptions.h>
#include <unittest/util.h>
#define ASSERT_EQUAL_WITH_FILE_AND_LINE(X, Y, FILE_, LINE_) unittest::assert_equal((X), (Y), FILE_, LINE_)
#define ASSERT_EQUAL_QUIET_WITH_FILE_AND_LINE(X, Y, FILE_, LINE_) unittest::assert_equal_quiet((X), (Y), FILE_, LINE_)
#define ASSERT_NOT_EQUAL_WITH_FILE_AND_LINE(X, Y, FILE_, LINE_) unittest::assert_not_equal((X), (Y), FILE_, LINE_)
#define ASSERT_NOT_EQUAL_QUIET_WITH_FILE_AND_LINE(X, Y, FILE_, LINE_) \
unittest::assert_not_equal_quiet((X), (Y), FILE_, LINE_)
#define ASSERT_LEQUAL_WITH_FILE_AND_LINE(X, Y, FILE_, LINE_) unittest::assert_lequal((X), (Y), FILE_, LINE_)
#define ASSERT_GEQUAL_WITH_FILE_AND_LINE(X, Y, FILE_, LINE_) unittest::assert_gequal((X), (Y), FILE_, LINE_)
#define ASSERT_LESS_WITH_FILE_AND_LINE(X, Y, FILE_, LINE_) unittest::assert_less((X), (Y), FILE_, LINE_)
#define ASSERT_GREATER_WITH_FILE_AND_LINE(X, Y, FILE_, LINE_) unittest::assert_greater((X), (Y), FILE_, LINE_)
#define ASSERT_ALMOST_EQUAL_WITH_FILE_AND_LINE(X, Y, FILE_, LINE_) unittest::assert_almost_equal((X), (Y), FILE_, LINE_)
#define ASSERT_EQUAL_RANGES_WITH_FILE_AND_LINE(X, Y, Z, FILE_, LINE_) \
unittest::assert_equal((X), (Y), (Z), FILE_, LINE_)
#define ASSERT_THROWS_WITH_FILE_AND_LINE(EXPR, EXCEPTION_TYPE, FILE_, LINE_) \
{ \
unittest::threw_status THRUST_PP_CAT2(__s, LINE_) = unittest::did_not_throw; \
try \
{ \
EXPR; \
} \
catch (EXCEPTION_TYPE const&) \
{ \
THRUST_PP_CAT2(__s, LINE_) = unittest::threw_right_type; \
} \
catch (...) \
{ \
THRUST_PP_CAT2(__s, LINE_) = unittest::threw_wrong_type; \
} \
unittest::check_assert_throws(THRUST_PP_CAT2(__s, LINE_), THRUST_PP_STRINGIZE(EXCEPTION_TYPE), FILE_, LINE_); \
} \
/**/
#define ASSERT_THROWS_EQUAL_WITH_FILE_AND_LINE(EXPR, EXCEPTION_TYPE, VALUE, FILE_, LINE_) \
{ \
unittest::threw_status THRUST_PP_CAT2(__s, LINE_) = unittest::did_not_throw; \
try \
{ \
EXPR; \
} \
catch (EXCEPTION_TYPE const& THRUST_PP_CAT2(__e, LINE_)) \
{ \
if (VALUE == THRUST_PP_CAT2(__e, LINE_)) \
THRUST_PP_CAT2(__s, LINE_) = unittest::threw_right_type; \
else \
THRUST_PP_CAT2(__s, LINE_) = unittest::threw_right_type_but_wrong_value; \
} \
catch (...) \
{ \
THRUST_PP_CAT2(__s, LINE_) = unittest::threw_wrong_type; \
} \
unittest::check_assert_throws(THRUST_PP_CAT2(__s, LINE_), THRUST_PP_STRINGIZE(EXCEPTION_TYPE), FILE_, LINE_); \
} \
/**/
#define KNOWN_FAILURE_WITH_FILE_AND_LINE(FILE_, LINE_) \
{ \
unittest::UnitTestKnownFailure f; \
f << "[" << FILE_ ":" << LINE_ << "]"; \
throw f; \
} \
/**/
#define ASSERT_EQUAL(X, Y) ASSERT_EQUAL_WITH_FILE_AND_LINE((X), (Y), __FILE__, __LINE__)
#define ASSERT_EQUAL_QUIET(X, Y) ASSERT_EQUAL_QUIET_WITH_FILE_AND_LINE((X), (Y), __FILE__, __LINE__)
#define ASSERT_NOT_EQUAL(X, Y) ASSERT_NOT_EQUAL_WITH_FILE_AND_LINE((X), (Y), __FILE__, __LINE__)
#define ASSERT_NOT_EQUAL_QUIET(X, Y) ASSERT_NOT_EQUAL_QUIET_WITH_FILE_AND_LINE((X), (Y), __FILE__, __LINE__)
#define ASSERT_LEQUAL(X, Y) ASSERT_LEQUAL_WITH_FILE_AND_LINE((X), (Y), __FILE__, __LINE__)
#define ASSERT_GEQUAL(X, Y) ASSERT_GEQUAL_WITH_FILE_AND_LINE((X), (Y), __FILE__, __LINE__)
#define ASSERT_LESS(X, Y) ASSERT_LESS_WITH_FILE_AND_LINE((X), (Y), __FILE__, __LINE__)
#define ASSERT_GREATER(X, Y) ASSERT_GREATER_WITH_FILE_AND_LINE((X), (Y), __FILE__, __LINE__)
#define ASSERT_ALMOST_EQUAL(X, Y) ASSERT_ALMOST_EQUAL_WITH_FILE_AND_LINE((X), (Y), __FILE__, __LINE__)
#define ASSERT_EQUAL_RANGES(X, Y, Z) ASSERT_EQUAL_WITH_FILE_AND_LINE((X), (Y), (Z), __FILE__, __LINE__)
#define ASSERT_THROWS(EXPR, EXCEPTION_TYPE) \
ASSERT_THROWS_WITH_FILE_AND_LINE(EXPR, EXCEPTION_TYPE, __FILE__, __LINE__) \
/**/
#define ASSERT_THROWS_EQUAL(EXPR, EXCEPTION_TYPE, VALUE) \
ASSERT_THROWS_EQUAL_WITH_FILE_AND_LINE(EXPR, EXCEPTION_TYPE, VALUE, __FILE__, __LINE__) \
/**/
#define KNOWN_FAILURE KNOWN_FAILURE_WITH_FILE_AND_LINE(__FILE__, __LINE__)
namespace unittest
{
size_t const MAX_OUTPUT_LINES = 10;
double const DEFAULT_RELATIVE_TOL = 1e-4;
double const DEFAULT_ABSOLUTE_TOL = 1e-4;
template <typename T>
struct value_type
{
using type = ::cuda::std::remove_const_t<::cuda::std::remove_reference_t<T>>;
};
template <typename T>
struct value_type<THRUST_NS_QUALIFIER::device_reference<T>>
{
using type = typename value_type<T>::type;
};
////
// check scalar values
template <typename T1, typename T2>
void assert_equal(T1 a, T2 b, const std::string& filename = "unknown", int lineno = -1)
{
if (!(a == b))
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << "values are not equal: " << a << " " << b;
f << " [type='" << type_name<T1>() << "']";
throw f;
}
}
void assert_equal(char a, char b, const std::string& filename = "unknown", int lineno = -1)
{
if (!(a == b))
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << "values are not equal: " << int(a) << " " << int(b);
f << " [type='" << type_name<char>() << "']";
throw f;
}
}
// sometimes its not possible to << a type
template <typename T1, typename T2>
void assert_equal_quiet(const T1& a, const T2& b, const std::string& filename = "unknown", int lineno = -1)
{
if (!(a == b))
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << "values are not equal";
f << " [type='" << type_name<T1>() << "']";
throw f;
}
}
////
// check scalar values
template <typename T1, typename T2>
void assert_not_equal(T1 a, T2 b, const std::string& filename = "unknown", int lineno = -1)
{
if (a == b)
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << "values are equal: " << a << " " << b;
f << " [type='" << type_name<T1>() << "']";
throw f;
}
}
void assert_not_equal(char a, char b, const std::string& filename = "unknown", int lineno = -1)
{
if (a == b)
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << "values are equal: " << int(a) << " " << int(b);
f << " [type='" << type_name<char>() << "']";
throw f;
}
}
// sometimes its not possible to << a type
template <typename T1, typename T2>
void assert_not_equal_quiet(const T1& a, const T2& b, const std::string& filename = "unknown", int lineno = -1)
{
if (a == b)
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << "values are equal";
f << " [type='" << type_name<T1>() << "']";
throw f;
}
}
template <typename T1, typename T2>
void assert_less(T1 a, T2 b, const std::string& filename = "unknown", int lineno = -1)
{
if (!(a < b))
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << a << " is greater or equal to " << b;
f << " [type='" << type_name<T1>() << "']";
throw f;
}
}
void assert_less(char a, char b, const std::string& filename = "unknown", int lineno = -1)
{
if (!(a < b))
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << int(a) << " is greater than or equal to " << int(b);
f << " [type='" << type_name<char>() << "']";
throw f;
}
}
template <typename T1, typename T2>
void assert_greater(T1 a, T2 b, const std::string& filename = "unknown", int lineno = -1)
{
if (!(a > b))
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << a << " is less than or equal to " << b;
f << " [type='" << type_name<T1>() << "']";
throw f;
}
}
void assert_greater(char a, char b, const std::string& filename = "unknown", int lineno = -1)
{
if (!(a > b))
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << int(a) << " is less than or equal to " << int(b);
f << " [type='" << type_name<char>() << "']";
throw f;
}
}
template <typename T1, typename T2>
void assert_lequal(T1 a, T2 b, const std::string& filename = "unknown", int lineno = -1)
{
if (!(a <= b))
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << a << " is greater than " << b;
f << " [type='" << type_name<T1>() << "']";
throw f;
}
}
void assert_lequal(char a, char b, const std::string& filename = "unknown", int lineno = -1)
{
if (!(a <= b))
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << int(a) << " is greater than " << int(b);
f << " [type='" << type_name<char>() << "']";
throw f;
}
}
template <typename T1, typename T2>
void assert_gequal(T1 a, T2 b, const std::string& filename = "unknown", int lineno = -1)
{
if (!(a >= b))
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << a << " is less than " << b;
f << " [type='" << type_name<T1>() << "']";
throw f;
}
}
void assert_gequal(char a, char b, const std::string& filename = "unknown", int lineno = -1)
{
if (!(a >= b))
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << int(a) << " is less than " << int(b);
f << " [type='" << type_name<char>() << "']";
throw f;
}
}
// will catch everything implicitly convertible to a double
bool almost_equal(double a, double b, double a_tol, double r_tol)
{
if (std::abs(a - b) > r_tol * (std::abs(a) + std::abs(b)) + a_tol)
{
return false;
}
else
{
return true;
}
}
namespace
{ // anonymous namespace
template <typename>
struct is_complex : public THRUST_NS_QUALIFIER::false_type
{};
template <typename T>
struct is_complex<THRUST_NS_QUALIFIER::complex<T>> : public THRUST_NS_QUALIFIER::true_type
{};
template <typename T>
struct is_complex<std::complex<T>> : public THRUST_NS_QUALIFIER::true_type
{};
} // namespace
template <typename T1, typename T2>
inline ::cuda::std::enable_if_t<is_complex<T1>::value && is_complex<T2>::value, bool>
almost_equal(const T1& a, const T2& b, double a_tol, double r_tol)
{
return almost_equal(a.real(), b.real(), a_tol, r_tol) && almost_equal(a.imag(), b.imag(), a_tol, r_tol);
}
template <typename T1, typename T2>
void assert_almost_equal(
T1 a,
T2 b,
const std::string& filename = "unknown",
int lineno = -1,
double a_tol = DEFAULT_ABSOLUTE_TOL,
double r_tol = DEFAULT_RELATIVE_TOL)
{
if (!almost_equal(a, b, a_tol, r_tol))
{
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
f << "values are not approximately equal: " << a << " " << b;
f << " [type='" << type_name<T1>() << "']";
throw f;
}
}
template <typename T>
class almost_equal_to
{
public:
double a_tol, r_tol;
almost_equal_to(double _a_tol = DEFAULT_ABSOLUTE_TOL, double _r_tol = DEFAULT_RELATIVE_TOL)
: a_tol(_a_tol)
, r_tol(_r_tol)
{}
bool operator()(const T& a, const T& b) const
{
return almost_equal((double) a, (double) b, a_tol, r_tol);
}
};
template <typename T>
class almost_equal_to<THRUST_NS_QUALIFIER::complex<T>>
{
public:
double a_tol, r_tol;
almost_equal_to(double _a_tol = DEFAULT_ABSOLUTE_TOL, double _r_tol = DEFAULT_RELATIVE_TOL)
: a_tol(_a_tol)
, r_tol(_r_tol)
{}
bool operator()(const THRUST_NS_QUALIFIER::complex<T>& a, const THRUST_NS_QUALIFIER::complex<T>& b) const
{
return almost_equal((double) a.real(), (double) b.real(), a_tol, r_tol)
&& almost_equal((double) a.imag(), (double) b.imag(), a_tol, r_tol);
}
};
////
// check sequences
inline int promote_char(char c)
{
return c;
}
template <typename T>
T&& promote_char(T&& t)
{
return ::cuda::std::forward<T>(t);
}
template <typename ForwardIterator1, typename ForwardIterator2, typename BinaryPredicate>
void assert_equal(
ForwardIterator1 first1,
ForwardIterator1 last1,
ForwardIterator2 first2,
ForwardIterator2 last2,
BinaryPredicate op,
const std::string& filename = "unknown",
int lineno = -1)
{
using difference_type = THRUST_NS_QUALIFIER::detail::it_difference_t<ForwardIterator1>;
using InputType = THRUST_NS_QUALIFIER::detail::it_value_t<ForwardIterator1>;
bool failure = false;
difference_type length1 = ::cuda::std::distance(first1, last1);
difference_type length2 = ::cuda::std::distance(first2, last2);
difference_type min_length = ::cuda::std::min(length1, length2);
unittest::UnitTestFailure f;
f << "[" << filename << ":" << lineno << "] ";
// check lengths
if (length1 != length2)
{
failure = true;
f << "Sequences have different sizes (" << length1 << " != " << length2 << ")\n";
}
// check values
size_t mismatches = 0;
for (difference_type i = 0; i < min_length; i++)
{
if (!op(*first1, *first2))
{
if (mismatches == 0)
{
failure = true;
f << "Sequences are not equal [type='" << type_name<InputType>() << "']\n";
f << "--------------------------------\n";
}
mismatches++;
if (mismatches <= MAX_OUTPUT_LINES)
{
f << " [" << i << "] " << promote_char(*first1) << " " << promote_char(*first2) << "\n";
}
}
first1++;
first2++;
}
if (mismatches > 0)
{
if (mismatches > MAX_OUTPUT_LINES)
{
f << " (output limit reached)\n";
}
f << "--------------------------------\n";
f << "Sequences differ at " << mismatches << " of " << min_length << " positions"
<< "\n";
}
else if (length1 != length2)
{
f << "Sequences agree through " << min_length << " positions [type='" << type_name<InputType>() << "']\n";
}
if (failure)
{
throw f;
}
}
template <typename ForwardIterator1, typename ForwardIterator2>
void assert_equal(
ForwardIterator1 first1,
ForwardIterator1 last1,
ForwardIterator2 first2,
ForwardIterator2 last2,
const std::string& filename = "unknown",
int lineno = -1)
{
using InputType = typename ::cuda::std::iterator_traits<ForwardIterator1>::value_type;
assert_equal(first1, last1, first2, last2, cuda::std::equal_to<InputType>(), filename, lineno);
}
template <typename ForwardIterator1, typename ForwardIterator2>
void assert_almost_equal(
ForwardIterator1 first1,
ForwardIterator1 last1,
ForwardIterator2 first2,
ForwardIterator2 last2,
const std::string& filename = "unknown",
int lineno = -1,
const double a_tol = DEFAULT_ABSOLUTE_TOL,
const double r_tol = DEFAULT_RELATIVE_TOL)
{
using InputType = typename ::cuda::std::iterator_traits<ForwardIterator1>::value_type;
assert_equal(first1, last1, first2, last2, almost_equal_to<InputType>(a_tol, r_tol), filename, lineno);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_equal(const THRUST_NS_QUALIFIER::host_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::host_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1)
{
assert_equal(A.begin(), A.end(), B.begin(), B.end(), filename, lineno);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_equal(const THRUST_NS_QUALIFIER::host_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::device_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1)
{
THRUST_NS_QUALIFIER::host_vector<T, Alloc1> B_host = B;
assert_equal(A, B_host, filename, lineno);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_equal(const THRUST_NS_QUALIFIER::device_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::host_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1)
{
THRUST_NS_QUALIFIER::host_vector<T, Alloc2> A_host = A;
assert_equal(A_host, B, filename, lineno);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_equal(const THRUST_NS_QUALIFIER::device_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::device_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1)
{
THRUST_NS_QUALIFIER::host_vector<T> A_host = A;
THRUST_NS_QUALIFIER::host_vector<T> B_host = B;
assert_equal(A_host, B_host, filename, lineno);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_equal(const THRUST_NS_QUALIFIER::universal_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::universal_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1)
{
assert_equal(A.begin(), A.end(), B.begin(), B.end(), filename, lineno);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_equal(const THRUST_NS_QUALIFIER::host_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::universal_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1)
{
assert_equal(A.begin(), A.end(), B.begin(), B.end(), filename, lineno);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_equal(const THRUST_NS_QUALIFIER::universal_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::host_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1)
{
assert_equal(A.begin(), A.end(), B.begin(), B.end(), filename, lineno);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_equal(const THRUST_NS_QUALIFIER::device_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::universal_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1)
{
THRUST_NS_QUALIFIER::host_vector<T, Alloc1> A_host = A;
assert_equal(A_host, B, filename, lineno);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_equal(const THRUST_NS_QUALIFIER::universal_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::device_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1)
{
THRUST_NS_QUALIFIER::host_vector<T, Alloc1> B_host = B;
assert_equal(A, B_host, filename, lineno);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_equal(const std::vector<T, Alloc1>& A,
const std::vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1)
{
assert_equal(A.begin(), A.end(), B.begin(), B.end(), filename, lineno);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_almost_equal(
const THRUST_NS_QUALIFIER::host_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::host_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1,
const double a_tol = DEFAULT_ABSOLUTE_TOL,
const double r_tol = DEFAULT_RELATIVE_TOL)
{
assert_almost_equal(A.begin(), A.end(), B.begin(), B.end(), filename, lineno, a_tol, r_tol);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_almost_equal(
const THRUST_NS_QUALIFIER::host_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::device_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1,
const double a_tol = DEFAULT_ABSOLUTE_TOL,
const double r_tol = DEFAULT_RELATIVE_TOL)
{
THRUST_NS_QUALIFIER::host_vector<T, Alloc1> B_host = B;
assert_almost_equal(A, B_host, filename, lineno, a_tol, r_tol);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_almost_equal(
const THRUST_NS_QUALIFIER::device_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::host_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1,
const double a_tol = DEFAULT_ABSOLUTE_TOL,
const double r_tol = DEFAULT_RELATIVE_TOL)
{
THRUST_NS_QUALIFIER::host_vector<T, Alloc2> A_host = A;
assert_almost_equal(A_host, B, filename, lineno, a_tol, r_tol);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_almost_equal(
const THRUST_NS_QUALIFIER::device_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::device_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1,
const double a_tol = DEFAULT_ABSOLUTE_TOL,
const double r_tol = DEFAULT_RELATIVE_TOL)
{
THRUST_NS_QUALIFIER::host_vector<T> A_host = A;
THRUST_NS_QUALIFIER::host_vector<T> B_host = B;
assert_almost_equal(A_host, B_host, filename, lineno, a_tol, r_tol);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_almost_equal(
const THRUST_NS_QUALIFIER::universal_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::universal_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1,
const double a_tol = DEFAULT_ABSOLUTE_TOL,
const double r_tol = DEFAULT_RELATIVE_TOL)
{
assert_almost_equal(A.begin(), A.end(), B.begin(), B.end(), filename, lineno, a_tol, r_tol);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_almost_equal(
const THRUST_NS_QUALIFIER::host_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::universal_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1,
const double a_tol = DEFAULT_ABSOLUTE_TOL,
const double r_tol = DEFAULT_RELATIVE_TOL)
{
assert_almost_equal(A.begin(), A.end(), B.begin(), B.end(), filename, lineno, a_tol, r_tol);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_almost_equal(
const THRUST_NS_QUALIFIER::universal_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::host_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1,
const double a_tol = DEFAULT_ABSOLUTE_TOL,
const double r_tol = DEFAULT_RELATIVE_TOL)
{
assert_almost_equal(A.begin(), A.end(), B.begin(), B.end(), filename, lineno, a_tol, r_tol);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_almost_equal(
const THRUST_NS_QUALIFIER::device_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::universal_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1,
const double a_tol = DEFAULT_ABSOLUTE_TOL,
const double r_tol = DEFAULT_RELATIVE_TOL)
{
THRUST_NS_QUALIFIER::host_vector<T, Alloc1> A_host = A;
assert_almost_equal(A_host, B, filename, lineno, a_tol, r_tol);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_almost_equal(
const THRUST_NS_QUALIFIER::universal_vector<T, Alloc1>& A,
const THRUST_NS_QUALIFIER::device_vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1,
const double a_tol = DEFAULT_ABSOLUTE_TOL,
const double r_tol = DEFAULT_RELATIVE_TOL)
{
THRUST_NS_QUALIFIER::host_vector<T, Alloc1> B_host = B;
assert_almost_equal(A, B_host, filename, lineno, a_tol, r_tol);
}
template <typename T, typename Alloc1, typename Alloc2>
void assert_almost_equal(
const std::vector<T, Alloc1>& A,
const std::vector<T, Alloc2>& B,
const std::string& filename = "unknown",
int lineno = -1,
const double a_tol = DEFAULT_ABSOLUTE_TOL,
const double r_tol = DEFAULT_RELATIVE_TOL)
{
assert_almost_equal(A.begin(), A.end(), B.begin(), B.end(), filename, lineno, a_tol, r_tol);
}
enum threw_status
{
did_not_throw,
threw_wrong_type,
threw_right_type_but_wrong_value,
threw_right_type
};
void check_assert_throws(
threw_status s, std::string const& exception_name, std::string const& file_name = "unknown", int line_number = -1)
{
switch (s)
{
case did_not_throw: {
unittest::UnitTestFailure f;
f << "[" << file_name << ":" << line_number << "] did not throw anything";
throw f;
}
case threw_wrong_type: {
unittest::UnitTestFailure f;
f << "[" << file_name << ":" << line_number << "] did not throw an "
<< "object of type " << exception_name;
throw f;
}
case threw_right_type_but_wrong_value: {
unittest::UnitTestFailure f;
f << "[" << file_name << ":" << line_number << "] threw an object of the "
<< "correct type (" << exception_name << ") but wrong value";
throw f;
}
case threw_right_type:
break;
default: {
unittest::UnitTestFailure f;
f << "[" << file_name << ":" << line_number << "] encountered an "
<< "unknown error";
throw f;
}
}
}
}; // end namespace unittest

View File

@@ -0,0 +1,231 @@
#include <thrust/system/cuda/memory.h>
#include <iostream>
#include <numeric>
#include <cuda_runtime.h>
#include <unittest/cuda/testframework.h>
#include <unittest/testframework.h>
__global__ void dummy_kernel() {}
bool binary_exists_for_current_device()
{
// check against the dummy_kernel
// if we're unable to get the attributes, then
// we didn't compile a binary compatible with the current device
cudaFuncAttributes attr;
cudaError_t error = cudaFuncGetAttributes(&attr, dummy_kernel);
// clear the CUDA global error state if we just set it, so that
// check_cuda_error doesn't complain
if (cudaSuccess != error)
{
(void) cudaGetLastError();
}
return cudaSuccess == error;
}
void list_devices()
{
int deviceCount;
cudaGetDeviceCount(&deviceCount);
if (deviceCount == 0)
{
std::cout << "There is no device supporting CUDA" << '\n';
}
int selected_device;
cudaGetDevice(&selected_device);
for (int dev = 0; dev < deviceCount; ++dev)
{
cudaDeviceProp deviceProp;
cudaGetDeviceProperties(&deviceProp, dev);
if (dev == 0)
{
if (deviceProp.major == 9999 && deviceProp.minor == 9999)
{
std::cout << "There is no device supporting CUDA." << '\n';
}
else if (deviceCount == 1)
{
std::cout << "There is 1 device supporting CUDA" << '\n';
}
else
{
std::cout << "There are " << deviceCount << " devices supporting CUDA" << '\n';
}
}
std::cout << "\nDevice " << dev << ": \"" << deviceProp.name << "\"";
if (dev == selected_device)
{
std::cout << " [SELECTED]";
}
std::cout << '\n';
std::cout << " Major revision number: " << deviceProp.major << '\n';
std::cout << " Minor revision number: " << deviceProp.minor << '\n';
std::cout << " Total amount of global memory: " << deviceProp.totalGlobalMem << " bytes" << '\n';
}
std::cout << '\n';
}
// provide next, which c++03 doesn't have
template <typename Iterator>
Iterator my_next(Iterator iter)
{
return ++iter;
}
std::vector<int> CUDATestDriver::target_devices(const ArgumentMap& kwargs)
{
std::vector<int> result;
// by default, test all devices in the system (device id -1)
int device_id = kwargs.count("device") ? atoi(kwargs.find("device")->second.c_str()) : -1;
if (device_id < 0)
{
// target all devices in the system
int count = 0;
cudaGetDeviceCount(&count);
result.resize(count);
std::iota(result.begin(), result.end(), 0);
}
else
{
// target the specified device
result = std::vector<int>(1, device_id);
}
return result;
}
bool CUDATestDriver::check_cuda_error(bool concise)
{
cudaError_t const error = cudaGetLastError();
if (cudaSuccess != error)
{
if (!concise)
{
std::cout << "[ERROR] CUDA error detected before running tests: [" << std::string(cudaGetErrorName(error)) << ": "
<< std::string(cudaGetErrorString(error)) << "]" << '\n';
}
}
return cudaSuccess != error;
}
bool CUDATestDriver::post_test_smoke_check(const UnitTest& test, bool concise)
{
cudaError_t const error = cudaDeviceSynchronize();
if (cudaSuccess != error)
{
if (!concise)
{
std::cout
<< "\t[ERROR] CUDA error detected after running " << test.name << ": [" << std::string(cudaGetErrorName(error))
<< ": " << std::string(cudaGetErrorString(error)) << "]" << '\n';
}
}
return cudaSuccess == error;
}
bool CUDATestDriver::run_tests(const ArgumentSet& args, const ArgumentMap& kwargs)
{
bool verbose = kwargs.count("verbose");
bool concise = kwargs.count("concise");
if (verbose && concise)
{
std::cout << "--verbose and --concise cannot be used together" << '\n';
exit(EXIT_FAILURE);
}
// check error status before doing anything
if (check_cuda_error(concise))
{
return false;
}
bool result = true;
if (kwargs.count("verbose"))
{
list_devices();
}
// figure out which devices to target
std::vector<int> devices = target_devices(kwargs);
// target each device
for (std::vector<int>::iterator device = devices.begin(); device != devices.end(); ++device)
{
cudaDeviceSynchronize();
// set the device
cudaSetDevice(*device);
// check if a binary exists for this device
// if none exists, skip the device silently unless this is the only one we're targeting
if (devices.size() > 1 && !binary_exists_for_current_device())
{
// note which device we're skipping
cudaDeviceProp deviceProp;
cudaGetDeviceProperties(&deviceProp, *device);
std::cout << "Skipping Device " << *device << ": \"" << deviceProp.name << "\"" << '\n';
continue;
}
if (!concise)
{
// note which device we're testing
cudaDeviceProp deviceProp;
cudaGetDeviceProperties(&deviceProp, *device);
std::cout << "Testing Device " << *device << ": \"" << deviceProp.name << "\"" << '\n';
}
// check error status before running any tests
if (check_cuda_error(concise))
{
return false;
}
// run tests
result &= UnitTestDriver::run_tests(args, kwargs);
if (!concise && my_next(device) != devices.end())
{
// provide some separation between the output of separate tests
std::cout << '\n';
}
}
return result;
}
int CUDATestDriver::current_device_architecture() const
{
int current = -1;
cudaGetDevice(&current);
cudaDeviceProp deviceProp;
cudaGetDeviceProperties(&deviceProp, current);
return 100 * deviceProp.major + 10 * deviceProp.minor;
}
UnitTestDriver& driver_instance(thrust::system::cuda::tag)
{
static CUDATestDriver s_instance;
return s_instance;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include <thrust/system/cuda/memory.h>
#include <thrust/system_error.h>
#include <vector>
#include <unittest/testframework.h>
class CUDATestDriver : public UnitTestDriver
{
public:
int current_device_architecture() const;
private:
std::vector<int> target_devices(const ArgumentMap& kwargs);
bool check_cuda_error(bool concise);
bool post_test_smoke_check(const UnitTest& test, bool concise) override;
bool run_tests(const ArgumentSet& args, const ArgumentMap& kwargs) override;
};
UnitTestDriver& driver_instance(thrust::system::cuda::tag);

View File

@@ -0,0 +1,61 @@
#pragma once
#include <iostream>
#include <sstream>
#include <string>
#include <utility>
namespace unittest
{
class UnitTestException
{
public:
std::string message;
UnitTestException() = default;
UnitTestException(std::string msg)
: message(std::move(msg))
{}
friend std::ostream& operator<<(std::ostream& os, const UnitTestException& e)
{
return os << e.message;
}
template <typename T>
UnitTestException& operator<<(const T& t)
{
std::ostringstream oss;
oss << t;
message += oss.str();
return *this;
}
};
class UnitTestError : public UnitTestException
{
public:
UnitTestError() = default;
UnitTestError(const std::string& msg)
: UnitTestException(msg)
{}
};
class UnitTestFailure : public UnitTestException
{
public:
UnitTestFailure() = default;
UnitTestFailure(const std::string& msg)
: UnitTestException(msg)
{}
};
class UnitTestKnownFailure : public UnitTestException
{
public:
UnitTestKnownFailure() = default;
UnitTestKnownFailure(const std::string& msg)
: UnitTestException(msg)
{}
};
} // end namespace unittest

View File

@@ -0,0 +1,173 @@
/*! \file meta.h
* \brief Defines template classes
* for metaprogramming in the
* unit tests.
*/
#pragma once
namespace unittest
{
// mark the absence of a type
struct null_type
{};
// this type encapsulates a list of
// types
template <typename... Ts>
struct type_list
{};
// this type provides a way of indexing
// into a type_list
template <typename List, unsigned int i>
struct get_type
{
using type = null_type;
};
template <typename T, typename... Ts>
struct get_type<type_list<T, Ts...>, 0>
{
using type = T;
};
template <typename T, typename... Ts, unsigned int i>
struct get_type<type_list<T, Ts...>, i>
{
using type = typename get_type<type_list<Ts...>, i - 1>::type;
};
template <typename T, unsigned int i>
using get_type_t = typename get_type<T, i>::type;
// this type and its specialization provides a way to
// iterate over a type_list, and
// applying a unary function to each type
template <typename TypeList, template <typename> class Function, typename T, unsigned int i = 0>
struct for_each_type
{
template <typename U>
void operator()(U n)
{
// run the function on type T
Function<T> f;
f(n);
// get the next type
using next_type = typename get_type<TypeList, i + 1>::type;
// recurse to i + 1
for_each_type<TypeList, Function, next_type, i + 1> loop;
loop(n);
}
void operator()()
{
// run the function on type T
Function<T> f;
f();
// get the next type
using next_type = typename get_type<TypeList, i + 1>::type;
// recurse to i + 1
for_each_type<TypeList, Function, next_type, i + 1> loop;
loop();
}
};
// terminal case: do nothing when encountering null_type
template <typename TypeList, template <typename> class Function, unsigned int i>
struct for_each_type<TypeList, Function, null_type, i>
{
template <typename U>
void operator()(U)
{
// no-op
}
void operator()()
{
// no-op
}
};
// this type and its specialization instantiates
// a template by applying T to Template.
// if T == null_type, then its result is also null_type
template <template <typename> class Template, typename T>
struct ApplyTemplate1
{
using type = Template<T>;
};
template <template <typename> class Template>
struct ApplyTemplate1<Template, null_type>
{
using type = null_type;
};
// this type and its specializations instantiates
// a template by applying T1 & T2 to Template.
// if either T1 or T2 == null_type, then its result
// is also null_type
template <template <typename, typename> class Template, typename T1, typename T2>
struct ApplyTemplate2
{
using type = Template<T1, T2>;
};
template <template <typename, typename> class Template, typename T>
struct ApplyTemplate2<Template, T, null_type>
{
using type = null_type;
};
template <template <typename, typename> class Template, typename T>
struct ApplyTemplate2<Template, null_type, T>
{
using type = null_type;
};
template <template <typename, typename> class Template>
struct ApplyTemplate2<Template, null_type, null_type>
{
using type = null_type;
};
// this type creates a new type_list by applying a Template to each of
// the Type_list's types
template <typename TypeList, template <typename> class Template>
struct transform1;
template <typename... Ts, template <typename> class Template>
struct transform1<type_list<Ts...>, Template>
{
using type = type_list<typename ApplyTemplate1<Template, Ts>::type...>;
};
template <typename TypeList1, typename TypeList2, template <typename, typename> class Template>
struct transform2;
template <typename... T1s, typename... T2s, template <typename, typename> class Template>
struct transform2<type_list<T1s...>, type_list<T2s...>, Template>
{
using type = type_list<typename ApplyTemplate2<Template, T1s, T2s>::type...>;
};
template <typename... Ls>
struct concat;
template <typename L>
struct concat<L>
{
using type = L;
};
template <template <typename...> class L, typename... T1s, typename... T2s, typename... Ls>
struct concat<L<T1s...>, L<T2s...>, Ls...>
{
using type = concat<L<T1s..., T2s...>, Ls...>;
};
} // namespace unittest

View File

@@ -0,0 +1,97 @@
#pragma once
#include <thrust/detail/type_traits.h>
#include <thrust/host_vector.h>
#include <thrust/random.h>
#include <limits>
namespace unittest
{
inline unsigned int hash(unsigned int a)
{
a = (a + 0x7ed55d16) + (a << 12);
a = (a ^ 0xc761c23c) ^ (a >> 19);
a = (a + 0x165667b1) + (a << 5);
a = (a + 0xd3a2646c) ^ (a << 9);
a = (a + 0xfd7046c5) + (a << 3);
a = (a ^ 0xb55a4f09) ^ (a >> 16);
return a;
}
template <typename T>
struct generate_random_integer
{
T operator()(unsigned int i) const
{
THRUST_NS_QUALIFIER::default_random_engine rng(hash(i));
if constexpr (::cuda::std::is_same_v<T, bool>)
{
THRUST_NS_QUALIFIER::uniform_int_distribution<unsigned int> dist(0, 1);
return dist(rng) == 1;
}
else if constexpr (::cuda::std::is_integral_v<T>)
{
T const min = ::cuda::std::numeric_limits<T>::min();
T const max = ::cuda::std::numeric_limits<T>::max();
THRUST_NS_QUALIFIER::uniform_int_distribution<T> dist(min, max);
return static_cast<T>(dist(rng));
}
else if constexpr (::cuda::std::is_floating_point_v<T>)
{
T const min = ::cuda::std::numeric_limits<T>::lowest();
T const max = ::cuda::std::numeric_limits<T>::max();
THRUST_NS_QUALIFIER::uniform_real_distribution<T> dist(min, max);
return static_cast<T>(dist(rng));
}
else
{
return static_cast<T>(rng());
}
}
};
template <typename T>
struct generate_random_sample
{
T operator()(unsigned int i) const
{
THRUST_NS_QUALIFIER::default_random_engine rng(hash(i));
THRUST_NS_QUALIFIER::uniform_int_distribution<unsigned int> dist(0, 20);
return static_cast<T>(dist(rng));
}
};
template <typename T>
THRUST_NS_QUALIFIER::host_vector<T> random_integers(const size_t N)
{
THRUST_NS_QUALIFIER::host_vector<T> vec(N);
THRUST_NS_QUALIFIER::transform(
THRUST_NS_QUALIFIER::counting_iterator{0u},
THRUST_NS_QUALIFIER::counting_iterator{static_cast<unsigned int>(N)},
vec.begin(),
generate_random_integer<T>());
return vec;
}
template <typename T>
T random_integer()
{
return generate_random_integer<T>()(0);
}
template <typename T>
THRUST_NS_QUALIFIER::host_vector<T> random_samples(const size_t N)
{
THRUST_NS_QUALIFIER::host_vector<T> vec(N);
THRUST_NS_QUALIFIER::transform(
THRUST_NS_QUALIFIER::counting_iterator{0u},
THRUST_NS_QUALIFIER::counting_iterator{static_cast<unsigned int>(N)},
vec.begin(),
generate_random_sample<T>());
return vec;
}
}; // end namespace unittest

View File

@@ -0,0 +1,183 @@
#pragma once
#include <thrust/execution_policy.h>
#include <iosfwd>
template <typename T, unsigned int N>
struct FixedVector
{
T data[N];
_CCCL_HOST_DEVICE FixedVector()
{
for (unsigned int i = 0; i < N; i++)
{
data[i] = T();
}
}
_CCCL_HOST_DEVICE explicit FixedVector(T init)
{
for (unsigned int i = 0; i < N; i++)
{
data[i] = init;
}
}
_CCCL_HOST_DEVICE
#if _CCCL_COMPILER(NVHPC)
__attribute__((noinline))
#endif
FixedVector
operator+(const FixedVector& bs) const
{
FixedVector output;
for (unsigned int i = 0; i < N; i++)
{
output.data[i] = data[i] + bs.data[i];
}
return output;
}
_CCCL_HOST_DEVICE bool operator<(const FixedVector& bs) const
{
for (unsigned int i = 0; i < N; i++)
{
if (data[i] < bs.data[i])
{
return true;
}
if (bs.data[i] < data[i])
{
return false;
}
}
return false;
}
_CCCL_HOST_DEVICE bool operator==(const FixedVector& bs) const
{
for (unsigned int i = 0; i < N; i++)
{
if (!(data[i] == bs.data[i]))
{
return false;
}
}
return true;
}
};
template <typename Key, typename Value>
struct key_value
{
using key_type = Key;
using value_type = Value;
_CCCL_HOST_DEVICE key_value()
: key()
, value()
{}
_CCCL_HOST_DEVICE key_value(key_type k, value_type v)
: key(k)
, value(v)
{}
_CCCL_HOST_DEVICE bool operator<(const key_value& rhs) const
{
return key < rhs.key;
}
_CCCL_HOST_DEVICE bool operator>(const key_value& rhs) const
{
return key > rhs.key;
}
_CCCL_HOST_DEVICE bool operator==(const key_value& rhs) const
{
return key == rhs.key && value == rhs.value;
}
_CCCL_HOST_DEVICE bool operator!=(const key_value& rhs) const
{
return !(*this == rhs);
}
friend std::ostream& operator<<(std::ostream& os, const key_value& kv)
{
return os << "(" << kv.key << ", " << kv.value << ")";
}
key_type key;
value_type value;
};
struct user_swappable
{
_CCCL_HOST_DEVICE user_swappable(bool swapped = false)
: was_swapped(swapped)
{}
bool was_swapped;
friend _CCCL_HOST_DEVICE bool operator==(const user_swappable& x, const user_swappable& y)
{
return x.was_swapped == y.was_swapped;
}
friend _CCCL_HOST_DEVICE void swap(user_swappable& x, user_swappable& y) noexcept
{
x.was_swapped = true;
y.was_swapped = false;
}
};
// Inheriting from classes in anonymous namespaces is not allowed.
// The anonymous namespace tests don't use these, so just disable them:
#ifndef THRUST_USE_ANON_NAMESPACE
struct my_system : THRUST_NS_QUALIFIER::device_execution_policy<my_system>
{
my_system(int) {}
my_system(const my_system& other)
: num_copies(other.num_copies + 1)
{}
void validate_dispatch()
{
correctly_dispatched = (num_copies == 0);
}
bool is_valid() const
{
return correctly_dispatched;
}
private:
bool correctly_dispatched = false;
// count the number of copies so that we can validate
// that dispatch does not introduce any
unsigned int num_copies = 0;
};
struct my_tag : THRUST_NS_QUALIFIER::device_execution_policy<my_tag>
{};
#endif // THRUST_USE_ANON_NAMESPACE
namespace unittest
{
using std::int16_t;
using std::int32_t;
using std::int64_t;
using std::int8_t;
using std::uint16_t;
using std::uint32_t;
using std::uint64_t;
using std::uint8_t;
} // namespace unittest

View File

@@ -0,0 +1,26 @@
#pragma once
// for demangling the result of type_info.name()
// with msvc, type_info.name() is already demangled
#ifdef __GNUC__
# include <cxxabi.h>
#endif // __GNUC__
#include <cstdlib>
#include <string>
namespace unittest
{
inline std::string demangle(const char* name)
{
#if __GNUC__ && !_NVHPC_CUDA
int status = 0;
char* realname = abi::__cxa_demangle(name, nullptr, nullptr, &status);
std::string result(realname);
std::free(realname);
return result;
#else
return name;
#endif
}
} // namespace unittest

View File

@@ -0,0 +1,505 @@
#include <thrust/memory.h>
#include "unittest/exceptions.h"
#include "unittest/testframework.h"
// #include backends' testframework.h, if they exist and are required for the build
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
# include <unittest/cuda/testframework.h>
#endif
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <limits>
#include <numeric>
#include <string>
void set_test_sizes(const std::string& val)
{
size_t threshold = 0;
if (val == "tiny")
{
threshold = tiny_threshold;
}
else if (val == "small")
{
threshold = small_threshold;
}
else if (val == "medium")
{
threshold = medium_threshold;
}
else if (val == "default")
{
threshold = default_threshold;
}
else if (val == "large")
{
threshold = large_threshold;
}
else if (val == "huge")
{
threshold = huge_threshold;
}
else if (val == "epic")
{
threshold = epic_threshold;
}
else if (val == "max")
{
threshold = max_threshold;
}
else
{
std::cerr << "invalid test size \"" << val << "\"" << '\n';
exit(1);
}
for (size_t s : standard_test_sizes)
{
if (s <= threshold)
{
test_sizes.push_back(s);
}
}
}
void UnitTestDriver::register_test(UnitTest* test)
{
if (UnitTestDriver::s_driver().test_map.count(test->name))
{
std::cout << "[WARNING] Test name \"" << test->name << " already encountered " << '\n';
}
UnitTestDriver::s_driver().test_map[test->name] = test;
}
UnitTest::UnitTest(const char* _name)
: name(_name)
{
UnitTestDriver::s_driver().register_test(this);
}
void process_args(int argc, char** argv, ArgumentSet& args, ArgumentMap& kwargs)
{
for (int i = 1; i < argc; i++)
{
std::string arg(argv[i]);
// look for --key or --key=value arguments
if (arg.substr(0, 2) == "--")
{
std::string::size_type n = arg.find('=', 2);
if (n == std::string::npos)
{
kwargs[arg.substr(2)] = std::string(); // (key,"")
}
else
{
kwargs[arg.substr(2, n - 2)] = arg.substr(n + 1); // (key,value)
}
}
else
{
args.insert(arg);
}
}
}
void usage(int /*argc*/, char** argv)
{
std::string indent = " ";
std::cout << "Example Usage:\n";
std::cout << indent << argv[0] << "\n";
std::cout << indent << argv[0] << " TestName1 [TestName2 ...] \n";
std::cout << indent << argv[0] << " PartialTestName1* [PartialTestName2* ...] \n";
std::cout << indent << argv[0] << " --device=1\n";
std::cout << indent << argv[0] << " --sizes={tiny,small,medium,default,large,huge,epic,max}\n";
std::cout << indent << argv[0] << " --verbose or --concise\n";
std::cout << indent << argv[0] << " --list\n";
std::cout << indent << argv[0] << " --help\n";
std::cout << "\n";
std::cout << "Options:\n";
std::cout << indent << "The sizes option determines which input sizes are tested.\n";
std::cout << indent << indent << "--sizes=tiny tests sizes up to " << tiny_threshold << "\n";
std::cout << indent << indent << "--sizes=small tests sizes up to " << small_threshold << "\n";
std::cout << indent << indent << "--sizes=medium tests sizes up to " << medium_threshold << "\n";
std::cout << indent << indent << "--sizes=default tests sizes up to " << default_threshold << "\n";
std::cout << indent << indent << "--sizes=large tests sizes up to " << large_threshold << " (0.25 GB memory)\n";
std::cout << indent << indent << "--sizes=huge tests sizes up to " << huge_threshold << " (1.50 GB memory)\n";
std::cout << indent << indent << "--sizes=epic tests sizes up to " << epic_threshold << " (3.00 GB memory)\n";
std::cout << indent << indent << "--sizes=max tests all available sizes\n";
}
struct TestResult
{
TestStatus status;
std::string name;
std::string message;
// XXX use a c++11 timer result when available
std::clock_t elapsed;
TestResult(const TestStatus status, std::clock_t elapsed, const UnitTest& u, const std::string& message = "")
: status(status)
, name(u.name)
, message(message)
, elapsed(elapsed)
{}
bool operator<(const TestResult& tr) const
{
if (status < tr.status)
{
return true;
}
else if (tr.status < status)
{
return false;
}
else
{
return name < tr.name;
}
}
};
void record_result(const TestResult& test_result, std::vector<TestResult>& test_results)
{
test_results.push_back(test_result);
}
void report_results(std::vector<TestResult>& test_results, double elapsed_minutes)
{
std::cout << '\n';
std::string hline = "================================================================";
std::sort(test_results.begin(), test_results.end());
size_t num_passes = 0;
size_t num_failures = 0;
size_t num_known_failures = 0;
size_t num_errors = 0;
for (size_t i = 0; i < test_results.size(); i++)
{
const TestResult& tr = test_results[i];
if (tr.status == Pass)
{
num_passes++;
}
else
{
std::cout << hline << '\n';
switch (tr.status)
{
case Failure:
std::cout << "FAILURE";
num_failures++;
break;
case KnownFailure:
std::cout << "KNOWN FAILURE";
num_known_failures++;
break;
case Error:
std::cout << "ERROR";
num_errors++;
break;
default:
break;
}
std::cout << ": " << tr.name << '\n' << tr.message << '\n';
}
}
std::cout << hline << '\n';
std::cout << "Totals: ";
std::cout << num_failures << " failures, ";
std::cout << num_known_failures << " known failures, ";
std::cout << num_errors << " errors, and ";
std::cout << num_passes << " passes." << '\n';
std::cout << "Time: " << elapsed_minutes << " minutes" << '\n';
}
void UnitTestDriver::list_tests()
{
for (TestMap::iterator iter = test_map.begin(); iter != test_map.end(); iter++)
{
std::cout << iter->second->name << '\n';
}
}
bool UnitTestDriver::post_test_smoke_check(const UnitTest& /*test*/, bool /*concise*/)
{
return true;
}
bool UnitTestDriver::run_tests(std::vector<UnitTest*>& tests_to_run, const ArgumentMap& kwargs)
{
std::time_t start_time = std::time(0);
_CCCL_DIAG_PUSH
_CCCL_DIAG_SUPPRESS_MSVC(4800) // Forcing value to bool
bool verbose = kwargs.count("verbose");
bool concise = kwargs.count("concise");
_CCCL_DIAG_POP
std::vector<TestResult> test_results;
if (verbose && concise)
{
std::cout << "--verbose and --concise cannot be used together" << '\n';
exit(EXIT_FAILURE);
}
if (!concise)
{
std::cout << "Running " << tests_to_run.size() << " unit tests." << '\n';
}
for (size_t i = 0; i < tests_to_run.size(); i++)
{
UnitTest& test = *tests_to_run[i];
if (verbose)
{
std::cout << "Running " << test.name << "..." << std::flush;
}
try
{
// time the test
std::clock_t start = std::clock();
// run the test
test.run();
// test passed
record_result(TestResult(Pass, std::clock() - start, test), test_results);
}
catch (unittest::UnitTestFailure& f)
{
record_result(TestResult(Failure, (std::numeric_limits<std::clock_t>::max)(), test, f.message), test_results);
}
catch (unittest::UnitTestKnownFailure& f)
{
record_result(TestResult(KnownFailure, (std::numeric_limits<std::clock_t>::max)(), test, f.message),
test_results);
}
catch (std::bad_alloc& e)
{
record_result(TestResult(Error, (std::numeric_limits<std::clock_t>::max)(), test, e.what()), test_results);
}
catch (unittest::UnitTestError& e)
{
record_result(TestResult(Error, (std::numeric_limits<std::clock_t>::max)(), test, e.message), test_results);
}
// immediate report
if (!concise)
{
if (verbose)
{
switch (test_results.back().status)
{
case Pass:
std::cout << "\r[PASS] ";
std::cout << std::setw(10) << 1000.f * float(test_results.back().elapsed) / CLOCKS_PER_SEC << " ms";
break;
case Failure:
std::cout << "\r[FAILURE] ";
break;
case KnownFailure:
std::cout << "\r[KNOWN FAILURE] ";
break;
case Error:
std::cout << "\r[ERROR] ";
break;
default:
break;
}
std::cout << " " << test.name << '\n';
}
else
{
switch (test_results.back().status)
{
case Pass:
std::cout << ".";
break;
case Failure:
std::cout << "F";
break;
case KnownFailure:
std::cout << "K";
break;
case Error:
std::cout << "E";
break;
default:
break;
}
}
}
if (!post_test_smoke_check(test, concise))
{
return false;
}
std::cout.flush();
}
double elapsed_minutes = double(std::time(0) - start_time) / 60;
// summary report
if (!concise)
{
report_results(test_results, elapsed_minutes);
}
// if any failures or errors return false
for (size_t i = 0; i < test_results.size(); i++)
{
if (test_results[i].status != Pass && test_results[i].status != KnownFailure)
{
return false;
}
}
// all tests pass or are known failures
return true;
}
bool UnitTestDriver::run_tests(const ArgumentSet& args, const ArgumentMap& kwargs)
{
if (args.empty())
{
// run all tests
std::vector<UnitTest*> tests_to_run;
for (TestMap::iterator iter = test_map.begin(); iter != test_map.end(); iter++)
{
tests_to_run.push_back(iter->second);
}
return run_tests(tests_to_run, kwargs);
}
else
{
// all non-keyword arguments are assumed to be test names or partial test names
using TestMapIterator = TestMap::iterator;
// vector to accumulate tests
std::vector<UnitTest*> tests_to_run;
for (ArgumentSet::const_iterator iter = args.begin(); iter != args.end(); iter++)
{
const std::string& arg = *iter;
size_t len = arg.size();
size_t matches = 0;
if (arg[len - 1] == '*')
{
// wildcard search
std::string search = arg.substr(0, len - 1);
TestMapIterator lb = test_map.lower_bound(search);
while (lb != test_map.end())
{
if (search != lb->first.substr(0, len - 1))
{
break;
}
tests_to_run.push_back(lb->second);
lb++;
matches++;
}
}
else
{
// non-wildcard search
TestMapIterator lb = test_map.find(arg);
if (lb != test_map.end())
{
tests_to_run.push_back(lb->second);
matches++;
}
}
if (matches == 0)
{
std::cout << "[ERROR] found no test names matching the pattern: " << arg << '\n';
return false;
}
}
return run_tests(tests_to_run, kwargs);
}
}
// driver_instance maps a DeviceSystem to a singleton UnitTestDriver
template <typename DeviceSystem>
UnitTestDriver& driver_instance(DeviceSystem)
{
static UnitTestDriver s_instance;
return s_instance;
}
// if we need a special kind of UnitTestDriver, overload
// driver_instance in that function
UnitTestDriver& UnitTestDriver::s_driver()
{
return driver_instance(thrust::device_system_tag());
}
int main(int argc, char** argv)
{
ArgumentSet args;
ArgumentMap kwargs;
process_args(argc, argv, args, kwargs);
if (kwargs.count("help"))
{
usage(argc, argv);
return 0;
}
if (kwargs.count("list"))
{
UnitTestDriver::s_driver().list_tests();
return 0;
}
if (kwargs.count("sizes"))
{
set_test_sizes(kwargs["sizes"]);
}
bool passed = UnitTestDriver::s_driver().run_tests(args, kwargs);
if (kwargs.count("concise"))
{
std::cout << ((passed) ? "PASSED" : "FAILED") << '\n';
}
return (passed) ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@@ -0,0 +1,642 @@
#pragma once
#include <thrust/detail/config.h>
#include <thrust/mr/allocator.h>
#include <thrust/mr/device_memory_resource.h>
#include <thrust/mr/host_memory_resource.h>
#include <thrust/mr/universal_memory_resource.h>
#include <cuda/std/limits>
#include <cstdio>
#include <iostream>
#include <limits>
#include <map>
#include <set>
#include <string>
#include <type_traits>
#include <vector>
#include "meta.h"
#include "util.h"
// define some common lists of types
using ThirtyTwoBitTypes = unittest::type_list<int, unsigned int, float>;
using SixtyFourBitTypes = unittest::type_list<long long, unsigned long long, double>;
using IntegralTypes = unittest::type_list<
char,
signed char,
unsigned char,
short,
unsigned short,
int,
unsigned int,
long,
unsigned long,
long long,
unsigned long long>;
using SignedIntegralTypes = unittest::type_list<signed char, short, int, long, long long>;
using UnsignedIntegralTypes =
unittest::type_list<unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long>;
using ByteTypes = unittest::type_list<char, signed char, unsigned char>;
using SmallIntegralTypes = unittest::type_list<char, signed char, unsigned char, short, unsigned short>;
using LargeIntegralTypes = unittest::type_list<long long, unsigned long long>;
using FloatingPointTypes = unittest::type_list<float, double>;
// A type that behaves as if it was a normal numeric type,
// so it can be used in the same tests as "normal" numeric types.
// NOTE: This is explicitly NOT proclaimed trivially reloctable.
class custom_numeric
{
public:
_CCCL_HOST_DEVICE constexpr custom_numeric()
{
fill(0);
}
// Allow construction from any integral numeric.
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
_CCCL_HOST_DEVICE constexpr custom_numeric(const T& i)
{
fill(static_cast<int>(i));
}
_CCCL_HOST_DEVICE constexpr custom_numeric(const custom_numeric& other)
{
fill(other.value[0]);
}
_CCCL_HOST_DEVICE constexpr custom_numeric& operator=(int val)
{
fill(val);
return *this;
}
_CCCL_HOST_DEVICE constexpr custom_numeric& operator=(const custom_numeric& other)
{
if (this != &other)
{
fill(other.value[0]);
}
return *this;
}
// cast to void * instead of bool to fool overload resolution
// WTB C++11 explicit conversion operators
_CCCL_HOST_DEVICE operator void*() const
{
// static cast first to avoid MSVC warning C4312
return reinterpret_cast<void*>(static_cast<std::size_t>(value[0])); // NOLINT(performance-no-int-to-ptr)
}
#define DEFINE_OPERATOR(op) \
_CCCL_HOST_DEVICE constexpr custom_numeric& operator op() \
{ \
fill(op value[0]); \
return *this; \
} \
_CCCL_HOST_DEVICE constexpr custom_numeric operator op(int) const \
{ \
custom_numeric ret(*this); \
op ret; \
return ret; \
}
DEFINE_OPERATOR(++)
DEFINE_OPERATOR(--)
#undef DEFINE_OPERATOR
#define DEFINE_OPERATOR(op) \
_CCCL_HOST_DEVICE constexpr custom_numeric operator op() const \
{ \
return custom_numeric(op value[0]); \
}
DEFINE_OPERATOR(+)
DEFINE_OPERATOR(-)
DEFINE_OPERATOR(~)
#undef DEFINE_OPERATOR
#define DEFINE_OPERATOR(op) \
_CCCL_HOST_DEVICE constexpr custom_numeric operator op(const custom_numeric& other) const \
{ \
return custom_numeric(value[0] op other.value[0]); \
}
DEFINE_OPERATOR(+)
DEFINE_OPERATOR(-)
DEFINE_OPERATOR(*)
DEFINE_OPERATOR(/)
DEFINE_OPERATOR(%)
DEFINE_OPERATOR(<<)
DEFINE_OPERATOR(>>)
DEFINE_OPERATOR(&)
DEFINE_OPERATOR(|)
DEFINE_OPERATOR(^)
#undef DEFINE_OPERATOR
#define CONCAT(X, Y) X##Y
#define DEFINE_OPERATOR(op) \
_CCCL_HOST_DEVICE constexpr custom_numeric& operator CONCAT(op, =)(const custom_numeric & other) \
{ \
fill(value[0] op other.value[0]); \
return *this; \
}
DEFINE_OPERATOR(+)
DEFINE_OPERATOR(-)
DEFINE_OPERATOR(*)
DEFINE_OPERATOR(/)
DEFINE_OPERATOR(%)
DEFINE_OPERATOR(<<)
DEFINE_OPERATOR(>>)
DEFINE_OPERATOR(&)
DEFINE_OPERATOR(|)
DEFINE_OPERATOR(^)
#undef DEFINE_OPERATOR
#define DEFINE_OPERATOR(op) \
_CCCL_HOST_DEVICE friend constexpr bool operator op(const custom_numeric& lhs, const custom_numeric& rhs) \
{ \
return lhs.value[0] op rhs.value[0]; \
}
DEFINE_OPERATOR(==)
DEFINE_OPERATOR(!=)
DEFINE_OPERATOR(<)
DEFINE_OPERATOR(<=)
DEFINE_OPERATOR(>)
DEFINE_OPERATOR(>=)
DEFINE_OPERATOR(&&)
DEFINE_OPERATOR(||)
#undef DEFINE_OPERATOR
friend std::ostream& operator<<(std::ostream& os, const custom_numeric& val)
{
return os << "custom_numeric{" << val.value[0] << "}";
}
private:
int value[5] = {0};
_CCCL_HOST_DEVICE constexpr void fill(int val)
{
for (auto& v : value)
{
v = val;
}
}
};
namespace std
{
template <>
struct numeric_limits<custom_numeric> : numeric_limits<int>
{};
} // namespace std
_CCCL_BEGIN_NAMESPACE_CUDA_STD
template <>
struct numeric_limits<custom_numeric> : numeric_limits<int>
{};
_CCCL_END_NAMESPACE_CUDA_STD
using NumericTypes = unittest::type_list<
char,
signed char,
unsigned char,
short,
unsigned short,
int,
unsigned int,
long,
unsigned long,
long long,
unsigned long long,
float,
double,
custom_numeric>;
using BuiltinNumericTypes = unittest::type_list<
char,
signed char,
unsigned char,
short,
unsigned short,
int,
unsigned int,
long,
unsigned long,
long long,
unsigned long long,
float,
double>;
inline void chop_prefix(std::string& str, const std::string& prefix)
{
str.replace(str.find(prefix) == 0 ? 0 : str.size(), prefix.size(), "");
}
inline std::string base_class_name(const std::string& name)
{
std::string result = name;
// if the name begins with "struct ", chop it off
chop_prefix(result, "struct ");
// if the name begins with "class ", chop it off
chop_prefix(result, "class ");
const std::size_t first_lt = result.find_first_of('<');
if (first_lt < result.size())
{
// chop everything including and after first "<"
return result.replace(first_lt, result.size(), "");
}
else
{
return result;
}
}
enum TestStatus
{
Pass = 0,
Failure = 1,
KnownFailure = 2,
Error = 3,
UnknownException = 4
};
using ArgumentSet = std::set<std::string>;
using ArgumentMap = std::map<std::string, std::string>;
// clang-format off
inline constexpr size_t standard_test_sizes[] =
{
0, 1, 2, 3, 4, 5, 8, 10, 13, 16, 17, 19, 27, 30, 31, 32,
33, 35, 42, 53, 58, 63, 64, 65, 72, 97, 100, 127, 128, 129, 142, 183, 192, 201, 240, 255, 256,
257, 302, 511, 512, 513, 687, 900, 1023, 1024, 1025, 1565, 1786, 1973, 2047, 2048, 2049, 3050, 4095, 4096,
4097, 5030, 7791, 10000, 10027, 12345, 16384, 17354, 26255, 32768, 43718, 65533, 65536,
65539, 123456, 131072, 731588, 1048575, 1048576,
3398570, 9760840, (1 << 24) - 1, (1 << 24),
(1 << 24) + 1, (1 << 25) - 1, (1 << 25), (1 << 25) + 1, (1 << 26) - 1, 1 << 26,
(1 << 26) + 1, (1 << 27) - 1, (1 << 27)
};
// clang-format on
inline constexpr size_t tiny_threshold = 1 << 5; // 32
inline constexpr size_t small_threshold = 1 << 8; // 256
inline constexpr size_t medium_threshold = 1 << 12; // 4K
inline constexpr size_t default_threshold = 1 << 16; // 64K
inline constexpr size_t large_threshold = 1 << 20; // 1M
inline constexpr size_t huge_threshold = 1 << 24; // 16M
inline constexpr size_t epic_threshold = 1 << 26; // 64M
inline constexpr size_t max_threshold = (std::numeric_limits<size_t>::max)();
inline std::vector<size_t> test_sizes = [] {
std::vector<size_t> v;
for (size_t s : standard_test_sizes)
{
if (s <= default_threshold)
{
v.push_back(s);
}
}
return v;
}();
inline const std::vector<size_t>& get_test_sizes()
{
return test_sizes;
}
void set_test_sizes(const std::string&);
class UnitTest
{
public:
std::string name;
UnitTest() = default;
UnitTest(const char* name);
virtual ~UnitTest() = default;
virtual void run() {}
bool operator<(const UnitTest& u) const
{
return name < u.name;
}
};
class UnitTestDriver;
class UnitTestDriver
{
using TestMap = std::map<std::string, UnitTest*>;
TestMap test_map;
bool run_tests(std::vector<UnitTest*>& tests_to_run, const ArgumentMap& kwargs);
protected:
// executed immediately after each test
// \param test The UnitTest of interest
// \param concise Whether or not to suppress output
// \return true if all is well; false if the tests must be immediately aborted
virtual bool post_test_smoke_check(const UnitTest& test, bool concise);
public:
inline virtual ~UnitTestDriver() = default;
void register_test(UnitTest* test);
virtual bool run_tests(const ArgumentSet& args, const ArgumentMap& kwargs);
void list_tests();
static UnitTestDriver& s_driver();
};
// Macro to create a single unittest
#define DECLARE_UNITTEST(TEST) \
class TEST##UnitTest : public UnitTest \
{ \
public: \
TEST##UnitTest() \
: UnitTest(#TEST) \
{} \
void run() \
{ \
TEST(); \
} \
}; \
TEST##UnitTest TEST##Instance
#define DECLARE_UNITTEST_WITH_NAME(TEST, NAME) \
class NAME##UnitTest : public UnitTest \
{ \
public: \
NAME##UnitTest() \
: UnitTest(#NAME) \
{} \
void run() \
{ \
TEST(); \
} \
}; \
NAME##UnitTest NAME##Instance
// Macro to create host and device versions of a
// unit test for a bunch of data types
#define DECLARE_VECTOR_UNITTEST(VTEST) \
void VTEST##Host() \
{ \
VTEST<thrust::host_vector<signed char>>(); \
VTEST<thrust::host_vector<short>>(); \
VTEST<thrust::host_vector<int>>(); \
VTEST<thrust::host_vector<float>>(); \
VTEST<thrust::host_vector<custom_numeric>>(); \
/* MR vectors */ \
VTEST<thrust::host_vector<int, thrust::mr::stateless_resource_allocator<int, thrust::host_memory_resource>>>(); \
} \
void VTEST##Device() \
{ \
VTEST<thrust::device_vector<signed char>>(); \
VTEST<thrust::device_vector<short>>(); \
VTEST<thrust::device_vector<int>>(); \
VTEST<thrust::device_vector<float>>(); \
VTEST<thrust::device_vector<custom_numeric>>(); \
/* MR vectors */ \
VTEST<thrust::device_vector<int, thrust::mr::stateless_resource_allocator<int, thrust::device_memory_resource>>>(); \
} \
void VTEST##Universal() \
{ \
VTEST<thrust::universal_vector<int>>(); \
VTEST<thrust::universal_host_pinned_vector<int>>(); \
} \
DECLARE_UNITTEST(VTEST##Host); \
DECLARE_UNITTEST(VTEST##Device); \
DECLARE_UNITTEST(VTEST##Universal);
// Same as above, but only for integral types
#define DECLARE_INTEGRAL_VECTOR_UNITTEST(VTEST) \
void VTEST##Host() \
{ \
VTEST<thrust::host_vector<signed char>>(); \
VTEST<thrust::host_vector<short>>(); \
VTEST<thrust::host_vector<int>>(); \
} \
void VTEST##Device() \
{ \
VTEST<thrust::device_vector<signed char>>(); \
VTEST<thrust::device_vector<short>>(); \
VTEST<thrust::device_vector<int>>(); \
} \
void VTEST##Universal() \
{ \
VTEST<thrust::universal_vector<int>>(); \
VTEST<thrust::universal_host_pinned_vector<int>>(); \
} \
DECLARE_UNITTEST(VTEST##Host); \
DECLARE_UNITTEST(VTEST##Device); \
DECLARE_UNITTEST(VTEST##Universal);
// Macro to create instances of a test for several data types.
#define DECLARE_GENERIC_UNITTEST(TEST) \
class TEST##UnitTest : public UnitTest \
{ \
public: \
TEST##UnitTest() \
: UnitTest(#TEST) \
{} \
void run() \
{ \
TEST<signed char>(); \
TEST<unsigned char>(); \
TEST<short>(); \
TEST<unsigned short>(); \
TEST<int>(); \
TEST<unsigned int>(); \
TEST<float>(); \
} \
}; \
TEST##UnitTest TEST##Instance
// Macro to create instances of a test for several array sizes.
#define DECLARE_SIZED_UNITTEST(TEST) \
class TEST##UnitTest : public UnitTest \
{ \
public: \
TEST##UnitTest() \
: UnitTest(#TEST) \
{} \
void run() \
{ \
const std::vector<size_t>& sizes = get_test_sizes(); \
for (size_t i = 0; i != sizes.size(); ++i) \
{ \
TEST(sizes[i]); \
} \
} \
}; \
TEST##UnitTest TEST##Instance
// Macro to create instances of a test for several data types and array sizes
#define DECLARE_VARIABLE_UNITTEST(TEST) \
class TEST##UnitTest : public UnitTest \
{ \
public: \
TEST##UnitTest() \
: UnitTest(#TEST) \
{} \
void run() \
{ \
const std::vector<size_t>& sizes = get_test_sizes(); \
for (size_t i = 0; i != sizes.size(); ++i) \
{ \
TEST<signed char>(sizes[i]); \
TEST<unsigned char>(sizes[i]); \
TEST<short>(sizes[i]); \
TEST<unsigned short>(sizes[i]); \
TEST<int>(sizes[i]); \
TEST<unsigned int>(sizes[i]); \
TEST<float>(sizes[i]); \
TEST<double>(sizes[i]); \
} \
} \
}; \
TEST##UnitTest TEST##Instance
#define DECLARE_INTEGRAL_VARIABLE_UNITTEST(TEST) \
class TEST##UnitTest : public UnitTest \
{ \
public: \
TEST##UnitTest() \
: UnitTest(#TEST) \
{} \
void run() \
{ \
const std::vector<size_t>& sizes = get_test_sizes(); \
for (size_t i = 0; i != sizes.size(); ++i) \
{ \
TEST<signed char>(sizes[i]); \
TEST<unsigned char>(sizes[i]); \
TEST<short>(sizes[i]); \
TEST<unsigned short>(sizes[i]); \
TEST<int>(sizes[i]); \
TEST<unsigned int>(sizes[i]); \
} \
} \
}; \
TEST##UnitTest TEST##Instance
#define DECLARE_GENERIC_UNITTEST_WITH_TYPES_AND_NAME(TEST, TYPES, NAME) \
::SimpleUnitTest<TEST, TYPES> NAME##_instance(#NAME) /**/
#define DECLARE_GENERIC_SIZED_UNITTEST_WITH_TYPES_AND_NAME(TEST, TYPES, NAME) \
::VariableUnitTest<TEST, TYPES> NAME##_instance(#NAME) /**/
#define DECLARE_GENERIC_UNITTEST_WITH_TYPES(TEST, TYPES) ::SimpleUnitTest<TEST, TYPES> TEST##_instance(#TEST) /**/
#define DECLARE_GENERIC_SIZED_UNITTEST_WITH_TYPES(TEST, TYPES) \
::VariableUnitTest<TEST, TYPES> TEST##_instance(#TEST) /**/
template <template <typename> class TestName, typename TypeList>
class SimpleUnitTest : public UnitTest
{
public:
SimpleUnitTest()
: UnitTest(base_class_name(unittest::type_name<TestName<int>>()).c_str())
{}
SimpleUnitTest(const char* name)
: UnitTest(name)
{}
void run() override
{
// get the first type in the list
using first_type = typename unittest::get_type<TypeList, 0>::type;
unittest::for_each_type<TypeList, TestName, first_type, 0> for_each;
// loop over the types
for_each();
}
}; // end SimpleUnitTest
template <template <typename> class TestName, typename TypeList>
class VariableUnitTest : public UnitTest
{
public:
VariableUnitTest()
: UnitTest(base_class_name(unittest::type_name<TestName<int>>()).c_str())
{}
VariableUnitTest(const char* name)
: UnitTest(name)
{}
void run() override
{
const std::vector<size_t>& sizes = get_test_sizes();
for (const auto size : sizes)
{
// get the first type in the list
using first_type = typename unittest::get_type<TypeList, 0>::type;
unittest::for_each_type<TypeList, TestName, first_type, 0> loop;
// loop over the types
loop(size);
}
}
}; // end VariableUnitTest
template <template <typename> class TestName,
typename TypeList,
template <typename, typename> class Vector,
template <typename> class Alloc>
struct VectorUnitTest : public UnitTest
{
VectorUnitTest()
: UnitTest((base_class_name(unittest::type_name<TestName<Vector<int, Alloc<int>>>>()) + "<"
+ base_class_name(unittest::type_name<Vector<int, Alloc<int>>>()) + ">")
.c_str())
{}
VectorUnitTest(const char* name)
: UnitTest(name)
{}
void run() override
{
// zip up the type list with Alloc
using AllocList = typename unittest::transform1<TypeList, Alloc>::type;
// zip up the type list & alloc list with Vector
using VectorList = typename unittest::transform2<TypeList, AllocList, Vector>::type;
// get the first type in the list
using first_type = typename unittest::get_type<VectorList, 0>::type;
unittest::for_each_type<VectorList, TestName, first_type, 0> loop;
// loop over the types
loop(0);
}
}; // end VectorUnitTest

View File

@@ -0,0 +1,10 @@
#pragma once
// this is the only header included by unittests
// it pulls in all the others used for unittesting
#include <unittest/assertions.h>
#include <unittest/meta.h>
#include <unittest/random.h>
#include <unittest/special_types.h>
#include <unittest/testframework.h>

View File

@@ -0,0 +1,59 @@
#pragma once
#include <thrust/detail/type_traits.h>
#include <thrust/extrema.h>
#include <iostream>
#include <string>
#include <typeinfo>
#include <unittest/system.h>
namespace unittest
{
template <typename T>
std::string type_name()
{
return demangle(typeid(T).name());
} // end type_name()
// Use this with counting_iterator to avoid generating a range larger than we
// can represent.
template <typename T>
typename THRUST_NS_QUALIFIER::detail::disable_if<::cuda::std::is_floating_point<T>::value, T>::type
truncate_to_max_representable(std::size_t n)
{
return static_cast<T>(
THRUST_NS_QUALIFIER::min<std::size_t>(n, static_cast<std::size_t>(::cuda::std::numeric_limits<T>::max())));
}
// TODO: This probably won't work for `half`.
template <typename T>
typename ::cuda::std::enable_if_t<::cuda::std::is_floating_point<T>::value, T>
truncate_to_max_representable(std::size_t n)
{
return THRUST_NS_QUALIFIER::min<T>(static_cast<T>(n), ::cuda::std::numeric_limits<T>::max());
}
} // namespace unittest
template <typename Iterator>
void PRINT(Iterator first, Iterator last)
{
size_t n = 0;
for (Iterator i = first; i != last; i++, n++)
{
std::cout << ">>> [" << n << "] = " << *i << '\n';
}
}
template <typename Container>
void PRINT(const Container& c)
{
PRINT(c.begin(), c.end());
}
template <size_t N>
void PRINT(const char (&c)[N])
{
std::cout << std::string(c, c + N) << '\n';
}