#include #include #include #include #include #include "archetypes/archetype_test.hpp" #define TEST_TAG "[collection]" using namespace rerun::components; // Input type that can be converted to the one held by the container. struct ConvertibleElement { ConvertibleElement(int v) : value(v) {} ConvertibleElement(const ConvertibleElement& e) = default; ConvertibleElement& operator=(const ConvertibleElement& e) = default; int value = 99999; }; // Type held by the container. struct Element { Element(int v) : value(v) {} Element(const Element& e) : value(e.value) { ++copy_count; } Element(Element&& e) : value(e.value) { ++move_count; } Element& operator=(const Element& e) = default; Element(const ConvertibleElement& e) : value(e.value) { ++move_convertible_count; } Element(ConvertibleElement&& e) : value(e.value) { ++copy_convertible_count; } bool operator==(const Element& other) const { return value == other.value; } int value = 99999; static int move_count; static int copy_count; static int move_convertible_count; static int copy_convertible_count; }; int Element::move_count = 0; int Element::copy_count = 0; int Element::move_convertible_count = 0; int Element::copy_convertible_count = 0; struct CheckElementMoveAndCopyCount { CheckElementMoveAndCopyCount() : copy_count_before(Element::copy_count), move_count_before(Element::move_count), copy_convertible_count_before(Element::copy_convertible_count), move_convertible_count_before(Element::move_convertible_count) {} ~CheckElementMoveAndCopyCount() { // Both moves and copies can be elided, so we can only check for a minimum. // But in debug builds this seems to be surprisingly reliable! #ifdef NDEBUG #define CMP >= #else #define CMP == #endif CHECK(Element::copy_count - copy_count_before CMP expected_copy_increase); CHECK(Element::move_count - move_count_before CMP expected_move_increase); CHECK( Element::copy_convertible_count - copy_convertible_count_before CMP expected_copy_convertible_increase ); CHECK( Element::move_convertible_count - move_convertible_count_before CMP expected_move_convertible_increase ); #undef CMP } CheckElementMoveAndCopyCount(const CheckElementMoveAndCopyCount&) = delete; CheckElementMoveAndCopyCount& expect_move(int i) { expected_move_increase = i; return *this; } CheckElementMoveAndCopyCount& expect_copy(int i) { expected_copy_increase = i; return *this; } CheckElementMoveAndCopyCount& expect_convertible_move(int i) { expected_copy_convertible_increase = i; return *this; } CheckElementMoveAndCopyCount& expect_convertible_copy(int i) { expected_move_convertible_increase = i; return *this; } int expected_copy_increase = 0; int expected_move_increase = 0; int expected_copy_convertible_increase = 0; int expected_move_convertible_increase = 0; int copy_count_before, move_count_before; int copy_convertible_count_before, move_convertible_count_before; }; #define EXPECTED_ELEMENT_LIST \ { 1337, 42 } // Checks if the collection contains the elements defined in `EXPECTED_ELEMENT_LIST`. void check_for_expected_list(const rerun::Collection& collection) { std::array expected = EXPECTED_ELEMENT_LIST; CHECK(collection.size() == expected.size()); CHECK(collection[0] == expected[0]); CHECK(collection[1] == expected[1]); } #define EXPECTED_SINGLE 666 // Checks if the collection contains the elements defined in `EXPECTED_SINGLE`. void check_for_expected_single(const rerun::Collection& collection) { Element expected = EXPECTED_SINGLE; CHECK(collection.size() == 1); CHECK(collection[0] == expected); } SCENARIO("Default constructing a collection", TEST_TAG) { GIVEN("a default constructed collection") { rerun::Collection collection; THEN("it is empty") { CHECK(collection.size() == 0); CHECK(collection.empty()); } THEN("it is borrowed") { CHECK(collection.get_ownership() == rerun::CollectionOwnership::Borrowed); } } } SCENARIO( "Collection creation via basic adapters, using the container's value_type as input", TEST_TAG ) { GIVEN("a vector of elements") { std::vector elements = EXPECTED_ELEMENT_LIST; THEN("a collection created from it borrows its data") { CheckElementMoveAndCopyCount check; // No copies or moves. const rerun::Collection collection(elements); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::Borrowed); } THEN("a collection created from moving it owns the data") { CheckElementMoveAndCopyCount check; // No element copies or moves, the vector itself is moved. const rerun::Collection collection(std::move(elements)); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } GIVEN("a temporary vector of elements") { THEN("a collection created from it owns its data") { CheckElementMoveAndCopyCount check; // No element moves, the vector itself is moved. check.expect_copy(2); // for constructing the temporary vector. const rerun::Collection collection(std::vector EXPECTED_ELEMENT_LIST); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } GIVEN("an std::array of elements") { std::array elements = EXPECTED_ELEMENT_LIST; THEN("a collection created from it borrows its data") { CheckElementMoveAndCopyCount check; // No copies or moves. const rerun::Collection collection(elements); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::Borrowed); } THEN("a collection created from it moving it owns the data") { CheckElementMoveAndCopyCount check; check.expect_move(2); const rerun::Collection collection(std::move(elements)); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } GIVEN("a temporary std::array of elements") { THEN("a collection created from it owns its data") { CheckElementMoveAndCopyCount check; check.expect_move(2); const rerun::Collection collection(std::array EXPECTED_ELEMENT_LIST ); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } GIVEN("a c-array of elements") { Element elements[] = EXPECTED_ELEMENT_LIST; THEN("a collection created from it borrows its data") { CheckElementMoveAndCopyCount check; // No copies or moves. const rerun::Collection collection(elements); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::Borrowed); } THEN("a collection created from moving it owns the data") { CheckElementMoveAndCopyCount check; check.expect_move(2); const rerun::Collection collection(std::move(elements)); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } GIVEN("a single element") { Element component = EXPECTED_SINGLE; THEN("a collection created from it borrows its data") { CheckElementMoveAndCopyCount check; // No copies or moves. const rerun::Collection collection(component); check_for_expected_single(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::Borrowed); } THEN("a collection created from moving it owns the data") { CheckElementMoveAndCopyCount check; check.expect_move(1); const rerun::Collection collection(std::move(component)); check_for_expected_single(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } GIVEN("a single temporary component") { THEN("a collection created from it owns the data") { CheckElementMoveAndCopyCount check; check.expect_move(1); const rerun::Collection collection(Element(EXPECTED_SINGLE)); check_for_expected_single(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } } SCENARIO( "Collection creation via basic adapters, using a type that is compatible to the container's value_type as input", TEST_TAG ) { GIVEN("a vector of convertible elements") { std::vector elements = EXPECTED_ELEMENT_LIST; THEN("a collection created from it copies its data") { CheckElementMoveAndCopyCount check; check.expect_convertible_copy(2); const rerun::Collection collection(elements); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } THEN("a collection created from it moves its data") { CheckElementMoveAndCopyCount check; check.expect_convertible_move(2); const rerun::Collection collection(std::move(elements)); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } GIVEN("a temporary vector of convertible elements") { THEN("a collection created from it moves its data") { CheckElementMoveAndCopyCount check; check.expect_convertible_move(2); const rerun::Collection collection(std::vector EXPECTED_ELEMENT_LIST); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } GIVEN("an std::array of convertible elements") { std::array elements = EXPECTED_ELEMENT_LIST; THEN("a collection created from it copies its data") { CheckElementMoveAndCopyCount check; check.expect_convertible_copy(2); const rerun::Collection collection(elements); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } THEN("a collection created from it moves its data") { CheckElementMoveAndCopyCount check; check.expect_convertible_move(2); const rerun::Collection collection(std::move(elements)); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } GIVEN("a temporary std::array of convertible elements") { THEN("a collection created from it moves its data") { CheckElementMoveAndCopyCount check; check.expect_convertible_move(2); const rerun::Collection collection(std::array EXPECTED_ELEMENT_LIST); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } // Not yet supported. // GIVEN("a c-array of convertible elements") { // ConvertibleElement elements[] = EXPECTED_ELEMENT_LIST; // // THEN("a collection created from it borrows its data") { // } // THEN("a collection created from moving it owns the data") { // } // } GIVEN("a single convertible element") { ConvertibleElement element = EXPECTED_SINGLE; THEN("a collection created from it copies its data") { CheckElementMoveAndCopyCount check; check.expect_convertible_copy(1); // The resulting value is moved internally into a vector. check.expect_move(1); const rerun::Collection collection(element); check_for_expected_single(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } THEN("a collection created from it move its data") { CheckElementMoveAndCopyCount check; check.expect_convertible_move(1); // The resulting value is moved internally into a vector. check.expect_move(1); const rerun::Collection collection(std::move(element)); check_for_expected_single(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } GIVEN("a single temporary convertible element") { THEN("a collection created from it move its data") { CheckElementMoveAndCopyCount check; check.expect_convertible_move(1); // The resulting value is moved internally into a vector. check.expect_move(1); const rerun::Collection collection(ConvertibleElement(EXPECTED_SINGLE)); check_for_expected_single(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } } struct MyVec2Container { std::vector vecs; }; namespace rerun { template <> struct CollectionAdapter { // We're using the void* version of `borrow` which doesn't do these checks for us. static_assert(sizeof(components::Position2D) == sizeof(float) * 2); static_assert(alignof(components::Position2D) <= alignof(float)); Collection operator()(const MyVec2Container& container) { return Collection::borrow( reinterpret_cast(container.vecs.data()), container.vecs.size() / 2 ); } Collection operator()(MyVec2Container&&) { throw std::runtime_error("Not implemented for temporaries"); } }; } // namespace rerun SCENARIO("Collection creation via a custom adapter for a datalayout compatible type", TEST_TAG) { GIVEN("A custom vec2 container with a defined adapter") { MyVec2Container container; container.vecs = {0.0f, 1.0f, 2.0f, 3.0f}; THEN("a collection created from it that its data") { const rerun::Collection batch(container); CHECK(batch.size() == 2); CHECK(batch.get_ownership() == rerun::CollectionOwnership::Borrowed); } THEN("A Point2D archetype can be directly created from this container") { const rerun::archetypes::Points2D from_custom_container(container); CHECK(from_custom_container.positions.has_value()); AND_THEN("it can be serialized and is identical to creation from rerun types directly" ) { const rerun::archetypes::Points2D from_rerun_vector({{0.0f, 1.0f}, {2.0f, 3.0f}}); test_compare_archetype_serialization(from_custom_container, from_rerun_vector); } } } } SCENARIO("Move construction/assignment of collections", TEST_TAG) { std::vector components = { Position2D(0.0f, 1.0f), Position2D(1.0f, 2.0f), }; GIVEN("A borrowed collection") { auto borrowed = rerun::Collection::borrow(components.data(), 2); THEN("then moving to a new batch moves the data and clears the source") { auto target(std::move(borrowed)); CHECK(target.size() == 2); CHECK(target.get_ownership() == rerun::CollectionOwnership::Borrowed); CHECK(borrowed.size() == 0); CHECK(borrowed.empty()); CHECK(borrowed.get_ownership() == rerun::CollectionOwnership::Borrowed); } THEN("moving it to an owned collection swaps their data") { auto target = rerun::Collection::take_ownership(std::vector(components)); target = std::move(borrowed); CHECK(target.size() == 2); CHECK(target.get_ownership() == rerun::CollectionOwnership::Borrowed); CHECK(borrowed.size() == 2); CHECK(borrowed.get_ownership() == rerun::CollectionOwnership::VectorOwned); } THEN("moving it to an borrowed collection swaps their data") { auto target = rerun::Collection::borrow(components.data(), 2); target = std::move(borrowed); CHECK(target.size() == 2); CHECK(target.get_ownership() == rerun::CollectionOwnership::Borrowed); CHECK(borrowed.size() == 2); CHECK(borrowed.get_ownership() == rerun::CollectionOwnership::Borrowed); } } GIVEN("A owned collection") { auto borrowed = rerun::Collection::take_ownership(std::vector(components)); THEN("moving it to an owned collection swaps their data") { auto target = rerun::Collection::take_ownership(std::vector(components)); target = std::move(borrowed); CHECK(target.size() == 2); CHECK(target.get_ownership() == rerun::CollectionOwnership::VectorOwned); CHECK(borrowed.size() == 2); CHECK(borrowed.get_ownership() == rerun::CollectionOwnership::VectorOwned); } THEN("moving it to an borrowed collection swaps their data") { auto target = rerun::Collection::borrow(components.data(), 2); target = std::move(borrowed); CHECK(target.size() == 2); CHECK(target.get_ownership() == rerun::CollectionOwnership::VectorOwned); CHECK(borrowed.size() == 2); CHECK(borrowed.get_ownership() == rerun::CollectionOwnership::Borrowed); } } // Uncomment to check if the error message for missing adapter is sane: //std::vector strings = {"a", "b", "c"}; //rerun::Collection batch(strings); } SCENARIO("Copy/move construction/assignment of collections", TEST_TAG) { GIVEN("A default constructed collection") { rerun::Collection collection; const int* old_data_ptr = collection.data(); THEN("it can be move constructed") { rerun::Collection collection2(std::move(collection)); CHECK(collection2.size() == 0); CHECK(collection2.empty()); CHECK(collection2.data() == old_data_ptr); } THEN("it can be move assigned") { rerun::Collection collection2; collection2 = std::move(collection); CHECK(collection2.size() == 0); CHECK(collection2.empty()); CHECK(collection2.data() == old_data_ptr); } THEN("it can be copy constructed") { rerun::Collection collection2(collection); CHECK(collection2.size() == 0); CHECK(collection2.empty()); } THEN("it can be copy assigned") { rerun::Collection collection2; collection2 = collection; CHECK(collection2.size() == 0); CHECK(collection2.empty()); } } GIVEN("a collection with owned data") { auto collection = rerun::Collection::take_ownership(std::vector EXPECTED_ELEMENT_LIST); const Element* old_data_ptr = collection.data(); THEN("it can be move constructed") { CheckElementMoveAndCopyCount check; // No move or copy. rerun::Collection collection2(std::move(collection)); check_for_expected_list(collection2); CHECK(collection2.data() == old_data_ptr); } THEN("it can be move assigned") { CheckElementMoveAndCopyCount check; // No move or copy. rerun::Collection collection2; collection2 = std::move(collection); check_for_expected_list(collection2); CHECK(collection2.data() == old_data_ptr); } THEN("it can be copy constructed") { CheckElementMoveAndCopyCount check; check.expect_copy(2); rerun::Collection collection2(collection); check_for_expected_list(collection2); } THEN("it can be copy assigned") { CheckElementMoveAndCopyCount check; check.expect_copy(2); rerun::Collection collection2; collection2 = collection; check_for_expected_list(collection2); } } GIVEN("a collection with borrowed data") { std::vector data EXPECTED_ELEMENT_LIST; auto collection = rerun::Collection::borrow(data.data(), data.size()); const Element* old_data_ptr = data.data(); THEN("it can be move constructed") { CheckElementMoveAndCopyCount check; // No move or copy. rerun::Collection collection2(std::move(collection)); check_for_expected_list(collection2); CHECK(collection2.data() == old_data_ptr); } THEN("it can be move assigned") { CheckElementMoveAndCopyCount check; // No move or copy. rerun::Collection collection2; collection2 = std::move(collection); check_for_expected_list(collection2); CHECK(collection2.data() == old_data_ptr); } THEN("it can be copy constructed") { CheckElementMoveAndCopyCount check; // No move or copy. rerun::Collection collection2(collection); check_for_expected_list(collection2); } THEN("it can be copy assigned") { CheckElementMoveAndCopyCount check; // No move or copy. rerun::Collection collection2; collection2 = collection; check_for_expected_list(collection2); } } } SCENARIO("Conversion to vector using `to_vector`", TEST_TAG) { auto expected_vector = std::vector EXPECTED_ELEMENT_LIST; GIVEN("a collection with owned data") { auto collection = rerun::Collection::take_ownership(std::vector EXPECTED_ELEMENT_LIST); THEN("it can be converted to a vector") { CheckElementMoveAndCopyCount check; check.expect_copy(2); CHECK(collection.to_vector() == expected_vector); } THEN("it can be moved to a vector, resulting in no copies") { CheckElementMoveAndCopyCount check; CHECK(std::move(collection).to_vector() == expected_vector); } } GIVEN("a collection with borrowed data") { std::vector data EXPECTED_ELEMENT_LIST; auto collection = rerun::Collection::borrow(data.data(), data.size()); THEN("it can be converted to a vector") { CheckElementMoveAndCopyCount check; check.expect_copy(2); CHECK(collection.to_vector() == expected_vector); } THEN("it can be moved to a vector, resulting in copies") { CheckElementMoveAndCopyCount check; check.expect_copy(2); CHECK(std::move(collection).to_vector() == expected_vector); } } } SCENARIO("Borrow and take ownership if easy with the free utility functions") { GIVEN("A vector") { std::vector data EXPECTED_ELEMENT_LIST; THEN("it can be borrowed without via `rerun::borrow` without specifying template arguments" ) { CheckElementMoveAndCopyCount check; // No element copies or moves expected. const auto collection = rerun::borrow(data); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::Borrowed); } THEN( "it can be taken ownership via `rerun::take_ownership` without specifying template arguments" ) { CheckElementMoveAndCopyCount check; // No element copies or moves expected. const auto collection = rerun::take_ownership(std::move(data)); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } GIVEN("A pointer to an array") { std::array data EXPECTED_ELEMENT_LIST; THEN("it can be borrowed via `rerun::borrow` without specifying template arguments") { CheckElementMoveAndCopyCount check; // No element copies or moves expected. const auto collection = rerun::borrow(data.data(), data.size()); check_for_expected_list(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::Borrowed); } } GIVEN("A single element") { Element data = EXPECTED_SINGLE; THEN( "it can be taken ownership via `rerun::take_ownership` without specifying template arguments" ) { WHEN("passed by value") { CheckElementMoveAndCopyCount check; check.expect_copy(1); // copy on call check.expect_move(1); // move to rerun::Collection const auto collection = rerun::take_ownership(data); check_for_expected_single(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } WHEN("passed moved") { CheckElementMoveAndCopyCount check; check.expect_move(2); // move on call, move to rerun::Collection const auto collection = rerun::take_ownership(std::move(data)); check_for_expected_single(collection); CHECK(collection.get_ownership() == rerun::CollectionOwnership::VectorOwned); } } } }