libcudf 中的单元测试

libcudf 中的单元测试使用 Google Test 编写。

重要提示: 请勿直接包含 gtest/gtest.h,而应使用 #include <cudf_test/cudf_gtest.hpp>

此外,请在全局命名空间中编写测试代码。也就是说,请勿在 cudfcudf::test 命名空间或其子命名空间中编写测试代码。同样,请勿在全局命名空间中使用 using namespace cudf;using namespace cudf::test;

最佳实践:我们应该测试什么?

一般来说,我们应该确保所有代码路径都得到覆盖。这并非总是容易或可能的。但通常这意味着我们测试所有支持的算法和数据类型的组合,以及支持多种操作的算法(例如 reductions, groupby)支持的所有操作符。以下是一些其他指南。

  • 一般来说,空输入在 libcudf 中并非错误。通常空输入会导致空输出。测试应验证这一点。
  • 任何涉及操作位掩码(尤其是手动编写的 kernels)的代码都应该有测试,检查不同数量的行,特别是像 warp size (32) 这样的边界。因此,应测试少于 32 行、多于 32 行、正好 32 行以及大于 64 行的情况。
  • 大多数算法应该有一个或多个测试,使用足够多的行作为输入,以需要启动多个线程块,特别是在值最终在块之间通信时(例如 reductions)。这对于自定义 kernels 尤为重要,但也适用于使用 lambdas / functors 调用 Thrust 和 CUB 算法的情况。
  • 对于任何涉及 strings 或 lists 的情况,应测试空 strings/lists、null strings/lists 以及含有 null 元素的 strings/lists 的所有可能组合。
  • Strings 测试数据应包含混合的非 ASCII UTF-8 字符,例如 é
  • 测试以 sliced columns 作为输入(即具有非零 offset 的列)。这是一个容易遗漏的情况。
  • 测试验证各种形式的“退化”列输入,例如:没有子列的空 string 列(cudf 中没有多少路径可以生成这种列,但它确实会发生);大小为零但 somehow 具有非空数据指针的列;以及没有子列的 struct 列。
  • Decimal types 不包含在 cudf::test::NumericTypes 类型列表中,但包含在 cudf::test::FixedWidthTypes 中,因此请注意测试应酌情包含或排除 decimal types。

目录和文件命名

单元测试目录和源文件的命名应与被测试的功能一致。例如,copying.hpp 中 API 的测试应该位于 cudf/cpp/tests/copying。每个功能(或一组相关功能)应该有自己的测试源文件,命名为 <feature>_tests.cu/cpp。例如,cudf/cpp/src/copying/scatter.cu 的测试位于 cudf/cpp/tests/copying/scatter_tests.cu

为了缩短编译时间,只要可能,测试源文件应为 .cpp 文件,因为 nvcc 在编译 host code 时比 gcc 慢。请注意,thrust::device_vector 包含 device code,因此只能在 .cu 文件中使用。rmm::device_uvectorrmm::device_buffer 以及后面描述的各种 column_wrapper 类型可在 .cpp 文件中使用,因此在测试代码中优先于 thrust::device_vector

基本 Fixture

所有 libcudf 单元测试都应使用 GTest "Test Fixture"。即使 fixture 为空,它也应继承自位于 include/cudf_test/base_fixture.hpp 中的基本 fixture cudf::test::BaseFixture。这确保了 RMM 被正确初始化和最终化。cudf::test::BaseFixture 已经继承自 testing::Test,因此您的测试 fixtures 无需再继承自它。

示例

class MyTestFixture : public cudf::test::BaseFixture {...};

类型化测试

一般来说,libcudf 的功能必须支持所有受支持的类型(例如,并非所有二元操作都支持所有类型,存在例外)。为了自动化在多种类型上运行相同测试的过程,我们使用 GTest 的 类型化测试 (Typed Tests)。类型化测试允许您编写一个测试,然后在类型列表上运行它。

例如

// Fixture 必须是模板
template <typename T>
class TypedTestFixture : cudf::test::BaseFixture {...};
using TestTypes = cudf::test:Types<int,float,double>; // 注意自定义的 cudf 类型列表类型
TYPED_TEST_SUITE(TypedTestFixture, TestTypes);
TYPED_TEST(TypedTestFixture, FirstTest){
// 使用 `TypeParam` 访问当前类型
using T = TypeParam;
}

要指定使用的类型列表,libcudf 不使用 GTest 的 testing::Types<...>,而是提供 cudf::test::Types<...>,这是一个自定义的 testing::Types 的直接替代品。在此示例中,所有使用 TypedTestFixture fixture 的测试将针对 TestTypes (int, float, double) 中定义的列表中的每种类型运行一次。

类型列表

测试中使用的类型列表应在所有测试中保持一致。为了确保一致性,include/cudf_test/type_lists.hpp 中提供了几组常用的类型列表。例如,cudf::test::NumericTypes 是所有数值类型的类型列表,FixedWidthTypes 是所有固定宽度元素类型的列表,cudf::test::AllTypes 是 libcudf 支持的每种元素类型的列表。

// 所有使用 TypeTestFixture 的测试将针对每种数值类型调用一次
TYPED_TEST_SUITE(TypedTestFixture, cudf::test::NumericTypes);
提供集中管理的类型列表,用于 Google Test 类型参数化测试。
Concat< IntegralTypes, FloatingPointTypes > NumericTypes
提供 libcudf 中支持的所有数值类型的列表,用于 GTest 类型化测试。

只要可能,请使用 include/utilities/test/type_lists.hpp 中提供的类型列表之一,而不是创建新的自定义列表。

高级类型列表

有时需要生成比上面 TypeList 示例中简单的单类型列表更高级的类型列表。libcudf 在 include/cudf_test/type_list_utilities.hpp 中提供了一组元编程工具,用于生成和组合更高级的类型列表。

例如,生成一个*嵌套*类型列表可能很有用,其中列表中的每个元素都是两种类型。在嵌套类型列表中,列表中的每个元素本身都是另一个列表。要访问嵌套列表中的第 N 种类型,请使用 GetType<NestedList, N>

假设测试 <int,float> 的所有可能的两种类型组合。这可以手动完成

template <typename TwoTypes>
TwoTypesFixture : cudf::test::BaseFixture{...};
using TwoTypesList = Types< Types<int, int>, Types<int, float>,
Types<float, int>, Types<float, float> >;
TYPED_TEST_SUITE(TwoTypesFixture, TwoTypesList);
TYPED_TEST(TwoTypesFixture, FirstTest){
// TypeParam 是一个包含两种类型的列表,即一个“嵌套”类型列表
// 使用 `cudf::test::GetType` 检索单个类型
using FirstType = GetType<TypeParam,0>;
using SecondType = GetType<TypeParam,1>;
}

上面的示例手动指定了由 intfloat 组成的所有对。CrossProducttype_list_utilities.hpp 中的一个工具,它自动实现这种笛卡尔积。

using TwoTypesList = Types< Types<int, int>, Types<int, float>,
Types<float, int>, Types<float, float> >;
using CrossProductTypeList = CrossProduct< Types<int, float>, Types<int, float> >;
// TwoTypesList 和 CrossProductTypeList 是相同的

CrossProduct 可以与任意数量的类型列表一起使用,以生成由两种或更多类型组成的嵌套类型列表。**然而**,过度使用 CrossProduct 会显着增加编译时间。两个大小分别为 nm 的类型列表的笛卡尔积将生成一个包含 n*m 个嵌套类型列表的新列表。这意味着将实例化 n*m 个模板;即使 nm 不大,编译时间也可能变得不合理。

type_list_utilities.hpp 中还有许多其他实用工具。更多详细信息,请参阅该文件中的文档以及 cudf/cpp/tests/utilities_tests/type_list_tests.cpp 中的相关测试。

实用工具

libcudf 在 include/cudf_test 中提供了许多实用工具,以方便进行常见的测试操作。在创建自己的测试实用工具之前,请查看是否已存在一个可以满足您需求的工具。如果不存在,请考虑添加一个新的实用工具来满足您的需求。但是,请确保该实用工具足够通用,对其他测试也有用,并且不要过度针对您的特定测试需求进行定制。

Column Wrappers

为了更容易生成输入列,libcudf 在 include/cudf_test/column_wrapper.hpp 中提供了 *_column_wrapper 类。这些类包装了一个 cudf::column,并提供了构造函数,用于初始化可与 libcudf API 一起使用的 cudf::column 对象。任何 *_column_wrapper 类都可以隐式转换为 column_viewmutable_column_view,因此可以透明地传递给任何期望 column_viewmutable_column_view 参数的 API。

fixed_width_column_wrapper

cudf::test::fixed_width_column_wrapper 类应用于构造和初始化任何固定宽度元素类型的列,例如数值类型、时间戳类型、布尔类型等。cudf::test::fixed_width_column_wrapper 提供了接受迭代器范围的构造函数,用于生成列中的每个元素。对于可空列,可以提供一个额外的迭代器来指示每个元素的有效性。还有构造函数接受 std::initializer_list<T> 用于列元素以及可选地用于每个元素的有效性。

示例

// 创建一个非可空 INT32 元素列,包含 5 个元素:{0, 1, 2, 3, 4}
auto elements = cudf::detail::make_counting_transform_iterator(0, [](auto i){return i;});
// 创建一个可空 INT32 元素列,包含 5 个元素:{null, 1, null, 3, null}
auto elements = cudf::detail::make_counting_transform_iterator(0, [](auto i){return i;});
auto validity = cudf::detail::make_counting_transform_iterator(0, [](auto i){return i % 2;})
cudf::test::fixed_width_column_wrapper<int32_t> w(elements, elements + 5, validity);
// 创建一个非可空 INT32 列,包含 4 个元素:{1, 2, 3, 4}
// 创建一个可空 INT32 列,包含 4 个元素:{1, NULL, 3, NULL}
包装固定宽度元素列的 column_wrapper 派生类。

fixed_point_column_wrapper

cudf::test::fixed_point_column_wrapper 类应用于构造和初始化任何定点元素类型(DECIMAL32 或 DECIMAL64)的列。cudf::test::fixed_point_column_wrapper 提供了接受迭代器范围的构造函数,用于生成列中的每个元素。对于可空列,可以提供一个额外的迭代器来指示每个元素的有效性。构造函数还接受要创建的定点值的比例 (scale)。

示例

// 创建一个非可空 DECIMAL32 列,包含 4 个元素,比例为 3:{1000, 2000, 3000, 4000}
auto elements = cudf::detail::make_counting_transform_iterator(0, [](auto i){ return i; });
// 创建一个可空 DECIMAL32 列,包含 5 个元素,比例为 2:{null, 100, null, 300, null}
auto elements = cudf::detail::make_counting_transform_iterator(0, [](auto i){ return i; });
auto validity = cudf::detail::make_counting_transform_iterator(0, [](auto i){ return i % 2; });
cudf::test::fixed_point_column_wrapper<int32_t> w(elements, elements + 5, validity, 2);
固定宽度元素列的包装器。

dictionary_column_wrapper

cudf::test::dictionary_column_wrapper 类应用于创建字典列。cudf::test::dictionary_column_wrapper 提供了接受迭代器范围的构造函数,用于生成列中的每个元素。对于可空列,可以提供一个额外的迭代器来指示每个元素的有效性。还有构造函数接受 std::initializer_list<T> 用于列元素以及可选地用于每个元素的有效性。

示例

// 创建一个非可空 INT32 元素字典列,包含 5 个元素
// 键 (keys) = {0, 2, 6}, 索引 (indices) = {0, 1, 1, 2, 2}
std::vector<int32_t> elements{0, 2, 2, 6, 6};
cudf::test::dictionary_column_wrapper<int32_t> w(element.begin(), elements.end());
// 创建一个可空字典列,包含 5 个元素和有效性迭代器。
std::vector<int32_t> elements{0, 2, 0, 6, 0};
// 此处的有效性迭代器将偶数行设置为 null。
auto validity = cudf::detail::make_counting_transform_iterator(0, [](auto i){return i % 2;})
// 键 (keys) = {2, 6}, 索引 (indices) = {NULL, 0, NULL, 1, NULL}
cudf::test::dictionary_column_wrapper<int32_t> w(elements, elements + 5, validity);
// 创建一个非可空字典列,包含 4 个元素。
// 键 (keys) = {1, 2, 3}, 索引 (indices) = {0, 1, 2, 0}
// 创建一个可空字典列,包含 4 个元素和有效性初始化列表。
// 键 (keys) = {1, 3}, 索引 (indices) = {0, NULL, 1, NULL}
cudf::test::dictionary_column_wrapper<int32_t> w{ {1, 0, 3, 0}, {1, 0, 1, 0}};
// 创建一个包含 5 个元素的字典元素的可空列,带有效性初始化列表。
std::vector<int32_t> elements{0, 2, 2, 6, 6};
// 键 (keys) = {2, 6}, 索引 (indices) = {NULL, 0, NULL, 1, NULL}
cudf::test::dictionary_width_column_wrapper<int32_t> w(elements, elements + 5, {0, 1, 0, 1, 0});
// 创建一个包含 7 个 string 元素的非可空字典列
std::vector<std::string> strings{"", "aaa", "bbb", "aaa", "bbb", "ccc", "bbb"};
// 键 (keys) = {"","aaa","bbb","ccc"}, 索引 (indices) = {0, 1, 2, 1, 2, 3, 2}
cudf::test::dictionary_column_wrapper<std::string> d(strings.begin(), strings.end());
// 创建一个包含 7 个 string 元素的可空字典列,带有效性迭代器。
// 此处的有效性迭代器将偶数行设置为 null。
// 键 (keys) = {"a", "bb"}, 索引 (indices) = {NULL, 1, NULL, 1, NULL, 0, NULL}
auto validity = cudf::detail::make_counting_transform_iterator(0, [](auto i){return i % 2;});
cudf::test::dictionary_column_wrapper<std::string> d({"", "bb", "", "bb", "", "a", ""}, validity);
包装具有 string 键的字典列的 column_wrapper 派生类。
包装字典列的 column_wrapper 派生类。

strings_column_wrapper

cudf::test::strings_column_wrapper 类应用于创建 string 列。它提供了接受迭代器范围的构造函数,用于生成列中的每个 string。对于可空列,可以提供一个额外的迭代器来指示每个 string 的有效性。还有构造函数接受 std::initializer_list<std::string> 用于列的 strings 以及可选地用于每个元素的有效性。

示例

// 创建一个包含 7 个 string 元素的非可空 STRING 列
// {"", "this", "is", "a", "column", "of", "strings"}
std::vector<std::string> strings{"", "this", "is", "a", "column", "of", "strings"};
cudf::test::strings_column_wrapper s(strings.begin(), strings.end());
// 创建一个包含 7 个 string 元素的可空 STRING 列
// {NULL, "this", NULL, "a", NULL, "of", NULL}
std::vector<std::string> strings{"", "this", "is", "a", "column", "of", "strings"};
auto validity = cudf::detail::make_counting_transform_iterator(0, [](auto i){return i % 2;});
cudf::test::strings_column_wrapper s(strings.begin(), strings.end(), validity);
// 创建一个包含 7 个 string 元素的非可空 STRING 列
// {"", "this", "is", "a", "column", "of", "strings"}
cudf::test::strings_column_wrapper s({"", "this", "is", "a", "column", "of", "strings"});
// 创建一个包含 7 个 string 元素的可空 STRING 列
// {NULL, "this", NULL, "a", NULL, "of", NULL}
auto validity = cudf::detail::make_counting_transform_iterator(0, [](auto i){return i % 2;});
cudf::test::strings_column_wrapper s({"", "this", "is", "a", "column", "of", "strings"}, validity);
包装 string 列的 column_wrapper 派生类。

lists_column_wrapper

cudf::test::lists_column_wrapper 类应用于创建 list 列。它提供了接受迭代器范围的构造函数,用于生成列中的每个 list。对于可空列,可以提供一个额外的迭代器来指示每个 list 的有效性。还有构造函数接受 std::initializer_list<T> 用于列的 lists 以及可选地用于每个元素的有效性。还提供了许多其他构造函数。

示例

// 创建一个空的 LIST 列
// []
// 创建一个 LIST 列,包含 1 个 list,由总共 2 个整数组成
// [{0, 1}]
// 创建一个包含 3 个 lists 的 LIST 列
// [{0, 1}, {2, 3}, {4, 5}]
cudf::test::lists_column_wrapper l{ {0, 1}, {2, 3}, {4, 5} };
// 创建一个 LIST of LIST 列,顶层包含 2 个 lists,底层包含
// 4 个
// [ {{0, 1}, {2, 3}}, {{4, 5}, {6, 7}} ]
cudf::test::lists_column_wrapper l{ {{0, 1}, {2, 3}}, {{4, 5}, {6, 7}} };
// 创建一个 LIST 列,包含 1 个 list,由总共 5 个整数组成
// [{0, 1, 2, 3, 4}]
auto elements = cudf::detail::make_counting_transform_iterator(0, [](auto i){return i*2;});
cudf::test::lists_column_wrapper l(elements, elements+5);
// 创建一个 LIST 列,包含 1 个 list,由总共 2 个整数组成
// [{0, NULL}]
auto validity = cudf::detail::make_counting_transform_iterator(0, [](auto i){return i % 2;});
// 创建一个 LIST 列,包含 1 个 list,由总共 5 个整数组成
// [{0, NULL, 2, NULL, 4}]
auto elements = cudf::detail::make_counting_transform_iterator(0, [](auto i){return i*2;});
auto validity = cudf::detail::make_counting_transform_iterator(0, [](auto i){return i % 2;});
cudf::test::lists_column_wrapper l(elements, elements+5, validity);
// 创建一个 LIST 列,包含 1 个 list,由总共 2 个 string 组成
// [{"abc", "def"}]
// 创建一个 LIST of LIST 列,顶层包含 2 个 lists,底层包含 4 个
// [ {{0, 1}, NULL}, {{4, 5}, NULL} ]
auto validity = cudf::detail::make_counting_transform_iterator(0, [](auto i){return i % 2;});
cudf::test::lists_column_wrapper l{ {{{0, 1}, {2, 3}}, validity}, {{{4, 5}, {6, 7}}, validity} };
包装 list 列的 column_wrapper 派生类。

structs_column_wrapper

cudf::test::structs_column_wrapper 类应用于创建 struct 列。它提供了接受预构建的子列或子列 column wrappers 的 vector 或 initializer list 的构造函数。对于可空列,可以提供一个额外的迭代器来指示每个 struct 的有效性。

示例

// 以下构造一个 struct< int, string > 的列。
auto child_int_col = cudf::test::fixed_width_column_wrapper<int32_t>{ 1, 2, 3, 4, 5 }.release();
auto child_string_col = cudf::test::string_column_wrapper {"All", "the", "leaves", "are", "brown"}.release();
std::vector<std::unique_ptr<cudf::column>> child_columns;
child_columns.push_back(std::move(child_int_col));
child_columns.push_back(std::move(child_string_col));
cudf::test::struct_col wrapper wrapper{
child_cols,
{1,0,1,0,1} // 有效性
};
auto struct_col {wrapper.release()};
// 以下构造一个 struct< int, string > 的列。
cudf::test::fixed_width_column_wrapper<int32_t> child_int_col_wrapper{ 1, 2, 3, 4, 5 };
cudf::test::string_column_wrapper child_string_col_wrapper {"All", "the", "leaves", "are", "brown"};
cudf::test::struct_column_wrapper wrapper{
{child_int_col_wrapper, child_string_col_wrapper}
{1,0,1,0,1} // 有效性
};
auto struct_col {wrapper.release()};
// 以下构造一个 struct< int, string > 的列。
cudf::test::fixed_width_column_wrapper<int32_t> child_int_col_wrapper{ 1, 2, 3, 4, 5 };
cudf::test::string_column_wrapper child_string_col_wrapper {"All", "the", "leaves", "are", "brown"};
cudf::test::struct_column_wrapper wrapper{
{child_int_col_wrapper, child_string_col_wrapper}
cudf::detail::make_counting_transform_iterator(0, [](auto i){ return i % 2; }) // 有效性
};
auto struct_col {wrapper.release()};
std::unique_ptr< cudf::column > release()
释放指向被包装列的内部 unique_ptr。

列比较实用工具

测试中的一项常见操作是验证两列是否相等、等价,或者它们是否具有相同的元数据。

CUDF_TEST_EXPECT_COLUMN_PROPERTIES_EQUAL

验证两列是否具有相同的类型、大小和可空性。对于嵌套类型,递归验证所有嵌套子列的类型、大小和可空性是否相等。

CUDF_TEST_EXPECT_COLUMN_PROPERTIES_EQUIVALENT

验证两列是否具有等价的类型和相等的大小,忽略可空性。对于嵌套类型,递归验证所有嵌套子列的类型是否等价以及大小是否相等,忽略可空性。

注意“等价类型”。大多数类型当且仅当它们相等时才等价。fixed_point 类型是一个例外。如果它们的表示类型相等,即使比例 (scales) 不同,它们也是等价的。嵌套类型列在大小都为零但一个有子列(也为空)而另一个没有子列的情况下可以等价。对于大小非零的列,相等和等价都要求子列数量相等。

CUDF_TEST_EXPECT_COLUMNS_EQUAL

验证两列属性相等,并验证列数据的逐元素相等性。Null 元素被视为相等。

CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT

验证两列属性等价,并验证列数据的逐元素等价性。Null 元素被视为等价。

CUDF_TEST_EXPECT_EQUAL_BUFFERS

验证两个 device memory buffers 的按位相等性。

注意事项

不应直接使用 cudf::test::detail 命名空间中的列比较函数。

打印和访问列数据

头文件 <cudf_test/debug_utilities.hpp> 定义了各种函数和重载,用于打印列 (print)、将列数据转换为 string (to_string, to_strings) 以及将数据复制到 host (to_host)。例如,要将 cudf::column_view 的内容或 column_wrapper 实例打印到控制台,请使用 cudf::test::print()

auto splits = cudf::split(input,{2});
cudf::test::print(input);
cudf::test::print(splits.front());
std::vector< column_view > split(column_view const &input, host_span< size_type const > splits, rmm::cuda_stream_view stream=cudf::get_default_stream())
根据从预期 s... 派生的一组索引,将 column_view 拆分为一组 column_view。

固定宽度和 string 列以逗号分隔的条目形式输出,包括 null 行。嵌套列也支持,输出包含 offsets 和数据子列以及 null mask 位。

验证流使用情况

背景

libcudf 使用定制的 preload library 来验证其内部流使用情况(代码可以在 此处 找到)。该库会包装每个接受流的异步 CUDA 运行时 API 调用,进行检查以确保传递的 CUDA 流是有效的,如果检测到无效流会立即抛出异常。加载此库运行测试会立即触发错误,如果任何测试意外地在无效流上运行代码。

流的有效性通过重载 libcudf 默认流的定义来确定。通常,在 libcudf 中,cudf::get_default_stream 返回 rmm 的默认流值之一(取决于 libcudf 是否启用了 per thread default stream 编译)。在 preload library 中,此函数被重新定义,转而返回一个使用函数局部静态 rmm::cuda_stream 管理的新创建的用户流。在这种情况下,无效流被定义为 CUDA 的任何默认流值(cudaStreamLegacy, cudaStreamDefault 或 cudaStreamPerThread),因为任何正确使用 cudf::get_default_stream 的 kernel 现在将转而使用 preload library 创建的自定义流。

preload library 支持两种不同的模式:cudf 模式和 testing 模式。上一段描述了 cudf 模式的行为,其中 cudf::get_default_stream 被重载。在 cudf 模式下,preload library 确保所有 CUDA 运行时 API 都被提供了 cudf 的默认流。这将检测到疏忽之处,例如 Thrust 调用未指定流,或者显式指定了 CUDA 默认流值之一给 kernel。然而,它不会检测到流未在调用堆栈中正确传递的情况,例如,如果某个接受流参数的 detail 函数未能将其传递下去,而是错误地调用了 cudf::get_default_stream

testing 模式下,库会转而重载 cudf::test::get_default_stream。此函数定义在 cudf::test 命名空间中,启用了一种更严格的测试模式。在 testing 模式下,preload library 会验证所有 CUDA 运行时 API 是否都使用测试命名空间的默认流进行调用。这种区别很重要,因为 cudf 内部从不使用 cudf::test::get_default_stream,因此这个流值只有在提供给公共 API 并沿调用堆栈正确传递的情况下才可能在内部出现。虽然 testing 模式比 cudf 模式更严格,但它也更具侵入性。cudf 模式无需更改库或测试即可运行,因为 preload library 会原地覆盖相关 API。然而,testing 模式只能用于验证正确将 cudf::test::get_default_stream 传递给公共 libcudf API 的测试。

除了 preload library,测试套件还实现了一个 custom memory resource,当其 do_allocate 方法被调用时,它会执行类似的流验证。在测试期间,rmm 的默认内存资源被设置为使用此 adaptor 进行额外的流验证。

使用方法

在为 libcudf API 编写测试时,应添加一组特殊的额外测试来验证 API 的流使用情况。这些测试应放置在 cpp/tests/streams 目录中,文件应对应于包含被测试 API 的头文件,例如,对于 cpp/include/cudf/copying.hpp 中声明的所有 API,测试文件为 cpp/tests/streams/copying_test.cpp。这些测试应包含对被测试 API 的最小化调用,不带任何额外断言,因为它们仅用于检查流使用情况。将这些测试添加到 cpp/tests/CMakeLists.txt 时,应向 ConfigureTest CMake 函数提供参数 STREAM_MODE testing。此更改足以让 CTest 设置测试在运行时自动加载在 testing 模式下编译的 preload library。

测试套件的其余部分配置为在 cudf 模式下使用 preload library 运行。因此,所有使用 ctest 运行的测试都将包含流验证。由于此配置通过 CMake 和 CTest 管理,直接执行测试可执行文件将完全不使用 preload library。然而,在这种情况下,测试仍将正常运行并通过(preload library 本身测试除外)。