diff --git a/cpp/include/cudf_test/base_fixture.hpp b/cpp/include/cudf_test/base_fixture.hpp index 00395536900..793260e4792 100644 --- a/cpp/include/cudf_test/base_fixture.hpp +++ b/cpp/include/cudf_test/base_fixture.hpp @@ -47,6 +47,8 @@ class BaseFixture : public ::testing::Test { * * Each test instantiates a fresh harness. Tests should construct results with `resources()`. * `TearDown` asserts that no output or temporary allocations remain live. + * Call `fail_on_current_device_resource_use()` in test scopes that must reject accidental + * current-device-resource allocations. */ struct BaseFixtureWithHarness : public BaseFixture { /** @@ -66,6 +68,19 @@ struct BaseFixtureWithHarness : public BaseFixture { */ cudf::memory_resources resources() { return _harness.resources(); } + /** + * @brief Install a scoped failing current-device resource. + * + * While the returned object is alive, allocations from the current device resource fail. + * Destroy it (or let it leave scope) to restore the previous current resource. + * + * @return Scoped guard around the failing current-device resource + */ + [[nodiscard]] scoped_current_device_resource fail_on_current_device_resource_use() + { + return _harness.fail_on_current_device_resource_use(); + } + protected: memory_resource_test_harness _harness{mr()}; }; diff --git a/cpp/include/cudf_test/column_wrapper.hpp b/cpp/include/cudf_test/column_wrapper.hpp index e749a086cd6..c9889822665 100644 --- a/cpp/include/cudf_test/column_wrapper.hpp +++ b/cpp/include/cudf_test/column_wrapper.hpp @@ -35,9 +35,12 @@ #include #include +#include +#include #include #include #include +#include #include namespace CUDF_EXPORT cudf { @@ -337,8 +340,175 @@ auto make_chars_and_offsets(StringsIterator begin, StringsIterator end, Validity } return std::pair(std::move(chars), std::move(offsets)); }; + } // namespace detail +// Forward declaration for lists_column_initializer::build +template +class lists_column_wrapper; + +/** + * @brief Host-side recursive initializer tree for constructing list columns with an + * explicit stream and memory resources at every nesting level. + * + * Prefer this over brace-nested `lists_column_wrapper` constructions that pass + * `stream`/`mr` only at the outer level, which leave brace-constructed children on + * the default test resources. + * + * Example: + * @code{.cpp} + * using Init = cudf::test::lists_column_initializer; + * // List: [{1, 2}, {3}] + * lists_column_wrapper col{Init{{{1, 2}, {3}}}, stream, mr}; + * @endcode + * + * Leaf and nested constructors accept the existing validity iterators + * (`valids`, `null_at(...)`, etc.) and materialize them into owned storage. + * + * @tparam T Host leaf element type (e.g. `int32_t` or `std::string`) + */ +template +class lists_column_initializer { + public: + /** + * @brief Construct an empty leaf. Avoids ambiguity between the leaf and nested + * empty `initializer_list` constructors. + */ + lists_column_initializer() = default; + + /** + * @brief Construct a leaf from scalar values. + * + * @param values Leaf element values + */ + lists_column_initializer(std::initializer_list values) : values_{values} {} + + /** + * @brief Construct a leaf from scalar values and a validity iterator. + * + * @tparam ValidityIterator Iterator convertible to `bool` + * @param values Leaf element values + * @param v Validity iterator over `values.size()` elements + */ + template + lists_column_initializer(std::initializer_list values, ValidityIterator v) : values_{values} + { + value_validity_.reserve(values_.size()); + for (std::size_t i = 0; i < values_.size(); ++i) { + value_validity_.push_back(static_cast(*v++)); + } + } + + /** + * @brief Construct a nested node from child initializers. + * + * This constructor is a template so the non-template leaf + * `initializer_list` constructor is preferred for scalar lists such as + * `{1, 2, 3}`. Otherwise both overloads are non-templates and constructing + * `Init` from an `int` via the nested overload recurses until the stack + * overflows. + * + * @param children Child list initializers + */ + template + lists_column_initializer(std::initializer_list children) + requires(std::is_same_v) + : children_{children.begin(), children.end()}, nested_{true} + { + } + + /** + * @brief Construct a nested node from child initializers and a row-validity iterator. + * + * @tparam ValidityIterator Iterator convertible to `bool` + * @param children Child list initializers + * @param v Validity iterator over `children.size()` rows + */ + template + lists_column_initializer(std::initializer_list children, ValidityIterator v) + requires(std::is_same_v) + : nested_{true} + { + children_.reserve(children.size()); + for (auto const& child : children) { + if (static_cast(*v++)) { + children_.push_back(child); + } else { + children_.emplace_back(); + children_.back().valid_ = false; + } + } + } + + /** + * @brief True if this node holds nested child initializers rather than leaf values. + * @return Whether this node is nested + */ + [[nodiscard]] bool nested() const { return nested_; } + /** + * @brief True if this row is valid (non-null) in its parent list. + * @return Whether this row is valid + */ + [[nodiscard]] bool valid() const { return valid_; } + /** + * @brief Leaf element values when `nested()` is false. + * @return Reference to the leaf values + */ + [[nodiscard]] auto const& values() const { return values_; } + /** + * @brief Per-element validity for leaf values; empty when all leaf values are valid. + * @return Reference to the leaf validity mask + */ + [[nodiscard]] auto const& value_validity() const { return value_validity_; } + /** + * @brief Child initializers when `nested()` is true. + * @return Reference to the child initializers + */ + [[nodiscard]] auto const& children() const { return children_; } + + /** + * @brief Recursively build child list wrappers and row validity for a nested node. + * + * Each valid child is allocated with the provided `stream` and `mr`. Null children are + * represented as default-constructed wrappers (skipped during concatenate). + * + * @tparam ElementT List wrapper element type + * @tparam SourceElementT Source type used by the list wrapper + * @param stream CUDA stream used for device memory operations + * @param mr Memory resources used to allocate child columns + * @return Child wrappers and an empty validity vector when all rows are valid, + * otherwise a validity mask matching `children().size()` + */ + template + [[nodiscard]] std::pair>, + std::vector> + build(rmm::cuda_stream_view stream, cudf::memory_resources mr) const + { + std::vector> children; + std::vector validity; + children.reserve(children_.size()); + validity.reserve(children_.size()); + bool any_null = false; + for (auto const& child : children_) { + any_null = any_null || !child.valid(); + validity.push_back(child.valid()); + if (child.valid()) { + children.emplace_back(child, stream, mr); + } else { + children.emplace_back(); // null rows are skipped during concatenate + } + } + return {std::move(children), any_null ? std::move(validity) : std::vector{}}; + } + + private: + std::vector values_; + std::vector value_validity_; + std::vector children_; + bool nested_{false}; + bool valid_{true}; +}; + /** * @brief `column_wrapper` derived class for wrapping columns of fixed-width * elements. @@ -1482,35 +1652,49 @@ class lists_column_wrapper : public detail::column_wrapper { operator lists_column_view() const { return cudf::lists_column_view{wrapped->view()}; } /** - * @brief Construct a lists column containing a single list of fixed-width - * type from an initializer list of values. + * @brief Host-side leaf element type (`std::string` for string lists, else `SourceElementT`). + */ + using host_element_t = + std::conditional_t, std::string, SourceElementT>; + /** + * @brief Column wrapper type used to materialize leaf list contents. + */ + using leaf_wrapper_t = std::conditional_t, + strings_column_wrapper, + fixed_width_column_wrapper>; + + /** + * @brief Construct a lists column containing a single list from an initializer + * list of values. * * Example: * @code{.cpp} - * Creates a LIST column with 1 list composed of 2 total integers - * [{0, 1}] + * // Creates a LIST column with 1 list composed of 2 total integers + * // [{0, 1}] * lists_column_wrapper l{0, 1}; * @endcode * + * These leaf constructors are templates (via `requires`) so that the non-template + * nested `initializer_list` constructor is preferred for + * ambiguous cases such as `lists_column_wrapper{{}, {}}`. + * * @param elements The list of elements * @param stream CUDA stream used for device memory operations * @param mr Memory resources used to allocate the returned column */ - template ()>* = nullptr> + template lists_column_wrapper(std::initializer_list elements, rmm::cuda_stream_view stream = cudf::test::get_default_stream(), cudf::memory_resources mr = cudf::get_current_device_resource_ref()) + requires(cudf::is_fixed_width()) : column_wrapper{} { build_from_non_nested( - cudf::test::fixed_width_column_wrapper(elements, stream, mr).release(), - stream, - mr); + fixed_width_column_wrapper(elements, stream, mr).release(), stream, mr); } /** - * @brief Construct a lists column containing a single list of fixed-width - * type from an iterator range. + * @brief Construct a lists column containing a single list from an iterator range. * * Example: * @code{.cpp} @@ -1525,28 +1709,23 @@ class lists_column_wrapper : public detail::column_wrapper { * @param stream CUDA stream used for device memory operations * @param mr Memory resources used to allocate the returned column */ - template ()>* = nullptr> + template lists_column_wrapper(InputIterator begin, InputIterator end, rmm::cuda_stream_view stream = cudf::test::get_default_stream(), cudf::memory_resources mr = cudf::get_current_device_resource_ref()) : column_wrapper{} { - build_from_non_nested( - cudf::test::fixed_width_column_wrapper(begin, end, stream, mr).release(), - stream, - mr); + build_from_non_nested(leaf_wrapper_t(begin, end, stream, mr).release(), stream, mr); } /** - * @brief Construct a lists column containing a single list of fixed-width - * type from an initializer list of values and a validity iterator. + * @brief Construct a lists column containing a single list from an initializer + * list of values and a validity iterator. * * Example: * @code{.cpp} - * // Creates a LIST column with 1 lists composed of 2 total integers + * // Creates a LIST column with 1 list composed of 2 total integers * auto validity = make_counting_transform_iterator(0, [](auto i){return i%2;}); * // [{0, NULL}] * lists_column_wrapper l{{0, 1}, validity}; @@ -1557,28 +1736,25 @@ class lists_column_wrapper : public detail::column_wrapper { * @param stream CUDA stream used for device memory operations * @param mr Memory resources used to allocate the returned column */ - template ()>* = nullptr> + template lists_column_wrapper(std::initializer_list elements, ValidityIterator v, rmm::cuda_stream_view stream = cudf::test::get_default_stream(), cudf::memory_resources mr = cudf::get_current_device_resource_ref()) + requires(cudf::is_fixed_width()) : column_wrapper{} { build_from_non_nested( - cudf::test::fixed_width_column_wrapper(elements, v, stream, mr).release(), - stream, - mr); + fixed_width_column_wrapper(elements, v, stream, mr).release(), stream, mr); } /** - * @brief Construct a lists column containing a single list of fixed-width - * type from an iterator range and a validity iterator. + * @brief Construct a lists column containing a single list from an iterator + * range and a validity iterator. * * Example: * @code{.cpp} - * // Creates a LIST column with 1 lists composed of 5 total integers + * // Creates a LIST column with 1 list composed of 5 total integers * auto elements = make_counting_transform_iterator(0, [](auto i){return i*2;}); * auto validity = make_counting_transform_iterator(0, [](auto i){return i%2;}); * // [{0, NULL, 2, NULL, 4}] @@ -1591,10 +1767,7 @@ class lists_column_wrapper : public detail::column_wrapper { * @param stream CUDA stream used for device memory operations * @param mr Memory resources used to allocate the returned column */ - template ()>* = nullptr> + template lists_column_wrapper(InputIterator begin, InputIterator end, ValidityIterator v, @@ -1602,71 +1775,58 @@ class lists_column_wrapper : public detail::column_wrapper { cudf::memory_resources mr = cudf::get_current_device_resource_ref()) : column_wrapper{} { - build_from_non_nested( - cudf::test::fixed_width_column_wrapper(begin, end, v, stream, mr) - .release(), - stream, - mr); + build_from_non_nested(leaf_wrapper_t(begin, end, v, stream, mr).release(), stream, mr); } /** - * @brief Construct a lists column containing a single list of strings - * from an initializer list of values. + * @brief Construct a lists column containing a single list of strings. * * Example: * @code{.cpp} * // Creates a LIST column with 1 list composed of 2 total strings * // [{"abc", "def"}] - * lists_column_wrapper l{"abc", "def"}; + * lists_column_wrapper s{"abc", "def"}; * @endcode * - * @param elements The list of elements + * @param elements The list of strings * @param stream CUDA stream used for device memory operations * @param mr Memory resources used to allocate the returned column */ - template >* = nullptr> + template lists_column_wrapper(std::initializer_list elements, rmm::cuda_stream_view stream = cudf::test::get_default_stream(), cudf::memory_resources mr = cudf::get_current_device_resource_ref()) + requires(std::is_same_v) : column_wrapper{} { - build_from_non_nested( - cudf::test::strings_column_wrapper(elements.begin(), elements.end(), stream, mr).release(), - stream, - mr); + build_from_non_nested(strings_column_wrapper(elements, stream, mr).release(), stream, mr); } /** - * @brief Construct a lists column containing a single list of strings - * from an initializer list of values and a validity iterator. + * @brief Construct a lists column containing a single list of strings and a + * validity iterator. * * Example: * @code{.cpp} - * // Creates a LIST column with 1 list composed of 2 total strings * auto validity = make_counting_transform_iterator(0, [](auto i){return i%2;}); * // [{"abc", NULL}] - * lists_column_wrapper l{{"abc", "def"}, validity}; + * lists_column_wrapper l{{"abc", "def"}, validity}; * @endcode * - * @param elements The list of elements + * @param elements The list of strings * @param v The validity iterator * @param stream CUDA stream used for device memory operations * @param mr Memory resources used to allocate the returned column */ - template >* = nullptr> + template lists_column_wrapper(std::initializer_list elements, ValidityIterator v, rmm::cuda_stream_view stream = cudf::test::get_default_stream(), cudf::memory_resources mr = cudf::get_current_device_resource_ref()) + requires(std::is_same_v) : column_wrapper{} { - build_from_non_nested( - cudf::test::strings_column_wrapper(elements.begin(), elements.end(), v, stream, mr).release(), - stream, - mr); + build_from_non_nested(strings_column_wrapper(elements, v, stream, mr).release(), stream, mr); } /** @@ -1688,6 +1848,11 @@ class lists_column_wrapper : public detail::column_wrapper { * lists_column_wrapper l{ {{0, 1}, {2, 3}}, {{4, 5}, {6, 7}} }; * @endcode * + * For multi-row (and deeper) columns that should allocate with an explicit stream/mr, use + * `lists_column_initializer` so every nesting level receives those arguments: + * `using Init = cudf::test::lists_column_initializer;` + * `lists_column_wrapper l{Init{{{0, 1}, {2, 3}, {4, 5}}}, stream, mr};` + * * @param elements The list of elements * @param stream CUDA stream used for device memory operations * @param mr Memory resources used to allocate the returned column @@ -1763,6 +1928,46 @@ class lists_column_wrapper : public detail::column_wrapper { build_from_nested(elements, validity, stream, mr); } + /** + * @brief Construct a lists column from a recursive `lists_column_initializer` tree. + * + * Every nesting level is allocated with the provided `stream` and `mr`. Prefer this over + * brace-nested `lists_column_wrapper` constructions that pass resources only at the outer + * level. + * + * Example: + * @code{.cpp} + * using Init = cudf::test::lists_column_initializer; + * // List: [{0, 1}, {2, 3}, {4, 5}] + * lists_column_wrapper l{Init{{{0, 1}, {2, 3}, {4, 5}}}, stream, mr}; + * + * // List>: [{{0, 1}, {2}}, {{3}}] + * lists_column_wrapper nested{Init{{{{0, 1}, {2}}, {{3}}}}, stream, mr}; + * @endcode + * + * @param init Host-side nested values (and optional validity) + * @param stream CUDA stream used for device memory operations + * @param mr Memory resources used to allocate the returned column + */ + lists_column_wrapper(lists_column_initializer init, + rmm::cuda_stream_view stream, + cudf::memory_resources mr) + : column_wrapper{} + { + if (!init.nested()) { + if (init.value_validity().empty()) { + *this = lists_column_wrapper(init.values().begin(), init.values().end(), stream, mr); + } else { + *this = lists_column_wrapper( + init.values().begin(), init.values().end(), init.value_validity().begin(), stream, mr); + } + return; + } + + auto [children, validity] = init.template build(stream, mr); + build_from_nested(children, validity, stream, mr); + } + /** * @brief Construct a list column containing a single empty, optionally null row. * @@ -1826,7 +2031,8 @@ class lists_column_wrapper : public detail::column_wrapper { * @param mr Memory resources used to allocate the returned column * */ - void build_from_nested(std::initializer_list> elements, + template + void build_from_nested(ListsRange const& elements, std::vector const& v, rmm::cuda_stream_view stream, cudf::memory_resources mr) @@ -1986,8 +2192,9 @@ class lists_column_wrapper : public detail::column_wrapper { cudf::copy_bitmask(col, stream, temp_mr)); } + template std::pair, std::vector>> preprocess_columns( - std::initializer_list> const& elements, + ListsRange const& elements, column_view& expected_hierarchy, int expected_depth, rmm::cuda_stream_view stream, diff --git a/cpp/tests/copying/gather_list_tests.cpp b/cpp/tests/copying/gather_list_tests.cpp index a07747a9403..2f8ad7db254 100644 --- a/cpp/tests/copying/gather_list_tests.cpp +++ b/cpp/tests/copying/gather_list_tests.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -17,7 +17,7 @@ #include template -class GatherTestListTyped : public cudf::test::BaseFixture {}; +class GatherTestListTyped : public cudf::test::BaseFixtureWithHarness {}; using FixedWidthTypesNotBool = cudf::test::Concat; TYPED_TEST_SUITE(GatherTestListTyped, FixedWidthTypesNotBool); -class GatherTestList : public cudf::test::BaseFixture {}; +class GatherTestList : public cudf::test::BaseFixtureWithHarness {}; // to disambiguate between {} == 0 and {} == List{0} // Also, see note about compiler issues when declaring nested @@ -33,46 +33,62 @@ class GatherTestList : public cudf::test::BaseFixture {}; template using LCW = cudf::test::lists_column_wrapper; +// Nested list values. Passing these to lists_column_wrapper builds every nesting level with the +// explicit stream and memory resources instead of the current device resource. +using Init = cudf::test::lists_column_initializer; + TYPED_TEST(GatherTestListTyped, Gather) { using T = TypeParam; + auto const st = this->stream(); + auto const mr = this->resources(); + // List - LCW list{{1, 2, 3, 4}, {5}, {6, 7}, {8, 9, 10}}; - cudf::test::fixed_width_column_wrapper gather_map{0, 2}; + LCW list{Init{{1, 2, 3, 4}, {5}, {6, 7}, {8, 9, 10}}, st, mr}; + cudf::test::fixed_width_column_wrapper gather_map{{0, 2}, st, mr}; cudf::table_view source_table({list}); - auto results = cudf::gather(source_table, gather_map); + auto results = cudf::gather( + source_table, gather_map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); - LCW expected{{1, 2, 3, 4}, {6, 7}}; + LCW expected{Init{{1, 2, 3, 4}, {6, 7}}, st, mr}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view().column(0), expected); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view().column(0), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } TYPED_TEST(GatherTestListTyped, GatherNothing) { using T = TypeParam; + auto const st = this->stream(); + auto const mr = this->resources(); + // List { - LCW list{{1, 2, 3, 4}, {5}, {6, 7}, {8, 9, 10}}; + LCW list{Init{{1, 2, 3, 4}, {5}, {6, 7}, {8, 9, 10}}, st, mr}; cudf::test::fixed_width_column_wrapper gather_map{}; cudf::table_view source_table({list}); - auto results = cudf::gather(source_table, gather_map); + auto results = cudf::gather( + source_table, gather_map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); LCW expected; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view().column(0), expected); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view().column(0), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } // List { - cudf::test::lists_column_wrapper list{{{{1, 2, 3, 4}, {5}}}, {{{6, 7}, {8, 9, 10}}}}; + cudf::test::lists_column_wrapper list{ + Init{{{{1, 2, 3, 4}, {5}}}, {{{6, 7}, {8, 9, 10}}}}, st, mr}; cudf::test::fixed_width_column_wrapper gather_map{}; cudf::table_view source_table({list}); - auto result = cudf::gather(source_table, gather_map); + auto result = cudf::gather( + source_table, gather_map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); // the result should preserve the full List>> hierarchy // even though it is empty past the first level @@ -94,58 +110,77 @@ TYPED_TEST(GatherTestListTyped, GatherNulls) { using T = TypeParam; + auto const st = this->stream(); + auto const mr = this->resources(); + auto valids = cudf::test::iterators::valids_at_multiples_of(2); // List - LCW list{{{1, 2, 3, 4}, valids}, {5}, {{6, 7}, valids}, {{8, 9, 10}, valids}}; - cudf::test::fixed_width_column_wrapper gather_map{0, 2}; + LCW list{Init{{{1, 2, 3, 4}, valids}, {5}, {{6, 7}, valids}, {{8, 9, 10}, valids}}, st, mr}; + cudf::test::fixed_width_column_wrapper gather_map{{0, 2}, st, mr}; cudf::table_view source_table({list}); - auto results = cudf::gather(source_table, gather_map); + auto results = cudf::gather( + source_table, gather_map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); - LCW expected{{{1, 2, 3, 4}, valids}, {{6, 7}, valids}}; + LCW expected{Init{{{1, 2, 3, 4}, valids}, {{6, 7}, valids}}, st, mr}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view().column(0), expected); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view().column(0), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } TYPED_TEST(GatherTestListTyped, GatherNested) { using T = TypeParam; + auto const st = this->stream(); + auto const mr = this->resources(); + // List> { - LCW list{{{2, 3}, {4, 5}}, - {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, - {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}; - cudf::test::fixed_width_column_wrapper gather_map{0, 2}; + LCW list{Init{{{2, 3}, {4, 5}}, + {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, + {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, + st, + mr}; + cudf::test::fixed_width_column_wrapper gather_map{{0, 2}, st, mr}; cudf::table_view source_table({list}); - auto results = cudf::gather(source_table, gather_map); + auto results = cudf::gather( + source_table, gather_map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); - LCW expected{{{2, 3}, {4, 5}}, {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}; + LCW expected{ + Init{{{2, 3}, {4, 5}}, {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, st, mr}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view().column(0), expected); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view().column(0), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } // List>> { - LCW list{{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, - {{{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, - {{LCW{0}}}, - {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, - {{0, 1, 3}, {5}}, - {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, - {{{10, 20}}, {LCW{30}}, {{40, 50}, {60, 70, 80}}}}; - cudf::test::fixed_width_column_wrapper gather_map{1, 2, 4}; + LCW list{Init{{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, + {{{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, + {{Init{0}}}, + {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, + {{0, 1, 3}, {5}}, + {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, + {{{10, 20}}, {Init{30}}, {{40, 50}, {60, 70, 80}}}}, + st, + mr}; + cudf::test::fixed_width_column_wrapper gather_map{{1, 2, 4}, st, mr}; cudf::table_view source_table({list}); - auto results = cudf::gather(source_table, gather_map); + auto results = cudf::gather( + source_table, gather_map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); - LCW expected{{{{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, - {{LCW{0}}}, - {{{10, 20}}, {LCW{30}}, {{40, 50}, {60, 70, 80}}}}; + LCW expected{Init{{{{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, + {{Init{0}}}, + {{{10, 20}}, {Init{30}}, {{40, 50}, {60, 70, 80}}}}, + st, + mr}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view().column(0), expected); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view().column(0), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } } @@ -153,21 +188,30 @@ TYPED_TEST(GatherTestListTyped, GatherOutOfOrder) { using T = TypeParam; + auto const st = this->stream(); + auto const mr = this->resources(); + // List> { - LCW list{{{2, 3}, {4, 5}}, - {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, - {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}; - cudf::test::fixed_width_column_wrapper gather_map{1, 2, 0}; + LCW list{Init{{{2, 3}, {4, 5}}, + {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, + {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, + st, + mr}; + cudf::test::fixed_width_column_wrapper gather_map{{1, 2, 0}, st, mr}; cudf::table_view source_table({list}); - auto results = cudf::gather(source_table, gather_map); + auto results = cudf::gather( + source_table, gather_map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); - LCW expected{{{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, - {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}, - {{2, 3}, {4, 5}}}; + LCW expected{Init{{{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, + {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}, + {{2, 3}, {4, 5}}}, + st, + mr}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view().column(0), expected); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view().column(0), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } } @@ -175,48 +219,64 @@ TYPED_TEST(GatherTestListTyped, GatherNestedNulls) { using T = TypeParam; + auto const st = this->stream(); + auto const mr = this->resources(); + auto valids = cudf::test::iterators::valids_at_multiples_of(2); // List> { - LCW list{{{{2, 3}, valids}, {4, 5}}, - {{{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, valids}, - {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}, - {{{{25, 26}, valids}, {27, 28}, {{29, 30}, valids}, {31, 32}, {33, 34}}, valids}}; + LCW list{ + Init{{{{2, 3}, valids}, {4, 5}}, + {{{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, valids}, + {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}, + {{{{25, 26}, valids}, {27, 28}, {{29, 30}, valids}, {31, 32}, {33, 34}}, valids}}, + st, + mr}; - cudf::test::fixed_width_column_wrapper gather_map{0, 1, 3}; + cudf::test::fixed_width_column_wrapper gather_map{{0, 1, 3}, st, mr}; cudf::table_view source_table({list}); - auto results = cudf::gather(source_table, gather_map); + auto results = cudf::gather( + source_table, gather_map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); LCW expected{ - {{{2, 3}, valids}, {4, 5}}, - {{{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, valids}, - {{{{25, 26}, valids}, {27, 28}, {{29, 30}, valids}, {31, 32}, {33, 34}}, valids}}; - - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view().column(0), expected); + Init{{{{2, 3}, valids}, {4, 5}}, + {{{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, valids}, + {{{{25, 26}, valids}, {27, 28}, {{29, 30}, valids}, {31, 32}, {33, 34}}, valids}}, + st, + mr}; + + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view().column(0), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } // List>> { - LCW list{{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, - {{{15, 16}, {{27, 28}, valids}, {{37, 38}, valids}, {47, 48}, {57, 58}}}, - {{LCW{0}}}, - {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, - {{0, 1, 3}, {5}}, - {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, - {{{{{10, 20}, valids}}, {LCW{30}}, {{40, 50}, {60, 70, 80}}}, valids}}; - - cudf::test::fixed_width_column_wrapper gather_map{1, 2, 4}; + LCW list{Init{{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, + {{{15, 16}, {{27, 28}, valids}, {{37, 38}, valids}, {47, 48}, {57, 58}}}, + {{Init{0}}}, + {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, + {{0, 1, 3}, {5}}, + {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, + {{{{{10, 20}, valids}}, {Init{30}}, {{40, 50}, {60, 70, 80}}}, valids}}, + st, + mr}; + + cudf::test::fixed_width_column_wrapper gather_map{{1, 2, 4}, st, mr}; cudf::table_view source_table({list}); - auto results = cudf::gather(source_table, gather_map); + auto results = cudf::gather( + source_table, gather_map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); - LCW expected{{{{15, 16}, {{27, 28}, valids}, {{37, 38}, valids}, {47, 48}, {57, 58}}}, - {{LCW{0}}}, - {{{{{10, 20}, valids}}, {LCW{30}}, {{40, 50}, {60, 70, 80}}}, valids}}; + LCW expected{Init{{{{15, 16}, {{27, 28}, valids}, {{37, 38}, valids}, {47, 48}, {57, 58}}}, + {{Init{0}}}, + {{{{{10, 20}, valids}}, {Init{30}}, {{40, 50}, {60, 70, 80}}}, valids}}, + st, + mr}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view().column(0), expected); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view().column(0), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } } @@ -224,55 +284,70 @@ TYPED_TEST(GatherTestListTyped, GatherNestedWithEmpties) { using T = TypeParam; - LCW list{{{2, 3}, LCW{}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, {LCW{}}}; - cudf::test::fixed_width_column_wrapper gather_map{0, 2}; + auto const st = this->stream(); + auto const mr = this->resources(); + + LCW list{Init{{{2, 3}, Init{}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, {Init{}}}, st, mr}; + cudf::test::fixed_width_column_wrapper gather_map{{0, 2}, st, mr}; cudf::table_view source_table({list}); - auto results = cudf::gather(source_table, gather_map); + auto results = cudf::gather( + source_table, gather_map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); - LCW expected{{{2, 3}, LCW{}}, {LCW{}}}; + LCW expected{Init{{{2, 3}, Init{}}, {Init{}}}, st, mr}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view().column(0), expected); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view().column(0), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } TYPED_TEST(GatherTestListTyped, GatherDetailInvalidIndex) { using T = TypeParam; + auto const st = this->stream(); + auto const mr = this->resources(); + // List> { - LCW list{{{2, 3}, {4, 5}}, - {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, - {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}; - cudf::test::fixed_width_column_wrapper gather_map{0, 15, 16, 2}; + LCW list{Init{{{2, 3}, {4, 5}}, + {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, + {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, + st, + mr}; + cudf::test::fixed_width_column_wrapper gather_map{{0, 15, 16, 2}, st, mr}; cudf::table_view source_table({list}); - auto results = cudf::gather(source_table, gather_map, cudf::out_of_bounds_policy::NULLIFY); + auto results = cudf::gather( + source_table, gather_map, cudf::out_of_bounds_policy::NULLIFY, st, mr.get_output_mr()); std::vector expected_validity{1, 0, 0, 1}; - LCW expected{{{{2, 3}, {4, 5}}, - {LCW{}}, - {LCW{}}, - {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, - expected_validity.begin()}; - - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view().column(0), expected); + LCW expected{ + Init{ + {{{2, 3}, {4, 5}}, {Init{}}, {Init{}}, {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, + expected_validity.begin()}, + st, + mr}; + + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view().column(0), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } } TEST_F(GatherTestList, GatherIncompleteHierarchies) { - using LCW = cudf::test::lists_column_wrapper; + auto const st = this->stream(); + auto const mr = this->resources(); { // List, but rows 1 and 2 are empty at the very top. // We expect to get back a "full" hierarchy of type List> anyway. - cudf::test::lists_column_wrapper list{{{{1, 2}}}, LCW{}, LCW{}}; + cudf::test::lists_column_wrapper list{Init{{{{1, 2}}}, Init{}, Init{}}, st, mr}; cudf::table_view source_table({list}); - cudf::test::fixed_width_column_wrapper row1_map{1}; - auto result = cudf::gather(source_table, row1_map); + cudf::test::fixed_width_column_wrapper row1_map{{1}, st, mr}; + auto result = cudf::gather( + source_table, row1_map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); // the result should preserve the full List>> hierarchy // even though it is empty past the first level @@ -292,12 +367,13 @@ TEST_F(GatherTestList, GatherIncompleteHierarchies) { // List, gathering nothing. // We expect to get back a "full" hierarchy of type List> anyway. - cudf::test::lists_column_wrapper list{{{{1, 2}}}, LCW{}}; + cudf::test::lists_column_wrapper list{Init{{{{1, 2}}}, Init{}}, st, mr}; cudf::table_view source_table({list}); cudf::test::fixed_width_column_wrapper empty_map{}; - auto result = cudf::gather(source_table, empty_map); + auto result = cudf::gather( + source_table, empty_map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); // the result should preserve the full List>> hierarchy // even though it is empty past the first level @@ -318,34 +394,56 @@ TEST_F(GatherTestList, GatherIncompleteHierarchies) TYPED_TEST(GatherTestListTyped, GatherSliced) { using T = TypeParam; + + auto const st = this->stream(); + auto const mr = this->resources(); + { - LCW a{ - {{1, 1, 1}, {2, 2}, {3, 3}}, - {{4, 4, 4}, {5, 5}, {6, 6}}, - {{7, 7, 7}, {8, 8}, {9, 9}}, - {{10, 10, 10}, {11, 11}, {12, 12}}, - {{20, 20, 20, 20}, {25}}, - {{30, 30, 30, 30}, {40}}, - {{50, 50, 50, 50}, {6, 13}}, - {{70, 70, 70, 70}, {80}}, - }; - auto split_a = cudf::split(a, {3}); + LCW a{Init{ + {{1, 1, 1}, {2, 2}, {3, 3}}, + {{4, 4, 4}, {5, 5}, {6, 6}}, + {{7, 7, 7}, {8, 8}, {9, 9}}, + {{10, 10, 10}, {11, 11}, {12, 12}}, + {{20, 20, 20, 20}, {25}}, + {{30, 30, 30, 30}, {40}}, + {{50, 50, 50, 50}, {6, 13}}, + {{70, 70, 70, 70}, {80}}, + }, + st, + mr}; + auto split_a = cudf::split(a, {3}, st); cudf::table_view tbl0({split_a[0]}); cudf::table_view tbl1({split_a[1]}); - auto result0 = cudf::gather(tbl0, cudf::test::fixed_width_column_wrapper{1, 2}); - LCW expected0{ - {{4, 4, 4}, {5, 5}, {6, 6}}, - {{7, 7, 7}, {8, 8}, {9, 9}}, - }; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(expected0, result0->get_column(0).view()); - - auto result1 = cudf::gather(tbl1, cudf::test::fixed_width_column_wrapper{0, 3}); - LCW expected1{ - {{10, 10, 10}, {11, 11}, {12, 12}}, - {{50, 50, 50, 50}, {6, 13}}, - }; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(expected1, result1->get_column(0).view()); + cudf::test::fixed_width_column_wrapper map0{{1, 2}, st, mr}; + auto result0 = + cudf::gather(tbl0, map0, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); + LCW expected0{Init{ + {{4, 4, 4}, {5, 5}, {6, 6}}, + {{7, 7, 7}, {8, 8}, {9, 9}}, + }, + st, + mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL(expected0, + result0->get_column(0).view(), + cudf::test::debug_output_level::FIRST_ERROR, + st, + mr); + + cudf::test::fixed_width_column_wrapper map1{{0, 3}, st, mr}; + auto result1 = + cudf::gather(tbl1, map1, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); + LCW expected1{Init{ + {{10, 10, 10}, {11, 11}, {12, 12}}, + {{50, 50, 50, 50}, {6, 13}}, + }, + st, + mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL(expected1, + result1->get_column(0).view(), + cudf::test::debug_output_level::FIRST_ERROR, + st, + mr); } auto valids = cudf::test::iterators::valids_at_multiples_of(2); @@ -353,70 +451,96 @@ TYPED_TEST(GatherTestListTyped, GatherSliced) // List>> { LCW list{ - // slice 0 - {{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, + Init{// slice 0 + {{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, - {{{15, 16}, {{27, 28}, valids}, {{37, 38}, valids}, {47, 48}, {57, 58}}, - {{11, 12}, {{42, 43, 44}, valids}, {{77, 78}, valids}}}, + {{{15, 16}, {{27, 28}, valids}, {{37, 38}, valids}, {47, 48}, {57, 58}}, + {{11, 12}, {{42, 43, 44}, valids}, {{77, 78}, valids}}}, - // slice 1 - {{LCW{0}}}, - {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, - {{0, 1, 3}, {5}}, - {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, - {{{{1, 6}, {60, 70, 80, 100}}, {{10, 11, 13}, {15}}, {{11, 12, 13, 14, 15}}}, valids}, + // slice 1 + {{Init{0}}}, + {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, + {{0, 1, 3}, {5}}, + {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, + {{{{1, 6}, {60, 70, 80, 100}}, {{10, 11, 13}, {15}}, {{11, 12, 13, 14, 15}}}, valids}, - // slice 2 - {{{{{10, 20}, valids}}, {LCW{30}}, {{40, 50}, {60, 70, 80}}}, valids}, - {{{{10, 20, 30}}, {LCW{30}}, {{{20, 30}, valids}, {62, 72, 82}}}, valids}}; + // slice 2 + {{{{{10, 20}, valids}}, {Init{30}}, {{40, 50}, {60, 70, 80}}}, valids}, + {{{{10, 20, 30}}, {Init{30}}, {{{20, 30}, valids}, {62, 72, 82}}}, valids}}, + st, + mr}; - auto sliced = cudf::slice(list, {0, 1, 2, 5, 5, 7}); + auto sliced = cudf::slice(list, {0, 1, 2, 5, 5, 7}, st); // gather from slice 0 { cudf::table_view tbl({sliced[0]}); - cudf::test::fixed_width_column_wrapper map{0}; - auto result = cudf::gather(tbl, map); - LCW expected{{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}}; - CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(expected, result->get_column(0).view()); + cudf::test::fixed_width_column_wrapper map{{0}, st, mr}; + auto result = + cudf::gather(tbl, map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); + LCW expected{Init{{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(expected, + result->get_column(0).view(), + cudf::test::debug_output_level::FIRST_ERROR, + cudf::test::default_ulp, + st, + mr); } // gather from slice 1 { cudf::table_view tbl({sliced[1]}); - cudf::test::fixed_width_column_wrapper map{1, 2, 0, 1}; - auto result = cudf::gather(tbl, map); + cudf::test::fixed_width_column_wrapper map{{1, 2, 0, 1}, st, mr}; + auto result = + cudf::gather(tbl, map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); LCW expected{ - {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, - {{0, 1, 3}, {5}}, - {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, - - {{{{1, 6}, {60, 70, 80, 100}}, {{10, 11, 13}, {15}}, {{11, 12, 13, 14, 15}}}, valids}, - - {{LCW{0}}}, - - {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, - {{0, 1, 3}, {5}}, - {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, - }; - CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(expected, result->get_column(0).view()); + Init{ + {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, + {{0, 1, 3}, {5}}, + {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, + + {{{{1, 6}, {60, 70, 80, 100}}, {{10, 11, 13}, {15}}, {{11, 12, 13, 14, 15}}}, valids}, + + {{Init{0}}}, + + {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, + {{0, 1, 3}, {5}}, + {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, + }, + st, + mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(expected, + result->get_column(0).view(), + cudf::test::debug_output_level::FIRST_ERROR, + cudf::test::default_ulp, + st, + mr); } // gather from slice 2 { cudf::table_view tbl({sliced[2]}); - cudf::test::fixed_width_column_wrapper map{1, 0, 0, 1, 1, 0}; - auto result = cudf::gather(tbl, map); - LCW expected{{{{{10, 20, 30}}, {LCW{30}}, {{{20, 30}, valids}, {62, 72, 82}}}, valids}, - {{{{{10, 20}, valids}}, {LCW{30}}, {{40, 50}, {60, 70, 80}}}, valids}, - {{{{{10, 20}, valids}}, {LCW{30}}, {{40, 50}, {60, 70, 80}}}, valids}, - {{{{10, 20, 30}}, {LCW{30}}, {{{20, 30}, valids}, {62, 72, 82}}}, valids}, - {{{{10, 20, 30}}, {LCW{30}}, {{{20, 30}, valids}, {62, 72, 82}}}, valids}, - {{{{{10, 20}, valids}}, {LCW{30}}, {{40, 50}, {60, 70, 80}}}, valids}}; - CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(expected, result->get_column(0).view()); + cudf::test::fixed_width_column_wrapper map{{1, 0, 0, 1, 1, 0}, st, mr}; + auto result = + cudf::gather(tbl, map, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); + LCW expected{ + Init{{{{{10, 20, 30}}, {Init{30}}, {{{20, 30}, valids}, {62, 72, 82}}}, valids}, + {{{{{10, 20}, valids}}, {Init{30}}, {{40, 50}, {60, 70, 80}}}, valids}, + {{{{{10, 20}, valids}}, {Init{30}}, {{40, 50}, {60, 70, 80}}}, valids}, + {{{{10, 20, 30}}, {Init{30}}, {{{20, 30}, valids}, {62, 72, 82}}}, valids}, + {{{{10, 20, 30}}, {Init{30}}, {{{20, 30}, valids}, {62, 72, 82}}}, valids}, + {{{{{10, 20}, valids}}, {Init{30}}, {{40, 50}, {60, 70, 80}}}, valids}}, + st, + mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(expected, + result->get_column(0).view(), + cudf::test::debug_output_level::FIRST_ERROR, + cudf::test::default_ulp, + st, + mr); } } } diff --git a/cpp/tests/copying/segmented_gather_list_tests.cpp b/cpp/tests/copying/segmented_gather_list_tests.cpp index 1827275b328..472c82adfb3 100644 --- a/cpp/tests/copying/segmented_gather_list_tests.cpp +++ b/cpp/tests/copying/segmented_gather_list_tests.cpp @@ -1,7 +1,15 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ + +// Nested `LCW{LCW...` constructions (including empty-list columns with explicit stream/mr) +// trip gcc14's -Wmaybe-uninitialized on column_view_base's copy constructor. Same diagnostic +// as lists/extract_tests.cpp; ignore for the whole file because the warning fires in headers. +#if defined(__GNUC__) && (__GNUC__ >= 14) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif #include #include #include @@ -17,7 +25,7 @@ #include template -class SegmentedGatherTest : public cudf::test::BaseFixture {}; +class SegmentedGatherTest : public cudf::test::BaseFixtureWithHarness {}; using FixedWidthTypesNotBool = cudf::test::Concat; using namespace cudf::test::iterators; auto constexpr NULLIFY = cudf::out_of_bounds_policy::NULLIFY; +// Nested list values. Passing these to lists_column_wrapper builds every nesting level with the +// explicit stream and memory resources instead of the current device resource. +using Init = cudf::test::lists_column_initializer; +using I8Init = cudf::test::lists_column_initializer; +using StrInit = cudf::test::lists_column_initializer; + TYPED_TEST(SegmentedGatherTest, Gather) { using T = TypeParam; + auto const st = this->stream(); + auto const mr = this->resources(); + // List - LCW list{{1, 2, 3, 4}, {5}, {6, 7}, {8, 9, 10}}; + LCW list{Init{{1, 2, 3, 4}, {5}, {6, 7}, {8, 9, 10}}, st, mr}; { // Straight-line case. - auto const gather_map = LCW{{3, 2, 1, 0}, {0}, {0, 1}, {0, 2, 1}}; - auto const expected = LCW{{4, 3, 2, 1}, {5}, {6, 7}, {8, 10, 9}}; + auto const gather_map = LCW{Init{{3, 2, 1, 0}, {0}, {0, 1}, {0, 2, 1}}, st, mr}; + auto const expected = LCW{Init{{4, 3, 2, 1}, {5}, {6, 7}, {8, 10, 9}}, st, mr}; auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, - cudf::lists_column_view{gather_map}); - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + cudf::lists_column_view{gather_map}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } { // Nullify out-of-bounds values. - auto const gather_map = LCW{{3, 2, 4, 0}, {0}, {0, -3}, {0, 2, 1}}; - auto const expected = LCW{{{4, 3, 2, 1}, null_at(2)}, {5}, {{6, 7}, null_at(1)}, {8, 10, 9}}; - auto const results = cudf::lists::segmented_gather( - cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, NULLIFY); - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + auto const gather_map = LCW{Init{{3, 2, 4, 0}, {0}, {0, -3}, {0, 2, 1}}, st, mr}; + auto const expected = + LCW{Init{{{4, 3, 2, 1}, null_at(2)}, {5}, {{6, 7}, null_at(1)}, {8, 10, 9}}, st, mr}; + auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, + cudf::lists_column_view{gather_map}, + NULLIFY, + st, + mr.get_output_mr()); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } } @@ -62,37 +88,52 @@ TYPED_TEST(SegmentedGatherTest, GatherNothing) { using T = TypeParam; + auto const st = this->stream(); + auto const mr = this->resources(); + // List { - auto const list = LCW{{1, 2, 3, 4}, {5}, {6, 7}, {8, 9, 10}}; + auto const list = LCW{Init{{1, 2, 3, 4}, {5}, {6, 7}, {8, 9, 10}}, st, mr}; auto const gather_map = LCW{LCW{}, LCW{}, LCW{}, LCW{}}; auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, - cudf::lists_column_view{gather_map}); + cudf::lists_column_view{gather_map}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); auto const expected = LCW{LCW{}, LCW{}, LCW{}, LCW{}}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + *results, expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } // List> { - auto const list = LCW{{{1, 2, 3, 4}, {5}}, {{6, 7}}, {{}, {8, 9, 10}}}; + auto const list = LCW{Init{{{1, 2, 3, 4}, {5}}, {{6, 7}}, {Init{}, {8, 9, 10}}}, st, mr}; auto const gather_map = LCW{LCW{}, LCW{}, LCW{}}; auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, - cudf::lists_column_view{gather_map}); + cudf::lists_column_view{gather_map}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); // hack to get column of empty list of list - auto const expected_dummy = LCW{{{1, 2, 3, 4}, {5}}, LCW{}, LCW{}, LCW{}}; - auto const expected = cudf::split(expected_dummy, {1})[1]; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected); + auto const expected_dummy = LCW{Init{{{1, 2, 3, 4}, {5}}, Init{}, Init{}, Init{}}, st, mr}; + auto const expected = cudf::split(expected_dummy, {1}, st)[1]; + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + *results, expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } // List>> { - auto const list = LCW{{{{1, 2, 3, 4}, {5}}}, {{{6, 7}, {8, 9, 10}}}}; + auto const list = LCW{Init{{{{1, 2, 3, 4}, {5}}}, {{{6, 7}, {8, 9, 10}}}}, st, mr}; auto const gather_map = LCW{LCW{}, LCW{}}; auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, - cudf::lists_column_view{gather_map}); + cudf::lists_column_view{gather_map}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); // hack to get column of empty list of list of list - auto const expected_dummy = LCW{{{{1, 2, 3, 4}}}, LCW{}, LCW{}}; - auto const expected = cudf::split(expected_dummy, {1})[1]; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected); + auto const expected_dummy = LCW{Init{{{{1, 2, 3, 4}}}, Init{}, Init{}}, st, mr}; + auto const expected = cudf::split(expected_dummy, {1}, st)[1]; + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + *results, expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); // the result should preserve the full List>> hierarchy // even though it is empty past the first level @@ -113,40 +154,61 @@ TYPED_TEST(SegmentedGatherTest, GatherNothing) using SegmentedGatherTestSingle = SegmentedGatherTest; TEST_F(SegmentedGatherTestSingle, GatherEmpty) { + auto const st = this->stream(); + auto const mr = this->resources(); + auto const list = LCW{}; auto const gather_map = LCW{}; auto const expected = LCW{}; auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, - cudf::lists_column_view{gather_map}); - CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected); + cudf::lists_column_view{gather_map}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + *results, expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } TYPED_TEST(SegmentedGatherTest, GatherNulls) { using T = TypeParam; + auto const st = this->stream(); + auto const mr = this->resources(); + auto valids = cudf::test::iterators::valids_at_multiples_of(2); // List - auto const list = LCW{{{1, 2, 3, 4}, valids}, {5}, {{6, 7}, valids}, {{8, 9, 10}, valids}}; + auto const list = + LCW{Init{{{1, 2, 3, 4}, valids}, {5}, {{6, 7}, valids}, {{8, 9, 10}, valids}}, st, mr}; { // Test gathering on lists that contain nulls. - auto const gather_map = LCW{{0, 1}, LCW{}, {1}, {2, 1, 0}}; + auto const gather_map = LCW{Init{{0, 1}, Init{}, {1}, {2, 1, 0}}, st, mr}; auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, - cudf::lists_column_view{gather_map}); + cudf::lists_column_view{gather_map}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); auto const expected = - LCW{{{1, 2}, valids}, LCW{}, {{7}, valids + 1}, {{10, 9, 8}, valids}}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + LCW{Init{{{1, 2}, valids}, Init{}, {{7}, valids + 1}, {{10, 9, 8}, valids}}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } { // Test gathering on lists that contain nulls, with out-of-bounds indices. - auto const gather_map = LCW{{10, -10}, LCW{}, {1}, {2, -10, 0}}; - auto const results = cudf::lists::segmented_gather( - cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, NULLIFY); + auto const gather_map = LCW{Init{{10, -10}, Init{}, {1}, {2, -10, 0}}, st, mr}; + auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, + cudf::lists_column_view{gather_map}, + NULLIFY, + st, + mr.get_output_mr()); auto const expected = - LCW{{{0, 0}, nulls_at({0, 1})}, LCW{}, {{7}, valids + 1}, {{10, 0, 8}, null_at(1)}}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + LCW{Init{{{0, 0}, nulls_at({0, 1})}, Init{}, {{7}, valids + 1}, {{10, 0, 8}, null_at(1)}}, + st, + mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } } @@ -154,78 +216,88 @@ TYPED_TEST(SegmentedGatherTest, GatherNested) { using T = TypeParam; + auto const st = this->stream(); + auto const mr = this->resources(); + // List> { // clang-format off - auto const list = LCW{{{2, 3}, {4, 5}}, + auto const list = LCW{Init{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, - {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {-17, -18}}}; - auto const gather_map = LCW{{0, -2, -2}, {1}, {1, 0, -1, -5}}; - auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}); - auto const expected = LCW{{{2, 3}, {2, 3}, {2, 3}}, + {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {-17, -18}}}, st, mr}; + auto const gather_map = LCW{Init{{0, -2, -2}, {1}, {1, 0, -1, -5}}, st, mr}; + auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); + auto const expected = LCW{Init{{{2, 3}, {2, 3}, {2, 3}}, {{9, 10, 11}}, - {{17, 18}, {15, 16}, {-17, -18}, {15, 16}}}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + {{17, 18}, {15, 16}, {-17, -18}, {15, 16}}}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); // clang-format on } // List>, with out-of-bounds gather indices. { // clang-format off - auto const list = LCW{{{2, 3}, {4, 5}}, + auto const list = LCW{Init{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, - {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {-17, -18}}}; - auto const gather_map = LCW{{0, 2, -2}, {1}, {1, 0, -1, -6}}; + {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {-17, -18}}}, st, mr}; + auto const gather_map = LCW{Init{{0, 2, -2}, {1}, {1, 0, -1, -6}}, st, mr}; auto const results = - cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, NULLIFY); - auto const expected = LCW{{{{2, 3}, LCW{}, {2, 3}}, null_at(1)}, + cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, NULLIFY, st, mr.get_output_mr()); + auto const expected = LCW{Init{{{{2, 3}, Init{}, {2, 3}}, null_at(1)}, {{9, 10, 11}}, - {{{17, 18}, {15, 16}, {-17, -18}, LCW{}}, null_at(3)}}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + {{{17, 18}, {15, 16}, {-17, -18}, Init{}}, null_at(3)}}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); // clang-format on } // List>> { // clang-format off - auto const list = LCW{{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, + auto const list = LCW{Init{{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, {{{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, - {{LCW{0}}}, + {{Init{0}}}, {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, {{0, 1, 3}, {5}}, {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, - {{{10, 20}}, {LCW{30}}, {{40, 50}, {60, 70, 80}}}}; - auto const gather_map = LCW{{1}, LCW{}, {0}, {1}, {0, -1, 1}}; - auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}); - auto const expected = LCW{{{{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, - LCW{}, - {{LCW{0}}}, + {{{10, 20}}, {Init{30}}, {{40, 50}, {60, 70, 80}}}}, st, mr}; + auto const gather_map = LCW{Init{{1}, Init{}, {0}, {1}, {0, -1, 1}}, st, mr}; + auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); + auto const expected = LCW{Init{{{{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, + Init{}, + {{Init{0}}}, {{{0, 1, 3}, {5}}}, - {{{10, 20}}, {{40, 50}, {60, 70, 80}}, {LCW{30}}}}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + {{{10, 20}}, {{40, 50}, {60, 70, 80}}, {Init{30}}}}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); // clang-format on } // List>>, with out-of-bounds gather indices. { - auto const list = LCW{{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, - {{{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, - {{LCW{0}}}, - {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, - {{0, 1, 3}, {5}}, - {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, - {{{10, 20}}, {LCW{30}}, {{40, 50}, {60, 70, 80}}}}; - auto const gather_map = LCW{{1}, LCW{}, {0}, {1}, {0, -1, 3, -4}}; - auto const results = cudf::lists::segmented_gather( - cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, NULLIFY); + auto const list = LCW{Init{{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, + {{{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, + {{Init{0}}}, + {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, + {{0, 1, 3}, {5}}, + {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, + {{{10, 20}}, {Init{30}}, {{40, 50}, {60, 70, 80}}}}, + st, + mr}; + auto const gather_map = LCW{Init{{1}, Init{}, {0}, {1}, {0, -1, 3, -4}}, st, mr}; + auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, + cudf::lists_column_view{gather_map}, + NULLIFY, + st, + mr.get_output_mr()); auto const expected = - LCW{{{{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, - LCW{}, - {{LCW{0}}}, - {{{0, 1, 3}, {5}}}, - {{{{10, 20}}, {{40, 50}, {60, 70, 80}}, LCW{}, LCW{}}, nulls_at({2, 3})}}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); - // clang-format on + LCW{Init{{{{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, + Init{}, + {{Init{0}}}, + {{{0, 1, 3}, {5}}}, + {{{{10, 20}}, {{40, 50}, {60, 70, 80}}, Init{}, Init{}}, nulls_at({2, 3})}}, + st, + mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } } @@ -233,33 +305,36 @@ TYPED_TEST(SegmentedGatherTest, GatherOutOfOrder) { using T = TypeParam; + auto const st = this->stream(); + auto const mr = this->resources(); + // List> { // clang-format off - auto const list = LCW{{{2, 3}, {4, 5}}, + auto const list = LCW{Init{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, - {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}; - auto const gather_map = LCW{{1, 0}, {1, 2, 0}, {4, 3, 2, 1, 0}}; - auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}); - auto const expected = LCW{{{4, 5}, {2, 3}}, + {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, st, mr}; + auto const gather_map = LCW{Init{{1, 0}, {1, 2, 0}, {4, 3, 2, 1, 0}}, st, mr}; + auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); + auto const expected = LCW{Init{{{4, 5}, {2, 3}}, {{9, 10, 11}, {12, 13, 14}, {6, 7, 8}}, - {{17, 18}, {17, 18}, {17, 18}, {17, 18}, {15, 16}}}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + {{17, 18}, {17, 18}, {17, 18}, {17, 18}, {15, 16}}}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); // clang-format on } // List>, with out-of-bounds gather indices. { // clang-format off - auto const list = LCW{{{2, 3}, {4, 5}}, + auto const list = LCW{Init{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, - {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}; - auto const gather_map = LCW{{1, 0}, {3, -1, -4}, {5, 4, 3, 2, 1, 0}}; - auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, NULLIFY); - auto const expected = LCW{{{4, 5}, {2, 3}}, - {{LCW{}, {12, 13, 14}, LCW{}}, nulls_at({0, 2})}, - {{LCW{}, {17, 18}, {17, 18}, {17, 18}, {17, 18}, {15, 16}}, null_at(0)}}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, st, mr}; + auto const gather_map = LCW{Init{{1, 0}, {3, -1, -4}, {5, 4, 3, 2, 1, 0}}, st, mr}; + auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, NULLIFY, st, mr.get_output_mr()); + auto const expected = LCW{Init{{{4, 5}, {2, 3}}, + {{Init{}, {12, 13, 14}, Init{}}, nulls_at({0, 2})}, + {{Init{}, {17, 18}, {17, 18}, {17, 18}, {17, 18}, {15, 16}}, null_at(0)}}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); // clang-format on } } @@ -268,33 +343,36 @@ TYPED_TEST(SegmentedGatherTest, GatherNegatives) { using T = TypeParam; + auto const st = this->stream(); + auto const mr = this->resources(); + // List> { // clang-format off - auto const list = LCW{{{2, 3}, {4, 5}}, + auto const list = LCW{Init{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, - {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}; - auto const gather_map = LCW{{-1, 0}, {-2, -1, 0}, {-5, -4, -3, -2, -1, 0}}; - auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}); - auto const expected = LCW{{{4, 5}, {2, 3}}, + {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, st, mr}; + auto const gather_map = LCW{Init{{-1, 0}, {-2, -1, 0}, {-5, -4, -3, -2, -1, 0}}, st, mr}; + auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); + auto const expected = LCW{Init{{{4, 5}, {2, 3}}, {{9, 10, 11}, {12, 13, 14}, {6, 7, 8}}, - {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}, {15, 16}}}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}, {15, 16}}}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); // clang-format on } // List>, with out-of-bounds gather indices. { // clang-format off - auto const list = LCW{{{2, 3}, {4, 5}}, + auto const list = LCW{Init{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, - {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}; - auto const gather_map = LCW{{-1, 0}, {-2, -1, -4}, {-6, -4, -3, -2, -1, 0}}; + {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}}, st, mr}; + auto const gather_map = LCW{Init{{-1, 0}, {-2, -1, -4}, {-6, -4, -3, -2, -1, 0}}, st, mr}; auto const results = - cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, NULLIFY); - auto const expected = LCW{{{4, 5}, {2, 3}}, - {{{9, 10, 11}, {12, 13, 14}, LCW{}}, null_at(2)}, - {{LCW{}, {17, 18}, {17, 18}, {17, 18}, {17, 18}, {15, 16}}, null_at(0)}}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, NULLIFY, st, mr.get_output_mr()); + auto const expected = LCW{Init{{{4, 5}, {2, 3}}, + {{{9, 10, 11}, {12, 13, 14}, Init{}}, null_at(2)}, + {{Init{}, {17, 18}, {17, 18}, {17, 18}, {17, 18}, {15, 16}}, null_at(0)}}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); // clang-format on } } @@ -303,41 +381,44 @@ TYPED_TEST(SegmentedGatherTest, GatherNestedNulls) { using T = TypeParam; + auto const st = this->stream(); + auto const mr = this->resources(); + auto valids = cudf::test::iterators::valids_at_multiples_of(2); // List> { // clang-format off - auto const list = LCW{{{{2, 3}, valids}, {4, 5}}, + auto const list = LCW{Init{{{{2, 3}, valids}, {4, 5}}, {{{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, valids}, {{15, 16}, {17, 18}, {17, 18}, {17, 18}, {17, 18}}, - {{{{25, 26}, valids}, {27, 28}, {{29, 30}, valids}, {31, 32}, {33, 34}}, valids}}; - auto const gather_map = LCW{{0, 1}, {0, 2}, LCW{}, {0, 1, 4}}; - auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}); - auto const expected = LCW{{{{2, 3}, valids}, {4, 5}}, + {{{{25, 26}, valids}, {27, 28}, {{29, 30}, valids}, {31, 32}, {33, 34}}, valids}}, st, mr}; + auto const gather_map = LCW{Init{{0, 1}, {0, 2}, Init{}, {0, 1, 4}}, st, mr}; + auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); + auto const expected = LCW{Init{{{{2, 3}, valids}, {4, 5}}, {{{6, 7, 8}, {12, 13, 14}}, no_nulls()}, - LCW{}, - {{{{25, 26}, valids}, {27, 28}, {33, 34}}, valids}}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + Init{}, + {{{{25, 26}, valids}, {27, 28}, {33, 34}}, valids}}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); // clang-format on } // List>>> { // clang-format off - auto const list = LCW{{{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, + auto const list = LCW{Init{{{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, {{{15, 16}, {{27, 28}, valids}, {{37, 38}, valids}, {47, 48}, {57, 58}}}, - {{LCW{0}}}, + {{Init{0}}}, {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, {{0, 1, 3}, {5}}, {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, - {{{{{10, 20}, valids}}, {LCW{30}}, {{40, 50}, {60, 70, 80}}}, valids}}}; - auto const gather_map = LCW{{1, 2, 4}}; - auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}); - auto const expected = LCW{{{{{15, 16}, {{27, 28}, valids}, {{37, 38}, valids}, {47, 48}, {57, 58}}}, - {{LCW{0}}}, - {{{{{10, 20}, valids}}, {LCW{30}}, {{40, 50}, {60, 70, 80}}}, valids}}}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + {{{{{10, 20}, valids}}, {Init{30}}, {{40, 50}, {60, 70, 80}}}, valids}}}, st, mr}; + auto const gather_map = LCW{Init{{1, 2, 4}}, st, mr}; + auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, cudf::out_of_bounds_policy::DONT_CHECK, st, mr.get_output_mr()); + auto const expected = LCW{Init{{{{{15, 16}, {{27, 28}, valids}, {{37, 38}, valids}, {47, 48}, {57, 58}}}, + {{Init{0}}}, + {{{{{10, 20}, valids}}, {Init{30}}, {{40, 50}, {60, 70, 80}}}, valids}}}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); // clang-format on } } @@ -346,52 +427,79 @@ TYPED_TEST(SegmentedGatherTest, GatherNestedWithEmpties) { using T = TypeParam; - auto const list = LCW{{{2, 3}, LCW{}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, {LCW{}}}; + auto const st = this->stream(); + auto const mr = this->resources(); + + auto const list = + LCW{Init{{{2, 3}, Init{}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}, {Init{}}}, st, mr}; + // Per-row singleton lists: brace LCWs (Init{{0},{0},{0}} flattens to one list of three). auto const gather_map = LCW{LCW{0}, LCW{0}, LCW{0}}; auto results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, - cudf::lists_column_view{gather_map}); + cudf::lists_column_view{gather_map}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); auto const expected = - LCW{{{2, 3}}, {{6, 7, 8}}, {LCW{}}}; // skip one null, gather one null. - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + LCW{Init{{{2, 3}}, {{6, 7, 8}}, {Init{}}}, st, mr}; // skip one null, gather one null. + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); } TYPED_TEST(SegmentedGatherTest, GatherSliced) { using T = TypeParam; + + auto const st = this->stream(); + auto const mr = this->resources(); + { - auto const a = LCW{ - {{1, 1, 1}, {2, 2}, {3, 3}}, - {{4, 4, 4}, {5, 5}, {6, 6}}, - {{7, 7, 7}, {8, 8}, {9, 9}}, - {{10, 10, 10}, {11, 11}, {12, 12}}, - {{20, 20, 20, 20}, {25}}, - {{30, 30, 30, 30}, {40}}, - {{50, 50, 50, 50}, {6, 13}}, - {{70, 70, 70, 70}, {80}}, - }; - auto const split_a = cudf::split(a, {3}); + auto const a = LCW{Init{ + {{1, 1, 1}, {2, 2}, {3, 3}}, + {{4, 4, 4}, {5, 5}, {6, 6}}, + {{7, 7, 7}, {8, 8}, {9, 9}}, + {{10, 10, 10}, {11, 11}, {12, 12}}, + {{20, 20, 20, 20}, {25}}, + {{30, 30, 30, 30}, {40}}, + {{50, 50, 50, 50}, {6, 13}}, + {{70, 70, 70, 70}, {80}}, + }, + st, + mr}; + auto const split_a = cudf::split(a, {3}, st); { - auto const list = LCW{{1, 2}, {0, 2}, {0, 1}}; + auto const list = LCW{Init{{1, 2}, {0, 2}, {0, 1}}, st, mr}; auto const gather_map = cudf::lists_column_view{list}; - auto const result = - cudf::lists::segmented_gather(cudf::lists_column_view{split_a[0]}, gather_map); - auto const expected = LCW{ - {{2, 2}, {3, 3}}, - {{4, 4, 4}, {6, 6}}, - {{7, 7, 7}, {8, 8}}, - }; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(expected, result->view()); + auto const result = cudf::lists::segmented_gather(cudf::lists_column_view{split_a[0]}, + gather_map, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); + auto const expected = LCW{Init{ + {{2, 2}, {3, 3}}, + {{4, 4, 4}, {6, 6}}, + {{7, 7, 7}, {8, 8}}, + }, + st, + mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + expected, result->view(), cudf::test::debug_output_level::FIRST_ERROR, st, mr); } { - auto const list = LCW{{0, 1}, LCW{}, LCW{}, {0, 1}, LCW{}}; + auto const list = LCW{Init{{0, 1}, Init{}, Init{}, {0, 1}, Init{}}, st, mr}; auto const gather_map = cudf::lists_column_view{list}; - auto const result = - cudf::lists::segmented_gather(cudf::lists_column_view{split_a[1]}, gather_map); + auto const result = cudf::lists::segmented_gather(cudf::lists_column_view{split_a[1]}, + gather_map, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); auto const expected = - LCW{{{10, 10, 10}, {11, 11}}, LCW{}, LCW{}, {{50, 50, 50, 50}, {6, 13}}, LCW{}}; - CUDF_TEST_EXPECT_COLUMNS_EQUAL(expected, result->view()); + LCW{Init{{{10, 10, 10}, {11, 11}}, Init{}, Init{}, {{50, 50, 50, 50}, {6, 13}}, Init{}}, + st, + mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + expected, result->view(), cudf::test::debug_output_level::FIRST_ERROR, st, mr); } } @@ -400,74 +508,105 @@ TYPED_TEST(SegmentedGatherTest, GatherSliced) // List>> { LCW list{ - // slice 0 - {{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, + Init{// slice 0 + {{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}, - {{{15, 16}, {{27, 28}, valids}, {{37, 38}, valids}, {47, 48}, {57, 58}}, - {{11, 12}, {{42, 43, 44}, valids}, {{77, 78}, valids}}}, + {{{15, 16}, {{27, 28}, valids}, {{37, 38}, valids}, {47, 48}, {57, 58}}, + {{11, 12}, {{42, 43, 44}, valids}, {{77, 78}, valids}}}, - // slice 1 - {{LCW{0}}}, - {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, - {{0, 1, 3}, {5}}, - {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, - {{{{1, 6}, {60, 70, 80, 100}}, {{10, 11, 13}, {15}}, {{11, 12, 13, 14, 15}}}, valids}, + // slice 1 + {{Init{0}}}, + {{{10}, {20, 30, 40, 50}, {60, 70, 80}}, + {{0, 1, 3}, {5}}, + {{11, 12, 13, 14, 15}, {16, 17}, {0}}}, + {{{{1, 6}, {60, 70, 80, 100}}, {{10, 11, 13}, {15}}, {{11, 12, 13, 14, 15}}}, valids}, - // slice 2 - {{{{{10, 20}, valids}}, {LCW{30}}, {{40, 50}, {60, 70, 80}}}, valids}, - {{{{10, 20, 30}}, {LCW{30}}, {{{20, 30}, valids}, {62, 72, 82}}}, valids}}; + // slice 2 + {{{{{10, 20}, valids}}, {Init{30}}, {{40, 50}, {60, 70, 80}}}, valids}, + {{{{10, 20, 30}}, {Init{30}}, {{{20, 30}, valids}, {62, 72, 82}}}, valids}}, + st, + mr}; - auto sliced = cudf::slice(list, {0, 1, 2, 5, 5, 7}); + auto sliced = cudf::slice(list, {0, 1, 2, 5, 5, 7}, st); // gather from slice 0 { - LCW map{{0, 1}}; + LCW map{Init{{0, 1}}, st, mr}; auto result = cudf::lists::segmented_gather(cudf::lists_column_view{sliced[0]}, - cudf::lists_column_view{map}); - LCW expected{{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}}; - CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(expected, result->view()); + cudf::lists_column_view{map}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); + LCW expected{Init{{{{2, 3}, {4, 5}}, {{6, 7, 8}, {9, 10, 11}, {12, 13, 14}}}}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(expected, + result->view(), + cudf::test::debug_output_level::FIRST_ERROR, + cudf::test::default_ulp, + st, + mr); } // gather from slice 1 { - LCW map{{0}, {1, 2, 0, 1}, {0, 1, 2}}; + LCW map{Init{{0}, {1, 2, 0, 1}, {0, 1, 2}}, st, mr}; auto result = cudf::lists::segmented_gather(cudf::lists_column_view{sliced[1]}, - cudf::lists_column_view{map}); + cudf::lists_column_view{map}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); LCW expected{ - {{LCW{0}}}, - - {{{0, 1, 3}, {5}}, - {{11, 12, 13, 14, 15}, {16, 17}, {0}}, - {{10}, {20, 30, 40, 50}, {60, 70, 80}}, - {{0, 1, 3}, {5}}}, - - {{{{1, 6}, {60, 70, 80, 100}}, {{10, 11, 13}, {15}}, {{11, 12, 13, 14, 15}}}, valids}, - }; - CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(expected, result->view()); + Init{ + {{Init{0}}}, + + {{{0, 1, 3}, {5}}, + {{11, 12, 13, 14, 15}, {16, 17}, {0}}, + {{10}, {20, 30, 40, 50}, {60, 70, 80}}, + {{0, 1, 3}, {5}}}, + + {{{{1, 6}, {60, 70, 80, 100}}, {{10, 11, 13}, {15}}, {{11, 12, 13, 14, 15}}}, valids}, + }, + st, + mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(expected, + result->view(), + cudf::test::debug_output_level::FIRST_ERROR, + cudf::test::default_ulp, + st, + mr); } // gather from slice 2 { - LCW map{{1, 0, 0, 1, 1, 0}, {1, 0, 0, 1, 1, 2}}; + LCW map{Init{{1, 0, 0, 1, 1, 0}, {1, 0, 0, 1, 1, 2}}, st, mr}; auto result = cudf::lists::segmented_gather(cudf::lists_column_view{sliced[2]}, - cudf::lists_column_view{map}); + cudf::lists_column_view{map}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); std::vector expected_valids = {false, true, true, false, false, true}; - LCW expected{{{{LCW{30}}, - {{{10, 20}, valids}}, - {{{10, 20}, valids}}, - {LCW{30}}, - {LCW{30}}, - {{{10, 20}, valids}}}, - expected_valids.begin()}, - {{{LCW{30}}, - {{10, 20, 30}}, - {{10, 20, 30}}, - {LCW{30}}, - {LCW{30}}, - {{{20, 30}, valids}, {62, 72, 82}}}, - expected_valids.begin()}}; - CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(expected, result->view()); + LCW expected{Init{{{{Init{30}}, + {{{10, 20}, valids}}, + {{{10, 20}, valids}}, + {Init{30}}, + {Init{30}}, + {{{10, 20}, valids}}}, + expected_valids.begin()}, + {{{Init{30}}, + {{10, 20, 30}}, + {{10, 20, 30}}, + {Init{30}}, + {Init{30}}, + {{{20, 30}, valids}, {62, 72, 82}}}, + expected_valids.begin()}}, + st, + mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(expected, + result->view(), + cudf::test::debug_output_level::FIRST_ERROR, + cudf::test::default_ulp, + st, + mr); } } } @@ -476,26 +615,46 @@ using SegmentedGatherTestString = SegmentedGatherTest; TEST_F(SegmentedGatherTestString, StringGather) { using T = cudf::string_view; + + auto const st = this->stream(); + auto const mr = this->resources(); + // List { - auto const list = LCW{{"a", "b", "c", "d"}, {"1", "22", "333", "4"}, {"x", "y", "z"}}; - auto const gather_map = LCW{{0, 1, 3, 2}, {1, 0, 3, 2}, LCW{}}; - auto const expected = LCW{{"a", "b", "d", "c"}, {"22", "1", "4", "333"}, LCW{}}; - auto const result = cudf::lists::segmented_gather(cudf::lists_column_view{list}, - cudf::lists_column_view{gather_map}); - CUDF_TEST_EXPECT_COLUMNS_EQUAL(expected, result->view()); + auto const list = + LCW{StrInit{{"a", "b", "c", "d"}, {"1", "22", "333", "4"}, {"x", "y", "z"}}, st, mr}; + auto const gather_map = cudf::test::lists_column_wrapper{ + I8Init{{0, 1, 3, 2}, {1, 0, 3, 2}, I8Init{}}, st, mr}; + auto const expected = + LCW{StrInit{{"a", "b", "d", "c"}, {"22", "1", "4", "333"}, StrInit{}}, st, mr}; + auto const result = cudf::lists::segmented_gather(cudf::lists_column_view{list}, + cudf::lists_column_view{gather_map}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + expected, result->view(), cudf::test::debug_output_level::FIRST_ERROR, st, mr); } // List, with out-of-order gather indices. { - auto const list = LCW{{"a", "b", "c", "d"}, {"1", "22", "333", "4"}, {"x", "y", "z"}}; - auto const gather_map = LCW{{0, 1, 3, 4}, {1, -5, 3, 2}, LCW{}}; - auto const expected = LCW{{{"a", "b", "d", "c"}, cudf::test::iterators::null_at(3)}, - {{"22", "1", "4", "333"}, cudf::test::iterators::null_at(1)}, - LCW{}}; - auto result = cudf::lists::segmented_gather( - cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, NULLIFY); - CUDF_TEST_EXPECT_COLUMNS_EQUAL(expected, result->view()); + auto const list = + LCW{StrInit{{"a", "b", "c", "d"}, {"1", "22", "333", "4"}, {"x", "y", "z"}}, st, mr}; + auto const gather_map = cudf::test::lists_column_wrapper{ + I8Init{{0, 1, 3, 4}, {1, -5, 3, 2}, I8Init{}}, st, mr}; + auto const expected = + LCW{StrInit{{{"a", "b", "d", "c"}, cudf::test::iterators::null_at(3)}, + {{"22", "1", "4", "333"}, cudf::test::iterators::null_at(1)}, + StrInit{}}, + st, + mr}; + auto result = cudf::lists::segmented_gather(cudf::lists_column_view{list}, + cudf::lists_column_view{gather_map}, + NULLIFY, + st, + mr.get_output_mr()); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + expected, result->view(), cudf::test::debug_output_level::FIRST_ERROR, st, mr); } } @@ -504,92 +663,178 @@ TEST_F(SegmentedGatherTestFloat, GatherMapSliced) { using T = float; + auto const st = this->stream(); + auto const mr = this->resources(); + // List { - auto const list = LCW{{1, 2, 3, 4}, {5}, {6, 7}, {8, 9, 10}, {11, 12}, {13, 14, 15, 16}}; - auto const gather_map = LCW{{3, 2, 1, 0}, {0}, {0, 1}, {0, 2, 1}, {0}, {1}}; + auto const list = + LCW{Init{{1, 2, 3, 4}, {5}, {6, 7}, {8, 9, 10}, {11, 12}, {13, 14, 15, 16}}, st, mr}; + auto const gather_map = LCW{Init{{3, 2, 1, 0}, {0}, {0, 1}, {0, 2, 1}, {0}, {1}}, st, mr}; // gather_map.offset: 0, 4, 5, 7, 10, 11, 12 - auto const expected = LCW{{4, 3, 2, 1}, {5}, {6, 7}, {8, 10, 9}, {11}, {14}}; + auto const expected = LCW{Init{{4, 3, 2, 1}, {5}, {6, 7}, {8, 10, 9}, {11}, {14}}, st, mr}; auto const results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, - cudf::lists_column_view{gather_map}); - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); + cudf::lists_column_view{gather_map}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); - auto const sliced = cudf::split(list, {1, 4}); - auto const split_m = cudf::split(gather_map, {1, 4}); - auto const split_e = cudf::split(expected, {1, 4}); + auto const sliced = cudf::split(list, {1, 4}, st); + auto const split_m = cudf::split(gather_map, {1, 4}, st); + auto const split_e = cudf::split(expected, {1, 4}, st); auto result0 = cudf::lists::segmented_gather(cudf::lists_column_view{sliced[0]}, - cudf::lists_column_view{split_m[0]}); - CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(split_e[0], result0->view()); + cudf::lists_column_view{split_m[0]}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); + CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(split_e[0], + result0->view(), + cudf::test::debug_output_level::FIRST_ERROR, + cudf::test::default_ulp, + st, + mr); auto result1 = cudf::lists::segmented_gather(cudf::lists_column_view{sliced[1]}, - cudf::lists_column_view{split_m[1]}); - CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(split_e[1], result1->view()); + cudf::lists_column_view{split_m[1]}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); + CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(split_e[1], + result1->view(), + cudf::test::debug_output_level::FIRST_ERROR, + cudf::test::default_ulp, + st, + mr); auto result2 = cudf::lists::segmented_gather(cudf::lists_column_view{sliced[2]}, - cudf::lists_column_view{split_m[2]}); - CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(split_e[2], result2->view()); + cudf::lists_column_view{split_m[2]}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()); + CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(split_e[2], + result2->view(), + cudf::test::debug_output_level::FIRST_ERROR, + cudf::test::default_ulp, + st, + mr); } // List, with out-of-bounds gather indices. { - auto const list = LCW{{1, 2, 3, 4}, {5}, {6, 7}, {8, 9, 10}, {11, 12}, {13, 14, 15, 16}}; - auto const gather_map = LCW{{3, -5, 1, 0}, {0}, {0, 1}, {0, 2, 3}, {0}, {1}}; + auto const list = + LCW{Init{{1, 2, 3, 4}, {5}, {6, 7}, {8, 9, 10}, {11, 12}, {13, 14, 15, 16}}, st, mr}; + auto const gather_map = LCW{Init{{3, -5, 1, 0}, {0}, {0, 1}, {0, 2, 3}, {0}, {1}}, st, mr}; // gather_map.offset: 0, 4, 5, 7, 10, 11, 12 - auto const expected = - LCW{{{4, 0, 2, 1}, null_at(1)}, {5}, {6, 7}, {{8, 10, 9}, null_at(2)}, {11}, {14}}; - auto results = cudf::lists::segmented_gather( - cudf::lists_column_view{list}, cudf::lists_column_view{gather_map}, NULLIFY); - CUDF_TEST_EXPECT_COLUMNS_EQUAL(results->view(), expected); - - auto const sliced = cudf::split(list, {1, 4}); - auto const split_m = cudf::split(gather_map, {1, 4}); - auto const split_e = cudf::split(expected, {1, 4}); - - auto const result0 = cudf::lists::segmented_gather( - cudf::lists_column_view{sliced[0]}, cudf::lists_column_view{split_m[0]}, NULLIFY); - CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(split_e[0], result0->view()); - auto const result1 = cudf::lists::segmented_gather( - cudf::lists_column_view{sliced[1]}, cudf::lists_column_view{split_m[1]}, NULLIFY); - CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(split_e[1], result1->view()); - auto const result2 = cudf::lists::segmented_gather( - cudf::lists_column_view{sliced[2]}, cudf::lists_column_view{split_m[2]}, NULLIFY); - CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(split_e[2], result2->view()); + auto const expected = LCW{ + Init{{{4, 0, 2, 1}, null_at(1)}, {5}, {6, 7}, {{8, 10, 9}, null_at(2)}, {11}, {14}}, st, mr}; + auto results = cudf::lists::segmented_gather(cudf::lists_column_view{list}, + cudf::lists_column_view{gather_map}, + NULLIFY, + st, + mr.get_output_mr()); + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + results->view(), expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); + + auto const sliced = cudf::split(list, {1, 4}, st); + auto const split_m = cudf::split(gather_map, {1, 4}, st); + auto const split_e = cudf::split(expected, {1, 4}, st); + + auto const result0 = cudf::lists::segmented_gather(cudf::lists_column_view{sliced[0]}, + cudf::lists_column_view{split_m[0]}, + NULLIFY, + st, + mr.get_output_mr()); + CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(split_e[0], + result0->view(), + cudf::test::debug_output_level::FIRST_ERROR, + cudf::test::default_ulp, + st, + mr); + auto const result1 = cudf::lists::segmented_gather(cudf::lists_column_view{sliced[1]}, + cudf::lists_column_view{split_m[1]}, + NULLIFY, + st, + mr.get_output_mr()); + CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(split_e[1], + result1->view(), + cudf::test::debug_output_level::FIRST_ERROR, + cudf::test::default_ulp, + st, + mr); + auto const result2 = cudf::lists::segmented_gather(cudf::lists_column_view{sliced[2]}, + cudf::lists_column_view{split_m[2]}, + NULLIFY, + st, + mr.get_output_mr()); + CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(split_e[2], + result2->view(), + cudf::test::debug_output_level::FIRST_ERROR, + cudf::test::default_ulp, + st, + mr); } } TEST_F(SegmentedGatherTestFloat, Fails) { using T = float; + + auto const st = this->stream(); + auto const mr = this->resources(); + // List - LCW list{{1, 2, 3, 4}, {5}, {6, 7}, {8, 9, 10}}; - LCW size_mismatch_map{{3, 2, 1, 0}, {0}, {0, 1}}; - cudf::test::fixed_width_column_wrapper nonlist_map0{1, 2, 0, 1}; - cudf::test::strings_column_wrapper nonlist_map1{"1", "2", "0", "1"}; - LCW nonlist_map2{{"1", "2", "0", "1"}}; + LCW list{Init{{1, 2, 3, 4}, {5}, {6, 7}, {8, 9, 10}}, st, mr}; + cudf::test::lists_column_wrapper size_mismatch_map{ + I8Init{{3, 2, 1, 0}, {0}, {0, 1}}, st, mr}; + cudf::test::fixed_width_column_wrapper nonlist_map0{{1, 2, 0, 1}, st, mr}; + cudf::test::strings_column_wrapper nonlist_map1{{"1", "2", "0", "1"}, st, mr}; + LCW nonlist_map2{StrInit{{"1", "2", "0", "1"}}, st, mr}; // Input must be a list of integer indices. It should fail for integers, // strings, or lists containing anything other than integers. EXPECT_THROW(cudf::lists::segmented_gather(cudf::lists_column_view{list}, - cudf::lists_column_view{nonlist_map0}), + cudf::lists_column_view{nonlist_map0}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()), cudf::logic_error); EXPECT_THROW(cudf::lists::segmented_gather(cudf::lists_column_view{list}, - cudf::lists_column_view{nonlist_map1}), + cudf::lists_column_view{nonlist_map1}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()), cudf::logic_error); EXPECT_THROW(cudf::lists::segmented_gather(cudf::lists_column_view{list}, - cudf::lists_column_view{nonlist_map2}), + cudf::lists_column_view{nonlist_map2}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()), cudf::logic_error); auto valids = cudf::test::iterators::valids_at_multiples_of(2); - LCW nulls_map{{{3, 2, 1, 0}, {0}, {0}, {0, 1}}, valids}; + cudf::test::lists_column_wrapper nulls_map{ + I8Init{{{3, 2, 1, 0}, {0}, {0}, {0, 1}}, valids}, st, mr}; // Nulls are not supported in the gather map. EXPECT_THROW(cudf::lists::segmented_gather(cudf::lists_column_view{list}, - cudf::lists_column_view{nulls_map}), + cudf::lists_column_view{nulls_map}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()), std::invalid_argument); // Gather map and list column sizes must be the same. EXPECT_THROW(cudf::lists::segmented_gather(cudf::lists_column_view{list}, - cudf::lists_column_view{size_mismatch_map}), + cudf::lists_column_view{size_mismatch_map}, + cudf::out_of_bounds_policy::DONT_CHECK, + st, + mr.get_output_mr()), cudf::logic_error); } + +#if defined(__GNUC__) && (__GNUC__ >= 14) +#pragma GCC diagnostic pop +#endif diff --git a/cpp/tests/utilities/memory_resource_utilities.cpp b/cpp/tests/utilities/memory_resource_utilities.cpp index 06e57bb027a..0baafeb9a5b 100644 --- a/cpp/tests/utilities/memory_resource_utilities.cpp +++ b/cpp/tests/utilities/memory_resource_utilities.cpp @@ -11,7 +11,11 @@ #include +#include +#include + #include +#include #include namespace cudf::test { @@ -27,11 +31,27 @@ scoped_current_device_resource::~scoped_current_device_resource() std::ignore = cudf::set_current_device_resource(std::move(_previous)); } +namespace { + +void print_allocation_stacktrace() +{ + constexpr int max_frames = 64; + void* frames[max_frames]; + int const nframes = ::backtrace(frames, max_frames); + std::cerr << "Unexpected allocation from the current device resource. Callstack (" << nframes + << " frames):\n"; + if (nframes > 0) { ::backtrace_symbols_fd(frames, nframes, STDERR_FILENO); } + std::cerr.flush(); +} + +} // namespace + memory_resource_test_harness::memory_resource_test_harness(rmm::device_async_resource_ref upstream) : _setup_mr{upstream}, _output_mr{upstream}, _temporary_mr{upstream}, _failing_mr{[](std::size_t, cuda::stream_ref, void*) -> void* { + print_allocation_stacktrace(); throw rmm::bad_alloc{"Unexpected allocation from the current device resource"}; }, [](void*, std::size_t, cuda::stream_ref, void*) {}} diff --git a/cpp/tests/utilities_tests/lists_column_wrapper_tests.cpp b/cpp/tests/utilities_tests/lists_column_wrapper_tests.cpp index ae83a6104ac..e8085b1cb3b 100644 --- a/cpp/tests/utilities_tests/lists_column_wrapper_tests.cpp +++ b/cpp/tests/utilities_tests/lists_column_wrapper_tests.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -1549,3 +1549,73 @@ TYPED_TEST(ListColumnWrapperTestTyped, LargeListsOfStructsWithValidity) CUDF_TEST_EXPECT_COLUMNS_EQUAL(*expected_struct_column, cudf::lists_column_view(*lists_column).child()); } + +// Harness-scoped construction via lists_column_initializer. +// Multi-row nested Init still routes through cudf::concatenate temporaries on the current +// resource; keep fail_on_current scopes on leaf / single-child paths until concatenate is ported. +struct ListsColumnInitializerHarnessTest : public cudf::test::BaseFixtureWithHarness {}; + +TEST_F(ListsColumnInitializerHarnessTest, LeafInitUsesExplicitResources) +{ + using Init = cudf::test::lists_column_initializer; + using LCW = cudf::test::lists_column_wrapper; + + auto const st = this->stream(); + auto const mr = this->resources(); + + std::unique_ptr built; + { + auto fail_on_current = this->fail_on_current_device_resource_use(); + LCW list{Init{{1, 2, 3, 4}}, st, mr}; + this->_harness.synchronize(st); + built = list.release(); + } + + LCW expected{Init{{1, 2, 3, 4}}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + *built, expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); +} + +TEST_F(ListsColumnInitializerHarnessTest, LeafNullableInitUsesExplicitResources) +{ + using Init = cudf::test::lists_column_initializer; + using LCW = cudf::test::lists_column_wrapper; + using cudf::test::iterators::null_at; + + auto const st = this->stream(); + auto const mr = this->resources(); + + std::unique_ptr built; + { + auto fail_on_current = this->fail_on_current_device_resource_use(); + LCW list{Init{{1, 2, 3, 4}, null_at(1)}, st, mr}; + this->_harness.synchronize(st); + built = list.release(); + } + + LCW expected{Init{{1, 2, 3, 4}, null_at(1)}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + *built, expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); +} + +TEST_F(ListsColumnInitializerHarnessTest, SingleChildNestedInitUsesExplicitResources) +{ + using Init = cudf::test::lists_column_initializer; + using LCW = cudf::test::lists_column_wrapper; + + auto const st = this->stream(); + auto const mr = this->resources(); + + // One outer row whose only child is a leaf list: both levels avoid concatenate. + std::unique_ptr built; + { + auto fail_on_current = this->fail_on_current_device_resource_use(); + LCW list{Init{{{{1, 2, 3}}}}, st, mr}; + this->_harness.synchronize(st); + built = list.release(); + } + + LCW expected{Init{{{{1, 2, 3}}}}, st, mr}; + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + *built, expected, cudf::test::debug_output_level::FIRST_ERROR, st, mr); +}