From 0c2d88cd36876df5eeb4ada2f9d339ce45b575be Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 9 Jul 2026 14:09:57 +0200 Subject: chore: rename test source files --- libs/kstd/CMakeLists.txt | 12 +- libs/kstd/kstd/bits/observer_ptr.test.cpp | 360 --- libs/kstd/kstd/bits/observer_ptr.tests.cpp | 360 +++ libs/kstd/kstd/flat_map.test.cpp | 351 --- libs/kstd/kstd/flat_map.tests.cpp | 351 +++ libs/kstd/kstd/format.test.cpp | 170 -- libs/kstd/kstd/format.tests.cpp | 170 ++ libs/kstd/kstd/string.test.cpp | 3166 ------------------------ libs/kstd/kstd/string.tests.cpp | 3166 ++++++++++++++++++++++++ libs/kstd/kstd/test_support/os_panic.test.cpp | 15 - libs/kstd/kstd/test_support/os_panic.tests.cpp | 15 + libs/kstd/kstd/vector.test.cpp | 2003 --------------- libs/kstd/kstd/vector.tests.cpp | 2003 +++++++++++++++ 13 files changed, 6071 insertions(+), 6071 deletions(-) delete mode 100644 libs/kstd/kstd/bits/observer_ptr.test.cpp create mode 100644 libs/kstd/kstd/bits/observer_ptr.tests.cpp delete mode 100644 libs/kstd/kstd/flat_map.test.cpp create mode 100644 libs/kstd/kstd/flat_map.tests.cpp delete mode 100644 libs/kstd/kstd/format.test.cpp create mode 100644 libs/kstd/kstd/format.tests.cpp delete mode 100644 libs/kstd/kstd/string.test.cpp create mode 100644 libs/kstd/kstd/string.tests.cpp delete mode 100644 libs/kstd/kstd/test_support/os_panic.test.cpp create mode 100644 libs/kstd/kstd/test_support/os_panic.tests.cpp delete mode 100644 libs/kstd/kstd/vector.test.cpp create mode 100644 libs/kstd/kstd/vector.tests.cpp (limited to 'libs/kstd') diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 002fb548..2c63f87d 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -74,12 +74,12 @@ if(BUILD_TESTING) add_executable("kstd::tests" ALIAS "kstd_tests") target_sources("kstd_tests" PRIVATE - "kstd/flat_map.test.cpp" - "kstd/format.test.cpp" - "kstd/vector.test.cpp" - "kstd/bits/observer_ptr.test.cpp" - "kstd/test_support/os_panic.test.cpp" - "kstd/string.test.cpp" + "kstd/flat_map.tests.cpp" + "kstd/format.tests.cpp" + "kstd/vector.tests.cpp" + "kstd/bits/observer_ptr.tests.cpp" + "kstd/test_support/os_panic.tests.cpp" + "kstd/string.tests.cpp" ) target_link_libraries("kstd_tests" PRIVATE diff --git a/libs/kstd/kstd/bits/observer_ptr.test.cpp b/libs/kstd/kstd/bits/observer_ptr.test.cpp deleted file mode 100644 index eca14ccd..00000000 --- a/libs/kstd/kstd/bits/observer_ptr.test.cpp +++ /dev/null @@ -1,360 +0,0 @@ -#include -#include - -#include - -#include -#include -#include -#include - -namespace -{ - struct base - { - }; - - struct derived : base - { - }; - - struct element - { - int value{}; - - constexpr auto operator<=>(element const &) const noexcept = default; - }; -} // namespace - -SCENARIO("Observer Pointer initialization and construction", "[observer_ptr]") -{ - GIVEN("An empty context") - { - WHEN("constructing by default") - { - auto ptr = kstd::observer_ptr{}; - - THEN("the observer pointer is null") - { - REQUIRE_FALSE(ptr); - } - } - - WHEN("constructing from a nullptr") - { - auto ptr = kstd::observer_ptr{nullptr}; - - THEN("the observer pointer is null") - { - REQUIRE_FALSE(ptr); - } - } - - WHEN("constructing from a raw pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - - THEN("the observer pointer is not null") - { - REQUIRE(ptr); - } - - THEN("the observer pointer points to the correct object") - { - REQUIRE(&*ptr == &value); - } - } - - WHEN("copy constructing from an existing observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - auto copy = ptr; - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("copy constructing from an existing observer pointer with a compatible type") - { - auto value = derived{}; - auto ptr = kstd::observer_ptr(&value); - kstd::observer_ptr copy = ptr; - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("copy assigning from an existing observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - auto copy = ptr; - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("move constructing from an existing observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - auto copy = std::move(ptr); - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("move assigning from an existing observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - auto copy = std::move(ptr); - - THEN("the new observer pointer points to the same object as the other observer pointer") - { - REQUIRE(&*copy == &value); - } - } - - WHEN("constructing an observer pointer using make_observer") - { - auto value = 1; - auto ptr = kstd::make_observer(&value); - - THEN("the observer pointer points to the correct object") - { - REQUIRE(&*ptr == &value); - } - - THEN("the observe pointer has the correct element type") - { - STATIC_REQUIRE(std::is_same_v>); - } - } - } -} - -SCENARIO("Observer pointer modifiers", "[observer_ptr]") -{ - GIVEN("A non-null observer pointer") - { - auto value = 1; - auto ptr = kstd::observer_ptr{&value}; - - WHEN("releasing the observer pointer") - { - auto raw_ptr = ptr.release(); - - THEN("the observer pointer is null") - { - REQUIRE_FALSE(ptr); - } - - THEN("the returned pointer points to the correct object") - { - REQUIRE(raw_ptr == &value); - } - } - - WHEN("resetting the observer pointer to nullptr") - { - ptr.reset(); - - THEN("the observer pointer is null") - { - REQUIRE_FALSE(ptr); - } - } - - WHEN("resetting the observer pointer to a new object") - { - auto other_value = 2; - ptr.reset(&other_value); - - THEN("the observer pointer points to the new object") - { - REQUIRE(&*ptr == &other_value); - } - } - - WHEN("swapping it with another observer pointer") - { - auto other_value = 2; - auto other_ptr = kstd::observer_ptr{&other_value}; - ptr.swap(other_ptr); - - THEN("the observer pointer points to the other object") - { - REQUIRE(&*ptr == &other_value); - } - - THEN("the other observer pointer points to the original object") - { - REQUIRE(&*other_ptr == &value); - } - } - - WHEN("using namespace-level swap to swap it with another observer pointer") - { - using std::swap; - auto other_value = 2; - auto other_ptr = kstd::observer_ptr{&other_value}; - swap(ptr, other_ptr); - - THEN("the observer pointer points to the other object") - { - REQUIRE(&*ptr == &other_value); - } - - THEN("the other observer pointer points to the original object") - { - REQUIRE(&*other_ptr == &value); - } - } - } -} - -SCENARIO("Observer pointer observers", "[observer_ptr]") -{ - GIVEN("A non-null observer pointer") - { - auto value = element{1}; - auto ptr = kstd::observer_ptr{&value}; - - WHEN("getting the raw pointer") - { - auto raw_ptr = ptr.get(); - - THEN("the raw pointer points to the correct object") - { - REQUIRE(raw_ptr == &value); - } - } - - WHEN("dereferencing the observer pointer") - { - auto dereferenced = *ptr; - - THEN("the dereferenced value is the correct value") - { - REQUIRE(dereferenced == value); - } - } - - WHEN("writing through the observer pointer with the arrow operator") - { - ptr->value = 2; - - THEN("the value is updated") - { - REQUIRE(value.value == 2); - } - } - - WHEN("converting the observer pointer to a raw pointer") - { - auto raw_ptr = static_cast(ptr); - - THEN("the raw pointer points to the correct object") - { - REQUIRE(raw_ptr == &value); - } - } - - WHEN("checking the observer pointer as a boolean") - { - THEN("it returns true") - { - REQUIRE(static_cast(ptr)); - } - } - } - - GIVEN("A null observer pointer") - { - auto ptr = kstd::observer_ptr{}; - - WHEN("checking the observer pointer as a boolean") - { - THEN("it returns false") - { - REQUIRE_FALSE(static_cast(ptr)); - } - } - - WHEN("dereferencing the observer pointer") - { - THEN("the observer pointer panics") - { - REQUIRE_THROWS_AS(*ptr, kstd::tests::os_panic); - } - } - - WHEN("writing through the observer pointer with the arrow operator") - { - THEN("the observer pointer panics") - { - REQUIRE_THROWS_AS(ptr->value = 2, kstd::tests::os_panic); - } - } - } -} - -SCENARIO("Observer pointer comparisons", "[observer_ptr]") -{ - GIVEN("Observer pointers to elements of an array") - { - auto arr = std::array{1, 2}; - auto ptr1 = kstd::observer_ptr{&arr[0]}; - auto ptr2 = kstd::observer_ptr{&arr[1]}; - - WHEN("comparing the same observer pointer") - { - THEN("they are equal") - { - REQUIRE(ptr1 == ptr1); - REQUIRE((ptr1 <=> ptr1) == std::strong_ordering::equal); - } - } - - WHEN("comparing different observer pointers") - { - THEN("they are ordered correctly") - { - REQUIRE(ptr1 != ptr2); - REQUIRE(ptr1 < ptr2); - REQUIRE(ptr1 <= ptr2); - REQUIRE(ptr2 > ptr1); - REQUIRE(ptr2 >= ptr1); - REQUIRE((ptr1 <=> ptr2) == std::strong_ordering::less); - REQUIRE((ptr2 <=> ptr1) == std::strong_ordering::greater); - } - } - } - - GIVEN("A null observer pointer") - { - auto ptr = kstd::observer_ptr{}; - - WHEN("comparing with another null observer pointer") - { - auto other_ptr = kstd::observer_ptr{}; - - THEN("they are equal") - { - REQUIRE(ptr == other_ptr); - REQUIRE((ptr <=> other_ptr) == std::strong_ordering::equal); - } - } - } -} \ No newline at end of file diff --git a/libs/kstd/kstd/bits/observer_ptr.tests.cpp b/libs/kstd/kstd/bits/observer_ptr.tests.cpp new file mode 100644 index 00000000..eca14ccd --- /dev/null +++ b/libs/kstd/kstd/bits/observer_ptr.tests.cpp @@ -0,0 +1,360 @@ +#include +#include + +#include + +#include +#include +#include +#include + +namespace +{ + struct base + { + }; + + struct derived : base + { + }; + + struct element + { + int value{}; + + constexpr auto operator<=>(element const &) const noexcept = default; + }; +} // namespace + +SCENARIO("Observer Pointer initialization and construction", "[observer_ptr]") +{ + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto ptr = kstd::observer_ptr{}; + + THEN("the observer pointer is null") + { + REQUIRE_FALSE(ptr); + } + } + + WHEN("constructing from a nullptr") + { + auto ptr = kstd::observer_ptr{nullptr}; + + THEN("the observer pointer is null") + { + REQUIRE_FALSE(ptr); + } + } + + WHEN("constructing from a raw pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + + THEN("the observer pointer is not null") + { + REQUIRE(ptr); + } + + THEN("the observer pointer points to the correct object") + { + REQUIRE(&*ptr == &value); + } + } + + WHEN("copy constructing from an existing observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + auto copy = ptr; + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("copy constructing from an existing observer pointer with a compatible type") + { + auto value = derived{}; + auto ptr = kstd::observer_ptr(&value); + kstd::observer_ptr copy = ptr; + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("copy assigning from an existing observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + auto copy = ptr; + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("move constructing from an existing observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + auto copy = std::move(ptr); + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("move assigning from an existing observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + auto copy = std::move(ptr); + + THEN("the new observer pointer points to the same object as the other observer pointer") + { + REQUIRE(&*copy == &value); + } + } + + WHEN("constructing an observer pointer using make_observer") + { + auto value = 1; + auto ptr = kstd::make_observer(&value); + + THEN("the observer pointer points to the correct object") + { + REQUIRE(&*ptr == &value); + } + + THEN("the observe pointer has the correct element type") + { + STATIC_REQUIRE(std::is_same_v>); + } + } + } +} + +SCENARIO("Observer pointer modifiers", "[observer_ptr]") +{ + GIVEN("A non-null observer pointer") + { + auto value = 1; + auto ptr = kstd::observer_ptr{&value}; + + WHEN("releasing the observer pointer") + { + auto raw_ptr = ptr.release(); + + THEN("the observer pointer is null") + { + REQUIRE_FALSE(ptr); + } + + THEN("the returned pointer points to the correct object") + { + REQUIRE(raw_ptr == &value); + } + } + + WHEN("resetting the observer pointer to nullptr") + { + ptr.reset(); + + THEN("the observer pointer is null") + { + REQUIRE_FALSE(ptr); + } + } + + WHEN("resetting the observer pointer to a new object") + { + auto other_value = 2; + ptr.reset(&other_value); + + THEN("the observer pointer points to the new object") + { + REQUIRE(&*ptr == &other_value); + } + } + + WHEN("swapping it with another observer pointer") + { + auto other_value = 2; + auto other_ptr = kstd::observer_ptr{&other_value}; + ptr.swap(other_ptr); + + THEN("the observer pointer points to the other object") + { + REQUIRE(&*ptr == &other_value); + } + + THEN("the other observer pointer points to the original object") + { + REQUIRE(&*other_ptr == &value); + } + } + + WHEN("using namespace-level swap to swap it with another observer pointer") + { + using std::swap; + auto other_value = 2; + auto other_ptr = kstd::observer_ptr{&other_value}; + swap(ptr, other_ptr); + + THEN("the observer pointer points to the other object") + { + REQUIRE(&*ptr == &other_value); + } + + THEN("the other observer pointer points to the original object") + { + REQUIRE(&*other_ptr == &value); + } + } + } +} + +SCENARIO("Observer pointer observers", "[observer_ptr]") +{ + GIVEN("A non-null observer pointer") + { + auto value = element{1}; + auto ptr = kstd::observer_ptr{&value}; + + WHEN("getting the raw pointer") + { + auto raw_ptr = ptr.get(); + + THEN("the raw pointer points to the correct object") + { + REQUIRE(raw_ptr == &value); + } + } + + WHEN("dereferencing the observer pointer") + { + auto dereferenced = *ptr; + + THEN("the dereferenced value is the correct value") + { + REQUIRE(dereferenced == value); + } + } + + WHEN("writing through the observer pointer with the arrow operator") + { + ptr->value = 2; + + THEN("the value is updated") + { + REQUIRE(value.value == 2); + } + } + + WHEN("converting the observer pointer to a raw pointer") + { + auto raw_ptr = static_cast(ptr); + + THEN("the raw pointer points to the correct object") + { + REQUIRE(raw_ptr == &value); + } + } + + WHEN("checking the observer pointer as a boolean") + { + THEN("it returns true") + { + REQUIRE(static_cast(ptr)); + } + } + } + + GIVEN("A null observer pointer") + { + auto ptr = kstd::observer_ptr{}; + + WHEN("checking the observer pointer as a boolean") + { + THEN("it returns false") + { + REQUIRE_FALSE(static_cast(ptr)); + } + } + + WHEN("dereferencing the observer pointer") + { + THEN("the observer pointer panics") + { + REQUIRE_THROWS_AS(*ptr, kstd::tests::os_panic); + } + } + + WHEN("writing through the observer pointer with the arrow operator") + { + THEN("the observer pointer panics") + { + REQUIRE_THROWS_AS(ptr->value = 2, kstd::tests::os_panic); + } + } + } +} + +SCENARIO("Observer pointer comparisons", "[observer_ptr]") +{ + GIVEN("Observer pointers to elements of an array") + { + auto arr = std::array{1, 2}; + auto ptr1 = kstd::observer_ptr{&arr[0]}; + auto ptr2 = kstd::observer_ptr{&arr[1]}; + + WHEN("comparing the same observer pointer") + { + THEN("they are equal") + { + REQUIRE(ptr1 == ptr1); + REQUIRE((ptr1 <=> ptr1) == std::strong_ordering::equal); + } + } + + WHEN("comparing different observer pointers") + { + THEN("they are ordered correctly") + { + REQUIRE(ptr1 != ptr2); + REQUIRE(ptr1 < ptr2); + REQUIRE(ptr1 <= ptr2); + REQUIRE(ptr2 > ptr1); + REQUIRE(ptr2 >= ptr1); + REQUIRE((ptr1 <=> ptr2) == std::strong_ordering::less); + REQUIRE((ptr2 <=> ptr1) == std::strong_ordering::greater); + } + } + } + + GIVEN("A null observer pointer") + { + auto ptr = kstd::observer_ptr{}; + + WHEN("comparing with another null observer pointer") + { + auto other_ptr = kstd::observer_ptr{}; + + THEN("they are equal") + { + REQUIRE(ptr == other_ptr); + REQUIRE((ptr <=> other_ptr) == std::strong_ordering::equal); + } + } + } +} \ No newline at end of file diff --git a/libs/kstd/kstd/flat_map.test.cpp b/libs/kstd/kstd/flat_map.test.cpp deleted file mode 100644 index bfd02303..00000000 --- a/libs/kstd/kstd/flat_map.test.cpp +++ /dev/null @@ -1,351 +0,0 @@ -#include - -#include - -#include - -#include -#include -#include - -SCENARIO("Flat Map initialization and construction", "[flat_map]") -{ - GIVEN("An empty context") - { - WHEN("constructing by default") - { - auto map = kstd::flat_map{}; - - THEN("the Flat Map does not contain elements") - { - REQUIRE_FALSE(map.contains(1)); - } - - THEN("the keys container is empty") - { - REQUIRE(map.keys().empty()); - } - - THEN("the values container is empty") - { - REQUIRE(map.values().empty()); - } - } - } -} - -SCENARIO("Flat Map modifiers", "[flat_map]") -{ - GIVEN("An empty Flat Map") - { - auto map = kstd::flat_map{}; - - WHEN("emplacing a new element") - { - auto [it, inserted] = map.emplace(1, 100); - - THEN("the map contains the new element") - { - REQUIRE(inserted); - REQUIRE(map.contains(1)); - } - } - - WHEN("emplacing an existing element") - { - map.emplace(1, 100); - auto [it, inserted] = map.emplace(1, 200); - - THEN("the map does not insert the duplicate") - { - REQUIRE_FALSE(inserted); - REQUIRE(map.contains(1)); - } - } - - AND_GIVEN("a populated Flat Map") - { - auto other = kstd::flat_map{}; - other.emplace(1, 10); - other.emplace(2, 20); - other.emplace(3, 30); - - WHEN("assigning the populated Flat Map to the empty one") - { - map = other; - - THEN("the elements are copied") - { - REQUIRE(map.at(1) == 10); - REQUIRE(map.at(2) == 20); - REQUIRE(map.at(3) == 30); - } - - THEN("the elements are still in the populated Flat Map") - { - REQUIRE(other.at(1) == 10); - REQUIRE(other.at(2) == 20); - REQUIRE(other.at(3) == 30); - } - } - } - } -} - -SCENARIO("Flat Map element access", "[flat_map]") -{ - GIVEN("A populated Flat Map") - { - auto map = kstd::flat_map{}; - map.emplace(1, 10); - map.emplace(2, 20); - map.emplace(3, 30); - - WHEN("accessing an existing element with at()") - { - auto & val = map.at(2); - - THEN("it returns a reference to the mapped value") - { - REQUIRE(val == 20); - } - - THEN("the mapped value can be modified") - { - val = 200; - REQUIRE(map.at(2) == 200); - } - } - - WHEN("accessing a non-existent element with at()") - { - THEN("it panics") - { - REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic); - } - } - } - - GIVEN("A const populated Flat Map") - { - auto map_builder = kstd::flat_map{}; - map_builder.emplace(1, 10); - map_builder.emplace(2, 20); - auto const map = map_builder; - - WHEN("accessing an existing element with const at()") - { - auto const & val = map.at(2); - - THEN("it returns a const reference to the mapped value") - { - REQUIRE(val == 20); - } - } - - WHEN("accessing a non-existent element with const at()") - { - THEN("it panics") - { - REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic); - } - } - } -} - -SCENARIO("Flat Map iterators", "[flat_map]") -{ - GIVEN("A populated Flat Map") - { - auto map = kstd::flat_map{}; - map.emplace(1, 10); - map.emplace(2, 20); - map.emplace(3, 30); - - WHEN("using forward iterators") - { - THEN("they navigate the elements in the correct forward order") - { - auto it = map.begin(); - REQUIRE(it != map.end()); - REQUIRE((*it).first == 1); - - ++it; - REQUIRE(it != map.end()); - REQUIRE((*it).first == 2); - - ++it; - REQUIRE(it != map.end()); - REQUIRE((*it).first == 3); - - ++it; - REQUIRE(it == map.end()); - } - - THEN("const forward iterators provide correct access") - { - auto it = map.cbegin(); - REQUIRE(it != map.cend()); - REQUIRE((*it).first == 1); - - ++it; - REQUIRE(it != map.cend()); - REQUIRE((*it).first == 2); - - ++it; - REQUIRE(it != map.cend()); - REQUIRE((*it).first == 3); - - ++it; - REQUIRE(it == map.cend()); - } - - THEN("assignment through the proxy modifies the mapped value") - { - auto it = map.begin(); - - *it = std::pair{1, 100}; - - REQUIRE(it->second == 100); - REQUIRE(map.at(1) == 100); - } - - THEN("structured bindings evaluate correctly") - { - auto it = map.cbegin(); - - auto [key, value] = *it; - - REQUIRE(key == 1); - REQUIRE(value == 10); - - STATIC_REQUIRE(std::is_same_v); - STATIC_REQUIRE(std::is_same_v); - } - } - - WHEN("using reverse iterators") - { - THEN("they navigate the elements in the correct reverse order") - { - auto it = map.rbegin(); - REQUIRE(it != map.rend()); - REQUIRE((*it).first == 3); - - ++it; - REQUIRE(it != map.rend()); - REQUIRE((*it).first == 2); - - ++it; - REQUIRE(it != map.rend()); - REQUIRE((*it).first == 1); - - ++it; - REQUIRE(it == map.rend()); - } - - THEN("const reverse iterators provide correct access") - { - auto it = map.crbegin(); - REQUIRE(it != map.crend()); - REQUIRE((*it).first == 3); - - ++it; - REQUIRE(it != map.crend()); - REQUIRE((*it).first == 2); - - ++it; - REQUIRE(it != map.crend()); - REQUIRE((*it).first == 1); - - ++it; - REQUIRE(it == map.crend()); - } - } - } - - GIVEN("an empty Flat Map") - { - auto map = kstd::flat_map{}; - - WHEN("getting iterators") - { - THEN("begin() equals end() and cbegin() equals cend()") - { - REQUIRE(map.begin() == map.end()); - REQUIRE(map.cbegin() == map.cend()); - } - - THEN("rbegin() equals rend() and crbegin() equals crend()") - { - REQUIRE(map.rbegin() == map.rend()); - REQUIRE(map.crbegin() == map.crend()); - } - } - } -} - -SCENARIO("Flat Map heterogeneous element access", "[flat_map]") -{ - GIVEN("A populated Flat Map with a transparent comparator") - { - auto map = kstd::flat_map>{}; - map.emplace(1, 10); - map.emplace(2, 20); - map.emplace(3, 30); - - WHEN("accessing an existing element with a different key type via at()") - { - long const key = 2L; - auto & val = map.at(key); - - THEN("it returns a reference to the mapped value") - { - REQUIRE(val == 20); - } - - THEN("the mapped value can be modified") - { - val = 200; - REQUIRE(map.at(2L) == 200); - } - } - - WHEN("accessing a non-existent element with a different key type via at()") - { - long const key = 4L; - THEN("it panics") - { - REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic); - } - } - } - - GIVEN("A const populated Flat Map with a transparent comparator") - { - auto map_builder = kstd::flat_map>{}; - map_builder.emplace(1, 10); - map_builder.emplace(2, 20); - auto const map = map_builder; - - WHEN("accessing an existing element with a different key type via const at()") - { - long const key = 2L; - auto const & val = map.at(key); - - THEN("it returns a const reference to the mapped value") - { - REQUIRE(val == 20); - } - } - - WHEN("accessing a non-existent element with a different key type via const at()") - { - long const key = 4L; - THEN("it panics") - { - REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic); - } - } - } -} diff --git a/libs/kstd/kstd/flat_map.tests.cpp b/libs/kstd/kstd/flat_map.tests.cpp new file mode 100644 index 00000000..bfd02303 --- /dev/null +++ b/libs/kstd/kstd/flat_map.tests.cpp @@ -0,0 +1,351 @@ +#include + +#include + +#include + +#include +#include +#include + +SCENARIO("Flat Map initialization and construction", "[flat_map]") +{ + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto map = kstd::flat_map{}; + + THEN("the Flat Map does not contain elements") + { + REQUIRE_FALSE(map.contains(1)); + } + + THEN("the keys container is empty") + { + REQUIRE(map.keys().empty()); + } + + THEN("the values container is empty") + { + REQUIRE(map.values().empty()); + } + } + } +} + +SCENARIO("Flat Map modifiers", "[flat_map]") +{ + GIVEN("An empty Flat Map") + { + auto map = kstd::flat_map{}; + + WHEN("emplacing a new element") + { + auto [it, inserted] = map.emplace(1, 100); + + THEN("the map contains the new element") + { + REQUIRE(inserted); + REQUIRE(map.contains(1)); + } + } + + WHEN("emplacing an existing element") + { + map.emplace(1, 100); + auto [it, inserted] = map.emplace(1, 200); + + THEN("the map does not insert the duplicate") + { + REQUIRE_FALSE(inserted); + REQUIRE(map.contains(1)); + } + } + + AND_GIVEN("a populated Flat Map") + { + auto other = kstd::flat_map{}; + other.emplace(1, 10); + other.emplace(2, 20); + other.emplace(3, 30); + + WHEN("assigning the populated Flat Map to the empty one") + { + map = other; + + THEN("the elements are copied") + { + REQUIRE(map.at(1) == 10); + REQUIRE(map.at(2) == 20); + REQUIRE(map.at(3) == 30); + } + + THEN("the elements are still in the populated Flat Map") + { + REQUIRE(other.at(1) == 10); + REQUIRE(other.at(2) == 20); + REQUIRE(other.at(3) == 30); + } + } + } + } +} + +SCENARIO("Flat Map element access", "[flat_map]") +{ + GIVEN("A populated Flat Map") + { + auto map = kstd::flat_map{}; + map.emplace(1, 10); + map.emplace(2, 20); + map.emplace(3, 30); + + WHEN("accessing an existing element with at()") + { + auto & val = map.at(2); + + THEN("it returns a reference to the mapped value") + { + REQUIRE(val == 20); + } + + THEN("the mapped value can be modified") + { + val = 200; + REQUIRE(map.at(2) == 200); + } + } + + WHEN("accessing a non-existent element with at()") + { + THEN("it panics") + { + REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic); + } + } + } + + GIVEN("A const populated Flat Map") + { + auto map_builder = kstd::flat_map{}; + map_builder.emplace(1, 10); + map_builder.emplace(2, 20); + auto const map = map_builder; + + WHEN("accessing an existing element with const at()") + { + auto const & val = map.at(2); + + THEN("it returns a const reference to the mapped value") + { + REQUIRE(val == 20); + } + } + + WHEN("accessing a non-existent element with const at()") + { + THEN("it panics") + { + REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic); + } + } + } +} + +SCENARIO("Flat Map iterators", "[flat_map]") +{ + GIVEN("A populated Flat Map") + { + auto map = kstd::flat_map{}; + map.emplace(1, 10); + map.emplace(2, 20); + map.emplace(3, 30); + + WHEN("using forward iterators") + { + THEN("they navigate the elements in the correct forward order") + { + auto it = map.begin(); + REQUIRE(it != map.end()); + REQUIRE((*it).first == 1); + + ++it; + REQUIRE(it != map.end()); + REQUIRE((*it).first == 2); + + ++it; + REQUIRE(it != map.end()); + REQUIRE((*it).first == 3); + + ++it; + REQUIRE(it == map.end()); + } + + THEN("const forward iterators provide correct access") + { + auto it = map.cbegin(); + REQUIRE(it != map.cend()); + REQUIRE((*it).first == 1); + + ++it; + REQUIRE(it != map.cend()); + REQUIRE((*it).first == 2); + + ++it; + REQUIRE(it != map.cend()); + REQUIRE((*it).first == 3); + + ++it; + REQUIRE(it == map.cend()); + } + + THEN("assignment through the proxy modifies the mapped value") + { + auto it = map.begin(); + + *it = std::pair{1, 100}; + + REQUIRE(it->second == 100); + REQUIRE(map.at(1) == 100); + } + + THEN("structured bindings evaluate correctly") + { + auto it = map.cbegin(); + + auto [key, value] = *it; + + REQUIRE(key == 1); + REQUIRE(value == 10); + + STATIC_REQUIRE(std::is_same_v); + STATIC_REQUIRE(std::is_same_v); + } + } + + WHEN("using reverse iterators") + { + THEN("they navigate the elements in the correct reverse order") + { + auto it = map.rbegin(); + REQUIRE(it != map.rend()); + REQUIRE((*it).first == 3); + + ++it; + REQUIRE(it != map.rend()); + REQUIRE((*it).first == 2); + + ++it; + REQUIRE(it != map.rend()); + REQUIRE((*it).first == 1); + + ++it; + REQUIRE(it == map.rend()); + } + + THEN("const reverse iterators provide correct access") + { + auto it = map.crbegin(); + REQUIRE(it != map.crend()); + REQUIRE((*it).first == 3); + + ++it; + REQUIRE(it != map.crend()); + REQUIRE((*it).first == 2); + + ++it; + REQUIRE(it != map.crend()); + REQUIRE((*it).first == 1); + + ++it; + REQUIRE(it == map.crend()); + } + } + } + + GIVEN("an empty Flat Map") + { + auto map = kstd::flat_map{}; + + WHEN("getting iterators") + { + THEN("begin() equals end() and cbegin() equals cend()") + { + REQUIRE(map.begin() == map.end()); + REQUIRE(map.cbegin() == map.cend()); + } + + THEN("rbegin() equals rend() and crbegin() equals crend()") + { + REQUIRE(map.rbegin() == map.rend()); + REQUIRE(map.crbegin() == map.crend()); + } + } + } +} + +SCENARIO("Flat Map heterogeneous element access", "[flat_map]") +{ + GIVEN("A populated Flat Map with a transparent comparator") + { + auto map = kstd::flat_map>{}; + map.emplace(1, 10); + map.emplace(2, 20); + map.emplace(3, 30); + + WHEN("accessing an existing element with a different key type via at()") + { + long const key = 2L; + auto & val = map.at(key); + + THEN("it returns a reference to the mapped value") + { + REQUIRE(val == 20); + } + + THEN("the mapped value can be modified") + { + val = 200; + REQUIRE(map.at(2L) == 200); + } + } + + WHEN("accessing a non-existent element with a different key type via at()") + { + long const key = 4L; + THEN("it panics") + { + REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic); + } + } + } + + GIVEN("A const populated Flat Map with a transparent comparator") + { + auto map_builder = kstd::flat_map>{}; + map_builder.emplace(1, 10); + map_builder.emplace(2, 20); + auto const map = map_builder; + + WHEN("accessing an existing element with a different key type via const at()") + { + long const key = 2L; + auto const & val = map.at(key); + + THEN("it returns a const reference to the mapped value") + { + REQUIRE(val == 20); + } + } + + WHEN("accessing a non-existent element with a different key type via const at()") + { + long const key = 4L; + THEN("it panics") + { + REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic); + } + } + } +} diff --git a/libs/kstd/kstd/format.test.cpp b/libs/kstd/kstd/format.test.cpp deleted file mode 100644 index d38a8107..00000000 --- a/libs/kstd/kstd/format.test.cpp +++ /dev/null @@ -1,170 +0,0 @@ -#include - -#include - -#include -#include -#include - -using namespace std::string_view_literals; - -SCENARIO("Formatting to a new string", "[format]") -{ - GIVEN("a format string without any placeholders") - { - constexpr auto fmt = "This is a test"sv; - - WHEN("calling format without any arguments.") - { - auto result = kstd::format(fmt); - - THEN("the result is the unmodified string") - { - REQUIRE(result == "This is a test"); - } - } - - WHEN("calling format with additional arguments") - { - auto result = kstd::format(fmt, 1, 2, 3); - - THEN("the result is the unmodified string") - { - REQUIRE(result == "This is a test"); - } - } - } - - GIVEN("a format string with placeholders") - { - constexpr auto fmt = "Here are some placeholders: {} {} {}"sv; - - WHEN("calling format with the same number of arguments as there are placeholders") - { - auto result = kstd::format(fmt, 1, true, 'a'); - - THEN("the result is the formatted string") - { - REQUIRE(result == "Here are some placeholders: 1 true a"); - } - } - - WHEN("calling format with too many arguments") - { - auto result = kstd::format(fmt, 2, false, 'b', 4, 5, 6); - - THEN("the result is the formatted string") - { - REQUIRE(result == "Here are some placeholders: 2 false b"); - } - } - } -} - -SCENARIO("Formatting to an output iterator", "[format]") -{ - auto buffer = std::ostringstream{}; - - GIVEN("a format string without any placeholders") - { - constexpr auto fmt = "This is a test"sv; - - WHEN("calling format without any arguments.") - { - kstd::format_to(std::ostream_iterator{buffer}, fmt); - - THEN("the unmodified string is written to the iterator") - { - REQUIRE(buffer.str() == "This is a test"); - } - } - - WHEN("calling format with additional arguments") - { - kstd::format_to(std::ostream_iterator{buffer}, fmt, 1, 2, 3); - - THEN("the unmodified string is written to the iterator") - { - REQUIRE(buffer.str() == "This is a test"); - } - } - } - - GIVEN("a format string with placeholders") - { - constexpr auto fmt = "Here are some placeholders: {} {} {}"sv; - - WHEN("calling format with the same number of arguments as there are placeholders") - { - kstd::format_to(std::ostream_iterator{buffer}, fmt, 1, true, -100); - - THEN("the formatted string is written to the iterator") - { - REQUIRE(buffer.str() == "Here are some placeholders: 1 true -100"); - } - } - - WHEN("calling format with too many arguments") - { - kstd::format_to(std::ostream_iterator{buffer}, fmt, 2, false, -200, 4, 5, 6); - - THEN("the formatted string is written to the iterator") - { - REQUIRE(buffer.str() == "Here are some placeholders: 2 false -200"); - } - } - } -} - -SCENARIO("Determining formatted size") -{ - GIVEN("a format string without any placeholders") - { - constexpr auto fmt = "This is a test"sv; - - WHEN("calling formatted_size without any arguments") - { - auto const size = kstd::formatted_size(fmt); - - THEN("the result is equal to the length of the format string") - { - REQUIRE(size == fmt.size()); - } - } - - WHEN("calling format with additional arguments") - { - auto const size = kstd::formatted_size(fmt, 1, 2, 3); - - THEN("the result is equal to the length of the format string") - { - REQUIRE(size == fmt.size()); - } - } - } - - GIVEN("a format string with placeholders") - { - constexpr auto fmt = "Here are some placeholders: {} {} {}"sv; - - WHEN("calling format with the same number of arguments as there are placeholders") - { - auto result = kstd::formatted_size(fmt, 1, true, 'a'); - - THEN("the result is the formatted length") - { - REQUIRE(result == std::string_view{"Here are some placeholders: 1 true a"}.size()); - } - } - - WHEN("calling format with too many arguments") - { - auto result = kstd::formatted_size(fmt, 2, false, 'b', 4, 5, 6); - - THEN("the result is the formatted length") - { - REQUIRE(result == std::string_view{"Here are some placeholders: 2 false b"}.size()); - } - } - } -} \ No newline at end of file diff --git a/libs/kstd/kstd/format.tests.cpp b/libs/kstd/kstd/format.tests.cpp new file mode 100644 index 00000000..d38a8107 --- /dev/null +++ b/libs/kstd/kstd/format.tests.cpp @@ -0,0 +1,170 @@ +#include + +#include + +#include +#include +#include + +using namespace std::string_view_literals; + +SCENARIO("Formatting to a new string", "[format]") +{ + GIVEN("a format string without any placeholders") + { + constexpr auto fmt = "This is a test"sv; + + WHEN("calling format without any arguments.") + { + auto result = kstd::format(fmt); + + THEN("the result is the unmodified string") + { + REQUIRE(result == "This is a test"); + } + } + + WHEN("calling format with additional arguments") + { + auto result = kstd::format(fmt, 1, 2, 3); + + THEN("the result is the unmodified string") + { + REQUIRE(result == "This is a test"); + } + } + } + + GIVEN("a format string with placeholders") + { + constexpr auto fmt = "Here are some placeholders: {} {} {}"sv; + + WHEN("calling format with the same number of arguments as there are placeholders") + { + auto result = kstd::format(fmt, 1, true, 'a'); + + THEN("the result is the formatted string") + { + REQUIRE(result == "Here are some placeholders: 1 true a"); + } + } + + WHEN("calling format with too many arguments") + { + auto result = kstd::format(fmt, 2, false, 'b', 4, 5, 6); + + THEN("the result is the formatted string") + { + REQUIRE(result == "Here are some placeholders: 2 false b"); + } + } + } +} + +SCENARIO("Formatting to an output iterator", "[format]") +{ + auto buffer = std::ostringstream{}; + + GIVEN("a format string without any placeholders") + { + constexpr auto fmt = "This is a test"sv; + + WHEN("calling format without any arguments.") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt); + + THEN("the unmodified string is written to the iterator") + { + REQUIRE(buffer.str() == "This is a test"); + } + } + + WHEN("calling format with additional arguments") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt, 1, 2, 3); + + THEN("the unmodified string is written to the iterator") + { + REQUIRE(buffer.str() == "This is a test"); + } + } + } + + GIVEN("a format string with placeholders") + { + constexpr auto fmt = "Here are some placeholders: {} {} {}"sv; + + WHEN("calling format with the same number of arguments as there are placeholders") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt, 1, true, -100); + + THEN("the formatted string is written to the iterator") + { + REQUIRE(buffer.str() == "Here are some placeholders: 1 true -100"); + } + } + + WHEN("calling format with too many arguments") + { + kstd::format_to(std::ostream_iterator{buffer}, fmt, 2, false, -200, 4, 5, 6); + + THEN("the formatted string is written to the iterator") + { + REQUIRE(buffer.str() == "Here are some placeholders: 2 false -200"); + } + } + } +} + +SCENARIO("Determining formatted size") +{ + GIVEN("a format string without any placeholders") + { + constexpr auto fmt = "This is a test"sv; + + WHEN("calling formatted_size without any arguments") + { + auto const size = kstd::formatted_size(fmt); + + THEN("the result is equal to the length of the format string") + { + REQUIRE(size == fmt.size()); + } + } + + WHEN("calling format with additional arguments") + { + auto const size = kstd::formatted_size(fmt, 1, 2, 3); + + THEN("the result is equal to the length of the format string") + { + REQUIRE(size == fmt.size()); + } + } + } + + GIVEN("a format string with placeholders") + { + constexpr auto fmt = "Here are some placeholders: {} {} {}"sv; + + WHEN("calling format with the same number of arguments as there are placeholders") + { + auto result = kstd::formatted_size(fmt, 1, true, 'a'); + + THEN("the result is the formatted length") + { + REQUIRE(result == std::string_view{"Here are some placeholders: 1 true a"}.size()); + } + } + + WHEN("calling format with too many arguments") + { + auto result = kstd::formatted_size(fmt, 2, false, 'b', 4, 5, 6); + + THEN("the result is the formatted length") + { + REQUIRE(result == std::string_view{"Here are some placeholders: 2 false b"}.size()); + } + } + } +} \ No newline at end of file diff --git a/libs/kstd/kstd/string.test.cpp b/libs/kstd/kstd/string.test.cpp deleted file mode 100644 index b8fa1139..00000000 --- a/libs/kstd/kstd/string.test.cpp +++ /dev/null @@ -1,3166 +0,0 @@ -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -TEST_CASE("String static interface") -{ - REQUIRE(std::is_same_v>); - REQUIRE(std::is_same_v); - REQUIRE(std::is_same_v>); - REQUIRE(std::is_same_v>::size_type>); - REQUIRE(std::is_same_v>::difference_type>); - REQUIRE(std::is_same_v); - REQUIRE(std::is_same_v); - REQUIRE(std::is_same_v>::pointer>); - REQUIRE(std::is_same_v>::const_pointer>); - REQUIRE(std::is_same_v); - REQUIRE(std::is_same_v); - REQUIRE(std::is_same_v>); - REQUIRE(std::is_same_v>); - - REQUIRE(kstd::string::npos == kstd::string::size_type(-1)); -} - -SCENARIO("String initialization and construction", "[string]") -{ - GIVEN("An empty context") - { - WHEN("constructing by default") - { - auto s = kstd::string{}; - - THEN("the string is empty") - { - REQUIRE(s.empty()); - } - - THEN("the size is equal to zero") - { - REQUIRE(s.size() == 0); - } - - THEN("the length is equal to zero") - { - REQUIRE(s.length() == 0); - } - - THEN("at raises a panic") - { - REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); - } - - THEN("data returns a pointer poiting to a null byte") - { - REQUIRE(*s.data() == '\0'); - } - - THEN("c_str returns a pointer poiting to a null byte") - { - REQUIRE(*s.c_str() == '\0'); - } - - THEN("the string is convertible to an empty string view") - { - auto view = static_cast(s); - REQUIRE(view.empty()); - } - } - - WHEN("constructing with 10 copies of the letter 'a'") - { - auto s = kstd::string{10, 'a'}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 10") - { - REQUIRE(s.size() == 10); - } - - THEN("the length is equal to 10") - { - REQUIRE(s.length() == 10); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns a") - { - REQUIRE(s.at(s.size() - 1) == 'a'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns a") - { - REQUIRE(s[s.size() - 1] == 'a'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns a") - { - REQUIRE(s.back() == 'a'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 10") - { - REQUIRE(std::strlen(s.c_str()) == 10); - } - - THEN("the string converts to a string view of length 10") - { - auto view = static_cast(s); - REQUIRE(view.length() == 10); - } - } - - WHEN("constructing with 20 copies of the letter 'a'") - { - auto s = kstd::string{20, 'a'}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 20") - { - REQUIRE(s.size() == 20); - } - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - - THEN("the capacity is equal to 20") - { - REQUIRE(s.capacity() == 20); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns a") - { - REQUIRE(s.at(s.size() - 1) == 'a'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns a") - { - REQUIRE(s[s.size() - 1] == 'a'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns a") - { - REQUIRE(s.back() == 'a'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 20") - { - REQUIRE(std::strlen(s.c_str()) == 20); - } - - THEN("the string converts to a string view of length 20") - { - auto view = static_cast(s); - REQUIRE(view.length() == 20); - } - } - - WHEN("constructing with an input iterator pair of 10 characters") - { - auto source = std::istringstream{"abcdefghij"}; - auto s = kstd::string{std::istream_iterator{source}, std::istream_iterator{}}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 10") - { - REQUIRE(s.size() == 10); - } - - THEN("the length is equal to 10") - { - REQUIRE(s.length() == 10); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns j") - { - REQUIRE(s.at(s.size() - 1) == 'j'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns j") - { - REQUIRE(s[s.size() - 1] == 'j'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns j") - { - REQUIRE(s.back() == 'j'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 10") - { - REQUIRE(std::strlen(s.c_str()) == 10); - } - - THEN("the string converts to a string view of length 10") - { - auto view = static_cast(s); - REQUIRE(view.length() == 10); - } - } - - WHEN("constructing with an input iterator pair of 20 characters") - { - auto source = std::istringstream{"abcdefghijABCDEFGHIJ"}; - auto s = kstd::string{std::istream_iterator{source}, std::istream_iterator{}}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 20") - { - REQUIRE(s.size() == 20); - } - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - - THEN("the capacity is greater than or equal to 20") - { - REQUIRE(s.capacity() >= 20); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns J") - { - REQUIRE(s.at(s.size() - 1) == 'J'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns J") - { - REQUIRE(s[s.size() - 1] == 'J'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns J") - { - REQUIRE(s.back() == 'J'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 20") - { - REQUIRE(std::strlen(s.c_str()) == 20); - } - - THEN("the string converts to a string view of length 20") - { - auto view = static_cast(s); - REQUIRE(view.length() == 20); - } - } - - WHEN("constructing with a forward iterator pair of 10 characters") - { - auto source = std::forward_list{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; - auto s = kstd::string{source.cbegin(), source.cend()}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 10") - { - REQUIRE(s.size() == 10); - } - - THEN("the length is equal to 10") - { - REQUIRE(s.length() == 10); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns j") - { - REQUIRE(s.at(s.size() - 1) == 'j'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns j") - { - REQUIRE(s[s.size() - 1] == 'j'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns j") - { - REQUIRE(s.back() == 'j'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 10") - { - REQUIRE(std::strlen(s.c_str()) == 10); - } - - THEN("the string converts to a string view of length 10") - { - auto view = static_cast(s); - REQUIRE(view.length() == 10); - } - } - - WHEN("constructing with a forward iterator pair of 20 characters") - { - auto source = std::forward_list{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'}; - auto s = kstd::string{source.cbegin(), source.cend()}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 20") - { - REQUIRE(s.size() == 20); - } - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - - THEN("the capacity is equal to 20") - { - REQUIRE(s.capacity() == 20); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns J") - { - REQUIRE(s.at(s.size() - 1) == 'J'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns J") - { - REQUIRE(s[s.size() - 1] == 'J'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns J") - { - REQUIRE(s.back() == 'J'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 20") - { - REQUIRE(std::strlen(s.c_str()) == 20); - } - - THEN("the string converts to a string view of length 20") - { - auto view = static_cast(s); - REQUIRE(view.length() == 20); - } - } - - WHEN("constructing from a null pointer with zero size") - { - auto s = kstd::string{static_cast(nullptr), 0}; - - THEN("the string is empty") - { - REQUIRE(s.empty()); - } - - THEN("the size is equal to 0") - { - REQUIRE(s.size() == 0); - } - - THEN("the length is equal to 0") - { - REQUIRE(s.length() == 0); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at raises a panic") - { - REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); - } - - THEN("data returns a pointer poiting to a null byte") - { - REQUIRE(*s.data() == '\0'); - } - - THEN("c_str returns a pointer poiting to a null byte") - { - REQUIRE(*s.c_str() == '\0'); - } - - THEN("the string is convertible to an empty string view") - { - auto view = static_cast(s); - REQUIRE(view.empty()); - } - } - - WHEN("constructing from a from a null pointer and non-zero size") - { - THEN("a panic is raised") - { - REQUIRE_THROWS_AS((kstd::string{static_cast(nullptr), 1}), kstd::tests::os_panic); - } - } - - WHEN("constructing from a non-null pointer and zero size") - { - auto ptr = "abcd"; - auto s = kstd::string{ptr, 0}; - - THEN("the string is empty") - { - REQUIRE(s.empty()); - } - - THEN("the size is equal to 0") - { - REQUIRE(s.size() == 0); - } - - THEN("the length is equal to 0") - { - REQUIRE(s.length() == 0); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at raises a panic") - { - REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); - } - - THEN("data returns a pointer poiting to a null byte") - { - REQUIRE(*s.data() == '\0'); - } - - THEN("c_str returns a pointer poiting to a null byte") - { - REQUIRE(*s.c_str() == '\0'); - } - - THEN("the string is convertible to an empty string view") - { - auto view = static_cast(s); - REQUIRE(view.empty()); - } - } - - WHEN("constructing from a non-null pointer and a size of 4") - { - auto ptr = "abcd"; - auto s = kstd::string{ptr, 4}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 4") - { - REQUIRE(s.size() == 4); - } - - THEN("the length is equal to 4") - { - REQUIRE(s.length() == 4); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns d") - { - REQUIRE(s.at(s.size() - 1) == 'd'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns d") - { - REQUIRE(s[s.size() - 1] == 'd'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns d") - { - REQUIRE(s.back() == 'd'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 4") - { - REQUIRE(std::strlen(s.c_str()) == 4); - } - - THEN("the string converts to a string view of length 4") - { - auto view = static_cast(s); - REQUIRE(view.length() == 4); - } - } - - WHEN("constructing from a non-null pointer and a size of 20") - { - auto ptr = "abcdefghijABCDEFGHIJ"; - auto s = kstd::string{ptr, 20}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 20") - { - REQUIRE(s.size() == 20); - } - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - - THEN("the capacity is equal to 20") - { - REQUIRE(s.capacity() == 20); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns J") - { - REQUIRE(s.at(s.size() - 1) == 'J'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns J") - { - REQUIRE(s[s.size() - 1] == 'J'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns J") - { - REQUIRE(s.back() == 'J'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 20") - { - REQUIRE(std::strlen(s.c_str()) == 20); - } - - THEN("the string converts to a string view of length 20") - { - auto view = static_cast(s); - REQUIRE(view.length() == 20); - } - } - - WHEN("constructing from a C-style string of length 4") - { - auto ptr = "abcd"; - auto s = kstd::string{ptr}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 4") - { - REQUIRE(s.size() == 4); - } - - THEN("the length is equal to 4") - { - REQUIRE(s.length() == 4); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns d") - { - REQUIRE(s.at(s.size() - 1) == 'd'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns d") - { - REQUIRE(s[s.size() - 1] == 'd'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns d") - { - REQUIRE(s.back() == 'd'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 4") - { - REQUIRE(std::strlen(s.c_str()) == 4); - } - - THEN("the string converts to a string view of length 4") - { - auto view = static_cast(s); - REQUIRE(view.length() == 4); - } - } - - WHEN("constructing from a C-style string of length 20") - { - auto ptr = "abcdefghijABCDEFGHIJ"; - auto s = kstd::string{ptr}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 20") - { - REQUIRE(s.size() == 20); - } - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - - THEN("the capacity is equal to 20") - { - REQUIRE(s.capacity() == 20); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns J") - { - REQUIRE(s.at(s.size() - 1) == 'J'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns J") - { - REQUIRE(s[s.size() - 1] == 'J'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns J") - { - REQUIRE(s.back() == 'J'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 20") - { - REQUIRE(std::strlen(s.c_str()) == 20); - } - - THEN("the string converts to a string view of length 20") - { - auto view = static_cast(s); - REQUIRE(view.length() == 20); - } - } - - WHEN("constructing from a string view of length 4") - { - using namespace std::string_view_literals; - - auto view = "abcd"sv; - auto s = kstd::string{view}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 4") - { - REQUIRE(s.size() == 4); - } - - THEN("the length is equal to 4") - { - REQUIRE(s.length() == 4); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns d") - { - REQUIRE(s.at(s.size() - 1) == 'd'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns d") - { - REQUIRE(s[s.size() - 1] == 'd'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns d") - { - REQUIRE(s.back() == 'd'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 4") - { - REQUIRE(std::strlen(s.c_str()) == 4); - } - - THEN("the string converts to a string view of length 4") - { - auto view = static_cast(s); - REQUIRE(view.length() == 4); - } - } - - WHEN("constructing from a string view of length 20") - { - using namespace std::string_view_literals; - - auto view = "abcdefghijABCDEFGHIJ"sv; - auto s = kstd::string{view}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 20") - { - REQUIRE(s.size() == 20); - } - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - - THEN("the capacity is equal to 20") - { - REQUIRE(s.capacity() == 20); - } - - THEN("at(0) returns a") - { - REQUIRE(s.at(0) == 'a'); - } - - THEN("at(size() - 1) returns J") - { - REQUIRE(s.at(s.size() - 1) == 'J'); - } - - THEN("[0] returns a") - { - REQUIRE(s[0] == 'a'); - } - - THEN("[size() - 1] returns J") - { - REQUIRE(s[s.size() - 1] == 'J'); - } - - THEN("front returns a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back returns J") - { - REQUIRE(s.back() == 'J'); - } - - THEN("data returns a pointer pointing to an a") - { - REQUIRE(*s.data() == 'a'); - } - - THEN("c_str returns a pointer pointing to an a") - { - REQUIRE(*s.c_str() == 'a'); - } - - THEN("c_str points to the start of a c-string of length 20") - { - REQUIRE(std::strlen(s.c_str()) == 20); - } - - THEN("the string converts to a string view of length 20") - { - auto view = static_cast(s); - REQUIRE(view.length() == 20); - } - } - - WHEN("constructing from a substring of a string view of length 4") - { - using namespace std::string_view_literals; - - auto view = "abcd"sv; - auto s = kstd::string{view, 1, 4}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 3") - { - REQUIRE(s.size() == 3); - } - - THEN("the length is equal to 3") - { - REQUIRE(s.length() == 3); - } - - THEN("the capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("at(0) returns b") - { - REQUIRE(s.at(0) == 'b'); - } - - THEN("at(size() - 1) returns d") - { - REQUIRE(s.at(s.size() - 1) == 'd'); - } - - THEN("[0] returns b") - { - REQUIRE(s[0] == 'b'); - } - - THEN("[size() - 1] returns d") - { - REQUIRE(s[s.size() - 1] == 'd'); - } - - THEN("front returns b") - { - REQUIRE(s.front() == 'b'); - } - - THEN("back returns d") - { - REQUIRE(s.back() == 'd'); - } - - THEN("data returns a pointer pointing to an b") - { - REQUIRE(*s.data() == 'b'); - } - - THEN("c_str returns a pointer pointing to an b") - { - REQUIRE(*s.c_str() == 'b'); - } - - THEN("c_str points to the start of a c-string of length 3") - { - REQUIRE(std::strlen(s.c_str()) == 3); - } - - THEN("the string converts to a string view of length 3") - { - auto view = static_cast(s); - REQUIRE(view.length() == 3); - } - } - - WHEN("constructing from a substring of a string view of length 20") - { - using namespace std::string_view_literals; - - auto view = "abcdefghijABCDEFGHIJ"sv; - auto s = kstd::string{view, 1, 20}; - - THEN("the string is not empty") - { - REQUIRE_FALSE(s.empty()); - } - - THEN("the size is equal to 19") - { - REQUIRE(s.size() == 19); - } - - THEN("the length is equal to 19") - { - REQUIRE(s.length() == 19); - } - - THEN("the capacity is equal to 19") - { - REQUIRE(s.capacity() == 19); - } - - THEN("at(0) returns b") - { - REQUIRE(s.at(0) == 'b'); - } - - THEN("at(size() - 1) returns J") - { - REQUIRE(s.at(s.size() - 1) == 'J'); - } - - THEN("[0] returns b") - { - REQUIRE(s[0] == 'b'); - } - - THEN("[size() - 1] returns J") - { - REQUIRE(s[s.size() - 1] == 'J'); - } - - THEN("front returns b") - { - REQUIRE(s.front() == 'b'); - } - - THEN("back returns J") - { - REQUIRE(s.back() == 'J'); - } - - THEN("data returns a pointer pointing to an b") - { - REQUIRE(*s.data() == 'b'); - } - - THEN("c_str returns a pointer pointing to an b") - { - REQUIRE(*s.c_str() == 'b'); - } - - THEN("c_str points to the start of a c-string of length 19") - { - REQUIRE(std::strlen(s.c_str()) == 19); - } - - THEN("the string converts to a string view of length 19") - { - auto view = static_cast(s); - REQUIRE(view.length() == 19); - } - } - } - - GIVEN("An existing short string, of length 10") - { - auto other = kstd::string{"abcdefghij"}; - - WHEN("constructing by copy") - { - auto s = other; - - THEN("the sizes are identical") - { - REQUIRE(s.size() == other.size()); - } - - THEN("the capacities are idendtical") - { - REQUIRE(s.capacity() == other.capacity()); - } - - THEN("the underlying strings are identical") - { - REQUIRE_FALSE(std::strcmp(s.data(), other.data())); - } - } - - WHEN("constructing by move") - { - auto s = std::move(other); - - THEN("size is equal to 10") - { - REQUIRE(s.size() == 10); - } - - THEN("capacity is equal to 15") - { - REQUIRE(s.capacity() == 15); - } - - THEN("front() return an a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back() return a j") - { - REQUIRE(s.back() == 'j'); - } - - THEN("The moved from object's size is 0") - { - REQUIRE(other.size() == 0); - } - - THEN("The moved from object's capacity is 15") - { - REQUIRE(other.capacity() == 15); - } - - THEN("c_str() on the moved from object points to a null byte") - { - REQUIRE(*other.c_str() == '\0'); - } - } - } - - GIVEN("An existing short string, of length 20") - { - auto other = kstd::string{"abcdefghijABCDEFGHIJ"}; - - WHEN("constructing by copy") - { - auto s = other; - - THEN("the sizes are identical") - { - REQUIRE(s.size() == other.size()); - } - - THEN("the capacities are idendtical") - { - REQUIRE(s.capacity() == other.capacity()); - } - - THEN("the underlying strings are identical") - { - REQUIRE_FALSE(std::strcmp(s.data(), other.data())); - } - } - - WHEN("constructing by move") - { - auto s = std::move(other); - - THEN("size is equal to 20") - { - REQUIRE(s.size() == 20); - } - - THEN("capacity is equal to 20") - { - REQUIRE(s.capacity() == 20); - } - - THEN("front() return an a") - { - REQUIRE(s.front() == 'a'); - } - - THEN("back() return a J") - { - REQUIRE(s.back() == 'J'); - } - - THEN("The moved from object's size is 0") - { - REQUIRE(other.size() == 0); - } - - THEN("The moved from object's capacity is 15") - { - REQUIRE(other.capacity() == 15); - } - - THEN("c_str() on the moved from object points to a null byte") - { - REQUIRE(*other.c_str() == '\0'); - } - } - } -} - -SCENARIO("String assignment", "[string]") -{ - GIVEN("Two short strings") - { - auto s1 = kstd::string{"abcd"}; - auto s2 = kstd::string{"defghij"}; - - WHEN("copy assigning the shorter to the longer one") - { - s2 = s1; - - THEN("the new length of the longer one is equal to the length of the shorter one") - { - REQUIRE(s2.length() == s1.length()); - } - - THEN("the underlying data of the two is identical") - { - REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); - } - } - - WHEN("copy assigning the longer to the shorter one") - { - s1 = s2; - - THEN("the new length of the shorter one is equal to the length of the longer one") - { - REQUIRE(s1.length() == s2.length()); - } - - THEN("the underlying data of the two is identical") - { - REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); - } - } - - WHEN("move assigning the shorter to the longer one") - { - s2 = std::move(s1); - - THEN("the new length of the longer one is equal to the original length of the shorter one") - { - REQUIRE(s2.length() == 4); - } - - THEN("the length of the moved from one is 0") - { - REQUIRE(s1.length() == 0); - } - - THEN("the capacity of the moved from one is 15") - { - REQUIRE(s1.capacity() == 15); - } - - THEN("the capacity of the moved to one is 15") - { - REQUIRE(s2.capacity() == 15); - } - } - - WHEN("move assigning the longer to the shorter one") - { - s1 = std::move(s2); - - THEN("the new length of the shorter one is equal to the original length of the longer one") - { - REQUIRE(s1.length() == 7); - } - - THEN("the length of the moved from one is 0") - { - REQUIRE(s2.length() == 0); - } - - THEN("the capacity of the moved from one is 15") - { - REQUIRE(s2.capacity() == 15); - } - - THEN("the capacity of the moved to one is 15") - { - REQUIRE(s1.capacity() == 15); - } - } - } - - GIVEN("One short and one long string") - { - auto s1 = kstd::string{"abcd"}; - auto s2 = kstd::string{"ABCDEFGHIJabcdefghij"}; - - WHEN("copy assigning the shorter to the longer one") - { - s2 = s1; - - THEN("the new length of the longer one is equal to the length of the shorter one") - { - REQUIRE(s2.length() == s1.length()); - } - - THEN("the new capacity of the longer one is 15") - { - REQUIRE(s2.capacity() == 15); - } - - THEN("the underlying data of the two is identical") - { - REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); - } - } - - WHEN("copy assigning the longer to the shorter one") - { - s1 = s2; - - THEN("the new length of the shorter one is equal to the length of the longer one") - { - REQUIRE(s1.length() == s2.length()); - } - - THEN("the new capacity of the shorter one is 20") - { - REQUIRE(s1.capacity() == 20); - } - - THEN("the underlying data of the two is identical") - { - REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); - } - } - - WHEN("move assigning the shorter to the longer one") - { - s2 = std::move(s1); - - THEN("the new length of the longer one is equal to the original length of the shorter one") - { - REQUIRE(s2.length() == 4); - } - - THEN("the length of the moved from one is 0") - { - REQUIRE(s1.length() == 0); - } - - THEN("the capacity of the moved from one is 15") - { - REQUIRE(s1.capacity() == 15); - } - - THEN("the capacity of the moved to one is 15") - { - REQUIRE(s2.capacity() == 15); - } - } - - WHEN("move assigning the longer to the shorter one") - { - s1 = std::move(s2); - - THEN("the new length of the shorter one is equal to the original length of the longer one") - { - REQUIRE(s1.length() == 20); - } - - THEN("the length of the moved from one is 0") - { - REQUIRE(s2.length() == 0); - } - - THEN("the capacity of the moved from one is 15") - { - REQUIRE(s2.capacity() == 15); - } - - THEN("the capacity of the moved to one is 15") - { - REQUIRE(s1.capacity() == 20); - } - } - } - - GIVEN("Two long strings") - { - auto s1 = kstd::string{"abcdefghijABCDEFGH"}; - auto s2 = kstd::string{"ABCDEFGHIJabcdefghij"}; - - WHEN("copy assigning the shorter to the longer one") - { - s2 = s1; - - THEN("the new length of the longer one is equal to the length of the shorter one") - { - REQUIRE(s2.length() == s1.length()); - } - - THEN("the new capacity of the longer one is still 20") - { - REQUIRE(s2.capacity() == 20); - } - - THEN("the underlying data of the two is identical") - { - REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); - } - } - - WHEN("copy assigning the longer to the shorter one") - { - s1 = s2; - - THEN("the new length of the shorter one is equal to the length of the longer one") - { - REQUIRE(s1.length() == s2.length()); - } - - THEN("the new capacity of the shorter one is 20") - { - REQUIRE(s1.capacity() == 20); - } - - THEN("the underlying data of the two is identical") - { - REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); - } - } - - WHEN("move assigning the shorter to the longer one") - { - s2 = std::move(s1); - - THEN("the new length of the longer one is equal to the original length of the shorter one") - { - REQUIRE(s2.length() == 18); - } - - THEN("the length of the moved from one is 0") - { - REQUIRE(s1.length() == 0); - } - - THEN("the capacity of the moved from one is 15") - { - REQUIRE(s1.capacity() == 15); - } - - THEN("the capacity of the moved to one is 15") - { - REQUIRE(s2.capacity() == 18); - } - } - - WHEN("move assigning the longer to the shorter one") - { - s1 = std::move(s2); - - THEN("the new length of the shorter one is equal to the original length of the longer one") - { - REQUIRE(s1.length() == 20); - } - - THEN("the length of the moved from one is 0") - { - REQUIRE(s2.length() == 0); - } - - THEN("the capacity of the moved from one is 15") - { - REQUIRE(s2.capacity() == 15); - } - - THEN("the capacity of the moved to one is 15") - { - REQUIRE(s1.capacity() == 20); - } - } - } - - GIVEN("An short string") - { - auto s = kstd::string{"abcd"}; - - WHEN("assigning a new, shorter c-style string of length 3") - { - s = "def"; - - THEN("the length is equal to 3") - { - REQUIRE(s.length() == 3); - } - } - - WHEN("assigning a new, longer, c-style string of length 5") - { - s = "defgh"; - - THEN("the length is equal to 5") - { - REQUIRE(s.length() == 5); - } - } - - WHEN("assigning a new, longer, c-style string of length 20") - { - s = "ABCDEFGHIJabcdefghij"; - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - } - - WHEN("assigning a single character") - { - s = 'z'; - - THEN("the length is equal to 1") - { - REQUIRE(s.length() == 1); - } - } - - WHEN("assigning an shorter initializer list of length 3") - { - s = {'d', 'e', 'f'}; - - THEN("the length is equal to 3") - { - REQUIRE(s.length() == 3); - } - } - - WHEN("assigning an longer initializer list of length 5") - { - s = {'d', 'e', 'f', 'g', 'h'}; - - THEN("the length is equal to 5") - { - REQUIRE(s.length() == 5); - } - } - - WHEN("assigning an longer initializer list of length 20") - { - s = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - } - - WHEN("assigning from a shorter string view of length 3") - { - using namespace std::string_view_literals; - - s = "def"sv; - - THEN("the length is equal to 3") - { - REQUIRE(s.length() == 3); - } - } - - WHEN("assigning from a longer string view of length 5") - { - using namespace std::string_view_literals; - - s = "defgh"sv; - - THEN("the length is equal to 5") - { - REQUIRE(s.length() == 5); - } - } - - WHEN("assigning from a longer string view of length 20") - { - using namespace std::string_view_literals; - - s = "ABCDEFGHIJabcdefghij"sv; - - THEN("the length is equal to 20") - { - REQUIRE(s.length() == 20); - } - } - } - - GIVEN("An long string") - { - auto s = kstd::string{"abcdefghijABCDEFGHIJ"}; - - WHEN("assigning a new, shorter c-style string of length 3") - { - s = "def"; - - THEN("the length is equal to 3") - { - REQUIRE(s.length() == 3); - } - } - - WHEN("assigning a new, longer, c-style string of length 22") - { - s = "ABCDEFGHIJabcdefghijkl"; - - THEN("the length is equal to 22") - { - REQUIRE(s.length() == 22); - } - } - - WHEN("assigning a single character") - { - s = 'z'; - - THEN("the length is equal to 1") - { - REQUIRE(s.length() == 1); - } - } - - WHEN("assigning an shorter initializer list of length 3") - { - s = {'d', 'e', 'f'}; - - THEN("the length is equal to 3") - { - REQUIRE(s.length() == 3); - } - } - - WHEN("assigning an longer initializer list of length 22") - { - s = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'a', - 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'}; - - THEN("the length is equal to 22") - { - REQUIRE(s.length() == 22); - } - } - - WHEN("assigning from a shorter string view of length 3") - { - using namespace std::string_view_literals; - - s = "def"sv; - - THEN("the length is equal to 3") - { - REQUIRE(s.length() == 3); - } - } - - WHEN("assigning from a longer string view of length 22") - { - using namespace std::string_view_literals; - - s = "ABCDEFGHIJabcdefghijkl"sv; - - THEN("the length is equal to 22") - { - REQUIRE(s.length() == 22); - } - } - } -} - -SCENARIO("String modifiers", "[string]") -{ - GIVEN("A short string") - { - auto s = kstd::string{"abcd"}; - auto old_capacity = s.capacity(); - auto old_size = s.size(); - auto old_data = s.data(); - - WHEN("reserving a smaller capacity") - { - s.reserve(1); - - THEN("the capacity does not change") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the content stays the same") - { - REQUIRE(s == "abcd"); - } - - THEN("the size stays the same") - { - REQUIRE(s.size() == old_size); - } - - THEN("the data pointer stays the same") - { - REQUIRE(s.data() == old_data); - } - } - - WHEN("reserving equal capacity") - { - s.reserve(old_capacity); - - THEN("the capacity does not change") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the content stays the same") - { - REQUIRE(s == "abcd"); - } - - THEN("the size stays the same") - { - REQUIRE(s.size() == old_size); - } - - THEN("the data pointer stays the same") - { - REQUIRE(static_cast(s.data()) == static_cast(old_data)); - } - } - - WHEN("reserving larger capacity") - { - s.reserve(old_capacity + 1); - - THEN("the capacity increases") - { - REQUIRE(s.capacity() >= old_capacity + 1); - } - - THEN("the content stays the same") - { - REQUIRE(s == "abcd"); - } - - THEN("the size stays the same") - { - REQUIRE(s.size() == old_size); - } - - THEN("the data pointer changes") - { - REQUIRE(s.data() != old_data); - } - } - - WHEN("resizing to a greater size within the capacity") - { - s.resize(10); - - THEN("the capacity stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the size increases") - { - CHECK(s.size() > old_size); - REQUIRE(s.size() == 10); - } - - THEN("the string get filled with null characters") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "abcd\0\0\0\0\0\0", s.size() + 1) == 0); - } - } - - WHEN("resizing to a greater size with a given character within the capacity") - { - s.resize(10, 'x'); - - THEN("the capacity stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the size increases") - { - CHECK(s.size() > old_size); - REQUIRE(s.size() == 10); - } - - THEN("the string get filled with the given character") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdxxxxxx", s.size() + 1) == 0); - } - } - - WHEN("resizing to a greater size outside the capacity") - { - s.resize(20); - - THEN("the capacity increases") - { - REQUIRE(s.capacity() > old_capacity); - } - - THEN("the size increases") - { - CHECK(s.size() > old_size); - REQUIRE(s.size() == 20); - } - - THEN("the string get filled with null characters") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "abcd\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", s.size() + 1) == - 0); - } - } - - WHEN("resizing to a greater size with a given character within the capacity") - { - s.resize(20, 'x'); - - THEN("the capacity increases") - { - REQUIRE(s.capacity() > old_capacity); - } - - THEN("the size increases") - { - CHECK(s.size() > old_size); - REQUIRE(s.size() == 20); - } - - THEN("the string get filled with the given character") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdxxxxxxxxxxxxxxxx", s.size() + 1) == 0); - } - } - - WHEN("resizing to a lesser size") - { - s.resize(2); - - THEN("the capacity stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the size decreases") - { - CHECK(s.size() < old_size); - REQUIRE(s.size() == 2); - } - - THEN("the string get filled with the given character") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "ab\0", s.size() + 1) == 0); - } - } - } - - GIVEN("A long string") - { - auto s = kstd::string{"abcdefghijABCDEFGHIJ"}; - auto old_capacity = s.capacity(); - auto old_size = s.size(); - auto old_data = s.data(); - - WHEN("reserving a smaller capacity") - { - s.reserve(1); - - THEN("the capacity does not change") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the content stays the same") - { - REQUIRE(s == "abcdefghijABCDEFGHIJ"); - } - - THEN("the size stays the same") - { - REQUIRE(s.size() == old_size); - } - - THEN("the data pointer stays the same") - { - REQUIRE(s.data() == old_data); - } - } - - WHEN("reserving equal capacity") - { - s.reserve(old_capacity); - - THEN("the capacity does not change") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the content stays the same") - { - REQUIRE(s == "abcdefghijABCDEFGHIJ"); - } - - THEN("the size stays the same") - { - REQUIRE(s.size() == old_size); - } - - THEN("the data pointer stays the same") - { - REQUIRE(static_cast(s.data()) == static_cast(old_data)); - } - } - - WHEN("reserving larger capacity") - { - s.reserve(old_capacity + 1); - - THEN("the capacity increases") - { - REQUIRE(s.capacity() >= old_capacity + 1); - } - - THEN("the content stays the same") - { - REQUIRE(s == "abcdefghijABCDEFGHIJ"); - } - - THEN("the size stays the same") - { - REQUIRE(s.size() == old_size); - } - - THEN("the data pointer changes") - { - REQUIRE(s.data() != old_data); - } - } - - WHEN("resizing to a greater size") - { - s.resize(22); // NOLINT - - THEN("the capacity increases") - { - REQUIRE(s.capacity() > old_capacity); - } - - THEN("the size increases") - { - CHECK(s.size() > old_size); - REQUIRE(s.size() == 22); - } - - THEN("the string get filled with null characters") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdefghijABCDEFGHIJ\0\0", s.size() + 1) == 0); - } - } - - WHEN("resizing to a greater size with a given character within the capacity") - { - s.resize(22, 'x'); // NOLINT - - THEN("the capacity increases") - { - REQUIRE(s.capacity() > old_capacity); - } - - THEN("the size increases") - { - CHECK(s.size() > old_size); - REQUIRE(s.size() == 22); - } - - THEN("the string get filled with the given character") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdefghijABCDEFGHIJxx", s.size() + 1) == 0); - } - } - - WHEN("resizing to a lesser size") - { - s.resize(2); - - THEN("the capacity stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("the size decreases") - { - CHECK(s.size() < old_size); - REQUIRE(s.size() == 2); - } - - THEN("the string get filled with the given character") - { - REQUIRE(kstd::string::traits_type::compare(s.data(), "ab\0", s.size() + 1) == 0); - } - } - } - - GIVEN("Two strings") - { - auto str1 = kstd::string{"Blub"}; - auto str2 = kstd::string{" Blub"}; - - WHEN("appending the second string to the first string") - { - str1.append(str2); - - THEN("the first string contains the characters of both strings concatenated") - { - REQUIRE(str1 == "Blub Blub"); - } - - THEN("the size of the first string is the sum of the sizes of both strings") - { - REQUIRE(str1.size() == str2.size() + 4); - } - } - - WHEN("using operator+= to append the second string to the first string") - { - str1 += str2; - - THEN("the first string contains the characters of both strings concatenated") - { - REQUIRE(str1 == "Blub Blub"); - } - - THEN("the size of the first string is the sum of the sizes of both strings") - { - REQUIRE(str1.size() == str2.size() + 4); - } - } - } - - GIVEN("A string and a string view") - { - auto str = kstd::string{"Blub"}; - auto view = std::string_view{" Blub"}; - - WHEN("appending the string view to the string") - { - str.append(view); - - THEN("the string contains the characters of both the original string and the appended view concatenated") - { - REQUIRE(str == "Blub Blub"); - } - - THEN("the size of the string is the sum of the sizes of the original string and the appended view") - { - REQUIRE(str.size() == view.size() + 4); - } - } - } - - GIVEN("A string and a character") - { - auto str = kstd::string{"Blub"}; - auto ch = '!'; - - WHEN("appending the character to the string") - { - str.push_back(ch); - - THEN("the string contains the original characters followed by the appended character") - { - REQUIRE(str == "Blub!"); - } - - THEN("the size of the string is one more than the original size") - { - REQUIRE(str.size() == 5); - } - } - - WHEN("using operator+= to append the character to the string") - { - str += ch; - - THEN("the string contains the original characters followed by the appended character") - { - REQUIRE(str == "Blub!"); - } - - THEN("the size of the string is one more than the original size") - { - REQUIRE(str.size() == 5); - } - } - } - - GIVEN("A non-empty string") - { - auto str = kstd::string{"Blub Blub"}; - - WHEN("clearing the string") - { - str.clear(); - - THEN("the string is empty and has size zero") - { - REQUIRE(str.empty()); - REQUIRE(str.size() == 0); - } - - THEN("the string contains no characters") - { - REQUIRE(str == std::string_view{}); - } - } - } -} - -SCENARIO("String insertion", "[string]") -{ - GIVEN("A short string") - { - auto s = kstd::string{"abcd"}; - auto old_capacity = s.capacity(); - auto old_size = s.size(); - - WHEN("inserting five copies of a character at the beginning") - { - s.insert(0, 5, 'A'); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("The string is equal to the old string with the new characters prepended") - { - REQUIRE(s == kstd::string{"AAAAAabcd"}); - } - } - - WHEN("inserting five copies of a character in the middle") - { - s.insert(2, 5, 'A'); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("The string is equal to the old string with the new characters inserted") - { - REQUIRE(s == kstd::string{"abAAAAAcd"}); - } - } - - WHEN("inserting five copies of a character at the end") - { - s.insert(4, 5, 'A'); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("The string is equal to the old string with the new characters appended") - { - REQUIRE(s == kstd::string{"abcdAAAAA"}); - } - } - - WHEN("inserting five copies of a character behind the end") - { - THEN("an OS panic is triggered") - { - REQUIRE_THROWS_AS(s.insert(5, 5, 'A'), kstd::tests::os_panic); - } - } - - WHEN("inserting 20 copies of a character") - { - s.insert(2, 20, 'A'); - - THEN("the capacity of the string grows") - { - REQUIRE(s.capacity() > old_capacity); - } - - THEN("the string is equal t o the old string with the new characters inserted") - { - REQUIRE(s == kstd::string{"abAAAAAAAAAAAAAAAAAAAAcd"}); - } - } - - WHEN("inserting a C-style string of length 5 at the beginning") - { - s.insert(0, "AAAAA"); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("The string is equal to the old string with the new characters prepended") - { - REQUIRE(s == kstd::string{"AAAAAabcd"}); - } - } - - WHEN("inserting a C-style string of length 5 in the middle") - { - s.insert(2, "AAAAA"); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("The string is equal to the old string with the new characters inserted") - { - REQUIRE(s == kstd::string{"abAAAAAcd"}); - } - } - - WHEN("inserting a C-style string of length 5 at the end") - { - s.insert(4, "AAAAA"); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string stays the same") - { - REQUIRE(s.capacity() == old_capacity); - } - - THEN("The string is equal to the old string with the new characters appended") - { - REQUIRE(s == kstd::string{"abcdAAAAA"}); - } - } - - WHEN("inserting a C-style string of length 5 behind the end") - { - THEN("an OS panic is triggered") - { - REQUIRE_THROWS_AS(s.insert(5, "AAAAA"), kstd::tests::os_panic); - } - } - } - - GIVEN("A long string") - { - auto s = kstd::string{"abcdefghijABCDEFGHIJ"}; - auto old_capacity = s.capacity(); - auto old_size = s.size(); - - WHEN("inserting five copies of a character at the beginning") - { - s.insert(0, 5, 'A'); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string increases") - { - REQUIRE(s.capacity() >= old_capacity + 5); - } - - THEN("The string is equal to the old string with the new characters prepended") - { - REQUIRE(s == kstd::string{"AAAAAabcdefghijABCDEFGHIJ"}); - } - } - - WHEN("inserting five copies of a character in the middle") - { - s.insert(10, 5, 'A'); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string increases") - { - REQUIRE(s.capacity() >= old_capacity + 5); - } - - THEN("The string is equal to the old string with the new characters inserted") - { - REQUIRE(s == kstd::string{"abcdefghijAAAAAABCDEFGHIJ"}); - } - } - - WHEN("inserting five copies of a character at the end") - { - s.insert(20, 5, 'A'); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string increases") - { - REQUIRE(s.capacity() >= old_capacity + 5); - } - - THEN("The string is equal to the old string with the new characters appended") - { - REQUIRE(s == kstd::string{"abcdefghijABCDEFGHIJAAAAA"}); - } - } - - WHEN("inserting five copies of a character behind the end") - { - THEN("an OS panic is triggered") - { - REQUIRE_THROWS_AS(s.insert(21, 5, 'A'), kstd::tests::os_panic); - } - } - - WHEN("inserting a C-style string of length 5 at the beginning") - { - s.insert(0, "AAAAA"); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string increases") - { - REQUIRE(s.capacity() >= old_capacity + 5); - } - - THEN("The string is equal to the old string with the new characters prepended") - { - REQUIRE(s == kstd::string{"AAAAAabcdefghijABCDEFGHIJ"}); - } - } - - WHEN("inserting a C-style string of length 5 in the middle") - { - s.insert(10, "AAAAA"); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the string increases") - { - REQUIRE(s.capacity() >= old_capacity + 5); - } - - THEN("The string is equal to the old string with the new characters inserted") - { - REQUIRE(s == kstd::string{"abcdefghijAAAAAABCDEFGHIJ"}); - } - } - - WHEN("inserting a C-style string of length 5 at the end") - { - s.insert(20, "AAAAA"); - - THEN("the size of the string is increased by 5") - { - REQUIRE(s.size() == old_size + 5); - } - - THEN("The capacity of the increases") - { - REQUIRE(s.capacity() >= old_capacity + 5); - } - - THEN("The string is equal to the old string with the new characters appended") - { - REQUIRE(s == kstd::string{"abcdefghijABCDEFGHIJAAAAA"}); - } - } - - WHEN("inserting a C-style string of length 5 behind the end") - { - THEN("an OS panic is triggered") - { - REQUIRE_THROWS_AS(s.insert(21, "AAAAA"), kstd::tests::os_panic); - } - } - } -} - -SCENARIO("String concatenation", "[string]") -{ - GIVEN("Two strings") - { - auto str1 = kstd::string{"Blub"}; - auto str2 = kstd::string{" Blub"}; - - WHEN("using operator+ to concatenate the two strings into a new string") - { - auto str3 = str1 + str2; - - THEN("the new string contains the characters of both strings concatenated") - { - REQUIRE(str3 == "Blub Blub"); - } - - THEN("the size of the new string is the sum of the sizes of both strings") - { - REQUIRE(str3.size() == str1.size() + str2.size()); - } - } - } -} - -SCENARIO("String conversion and comparison", "[string]") -{ - GIVEN("An unsigned integer") - { - constexpr auto value1 = 12345u; - constexpr auto value2 = 0u; - - WHEN("converting the unsigned integer to a string") - { - auto str1 = kstd::to_string(value1); - auto str2 = kstd::to_string(value2); - - THEN("the string contains the decimal representation of the unsigned integer") - { - REQUIRE(str1 == "12345"); - REQUIRE(str2 == "0"); - } - } - } - - GIVEN("Two strings with the same characters") - { - auto str1 = kstd::string{"Blub Blub"}; - auto str2 = kstd::string{"Blub Blub"}; - - THEN("the strings are equal") - { - REQUIRE(str1 == str2); - } - - THEN("the strings are not unequal") - { - REQUIRE_FALSE(str1 != str2); - } - } - - GIVEN("A string and a string view with the same characters") - { - auto str = kstd::string{"Blub Blub"}; - auto view = std::string_view{"Blub Blub"}; - - THEN("the string and the string view are equal") - { - REQUIRE(str == view); - REQUIRE(view == str); - } - - THEN("the string and the string view are not unequal") - { - REQUIRE_FALSE(str != view); - REQUIRE_FALSE(view != str); - } - } - - GIVEN("The string 'abc'") - { - auto s1 = kstd::string{"abc"}; - - WHEN("comparing with the string 'def'") - { - auto result = s1.compare(kstd::string{"def"}); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing with the string 'abb'") - { - auto result = s1.compare(kstd::string{"abb"}); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing with the string 'abc'") - { - auto result = s1.compare(kstd::string{"abc"}); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,2] with the string 'def'") - { - auto result = s1.compare(1, 2, kstd::string{"def"}); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,2] with the string 'abb'") - { - auto result = s1.compare(1, 2, kstd::string{"abb"}); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,2] with the string 'cc'") - { - auto result = s1.compare(1, 2, kstd::string{"bc"}); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,npos] with the string 'ef'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"ef"}); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,npos] with the string 'bb'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"bb"}); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,npos] with the string 'bc'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"bc"}); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,2] with the substring [1,2] of 'def'") - { - auto result = s1.compare(1, 2, kstd::string{"def"}, 1, 2); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,2] with the substring [1,2] of 'abb'") - { - auto result = s1.compare(1, 2, kstd::string{"abb"}, 1, 2); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,2] with the substring [1,2] of 'abc'") - { - auto result = s1.compare(1, 2, kstd::string{"abc"}, 1, 2); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,npos] with the substring [1,2] of 'def'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"def"}, 1, 2); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,npos] with the substring [1,2] of 'abb'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"abb"}, 1, 2); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,npos] with the substring [1,2] of 'abc'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"abc"}, 1, 2); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,2] with the substring [1,npos] of 'def'") - { - auto result = s1.compare(1, 2, kstd::string{"def"}, 1, kstd::string::npos); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,2] with the substring [1,npos] of 'abb'") - { - auto result = s1.compare(1, 2, kstd::string{"abb"}, 1, kstd::string::npos); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,2] with the substring [1,npos] of 'abc'") - { - auto result = s1.compare(1, 2, kstd::string{"abc"}, 1, kstd::string::npos); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,npos] with the substring [1,npos] of 'def'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"def"}, 1, kstd::string::npos); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,npos] with the substring [1,npos] of 'abb'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"abb"}, 1, kstd::string::npos); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,npos] with the substring [1,npos] of 'abc'") - { - auto result = s1.compare(1, kstd::string::npos, kstd::string{"abc"}, 1, kstd::string::npos); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing with the C-style string 'def'") - { - auto result = s1.compare("def"); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing with the C-style string 'abb'") - { - auto result = s1.compare("abb"); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing with the C-style string 'abc'") - { - auto result = s1.compare("abc"); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,2] with the C-style string 'def'") - { - auto result = s1.compare(1, 2, "def"); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,2] with the C-style string 'abb'") - { - auto result = s1.compare(1, 2, "abb"); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,2] with the C-style string 'bc'") - { - auto result = s1.compare(1, 2, "bc"); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - - WHEN("comparing substring [1,npos] with the C-style string 'def'") - { - auto result = s1.compare(1, 2, "ef"); - - THEN("the result is negative") - { - REQUIRE(result < 0); - } - } - - WHEN("comparing substring [1,npos] with the C-style string 'abb'") - { - auto result = s1.compare(1, 2, "abb"); - - THEN("the result is positive") - { - REQUIRE(result > 0); - } - } - - WHEN("comparing substring [1,npos] with the C-style string 'bc'") - { - auto result = s1.compare(1, 2, "bc"); - - THEN("the result is zero") - { - REQUIRE(result == 0); - } - } - } -} - -SCENARIO("String iteration", "[string]") -{ - GIVEN("A string") - { - auto str = kstd::string{"Blub"}; - - WHEN("iterating over the characters of the string as string_view using a range-based for loop") - { - kstd::string result; - - for (auto ch : static_cast(str)) - { - result.push_back(ch); - } - - THEN("the iterated characters are the same as the characters in the string") - { - REQUIRE(result == str); - } - } - - WHEN("using std::ranges::for_each to iterate over the characters of the string") - { - kstd::string result; - - std::ranges::for_each(str, [&result](auto ch) { result.push_back(ch); }); - - THEN("the iterated characters are the same as the characters in the string") - { - REQUIRE(result == str); - } - } - - WHEN("using front and back to access the first and last characters of the string") - { - THEN("front returns the first character of the string") - { - REQUIRE(str.front() == 'B'); - } - - THEN("back returns the last character of the string") - { - REQUIRE(str.back() == 'b'); - } - } - } - - GIVEN("A const string") - { - auto const str = kstd::string{"Blub"}; - - WHEN("iterating over the characters of the string as string_view using a range-based for loop") - { - kstd::string result; - - for (auto ch : static_cast(str)) - { - result.push_back(ch); - } - - THEN("the iterated characters are the same as the characters in the string") - { - REQUIRE(result == static_cast(str)); - } - } - - WHEN("using front and back to access the first and last characters of the string") - { - THEN("front returns the first character of the string") - { - REQUIRE(str.front() == 'B'); - } - - THEN("back returns the last character of the string") - { - REQUIRE(str.back() == 'b'); - } - } - } - - GIVEN("An empty string") - { - auto str = kstd::string{}; - - WHEN("iterating over the characters of an empty string") - { - kstd::string result; - - for (auto ch : static_cast(str)) - { - result.push_back(ch); - } - - THEN("no characters are iterated and the result is an empty string") - { - REQUIRE(result.empty()); - REQUIRE(result.size() == 0); - REQUIRE(static_cast(result) == std::string_view{}); - } - } - } -} - -SCENARIO("String STL integration", "[string]") -{ - GIVEN("An empty string") - { - auto s = kstd::string{""}; - - WHEN("hashing the string") - { - auto hash_result = std::hash{}(s); - - THEN("the result is equal to hashing an empty std::string") - { - REQUIRE(hash_result == std::hash{}("")); - } - } - } - - GIVEN("A non-empty string") - { - auto s = kstd::string{"abcd"}; - - WHEN("hashing the string") - { - auto hash_result = std::hash{}(s); - - THEN("the result is equal to hashing the same std::string") - { - REQUIRE(hash_result == std::hash{}("abcd")); - } - } - } -} - -SCENARIO("String user-defined literals") -{ - GIVEN("the user defined literals are in scope") - { - using namespace kstd::string_literals; - - WHEN("creating a string using the _s literal suffix") - { - auto s = "abcd"_s; - - THEN("the result is equal to the string created with the same literal") - { - REQUIRE(s == kstd::string{"abcd"}); - } - } - } -} diff --git a/libs/kstd/kstd/string.tests.cpp b/libs/kstd/kstd/string.tests.cpp new file mode 100644 index 00000000..b8fa1139 --- /dev/null +++ b/libs/kstd/kstd/string.tests.cpp @@ -0,0 +1,3166 @@ +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE("String static interface") +{ + REQUIRE(std::is_same_v>); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v>); + REQUIRE(std::is_same_v>::size_type>); + REQUIRE(std::is_same_v>::difference_type>); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v>::pointer>); + REQUIRE(std::is_same_v>::const_pointer>); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v); + REQUIRE(std::is_same_v>); + REQUIRE(std::is_same_v>); + + REQUIRE(kstd::string::npos == kstd::string::size_type(-1)); +} + +SCENARIO("String initialization and construction", "[string]") +{ + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto s = kstd::string{}; + + THEN("the string is empty") + { + REQUIRE(s.empty()); + } + + THEN("the size is equal to zero") + { + REQUIRE(s.size() == 0); + } + + THEN("the length is equal to zero") + { + REQUIRE(s.length() == 0); + } + + THEN("at raises a panic") + { + REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); + } + + THEN("data returns a pointer poiting to a null byte") + { + REQUIRE(*s.data() == '\0'); + } + + THEN("c_str returns a pointer poiting to a null byte") + { + REQUIRE(*s.c_str() == '\0'); + } + + THEN("the string is convertible to an empty string view") + { + auto view = static_cast(s); + REQUIRE(view.empty()); + } + } + + WHEN("constructing with 10 copies of the letter 'a'") + { + auto s = kstd::string{10, 'a'}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 10") + { + REQUIRE(s.size() == 10); + } + + THEN("the length is equal to 10") + { + REQUIRE(s.length() == 10); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns a") + { + REQUIRE(s.at(s.size() - 1) == 'a'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns a") + { + REQUIRE(s[s.size() - 1] == 'a'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns a") + { + REQUIRE(s.back() == 'a'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 10") + { + REQUIRE(std::strlen(s.c_str()) == 10); + } + + THEN("the string converts to a string view of length 10") + { + auto view = static_cast(s); + REQUIRE(view.length() == 10); + } + } + + WHEN("constructing with 20 copies of the letter 'a'") + { + auto s = kstd::string{20, 'a'}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns a") + { + REQUIRE(s.at(s.size() - 1) == 'a'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns a") + { + REQUIRE(s[s.size() - 1] == 'a'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns a") + { + REQUIRE(s.back() == 'a'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing with an input iterator pair of 10 characters") + { + auto source = std::istringstream{"abcdefghij"}; + auto s = kstd::string{std::istream_iterator{source}, std::istream_iterator{}}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 10") + { + REQUIRE(s.size() == 10); + } + + THEN("the length is equal to 10") + { + REQUIRE(s.length() == 10); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns j") + { + REQUIRE(s.at(s.size() - 1) == 'j'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns j") + { + REQUIRE(s[s.size() - 1] == 'j'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns j") + { + REQUIRE(s.back() == 'j'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 10") + { + REQUIRE(std::strlen(s.c_str()) == 10); + } + + THEN("the string converts to a string view of length 10") + { + auto view = static_cast(s); + REQUIRE(view.length() == 10); + } + } + + WHEN("constructing with an input iterator pair of 20 characters") + { + auto source = std::istringstream{"abcdefghijABCDEFGHIJ"}; + auto s = kstd::string{std::istream_iterator{source}, std::istream_iterator{}}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is greater than or equal to 20") + { + REQUIRE(s.capacity() >= 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing with a forward iterator pair of 10 characters") + { + auto source = std::forward_list{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; + auto s = kstd::string{source.cbegin(), source.cend()}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 10") + { + REQUIRE(s.size() == 10); + } + + THEN("the length is equal to 10") + { + REQUIRE(s.length() == 10); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns j") + { + REQUIRE(s.at(s.size() - 1) == 'j'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns j") + { + REQUIRE(s[s.size() - 1] == 'j'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns j") + { + REQUIRE(s.back() == 'j'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 10") + { + REQUIRE(std::strlen(s.c_str()) == 10); + } + + THEN("the string converts to a string view of length 10") + { + auto view = static_cast(s); + REQUIRE(view.length() == 10); + } + } + + WHEN("constructing with a forward iterator pair of 20 characters") + { + auto source = std::forward_list{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'}; + auto s = kstd::string{source.cbegin(), source.cend()}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing from a null pointer with zero size") + { + auto s = kstd::string{static_cast(nullptr), 0}; + + THEN("the string is empty") + { + REQUIRE(s.empty()); + } + + THEN("the size is equal to 0") + { + REQUIRE(s.size() == 0); + } + + THEN("the length is equal to 0") + { + REQUIRE(s.length() == 0); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at raises a panic") + { + REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); + } + + THEN("data returns a pointer poiting to a null byte") + { + REQUIRE(*s.data() == '\0'); + } + + THEN("c_str returns a pointer poiting to a null byte") + { + REQUIRE(*s.c_str() == '\0'); + } + + THEN("the string is convertible to an empty string view") + { + auto view = static_cast(s); + REQUIRE(view.empty()); + } + } + + WHEN("constructing from a from a null pointer and non-zero size") + { + THEN("a panic is raised") + { + REQUIRE_THROWS_AS((kstd::string{static_cast(nullptr), 1}), kstd::tests::os_panic); + } + } + + WHEN("constructing from a non-null pointer and zero size") + { + auto ptr = "abcd"; + auto s = kstd::string{ptr, 0}; + + THEN("the string is empty") + { + REQUIRE(s.empty()); + } + + THEN("the size is equal to 0") + { + REQUIRE(s.size() == 0); + } + + THEN("the length is equal to 0") + { + REQUIRE(s.length() == 0); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at raises a panic") + { + REQUIRE_THROWS_AS(s.at(0), kstd::tests::os_panic); + } + + THEN("data returns a pointer poiting to a null byte") + { + REQUIRE(*s.data() == '\0'); + } + + THEN("c_str returns a pointer poiting to a null byte") + { + REQUIRE(*s.c_str() == '\0'); + } + + THEN("the string is convertible to an empty string view") + { + auto view = static_cast(s); + REQUIRE(view.empty()); + } + } + + WHEN("constructing from a non-null pointer and a size of 4") + { + auto ptr = "abcd"; + auto s = kstd::string{ptr, 4}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 4") + { + REQUIRE(s.size() == 4); + } + + THEN("the length is equal to 4") + { + REQUIRE(s.length() == 4); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns d") + { + REQUIRE(s.at(s.size() - 1) == 'd'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns d") + { + REQUIRE(s[s.size() - 1] == 'd'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns d") + { + REQUIRE(s.back() == 'd'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 4") + { + REQUIRE(std::strlen(s.c_str()) == 4); + } + + THEN("the string converts to a string view of length 4") + { + auto view = static_cast(s); + REQUIRE(view.length() == 4); + } + } + + WHEN("constructing from a non-null pointer and a size of 20") + { + auto ptr = "abcdefghijABCDEFGHIJ"; + auto s = kstd::string{ptr, 20}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing from a C-style string of length 4") + { + auto ptr = "abcd"; + auto s = kstd::string{ptr}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 4") + { + REQUIRE(s.size() == 4); + } + + THEN("the length is equal to 4") + { + REQUIRE(s.length() == 4); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns d") + { + REQUIRE(s.at(s.size() - 1) == 'd'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns d") + { + REQUIRE(s[s.size() - 1] == 'd'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns d") + { + REQUIRE(s.back() == 'd'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 4") + { + REQUIRE(std::strlen(s.c_str()) == 4); + } + + THEN("the string converts to a string view of length 4") + { + auto view = static_cast(s); + REQUIRE(view.length() == 4); + } + } + + WHEN("constructing from a C-style string of length 20") + { + auto ptr = "abcdefghijABCDEFGHIJ"; + auto s = kstd::string{ptr}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing from a string view of length 4") + { + using namespace std::string_view_literals; + + auto view = "abcd"sv; + auto s = kstd::string{view}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 4") + { + REQUIRE(s.size() == 4); + } + + THEN("the length is equal to 4") + { + REQUIRE(s.length() == 4); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns d") + { + REQUIRE(s.at(s.size() - 1) == 'd'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns d") + { + REQUIRE(s[s.size() - 1] == 'd'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns d") + { + REQUIRE(s.back() == 'd'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 4") + { + REQUIRE(std::strlen(s.c_str()) == 4); + } + + THEN("the string converts to a string view of length 4") + { + auto view = static_cast(s); + REQUIRE(view.length() == 4); + } + } + + WHEN("constructing from a string view of length 20") + { + using namespace std::string_view_literals; + + auto view = "abcdefghijABCDEFGHIJ"sv; + auto s = kstd::string{view}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + + THEN("the capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("at(0) returns a") + { + REQUIRE(s.at(0) == 'a'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns a") + { + REQUIRE(s[0] == 'a'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an a") + { + REQUIRE(*s.data() == 'a'); + } + + THEN("c_str returns a pointer pointing to an a") + { + REQUIRE(*s.c_str() == 'a'); + } + + THEN("c_str points to the start of a c-string of length 20") + { + REQUIRE(std::strlen(s.c_str()) == 20); + } + + THEN("the string converts to a string view of length 20") + { + auto view = static_cast(s); + REQUIRE(view.length() == 20); + } + } + + WHEN("constructing from a substring of a string view of length 4") + { + using namespace std::string_view_literals; + + auto view = "abcd"sv; + auto s = kstd::string{view, 1, 4}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 3") + { + REQUIRE(s.size() == 3); + } + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + + THEN("the capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("at(0) returns b") + { + REQUIRE(s.at(0) == 'b'); + } + + THEN("at(size() - 1) returns d") + { + REQUIRE(s.at(s.size() - 1) == 'd'); + } + + THEN("[0] returns b") + { + REQUIRE(s[0] == 'b'); + } + + THEN("[size() - 1] returns d") + { + REQUIRE(s[s.size() - 1] == 'd'); + } + + THEN("front returns b") + { + REQUIRE(s.front() == 'b'); + } + + THEN("back returns d") + { + REQUIRE(s.back() == 'd'); + } + + THEN("data returns a pointer pointing to an b") + { + REQUIRE(*s.data() == 'b'); + } + + THEN("c_str returns a pointer pointing to an b") + { + REQUIRE(*s.c_str() == 'b'); + } + + THEN("c_str points to the start of a c-string of length 3") + { + REQUIRE(std::strlen(s.c_str()) == 3); + } + + THEN("the string converts to a string view of length 3") + { + auto view = static_cast(s); + REQUIRE(view.length() == 3); + } + } + + WHEN("constructing from a substring of a string view of length 20") + { + using namespace std::string_view_literals; + + auto view = "abcdefghijABCDEFGHIJ"sv; + auto s = kstd::string{view, 1, 20}; + + THEN("the string is not empty") + { + REQUIRE_FALSE(s.empty()); + } + + THEN("the size is equal to 19") + { + REQUIRE(s.size() == 19); + } + + THEN("the length is equal to 19") + { + REQUIRE(s.length() == 19); + } + + THEN("the capacity is equal to 19") + { + REQUIRE(s.capacity() == 19); + } + + THEN("at(0) returns b") + { + REQUIRE(s.at(0) == 'b'); + } + + THEN("at(size() - 1) returns J") + { + REQUIRE(s.at(s.size() - 1) == 'J'); + } + + THEN("[0] returns b") + { + REQUIRE(s[0] == 'b'); + } + + THEN("[size() - 1] returns J") + { + REQUIRE(s[s.size() - 1] == 'J'); + } + + THEN("front returns b") + { + REQUIRE(s.front() == 'b'); + } + + THEN("back returns J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("data returns a pointer pointing to an b") + { + REQUIRE(*s.data() == 'b'); + } + + THEN("c_str returns a pointer pointing to an b") + { + REQUIRE(*s.c_str() == 'b'); + } + + THEN("c_str points to the start of a c-string of length 19") + { + REQUIRE(std::strlen(s.c_str()) == 19); + } + + THEN("the string converts to a string view of length 19") + { + auto view = static_cast(s); + REQUIRE(view.length() == 19); + } + } + } + + GIVEN("An existing short string, of length 10") + { + auto other = kstd::string{"abcdefghij"}; + + WHEN("constructing by copy") + { + auto s = other; + + THEN("the sizes are identical") + { + REQUIRE(s.size() == other.size()); + } + + THEN("the capacities are idendtical") + { + REQUIRE(s.capacity() == other.capacity()); + } + + THEN("the underlying strings are identical") + { + REQUIRE_FALSE(std::strcmp(s.data(), other.data())); + } + } + + WHEN("constructing by move") + { + auto s = std::move(other); + + THEN("size is equal to 10") + { + REQUIRE(s.size() == 10); + } + + THEN("capacity is equal to 15") + { + REQUIRE(s.capacity() == 15); + } + + THEN("front() return an a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back() return a j") + { + REQUIRE(s.back() == 'j'); + } + + THEN("The moved from object's size is 0") + { + REQUIRE(other.size() == 0); + } + + THEN("The moved from object's capacity is 15") + { + REQUIRE(other.capacity() == 15); + } + + THEN("c_str() on the moved from object points to a null byte") + { + REQUIRE(*other.c_str() == '\0'); + } + } + } + + GIVEN("An existing short string, of length 20") + { + auto other = kstd::string{"abcdefghijABCDEFGHIJ"}; + + WHEN("constructing by copy") + { + auto s = other; + + THEN("the sizes are identical") + { + REQUIRE(s.size() == other.size()); + } + + THEN("the capacities are idendtical") + { + REQUIRE(s.capacity() == other.capacity()); + } + + THEN("the underlying strings are identical") + { + REQUIRE_FALSE(std::strcmp(s.data(), other.data())); + } + } + + WHEN("constructing by move") + { + auto s = std::move(other); + + THEN("size is equal to 20") + { + REQUIRE(s.size() == 20); + } + + THEN("capacity is equal to 20") + { + REQUIRE(s.capacity() == 20); + } + + THEN("front() return an a") + { + REQUIRE(s.front() == 'a'); + } + + THEN("back() return a J") + { + REQUIRE(s.back() == 'J'); + } + + THEN("The moved from object's size is 0") + { + REQUIRE(other.size() == 0); + } + + THEN("The moved from object's capacity is 15") + { + REQUIRE(other.capacity() == 15); + } + + THEN("c_str() on the moved from object points to a null byte") + { + REQUIRE(*other.c_str() == '\0'); + } + } + } +} + +SCENARIO("String assignment", "[string]") +{ + GIVEN("Two short strings") + { + auto s1 = kstd::string{"abcd"}; + auto s2 = kstd::string{"defghij"}; + + WHEN("copy assigning the shorter to the longer one") + { + s2 = s1; + + THEN("the new length of the longer one is equal to the length of the shorter one") + { + REQUIRE(s2.length() == s1.length()); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); + } + } + + WHEN("copy assigning the longer to the shorter one") + { + s1 = s2; + + THEN("the new length of the shorter one is equal to the length of the longer one") + { + REQUIRE(s1.length() == s2.length()); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); + } + } + + WHEN("move assigning the shorter to the longer one") + { + s2 = std::move(s1); + + THEN("the new length of the longer one is equal to the original length of the shorter one") + { + REQUIRE(s2.length() == 4); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s1.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s1.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s2.capacity() == 15); + } + } + + WHEN("move assigning the longer to the shorter one") + { + s1 = std::move(s2); + + THEN("the new length of the shorter one is equal to the original length of the longer one") + { + REQUIRE(s1.length() == 7); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s2.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s2.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s1.capacity() == 15); + } + } + } + + GIVEN("One short and one long string") + { + auto s1 = kstd::string{"abcd"}; + auto s2 = kstd::string{"ABCDEFGHIJabcdefghij"}; + + WHEN("copy assigning the shorter to the longer one") + { + s2 = s1; + + THEN("the new length of the longer one is equal to the length of the shorter one") + { + REQUIRE(s2.length() == s1.length()); + } + + THEN("the new capacity of the longer one is 15") + { + REQUIRE(s2.capacity() == 15); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); + } + } + + WHEN("copy assigning the longer to the shorter one") + { + s1 = s2; + + THEN("the new length of the shorter one is equal to the length of the longer one") + { + REQUIRE(s1.length() == s2.length()); + } + + THEN("the new capacity of the shorter one is 20") + { + REQUIRE(s1.capacity() == 20); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); + } + } + + WHEN("move assigning the shorter to the longer one") + { + s2 = std::move(s1); + + THEN("the new length of the longer one is equal to the original length of the shorter one") + { + REQUIRE(s2.length() == 4); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s1.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s1.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s2.capacity() == 15); + } + } + + WHEN("move assigning the longer to the shorter one") + { + s1 = std::move(s2); + + THEN("the new length of the shorter one is equal to the original length of the longer one") + { + REQUIRE(s1.length() == 20); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s2.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s2.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s1.capacity() == 20); + } + } + } + + GIVEN("Two long strings") + { + auto s1 = kstd::string{"abcdefghijABCDEFGH"}; + auto s2 = kstd::string{"ABCDEFGHIJabcdefghij"}; + + WHEN("copy assigning the shorter to the longer one") + { + s2 = s1; + + THEN("the new length of the longer one is equal to the length of the shorter one") + { + REQUIRE(s2.length() == s1.length()); + } + + THEN("the new capacity of the longer one is still 20") + { + REQUIRE(s2.capacity() == 20); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); + } + } + + WHEN("copy assigning the longer to the shorter one") + { + s1 = s2; + + THEN("the new length of the shorter one is equal to the length of the longer one") + { + REQUIRE(s1.length() == s2.length()); + } + + THEN("the new capacity of the shorter one is 20") + { + REQUIRE(s1.capacity() == 20); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s2.data(), s1.data())); + } + } + + WHEN("move assigning the shorter to the longer one") + { + s2 = std::move(s1); + + THEN("the new length of the longer one is equal to the original length of the shorter one") + { + REQUIRE(s2.length() == 18); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s1.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s1.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s2.capacity() == 18); + } + } + + WHEN("move assigning the longer to the shorter one") + { + s1 = std::move(s2); + + THEN("the new length of the shorter one is equal to the original length of the longer one") + { + REQUIRE(s1.length() == 20); + } + + THEN("the length of the moved from one is 0") + { + REQUIRE(s2.length() == 0); + } + + THEN("the capacity of the moved from one is 15") + { + REQUIRE(s2.capacity() == 15); + } + + THEN("the capacity of the moved to one is 15") + { + REQUIRE(s1.capacity() == 20); + } + } + } + + GIVEN("An short string") + { + auto s = kstd::string{"abcd"}; + + WHEN("assigning a new, shorter c-style string of length 3") + { + s = "def"; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning a new, longer, c-style string of length 5") + { + s = "defgh"; + + THEN("the length is equal to 5") + { + REQUIRE(s.length() == 5); + } + } + + WHEN("assigning a new, longer, c-style string of length 20") + { + s = "ABCDEFGHIJabcdefghij"; + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + } + + WHEN("assigning a single character") + { + s = 'z'; + + THEN("the length is equal to 1") + { + REQUIRE(s.length() == 1); + } + } + + WHEN("assigning an shorter initializer list of length 3") + { + s = {'d', 'e', 'f'}; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning an longer initializer list of length 5") + { + s = {'d', 'e', 'f', 'g', 'h'}; + + THEN("the length is equal to 5") + { + REQUIRE(s.length() == 5); + } + } + + WHEN("assigning an longer initializer list of length 20") + { + s = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + } + + WHEN("assigning from a shorter string view of length 3") + { + using namespace std::string_view_literals; + + s = "def"sv; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning from a longer string view of length 5") + { + using namespace std::string_view_literals; + + s = "defgh"sv; + + THEN("the length is equal to 5") + { + REQUIRE(s.length() == 5); + } + } + + WHEN("assigning from a longer string view of length 20") + { + using namespace std::string_view_literals; + + s = "ABCDEFGHIJabcdefghij"sv; + + THEN("the length is equal to 20") + { + REQUIRE(s.length() == 20); + } + } + } + + GIVEN("An long string") + { + auto s = kstd::string{"abcdefghijABCDEFGHIJ"}; + + WHEN("assigning a new, shorter c-style string of length 3") + { + s = "def"; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning a new, longer, c-style string of length 22") + { + s = "ABCDEFGHIJabcdefghijkl"; + + THEN("the length is equal to 22") + { + REQUIRE(s.length() == 22); + } + } + + WHEN("assigning a single character") + { + s = 'z'; + + THEN("the length is equal to 1") + { + REQUIRE(s.length() == 1); + } + } + + WHEN("assigning an shorter initializer list of length 3") + { + s = {'d', 'e', 'f'}; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning an longer initializer list of length 22") + { + s = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'a', + 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'}; + + THEN("the length is equal to 22") + { + REQUIRE(s.length() == 22); + } + } + + WHEN("assigning from a shorter string view of length 3") + { + using namespace std::string_view_literals; + + s = "def"sv; + + THEN("the length is equal to 3") + { + REQUIRE(s.length() == 3); + } + } + + WHEN("assigning from a longer string view of length 22") + { + using namespace std::string_view_literals; + + s = "ABCDEFGHIJabcdefghijkl"sv; + + THEN("the length is equal to 22") + { + REQUIRE(s.length() == 22); + } + } + } +} + +SCENARIO("String modifiers", "[string]") +{ + GIVEN("A short string") + { + auto s = kstd::string{"abcd"}; + auto old_capacity = s.capacity(); + auto old_size = s.size(); + auto old_data = s.data(); + + WHEN("reserving a smaller capacity") + { + s.reserve(1); + + THEN("the capacity does not change") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the content stays the same") + { + REQUIRE(s == "abcd"); + } + + THEN("the size stays the same") + { + REQUIRE(s.size() == old_size); + } + + THEN("the data pointer stays the same") + { + REQUIRE(s.data() == old_data); + } + } + + WHEN("reserving equal capacity") + { + s.reserve(old_capacity); + + THEN("the capacity does not change") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the content stays the same") + { + REQUIRE(s == "abcd"); + } + + THEN("the size stays the same") + { + REQUIRE(s.size() == old_size); + } + + THEN("the data pointer stays the same") + { + REQUIRE(static_cast(s.data()) == static_cast(old_data)); + } + } + + WHEN("reserving larger capacity") + { + s.reserve(old_capacity + 1); + + THEN("the capacity increases") + { + REQUIRE(s.capacity() >= old_capacity + 1); + } + + THEN("the content stays the same") + { + REQUIRE(s == "abcd"); + } + + THEN("the size stays the same") + { + REQUIRE(s.size() == old_size); + } + + THEN("the data pointer changes") + { + REQUIRE(s.data() != old_data); + } + } + + WHEN("resizing to a greater size within the capacity") + { + s.resize(10); + + THEN("the capacity stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the size increases") + { + CHECK(s.size() > old_size); + REQUIRE(s.size() == 10); + } + + THEN("the string get filled with null characters") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "abcd\0\0\0\0\0\0", s.size() + 1) == 0); + } + } + + WHEN("resizing to a greater size with a given character within the capacity") + { + s.resize(10, 'x'); + + THEN("the capacity stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the size increases") + { + CHECK(s.size() > old_size); + REQUIRE(s.size() == 10); + } + + THEN("the string get filled with the given character") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdxxxxxx", s.size() + 1) == 0); + } + } + + WHEN("resizing to a greater size outside the capacity") + { + s.resize(20); + + THEN("the capacity increases") + { + REQUIRE(s.capacity() > old_capacity); + } + + THEN("the size increases") + { + CHECK(s.size() > old_size); + REQUIRE(s.size() == 20); + } + + THEN("the string get filled with null characters") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "abcd\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", s.size() + 1) == + 0); + } + } + + WHEN("resizing to a greater size with a given character within the capacity") + { + s.resize(20, 'x'); + + THEN("the capacity increases") + { + REQUIRE(s.capacity() > old_capacity); + } + + THEN("the size increases") + { + CHECK(s.size() > old_size); + REQUIRE(s.size() == 20); + } + + THEN("the string get filled with the given character") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdxxxxxxxxxxxxxxxx", s.size() + 1) == 0); + } + } + + WHEN("resizing to a lesser size") + { + s.resize(2); + + THEN("the capacity stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the size decreases") + { + CHECK(s.size() < old_size); + REQUIRE(s.size() == 2); + } + + THEN("the string get filled with the given character") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "ab\0", s.size() + 1) == 0); + } + } + } + + GIVEN("A long string") + { + auto s = kstd::string{"abcdefghijABCDEFGHIJ"}; + auto old_capacity = s.capacity(); + auto old_size = s.size(); + auto old_data = s.data(); + + WHEN("reserving a smaller capacity") + { + s.reserve(1); + + THEN("the capacity does not change") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the content stays the same") + { + REQUIRE(s == "abcdefghijABCDEFGHIJ"); + } + + THEN("the size stays the same") + { + REQUIRE(s.size() == old_size); + } + + THEN("the data pointer stays the same") + { + REQUIRE(s.data() == old_data); + } + } + + WHEN("reserving equal capacity") + { + s.reserve(old_capacity); + + THEN("the capacity does not change") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the content stays the same") + { + REQUIRE(s == "abcdefghijABCDEFGHIJ"); + } + + THEN("the size stays the same") + { + REQUIRE(s.size() == old_size); + } + + THEN("the data pointer stays the same") + { + REQUIRE(static_cast(s.data()) == static_cast(old_data)); + } + } + + WHEN("reserving larger capacity") + { + s.reserve(old_capacity + 1); + + THEN("the capacity increases") + { + REQUIRE(s.capacity() >= old_capacity + 1); + } + + THEN("the content stays the same") + { + REQUIRE(s == "abcdefghijABCDEFGHIJ"); + } + + THEN("the size stays the same") + { + REQUIRE(s.size() == old_size); + } + + THEN("the data pointer changes") + { + REQUIRE(s.data() != old_data); + } + } + + WHEN("resizing to a greater size") + { + s.resize(22); // NOLINT + + THEN("the capacity increases") + { + REQUIRE(s.capacity() > old_capacity); + } + + THEN("the size increases") + { + CHECK(s.size() > old_size); + REQUIRE(s.size() == 22); + } + + THEN("the string get filled with null characters") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdefghijABCDEFGHIJ\0\0", s.size() + 1) == 0); + } + } + + WHEN("resizing to a greater size with a given character within the capacity") + { + s.resize(22, 'x'); // NOLINT + + THEN("the capacity increases") + { + REQUIRE(s.capacity() > old_capacity); + } + + THEN("the size increases") + { + CHECK(s.size() > old_size); + REQUIRE(s.size() == 22); + } + + THEN("the string get filled with the given character") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "abcdefghijABCDEFGHIJxx", s.size() + 1) == 0); + } + } + + WHEN("resizing to a lesser size") + { + s.resize(2); + + THEN("the capacity stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("the size decreases") + { + CHECK(s.size() < old_size); + REQUIRE(s.size() == 2); + } + + THEN("the string get filled with the given character") + { + REQUIRE(kstd::string::traits_type::compare(s.data(), "ab\0", s.size() + 1) == 0); + } + } + } + + GIVEN("Two strings") + { + auto str1 = kstd::string{"Blub"}; + auto str2 = kstd::string{" Blub"}; + + WHEN("appending the second string to the first string") + { + str1.append(str2); + + THEN("the first string contains the characters of both strings concatenated") + { + REQUIRE(str1 == "Blub Blub"); + } + + THEN("the size of the first string is the sum of the sizes of both strings") + { + REQUIRE(str1.size() == str2.size() + 4); + } + } + + WHEN("using operator+= to append the second string to the first string") + { + str1 += str2; + + THEN("the first string contains the characters of both strings concatenated") + { + REQUIRE(str1 == "Blub Blub"); + } + + THEN("the size of the first string is the sum of the sizes of both strings") + { + REQUIRE(str1.size() == str2.size() + 4); + } + } + } + + GIVEN("A string and a string view") + { + auto str = kstd::string{"Blub"}; + auto view = std::string_view{" Blub"}; + + WHEN("appending the string view to the string") + { + str.append(view); + + THEN("the string contains the characters of both the original string and the appended view concatenated") + { + REQUIRE(str == "Blub Blub"); + } + + THEN("the size of the string is the sum of the sizes of the original string and the appended view") + { + REQUIRE(str.size() == view.size() + 4); + } + } + } + + GIVEN("A string and a character") + { + auto str = kstd::string{"Blub"}; + auto ch = '!'; + + WHEN("appending the character to the string") + { + str.push_back(ch); + + THEN("the string contains the original characters followed by the appended character") + { + REQUIRE(str == "Blub!"); + } + + THEN("the size of the string is one more than the original size") + { + REQUIRE(str.size() == 5); + } + } + + WHEN("using operator+= to append the character to the string") + { + str += ch; + + THEN("the string contains the original characters followed by the appended character") + { + REQUIRE(str == "Blub!"); + } + + THEN("the size of the string is one more than the original size") + { + REQUIRE(str.size() == 5); + } + } + } + + GIVEN("A non-empty string") + { + auto str = kstd::string{"Blub Blub"}; + + WHEN("clearing the string") + { + str.clear(); + + THEN("the string is empty and has size zero") + { + REQUIRE(str.empty()); + REQUIRE(str.size() == 0); + } + + THEN("the string contains no characters") + { + REQUIRE(str == std::string_view{}); + } + } + } +} + +SCENARIO("String insertion", "[string]") +{ + GIVEN("A short string") + { + auto s = kstd::string{"abcd"}; + auto old_capacity = s.capacity(); + auto old_size = s.size(); + + WHEN("inserting five copies of a character at the beginning") + { + s.insert(0, 5, 'A'); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("The string is equal to the old string with the new characters prepended") + { + REQUIRE(s == kstd::string{"AAAAAabcd"}); + } + } + + WHEN("inserting five copies of a character in the middle") + { + s.insert(2, 5, 'A'); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("The string is equal to the old string with the new characters inserted") + { + REQUIRE(s == kstd::string{"abAAAAAcd"}); + } + } + + WHEN("inserting five copies of a character at the end") + { + s.insert(4, 5, 'A'); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("The string is equal to the old string with the new characters appended") + { + REQUIRE(s == kstd::string{"abcdAAAAA"}); + } + } + + WHEN("inserting five copies of a character behind the end") + { + THEN("an OS panic is triggered") + { + REQUIRE_THROWS_AS(s.insert(5, 5, 'A'), kstd::tests::os_panic); + } + } + + WHEN("inserting 20 copies of a character") + { + s.insert(2, 20, 'A'); + + THEN("the capacity of the string grows") + { + REQUIRE(s.capacity() > old_capacity); + } + + THEN("the string is equal t o the old string with the new characters inserted") + { + REQUIRE(s == kstd::string{"abAAAAAAAAAAAAAAAAAAAAcd"}); + } + } + + WHEN("inserting a C-style string of length 5 at the beginning") + { + s.insert(0, "AAAAA"); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("The string is equal to the old string with the new characters prepended") + { + REQUIRE(s == kstd::string{"AAAAAabcd"}); + } + } + + WHEN("inserting a C-style string of length 5 in the middle") + { + s.insert(2, "AAAAA"); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("The string is equal to the old string with the new characters inserted") + { + REQUIRE(s == kstd::string{"abAAAAAcd"}); + } + } + + WHEN("inserting a C-style string of length 5 at the end") + { + s.insert(4, "AAAAA"); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string stays the same") + { + REQUIRE(s.capacity() == old_capacity); + } + + THEN("The string is equal to the old string with the new characters appended") + { + REQUIRE(s == kstd::string{"abcdAAAAA"}); + } + } + + WHEN("inserting a C-style string of length 5 behind the end") + { + THEN("an OS panic is triggered") + { + REQUIRE_THROWS_AS(s.insert(5, "AAAAA"), kstd::tests::os_panic); + } + } + } + + GIVEN("A long string") + { + auto s = kstd::string{"abcdefghijABCDEFGHIJ"}; + auto old_capacity = s.capacity(); + auto old_size = s.size(); + + WHEN("inserting five copies of a character at the beginning") + { + s.insert(0, 5, 'A'); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string increases") + { + REQUIRE(s.capacity() >= old_capacity + 5); + } + + THEN("The string is equal to the old string with the new characters prepended") + { + REQUIRE(s == kstd::string{"AAAAAabcdefghijABCDEFGHIJ"}); + } + } + + WHEN("inserting five copies of a character in the middle") + { + s.insert(10, 5, 'A'); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string increases") + { + REQUIRE(s.capacity() >= old_capacity + 5); + } + + THEN("The string is equal to the old string with the new characters inserted") + { + REQUIRE(s == kstd::string{"abcdefghijAAAAAABCDEFGHIJ"}); + } + } + + WHEN("inserting five copies of a character at the end") + { + s.insert(20, 5, 'A'); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string increases") + { + REQUIRE(s.capacity() >= old_capacity + 5); + } + + THEN("The string is equal to the old string with the new characters appended") + { + REQUIRE(s == kstd::string{"abcdefghijABCDEFGHIJAAAAA"}); + } + } + + WHEN("inserting five copies of a character behind the end") + { + THEN("an OS panic is triggered") + { + REQUIRE_THROWS_AS(s.insert(21, 5, 'A'), kstd::tests::os_panic); + } + } + + WHEN("inserting a C-style string of length 5 at the beginning") + { + s.insert(0, "AAAAA"); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string increases") + { + REQUIRE(s.capacity() >= old_capacity + 5); + } + + THEN("The string is equal to the old string with the new characters prepended") + { + REQUIRE(s == kstd::string{"AAAAAabcdefghijABCDEFGHIJ"}); + } + } + + WHEN("inserting a C-style string of length 5 in the middle") + { + s.insert(10, "AAAAA"); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the string increases") + { + REQUIRE(s.capacity() >= old_capacity + 5); + } + + THEN("The string is equal to the old string with the new characters inserted") + { + REQUIRE(s == kstd::string{"abcdefghijAAAAAABCDEFGHIJ"}); + } + } + + WHEN("inserting a C-style string of length 5 at the end") + { + s.insert(20, "AAAAA"); + + THEN("the size of the string is increased by 5") + { + REQUIRE(s.size() == old_size + 5); + } + + THEN("The capacity of the increases") + { + REQUIRE(s.capacity() >= old_capacity + 5); + } + + THEN("The string is equal to the old string with the new characters appended") + { + REQUIRE(s == kstd::string{"abcdefghijABCDEFGHIJAAAAA"}); + } + } + + WHEN("inserting a C-style string of length 5 behind the end") + { + THEN("an OS panic is triggered") + { + REQUIRE_THROWS_AS(s.insert(21, "AAAAA"), kstd::tests::os_panic); + } + } + } +} + +SCENARIO("String concatenation", "[string]") +{ + GIVEN("Two strings") + { + auto str1 = kstd::string{"Blub"}; + auto str2 = kstd::string{" Blub"}; + + WHEN("using operator+ to concatenate the two strings into a new string") + { + auto str3 = str1 + str2; + + THEN("the new string contains the characters of both strings concatenated") + { + REQUIRE(str3 == "Blub Blub"); + } + + THEN("the size of the new string is the sum of the sizes of both strings") + { + REQUIRE(str3.size() == str1.size() + str2.size()); + } + } + } +} + +SCENARIO("String conversion and comparison", "[string]") +{ + GIVEN("An unsigned integer") + { + constexpr auto value1 = 12345u; + constexpr auto value2 = 0u; + + WHEN("converting the unsigned integer to a string") + { + auto str1 = kstd::to_string(value1); + auto str2 = kstd::to_string(value2); + + THEN("the string contains the decimal representation of the unsigned integer") + { + REQUIRE(str1 == "12345"); + REQUIRE(str2 == "0"); + } + } + } + + GIVEN("Two strings with the same characters") + { + auto str1 = kstd::string{"Blub Blub"}; + auto str2 = kstd::string{"Blub Blub"}; + + THEN("the strings are equal") + { + REQUIRE(str1 == str2); + } + + THEN("the strings are not unequal") + { + REQUIRE_FALSE(str1 != str2); + } + } + + GIVEN("A string and a string view with the same characters") + { + auto str = kstd::string{"Blub Blub"}; + auto view = std::string_view{"Blub Blub"}; + + THEN("the string and the string view are equal") + { + REQUIRE(str == view); + REQUIRE(view == str); + } + + THEN("the string and the string view are not unequal") + { + REQUIRE_FALSE(str != view); + REQUIRE_FALSE(view != str); + } + } + + GIVEN("The string 'abc'") + { + auto s1 = kstd::string{"abc"}; + + WHEN("comparing with the string 'def'") + { + auto result = s1.compare(kstd::string{"def"}); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing with the string 'abb'") + { + auto result = s1.compare(kstd::string{"abb"}); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing with the string 'abc'") + { + auto result = s1.compare(kstd::string{"abc"}); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,2] with the string 'def'") + { + auto result = s1.compare(1, 2, kstd::string{"def"}); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,2] with the string 'abb'") + { + auto result = s1.compare(1, 2, kstd::string{"abb"}); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,2] with the string 'cc'") + { + auto result = s1.compare(1, 2, kstd::string{"bc"}); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,npos] with the string 'ef'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"ef"}); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,npos] with the string 'bb'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"bb"}); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,npos] with the string 'bc'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"bc"}); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,2] with the substring [1,2] of 'def'") + { + auto result = s1.compare(1, 2, kstd::string{"def"}, 1, 2); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,2] with the substring [1,2] of 'abb'") + { + auto result = s1.compare(1, 2, kstd::string{"abb"}, 1, 2); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,2] with the substring [1,2] of 'abc'") + { + auto result = s1.compare(1, 2, kstd::string{"abc"}, 1, 2); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,npos] with the substring [1,2] of 'def'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"def"}, 1, 2); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,npos] with the substring [1,2] of 'abb'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"abb"}, 1, 2); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,npos] with the substring [1,2] of 'abc'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"abc"}, 1, 2); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,2] with the substring [1,npos] of 'def'") + { + auto result = s1.compare(1, 2, kstd::string{"def"}, 1, kstd::string::npos); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,2] with the substring [1,npos] of 'abb'") + { + auto result = s1.compare(1, 2, kstd::string{"abb"}, 1, kstd::string::npos); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,2] with the substring [1,npos] of 'abc'") + { + auto result = s1.compare(1, 2, kstd::string{"abc"}, 1, kstd::string::npos); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,npos] with the substring [1,npos] of 'def'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"def"}, 1, kstd::string::npos); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,npos] with the substring [1,npos] of 'abb'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"abb"}, 1, kstd::string::npos); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,npos] with the substring [1,npos] of 'abc'") + { + auto result = s1.compare(1, kstd::string::npos, kstd::string{"abc"}, 1, kstd::string::npos); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing with the C-style string 'def'") + { + auto result = s1.compare("def"); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing with the C-style string 'abb'") + { + auto result = s1.compare("abb"); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing with the C-style string 'abc'") + { + auto result = s1.compare("abc"); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,2] with the C-style string 'def'") + { + auto result = s1.compare(1, 2, "def"); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,2] with the C-style string 'abb'") + { + auto result = s1.compare(1, 2, "abb"); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,2] with the C-style string 'bc'") + { + auto result = s1.compare(1, 2, "bc"); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + + WHEN("comparing substring [1,npos] with the C-style string 'def'") + { + auto result = s1.compare(1, 2, "ef"); + + THEN("the result is negative") + { + REQUIRE(result < 0); + } + } + + WHEN("comparing substring [1,npos] with the C-style string 'abb'") + { + auto result = s1.compare(1, 2, "abb"); + + THEN("the result is positive") + { + REQUIRE(result > 0); + } + } + + WHEN("comparing substring [1,npos] with the C-style string 'bc'") + { + auto result = s1.compare(1, 2, "bc"); + + THEN("the result is zero") + { + REQUIRE(result == 0); + } + } + } +} + +SCENARIO("String iteration", "[string]") +{ + GIVEN("A string") + { + auto str = kstd::string{"Blub"}; + + WHEN("iterating over the characters of the string as string_view using a range-based for loop") + { + kstd::string result; + + for (auto ch : static_cast(str)) + { + result.push_back(ch); + } + + THEN("the iterated characters are the same as the characters in the string") + { + REQUIRE(result == str); + } + } + + WHEN("using std::ranges::for_each to iterate over the characters of the string") + { + kstd::string result; + + std::ranges::for_each(str, [&result](auto ch) { result.push_back(ch); }); + + THEN("the iterated characters are the same as the characters in the string") + { + REQUIRE(result == str); + } + } + + WHEN("using front and back to access the first and last characters of the string") + { + THEN("front returns the first character of the string") + { + REQUIRE(str.front() == 'B'); + } + + THEN("back returns the last character of the string") + { + REQUIRE(str.back() == 'b'); + } + } + } + + GIVEN("A const string") + { + auto const str = kstd::string{"Blub"}; + + WHEN("iterating over the characters of the string as string_view using a range-based for loop") + { + kstd::string result; + + for (auto ch : static_cast(str)) + { + result.push_back(ch); + } + + THEN("the iterated characters are the same as the characters in the string") + { + REQUIRE(result == static_cast(str)); + } + } + + WHEN("using front and back to access the first and last characters of the string") + { + THEN("front returns the first character of the string") + { + REQUIRE(str.front() == 'B'); + } + + THEN("back returns the last character of the string") + { + REQUIRE(str.back() == 'b'); + } + } + } + + GIVEN("An empty string") + { + auto str = kstd::string{}; + + WHEN("iterating over the characters of an empty string") + { + kstd::string result; + + for (auto ch : static_cast(str)) + { + result.push_back(ch); + } + + THEN("no characters are iterated and the result is an empty string") + { + REQUIRE(result.empty()); + REQUIRE(result.size() == 0); + REQUIRE(static_cast(result) == std::string_view{}); + } + } + } +} + +SCENARIO("String STL integration", "[string]") +{ + GIVEN("An empty string") + { + auto s = kstd::string{""}; + + WHEN("hashing the string") + { + auto hash_result = std::hash{}(s); + + THEN("the result is equal to hashing an empty std::string") + { + REQUIRE(hash_result == std::hash{}("")); + } + } + } + + GIVEN("A non-empty string") + { + auto s = kstd::string{"abcd"}; + + WHEN("hashing the string") + { + auto hash_result = std::hash{}(s); + + THEN("the result is equal to hashing the same std::string") + { + REQUIRE(hash_result == std::hash{}("abcd")); + } + } + } +} + +SCENARIO("String user-defined literals") +{ + GIVEN("the user defined literals are in scope") + { + using namespace kstd::string_literals; + + WHEN("creating a string using the _s literal suffix") + { + auto s = "abcd"_s; + + THEN("the result is equal to the string created with the same literal") + { + REQUIRE(s == kstd::string{"abcd"}); + } + } + } +} diff --git a/libs/kstd/kstd/test_support/os_panic.test.cpp b/libs/kstd/kstd/test_support/os_panic.test.cpp deleted file mode 100644 index c30411aa..00000000 --- a/libs/kstd/kstd/test_support/os_panic.test.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include - -#include -#include -#include - -namespace kstd::os -{ - - auto panic(std::string_view message, std::source_location location) - { - throw kstd::tests::os_panic{std::string{message}, location}; - } - -} // namespace kstd::os \ No newline at end of file diff --git a/libs/kstd/kstd/test_support/os_panic.tests.cpp b/libs/kstd/kstd/test_support/os_panic.tests.cpp new file mode 100644 index 00000000..c30411aa --- /dev/null +++ b/libs/kstd/kstd/test_support/os_panic.tests.cpp @@ -0,0 +1,15 @@ +#include + +#include +#include +#include + +namespace kstd::os +{ + + auto panic(std::string_view message, std::source_location location) + { + throw kstd::tests::os_panic{std::string{message}, location}; + } + +} // namespace kstd::os \ No newline at end of file diff --git a/libs/kstd/kstd/vector.test.cpp b/libs/kstd/kstd/vector.test.cpp deleted file mode 100644 index 72c1127a..00000000 --- a/libs/kstd/kstd/vector.test.cpp +++ /dev/null @@ -1,2003 +0,0 @@ -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include - -SCENARIO("Vector initialization and construction", "[vector]") -{ - GIVEN("An empty context") - { - WHEN("constructing by default") - { - auto v = kstd::vector{}; - - THEN("the vector is empty") - { - REQUIRE(v.empty()); - } - - THEN("the size and capacity are zero") - { - REQUIRE(v.size() == 0); - REQUIRE(v.capacity() == 0); - } - } - - WHEN("constructing with a specific size") - { - auto v = kstd::vector(10); - - THEN("the vector is not empty") - { - REQUIRE_FALSE(v.empty()); - } - - THEN("the size is and capacity match the specified value") - { - REQUIRE(v.size() == 10); - REQUIRE(v.capacity() == 10); - } - } - - WHEN("constructing from an initializer list") - { - auto v = kstd::vector{1, 2, 3, 4, 5}; - - THEN("the vector is not empty") - { - REQUIRE_FALSE(v.empty()); - } - - THEN("the size is and capacity match the specified value") - { - REQUIRE(v.size() == 5); - REQUIRE(v.capacity() == 5); - } - - THEN("the elements are correctly initialized") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - REQUIRE(v[3] == 4); - REQUIRE(v[4] == 5); - } - } - } - - GIVEN("A non-empty range") - { - auto range = std::array{1, 2, 3}; - - WHEN("constructing from a random-access iterator range") - { - auto v = kstd::vector{std::begin(range), std::end(range)}; - - THEN("the vector is not empty") - { - REQUIRE_FALSE(v.empty()); - } - - THEN("the size and capacity match the range size") - { - REQUIRE(v.size() == std::size(range)); - REQUIRE(v.capacity() == std::size(range)); - } - - THEN("the elements are correctly initialized") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - - WHEN("constructing from a range") - { - auto v = kstd::vector{kstd::from_range, range}; - - THEN("the vector is not empty") - { - REQUIRE_FALSE(v.empty()); - } - - THEN("the size and capacity match the range size") - { - REQUIRE(v.size() == std::ranges::size(range)); - REQUIRE(v.capacity() == std::ranges::size(range)); - } - - THEN("the elements are correctly initialized") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - } - - GIVEN("A populated vector") - { - auto source = kstd::vector{1, 2, 3, 4, 5}; - - WHEN("copy constructing a new vector") - { - auto copy = kstd::vector(source); - - THEN("the copy matches the original") - { - REQUIRE(copy.size() == source.size()); - REQUIRE(copy.capacity() == source.capacity()); - REQUIRE(copy[0] == 1); - REQUIRE(copy[1] == 2); - REQUIRE(copy[2] == 3); - REQUIRE(copy[3] == 4); - REQUIRE(copy[4] == 5); - } - - THEN("the original is left unchanged") - { - REQUIRE(source.size() == 5); - REQUIRE(source.capacity() == 5); - REQUIRE(source[0] == 1); - REQUIRE(source[1] == 2); - REQUIRE(source[2] == 3); - REQUIRE(source[3] == 4); - REQUIRE(source[4] == 5); - } - } - - WHEN("move constructing a new vector") - { - auto moved = kstd::vector(std::move(source)); - - THEN("The new vector has the original elements") - { - REQUIRE(moved.size() == 5); - REQUIRE(moved.capacity() == 5); - REQUIRE(moved[0] == 1); - REQUIRE(moved[1] == 2); - REQUIRE(moved[2] == 3); - REQUIRE(moved[3] == 4); - REQUIRE(moved[4] == 5); - } - - THEN("The original vector is left in a valid but unspecified state") - { - REQUIRE(source.empty()); - REQUIRE(source.size() == 0); - REQUIRE(source.capacity() == 0); - } - } - } -} - -SCENARIO("Vector element access", "[vector]") -{ - GIVEN("A populated vector") - { - auto v = kstd::vector{10, 20, 30}; - - WHEN("accessing elements for reading") - { - THEN("operator[] and at() return the correct elements") - { - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - - REQUIRE(v.at(0) == 10); - REQUIRE(v.at(1) == 20); - REQUIRE(v.at(2) == 30); - } - - THEN("front() and back() return the first and last elements") - { - REQUIRE(v.front() == 10); - REQUIRE(v.back() == 30); - } - - THEN("data() return a pointer to the contiguous storage") - { - auto ptr = v.data(); - REQUIRE(ptr); - REQUIRE(ptr[0] == 10); - REQUIRE(ptr[1] == 20); - REQUIRE(ptr[2] == 30); - } - - THEN("accessing out of bounds elements panics") - { - REQUIRE_THROWS_AS(v.at(3), kstd::tests::os_panic); - } - } - - WHEN("accessing elements for writing") - { - v[0] = 100; - v.at(1) = 200; - v.back() = 300; - - THEN("the elements are correctly modified") - { - REQUIRE(v[0] == 100); - REQUIRE(v[1] == 200); - REQUIRE(v[2] == 300); - } - } - } -} - -SCENARIO("Vector iterators", "[vector]") -{ - GIVEN("A populated vector") - { - auto v = kstd::vector{1, 2, 3}; - - WHEN("using forward iterators") - { - THEN("they navigate the elements in the correct forward order") - { - auto it = v.begin(); - REQUIRE(it != v.end()); - REQUIRE(*it == 1); - - ++it; - REQUIRE(it != v.end()); - REQUIRE(*it == 2); - - ++it; - REQUIRE(it != v.end()); - REQUIRE(*it == 3); - - ++it; - REQUIRE(it == v.end()); - } - - THEN("const forward iterators provide correct access") - { - auto it = v.cbegin(); - REQUIRE(it != v.cend()); - REQUIRE(*it == 1); - - ++it; - REQUIRE(it != v.cend()); - REQUIRE(*it == 2); - - ++it; - REQUIRE(it != v.cend()); - REQUIRE(*it == 3); - - ++it; - REQUIRE(it == v.cend()); - } - } - - WHEN("using reverse iterators") - { - THEN("they navigate the elements in the correct reverse order") - { - auto it = v.rbegin(); - REQUIRE(it != v.rend()); - REQUIRE(*it == 3); - - ++it; - REQUIRE(it != v.rend()); - REQUIRE(*it == 2); - - ++it; - REQUIRE(it != v.rend()); - REQUIRE(*it == 1); - - ++it; - REQUIRE(it == v.rend()); - } - - THEN("const reverse iterators provide correct access") - { - auto it = v.crbegin(); - REQUIRE(it != v.crend()); - REQUIRE(*it == 3); - - ++it; - REQUIRE(it != v.crend()); - REQUIRE(*it == 2); - - ++it; - REQUIRE(it != v.crend()); - REQUIRE(*it == 1); - - ++it; - REQUIRE(it == v.crend()); - } - } - } - - GIVEN("an empty vector") - { - auto v = kstd::vector{}; - - WHEN("getting iterators") - { - THEN("begin() equals end() and cbegin() equals cend()") - { - REQUIRE(v.begin() == v.end()); - REQUIRE(v.cbegin() == v.cend()); - } - - THEN("rbegin() equals rend() and crbegin() equals crend()") - { - REQUIRE(v.rbegin() == v.rend()); - REQUIRE(v.crbegin() == v.crend()); - } - } - } -} - -SCENARIO("Vector capacity management", "[vector]") -{ - GIVEN("An empty vector") - { - auto v = kstd::vector{}; - - WHEN("reserving space") - { - v.reserve(10); - - THEN("the capacity is at least the reserved amount") - { - REQUIRE(v.capacity() >= 10); - } - - THEN("the size is still zero") - { - REQUIRE(v.size() == 0); - } - - THEN("the vector is still empty") - { - REQUIRE(v.empty()); - } - } - - WHEN("reserving space less than or equal to current capacity") - { - v.reserve(10); - auto const current_capacity = v.capacity(); - v.reserve(5); - - THEN("the capacity remains unchanged") - { - REQUIRE(v.capacity() == current_capacity); - } - } - - WHEN("reserving space greater than max_size") - { - THEN("a panic is triggered") - { - REQUIRE_THROWS_AS(v.reserve(v.max_size() + 1), kstd::tests::os_panic); - } - } - } - - GIVEN("A populated vector with excess capacity") - { - auto v = kstd::vector{1, 2, 3}; - v.reserve(10); - - REQUIRE(v.capacity() == 10); - - WHEN("calling shrink_to_fit") - { - v.shrink_to_fit(); - - THEN("the capacity is reduced to match the size") - { - REQUIRE(v.capacity() == 3); - REQUIRE(v.size() == 3); - } - - THEN("the elements remain unchanged") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - } -} - -SCENARIO("Vector modifiers", "[vector]") -{ - GIVEN("An empty vector") - { - auto v = kstd::vector{}; - - WHEN("push_back is called with a value") - { - v.push_back(10); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v.back() == 10); - } - } - - WHEN("emplace_back is called with constructor arguments") - { - v.emplace_back(20); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v.back() == 20); - } - } - - WHEN("elements are added while capacity is sufficient") - { - v.reserve(10); - auto const capacity = v.capacity(); - - v.push_back(10); - v.emplace_back(20); - - THEN("the elements are added without reallocation") - { - REQUIRE(v.size() == 2); - REQUIRE(v.capacity() == capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - } - } - - WHEN("emplace is called with the end iterator and constructor arguments") - { - v.emplace(v.end(), 20); - - THEN("the element is appended and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v.back() == 20); - } - } - - WHEN("emplace is called while capacity is sufficient") - { - v.reserve(10); - auto const capacity = v.capacity(); - - v.emplace(v.end(), 20); - - THEN("the element is appended without reallocation") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() == capacity); - REQUIRE(v.back() == 20); - } - } - - WHEN("inserting an element") - { - auto it = v.insert(v.cbegin(), 40); - - THEN("the size and capacity increase and the element is inserted") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v[0] == 40); - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting an lvalue element") - { - auto const value = 40; - auto it = v.insert(v.cbegin(), value); - - THEN("the size and capacity increase and the element is inserted") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() >= 1); - REQUIRE(v[0] == 40); - REQUIRE(it == v.begin()); - } - } - - WHEN("appending a range") - { - auto const range = std::views::iota(0, 3); - v.append_range(range); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are appended") - { - REQUIRE(v[0] == 0); - REQUIRE(v[1] == 1); - REQUIRE(v[2] == 2); - } - } - - WHEN("appending from an input range") - { - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - - v.append_range(std::ranges::subrange{first, last}); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are appended") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - - WHEN("appending from an input range with sufficient capacity") - { - v.reserve(3); - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - - v.append_range(std::ranges::subrange{first, last}); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity stays the same") - { - REQUIRE(v.capacity() == 3); - } - - THEN("the elements are appended") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - } - - WHEN("resizing the vector to a greater size") - { - v.resize(3); - - THEN("the size and capacity increase and the elements are value initialized") - { - REQUIRE(v.size() == 3); - REQUIRE(v.capacity() >= 3); - REQUIRE(v[0] == 0); - REQUIRE(v[1] == 0); - REQUIRE(v[2] == 0); - } - } - - WHEN("resizing the vector to a greater size with initial value") - { - v.resize(3, 2); - - THEN("the size and capacity increase and the elements are initialized to the given value") - { - REQUIRE(v.size() == 3); - REQUIRE(v.capacity() >= 3); - REQUIRE(v[0] == 2); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 2); - } - } - - WHEN("inserting a range at the beginning") - { - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.begin(), arr); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting a range at the end") - { - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.end(), arr); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting from an input range without sufficient capacity") - { - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last}); - - THEN("the size increases") - { - REQUIRE(v.size() == 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - } - - GIVEN("A populated vector") - { - auto v = kstd::vector{10, 20, 30}; - auto initial_capacity = v.capacity(); - - WHEN("push_back is called") - { - v.push_back(40); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() >= initial_capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 40); - } - } - - WHEN("emplace_back is called with constructor arguments") - { - v.emplace_back(40); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() >= initial_capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 40); - } - } - - WHEN("emplace is called with an iterator and constructor arguments") - { - auto it = v.emplace(v.begin() + 2, 25); - - THEN("the element is inserted and the size and capacity increase") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() >= initial_capacity); - REQUIRE(v.at(2) == 25); - } - - THEN("the returned iterator points to the inserted element") - { - REQUIRE(it == v.cbegin() + 2); - REQUIRE(*it == 25); - } - } - - WHEN("emplace is called with an iterator and sufficient capacity") - { - v.reserve(v.size() + 1); - - auto it = v.emplace(v.begin() + 2, 25); - - THEN("the element is inserted and the size and capacity increase") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() >= initial_capacity); - REQUIRE(v.at(2) == 25); - } - - THEN("the returned iterator points to the inserted element") - { - REQUIRE(it == v.cbegin() + 2); - REQUIRE(*it == 25); - } - } - - WHEN("push_back is called with a reference to an internal element") - { - v.shrink_to_fit(); - auto const original_value = v[0]; - - v.push_back(v[0]); - - THEN("reallocation handles the internal reference safely without dangling") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == original_value); - REQUIRE(v.back() == original_value); - } - } - - WHEN("pop_back is called") - { - v.pop_back(); - - THEN("the last element is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v.capacity() == initial_capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - } - } - - WHEN("clear is called") - { - v.clear(); - - THEN("the vector is empty") - { - REQUIRE(v.empty()); - REQUIRE(v.size() == 0); - REQUIRE(v.capacity() == initial_capacity); - } - } - - WHEN("inserting at the beginning") - { - auto it = v.insert(v.cbegin(), 5); - - THEN("the element is inserted at the front") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 5); - REQUIRE(v[1] == 10); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting in the middle") - { - auto it = v.insert(v.cbegin() + 1, 15); - - THEN("the element is inserted in the middle") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 15); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting at the end") - { - auto it = v.insert(v.cend(), 40); - - THEN("the element is inserted at the back") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 40); - REQUIRE(it == v.begin() + 3); - } - } - - WHEN("inserting an lvalue at the end") - { - auto const value = 40; - auto it = v.insert(v.cend(), value); - - THEN("the element is inserted at the back") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 40); - REQUIRE(it == v.begin() + 3); - } - } - - WHEN("inserting when capacity is sufficient") - { - v.reserve(10); - auto const capacity = v.capacity(); - - auto it = v.insert(v.cbegin() + 1, 15); - - THEN("the element is added without reallocation") - { - REQUIRE(v.size() == 4); - REQUIRE(v.capacity() == capacity); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 15); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting a reference to an existing element with reallocation") - { - v.shrink_to_fit(); - REQUIRE(v.capacity() == v.size()); - auto it = v.insert(v.cbegin() + 1, v[2]); - - THEN("the element is correctly copied and inserted") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting a reference to an existing element without reallocation") - { - v.reserve(10); - REQUIRE(v.capacity() > v.size()); - auto it = v.insert(v.cbegin() + 1, v[2]); - - THEN("the element is correctly copied and inserted") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting an rvalue reference to an existing element with reallocation") - { - v.shrink_to_fit(); - REQUIRE(v.capacity() == v.size()); - auto it = v.insert(v.cbegin() + 1, std::move(v[2])); - - THEN("the element is correctly moved and inserted") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting an rvalue reference to an existing element without reallocation") - { - v.reserve(10); - REQUIRE(v.capacity() > v.size()); - auto it = v.insert(v.cbegin() + 1, std::move(v[2])); - - THEN("the element is correctly moved and inserted") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("erasing the first element") - { - auto it = v.erase(v.cbegin()); - - THEN("the first element is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v[0] == 20); - REQUIRE(v[1] == 30); - REQUIRE(it == v.begin()); - } - } - - WHEN("erasing a middle element") - { - auto it = v.erase(v.cbegin() + 1); - - THEN("the middle element is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("erasing the last element") - { - auto it = v.erase(v.cend() - 1); - - THEN("the last element is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(it == v.end()); - } - } - - WHEN("erasing the end() iterator") - { - THEN("a panic is triggered") - { - REQUIRE_THROWS_AS(v.erase(v.end()), kstd::tests::os_panic); - } - } - - WHEN("erasing a range of elements") - { - auto it = v.erase(v.cbegin() + 1, v.cend() - 1); - - THEN("the specified range is removed and the size decreases") - { - REQUIRE(v.size() == 2); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("erasing an empty range") - { - auto it = v.erase(v.cbegin() + 1, v.cbegin() + 1); - - THEN("the vector is unchanged") - { - REQUIRE(v.size() == 3); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("appending a range") - { - auto initial_size = v.size(); - v.append_range(std::views::iota(0, 3)); - - THEN("capacity is increased") - { - REQUIRE(v.capacity() >= initial_capacity); - } - - THEN("size is increased") - { - REQUIRE(v.size() == initial_size + 3); - } - - THEN("the elements are appended") - { - REQUIRE(v[initial_size + 0] == 0); - REQUIRE(v[initial_size + 1] == 1); - REQUIRE(v[initial_size + 2] == 2); - } - } - - WHEN("resizing the vector to a greater size") - { - auto initial_size = v.size(); - v.resize(initial_size + 3); - - THEN("the size and capacity increase and the elements are value initialized") - { - REQUIRE(v.size() == initial_size + 3); - REQUIRE(v.capacity() >= initial_size + 3); - REQUIRE(v[initial_size + 0] == 0); - REQUIRE(v[initial_size + 1] == 0); - REQUIRE(v[initial_size + 2] == 0); - } - } - - WHEN("resizing the vector to a greater size with initial value") - { - auto initial_size = v.size(); - v.resize(initial_size + 3, 2); - - THEN("the size and capacity increase and the elements are initialized to the given value") - { - REQUIRE(v.size() == initial_size + 3); - REQUIRE(v.capacity() >= initial_size + 3); - REQUIRE(v[initial_size + 0] == 2); - REQUIRE(v[initial_size + 1] == 2); - REQUIRE(v[initial_size + 2] == 2); - } - } - - WHEN("resizing the vector to a smaller size") - { - v.resize(1); - - THEN("the size decreases and the elements are destroyed") - { - REQUIRE(v.size() == 1); - REQUIRE(v[0] == 10); - } - } - - WHEN("inserting an empty range") - { - auto initial_size = v.size(); - auto it = v.insert_range(v.begin(), std::views::empty); - - THEN("the size does not change") - { - REQUIRE(v.size() == initial_size); - } - - THEN("the capacity does not change") - { - REQUIRE(v.capacity() == initial_capacity); - } - - THEN("the content is unchanged") - { - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - } - - THEN("the returned iterator points to the position of insertion") - { - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting a range at the beginning") - { - auto initial_size = v.size(); - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.begin(), arr); - - THEN("the size increases") - { - REQUIRE(v.size() == initial_size + 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= initial_size + 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - REQUIRE(v[3] == 10); - REQUIRE(v[4] == 20); - REQUIRE(v[5] == 30); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - - WHEN("inserting a range at the end") - { - auto initial_size = v.size(); - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.end(), arr); - - THEN("the size increases") - { - REQUIRE(v.size() == initial_size + 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= initial_size + 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 20); - REQUIRE(v[2] == 30); - REQUIRE(v[3] == 1); - REQUIRE(v[4] == 2); - REQUIRE(v[5] == 3); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin() + initial_size); - } - } - - WHEN("inserting a range that causes reallocation") - { - auto initial_size = v.size(); - auto const arr = std::array{1, 2, 3}; - auto it = v.insert_range(v.begin() + 1, arr); - - THEN("the size increases") - { - REQUIRE(v.size() == initial_size + 3); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= initial_size + 3); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 1); - REQUIRE(v[2] == 2); - REQUIRE(v[3] == 3); - REQUIRE(v[4] == 20); - REQUIRE(v[5] == 30); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting fewer elements than the suffix without reallocation") - { - v.reserve(10); - auto const capacity = v.capacity(); - auto const arr = std::array{1}; - auto it = v.insert_range(v.begin() + 1, arr); - - THEN("the elements are correctly placed") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 1); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - } - - THEN("no reallocation occurs") - { - REQUIRE(v.capacity() == capacity); - } - - THEN("the returned iterator points to the inserted element") - { - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting fewer elements than the suffix without sufficient capacity") - { - v.shrink_to_fit(); - auto const arr = std::array{1}; - auto it = v.insert_range(v.begin() + 1, arr); - - THEN("the elements are correctly placed") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0] == 10); - REQUIRE(v[1] == 1); - REQUIRE(v[2] == 20); - REQUIRE(v[3] == 30); - } - - THEN("the returned iterator points to the inserted element") - { - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("inserting from an input range without sufficient capacity") - { - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last}); - - THEN("the size increases") - { - REQUIRE(v.size() == 6); - } - - THEN("the capacity increases") - { - REQUIRE(v.capacity() >= 6); - } - - THEN("the elements are inserted") - { - REQUIRE(v[0] == 1); - REQUIRE(v[1] == 2); - REQUIRE(v[2] == 3); - REQUIRE(v[3] == 10); - REQUIRE(v[4] == 20); - REQUIRE(v[5] == 30); - } - - THEN("the returned iterator points to the beginning of the inserted range") - { - REQUIRE(it == v.begin()); - } - } - } -} - -SCENARIO("Vector comparison", "[vector]") -{ - GIVEN("Two identical vectors") - { - auto v1 = kstd::vector{1, 2, 3}; - auto v2 = kstd::vector{1, 2, 3}; - - WHEN("comparing for equality") - { - THEN("the vectors are equal") - { - REQUIRE(v1 == v2); - } - - THEN("the vectors and not not-equal") - { - REQUIRE_FALSE(v1 != v2); - } - } - - WHEN("comparing using the spaceship operator") - { - THEN("the vectors are equivalent") - { - REQUIRE((v1 <=> v2) == 0); - REQUIRE(v1 <= v2); - REQUIRE(v1 >= v2); - } - } - } - - GIVEN("Two vectors of different sizes") - { - auto v1 = kstd::vector{1, 2, 3}; - auto v2 = kstd::vector{1, 2, 3, 4}; - - WHEN("comparing for equality") - { - THEN("the vectors are not equal") - { - REQUIRE_FALSE(v1 == v2); - } - - THEN("the vectors are not-equal") - { - REQUIRE(v1 != v2); - } - } - - WHEN("comparing for ordering") - { - THEN("the shorter vector evaluates as less than the longer vector") - { - REQUIRE(v1 < v2); - REQUIRE(v2 > v1); - } - } - } - - GIVEN("Two vectors of the same size but different elements") - { - auto v1 = kstd::vector{1, 2, 3}; - auto v2 = kstd::vector{1, 2, 4}; - - WHEN("comparing for ordering") - { - THEN("they are ordered lexicographically") - { - REQUIRE(v1 < v2); - REQUIRE(v2 > v1); - } - } - } -} - -SCENARIO("Vector with non-default-constructible types", "[vector]") -{ - GIVEN("A type without a default constructor") - { - WHEN("constructing an empty vector") - { - auto v = kstd::vector{}; - - THEN("the vector is empty") - { - REQUIRE(v.empty()); - } - } - - WHEN("using emplace_back") - { - auto v = kstd::vector{}; - v.emplace_back(40); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.back() == kstd::tests::non_default_constructible{40}); - } - } - } -} - -SCENARIO("Vector with custom allocator", "[vector]") -{ - GIVEN("a tracking allocator acting as the vector's memory manager") - { - auto allocations = 0; - auto allocator = kstd::tests::tracking_allocator{&allocations}; - - WHEN("a vector uses this allocator to allocate memory") - { - auto v = kstd::vector>(allocator); - REQUIRE(allocations == 0); - - v.reserve(10); - - THEN("the allocator was used to allocate memory") - { - REQUIRE(allocations > 0); - } - } - } -} - -SCENARIO("Vector modifier move semantics", "[vector]") -{ - GIVEN("An empty vector and a move tracker element") - { - auto v = kstd::vector{}; - auto tracker = kstd::tests::special_member_tracker{40}; - - WHEN("push_back is called with the move tracker") - { - v.push_back(std::move(tracker)); - - THEN("the element is added and the size and capacity increase") - { - REQUIRE(v.size() == 1); - REQUIRE(v.back().move_constructed_count == 1); - REQUIRE(v.back().copy_constructed_count == 0); - REQUIRE(v.back().value == 40); - } - - THEN("the original tracker is left in a valid but unspecified state") - { - REQUIRE(tracker.value == -1); - } - } - } - - GIVEN("An empty vector") - { - auto v = kstd::vector{}; - - WHEN("emplace_back is called with constructor arguments") - { - v.emplace_back(40); - - THEN("the element is constructed directly, without moves or copies") - { - REQUIRE(v.size() == 1); - REQUIRE(v.back().move_constructed_count == 0); - REQUIRE(v.back().copy_constructed_count == 0); - REQUIRE(v.back().value == 40); - } - } - } - - GIVEN("A populated vector of move trackers") - { - auto v = kstd::vector{}; - v.reserve(10); - v.emplace_back(10); - v.emplace_back(20); - v.emplace_back(30); - - WHEN("inserting an element in the middle with sufficient capacity") - { - auto const tracker = kstd::tests::special_member_tracker{15}; - v.insert(v.cbegin() + 1, tracker); - - THEN("the shifted elements are move-assigned and the new element is copy-assigned") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - REQUIRE(v[1].value == 15); - REQUIRE(v[1].move_assigned_count == 0); - REQUIRE(v[1].copy_assigned_count == 1); - REQUIRE(tracker.copied_from_count == 1); - REQUIRE(v[2].value == 20); - REQUIRE(v[2].move_assigned_count == 1); - REQUIRE(v[2].copy_assigned_count == 0); - REQUIRE(v[3].value == 30); - REQUIRE(v[3].move_constructed_count == 1); - } - } - - WHEN("inserting an rvalue element in the middle with sufficient capacity") - { - auto tracker = kstd::tests::special_member_tracker{15}; - v.insert(v.cbegin() + 1, std::move(tracker)); - - THEN("the shifted elements are move-assigned and the new element is move-assigned") - { - REQUIRE(v.size() == 4); - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - REQUIRE(v[1].value == 15); - REQUIRE(v[1].move_assigned_count == 1); - REQUIRE(v[1].copy_assigned_count == 0); - REQUIRE(tracker.moved_from_count == 1); - REQUIRE(v[2].value == 20); - REQUIRE(v[2].move_assigned_count == 1); - REQUIRE(v[3].value == 30); - REQUIRE(v[3].move_constructed_count == 1); - } - } - - WHEN("erasing an element in the middle") - { - for (auto & elem : v) - { - elem.reset_counts(); - } - - auto it = v.erase(v.cbegin() + 1); - - THEN("the subsequent elements are move-assigned leftwards") - { - REQUIRE(v.size() == 2); - - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - - REQUIRE(v[1].value == 30); - REQUIRE(v[1].move_assigned_count == 1); - REQUIRE(v[1].copy_assigned_count == 0); - - REQUIRE(v.data()[2].destroyed_count == 1); - REQUIRE(v.data()[2].moved_from_count == 1); - - REQUIRE(it == v.begin() + 1); - } - } - - WHEN("erasing the last element") - { - for (auto & elem : v) - { - elem.reset_counts(); - } - - auto it = v.erase(v.cend() - 1); - - THEN("no elements are moved, just the last element destroyed") - { - REQUIRE(v.size() == 2); - - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_constructed_count == 0); - REQUIRE(v[0].copy_constructed_count == 0); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - - REQUIRE(v[1].value == 20); - REQUIRE(v[1].move_constructed_count == 0); - REQUIRE(v[1].copy_constructed_count == 0); - REQUIRE(v[1].move_assigned_count == 0); - REQUIRE(v[1].copy_assigned_count == 0); - - REQUIRE(v.data()[2].destroyed_count == 1); - REQUIRE(v.data()[2].moved_from_count == 0); - REQUIRE(v.data()[2].copied_from_count == 0); - - REQUIRE(it == v.end()); - } - } - - WHEN("erasing a range of elements in the middle") - { - v.emplace_back(40); - v.emplace_back(50); - - for (auto & elem : v) - { - elem.reset_counts(); - } - - auto it = v.erase(v.cbegin() + 1, v.cbegin() + 3); - - THEN("the specified elements are destroyed and subsequent elements are move-assigned leftwards") - { - REQUIRE(v.size() == 3); - - REQUIRE(v[0].value == 10); - REQUIRE(v[0].move_constructed_count == 0); - REQUIRE(v[0].copy_constructed_count == 0); - REQUIRE(v[0].move_assigned_count == 0); - REQUIRE(v[0].copy_assigned_count == 0); - - REQUIRE(v[1].value == 40); - REQUIRE(v[1].move_constructed_count == 0); - REQUIRE(v[1].copy_constructed_count == 0); - REQUIRE(v[1].move_assigned_count == 1); - REQUIRE(v[1].copy_assigned_count == 0); - - REQUIRE(v[2].value == 50); - REQUIRE(v[2].move_constructed_count == 0); - REQUIRE(v[2].copy_constructed_count == 0); - REQUIRE(v[2].move_assigned_count == 1); - REQUIRE(v[2].copy_assigned_count == 0); - - REQUIRE(v.data()[3].destroyed_count == 1); - REQUIRE(v.data()[3].moved_from_count == 1); - REQUIRE(v.data()[3].copied_from_count == 0); - - REQUIRE(v.data()[4].destroyed_count == 1); - REQUIRE(v.data()[4].moved_from_count == 1); - REQUIRE(v.data()[4].copied_from_count == 0); - - REQUIRE(it == v.begin() + 1); - } - } - } -} - -SCENARIO("Vector advanced construction", "[vector]") -{ - GIVEN("A count and a default value") - { - WHEN("constructing with count and default argument") - { - auto const count = 5uz; - auto const value = 42; - auto v = kstd::vector(count, value); - - THEN("the vector is initialized with count copies of value") - { - REQUIRE(v.size() == 5); - REQUIRE(v.capacity() == 5); - REQUIRE(v.front() == 42); - REQUIRE(v.back() == 42); - } - } - } - - GIVEN("A pure input iterator range") - { - WHEN("constructing from input iterators") - { - auto const arr = std::array{1, 2, 3}; - auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; - auto const last = kstd::tests::test_input_iterator{}; - - auto v = kstd::vector(first, last); - - THEN("the vector is generated dynamically and initialized correctly") - { - REQUIRE(v.size() == 3); - REQUIRE(v[0] == 1); - REQUIRE(v[2] == 3); - } - } - } - - GIVEN("A tracking allocator and a source vector") - { - auto allocs = 0; - auto allocator = kstd::tests::tracking_allocator{&allocs}; - auto source = kstd::vector>{allocator}; - source.push_back(1); - source.push_back(2); - source.push_back(3); - - allocs = 0; - - WHEN("copy constructing with an allocator") - { - auto copy = kstd::vector>(source, allocator); - - THEN("the copy succeeds and the allocator is used") - { - REQUIRE(copy.size() == 3); - REQUIRE(allocs > 0); - REQUIRE(copy[0] == 1); - REQUIRE(copy[2] == 3); - } - } - - WHEN("move constructing with an identically comparing allocator") - { - auto moved = kstd::vector>(std::move(source), allocator); - - THEN("the move succeeds and no new allocations are made (memory is stolen)") - { - REQUIRE(moved.size() == 3); - REQUIRE(allocs == 0); - REQUIRE(moved[0] == 1); - REQUIRE(moved[2] == 3); - } - } - - WHEN("move constructing with a non-equal allocator") - { - auto allocs2 = 0; - auto allocator2 = kstd::tests::tracking_allocator{&allocs2}; - - auto moved = kstd::vector>(std::move(source), allocator2); - - THEN("the move allocates new memory and moves elements") - { - REQUIRE(allocs2 > 0); - REQUIRE(moved.size() == 3); - REQUIRE(source.empty()); - } - } - } -} - -SCENARIO("Vector assignment operators", "[vector]") -{ - GIVEN("A source vector and an empty target vector") - { - auto source = kstd::vector{1, 2, 3}; - auto target = kstd::vector{}; - - WHEN("copy assigning") - { - target = source; - - THEN("the target matches the source") - { - REQUIRE(target.size() == 3); - REQUIRE(target[0] == 1); - REQUIRE(target[2] == 3); - REQUIRE(source.size() == 3); - } - } - - WHEN("move assigning") - { - target = std::move(source); - - THEN("the target assumes the source's data") - { - REQUIRE(target.size() == 3); - REQUIRE(target[0] == 1); - REQUIRE(target[2] == 3); - REQUIRE(source.empty()); - } - } - } - - GIVEN("Vectors with propagating copy allocator") - { - auto allocs1 = 0; - auto allocs2 = 0; - auto alloc1 = kstd::tests::propagating_allocator{&allocs1}; - auto alloc2 = kstd::tests::propagating_allocator{&allocs2}; - - auto v1 = kstd::vector>{alloc1}; - v1.push_back(1); - v1.push_back(2); - auto v2 = kstd::vector>{alloc2}; - - WHEN("copy assigning") - { - v2 = v1; - THEN("the allocator propagates") - { - REQUIRE(v2.get_allocator() == v1.get_allocator()); - } - } - } - - GIVEN("Vectors for copy assignment overlap") - { - auto v1 = kstd::vector{1, 2, 3}; - auto v2 = kstd::vector{4, 5}; - v2.reserve(10); - - WHEN("copy assigning a larger vector to a smaller one with enough capacity") - { - v2 = v1; - THEN("elements are copied and size is updated") - { - REQUIRE(v2.size() == 3); - REQUIRE(v2.capacity() >= 10); - REQUIRE(v2[2] == 3); - } - } - - auto v3 = kstd::vector{1, 2, 3, 4}; - v3.reserve(10); - WHEN("copy assigning a smaller vector to a larger one") - { - v3 = v1; - THEN("excess elements are destroyed") - { - REQUIRE(v3.size() == 3); - REQUIRE(v3[0] == 1); - } - } - } - - GIVEN("Vectors with the same tracking allocator") - { - auto allocs = 0; - auto alloc = kstd::tests::tracking_allocator{&allocs}; - auto v1 = kstd::vector>{alloc}; - v1.push_back(1); - auto v2 = kstd::vector>{alloc}; - - WHEN("move assigning") - { - v2 = std::move(v1); - THEN("memory is stolen without allocation") - { - REQUIRE(v2.size() == 1); - REQUIRE(allocs == 1); - } - } - } - - GIVEN("Vectors with different non-propagating tracking allocators") - { - auto allocs1 = 0; - auto allocs2 = 0; - auto alloc1 = kstd::tests::tracking_allocator{&allocs1}; - auto alloc2 = kstd::tests::tracking_allocator{&allocs2}; - - auto v1 = kstd::vector>{alloc1}; - v1.push_back(1); - v1.push_back(2); - v1.push_back(3); - - auto v2 = kstd::vector>{alloc2}; - v2.push_back(4); - v2.push_back(5); - - WHEN("move assigning a larger vector to a smaller one without enough capacity") - { - v2.shrink_to_fit(); - v2 = std::move(v1); - THEN("memory is reallocated and elements are moved") - { - REQUIRE(v2.size() == 3); - REQUIRE(allocs2 > 2); - } - } - - auto v3 = kstd::vector>{alloc2}; - v3.reserve(10); - v3.push_back(4); - v3.push_back(5); - WHEN("move assigning a larger vector to a smaller one with enough capacity") - { - v3 = std::move(v1); - THEN("elements are move-assigned over overlap and move-constructed over remainder") - { - REQUIRE(v3.size() == 3); - } - } - - auto v4 = kstd::vector>{alloc2}; - v4.reserve(10); - v4.push_back(4); - v4.push_back(5); - v4.push_back(6); - v4.push_back(7); - WHEN("move assigning a smaller vector to a larger one with enough capacity") - { - v4 = std::move(v1); - THEN("overlap is moved and excess is destroyed") - { - REQUIRE(v4.size() == 3); - } - } - } -} - -SCENARIO("Vector self-assignment operators", "[vector]") -{ - GIVEN("A populated vector") - { - auto v = kstd::vector{1, 2, 3}; - auto const initial_capacity = v.capacity(); - auto const * initial_data = v.data(); - - WHEN("copy assigning to itself") - { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" -#pragma GCC diagnostic ignored "-Wunknown-warning-option" -#pragma GCC diagnostic ignored "-Wself-assign-overloaded" - v = v; -#pragma GCC diagnostic pop - - THEN("the vector remains unchanged") - { - REQUIRE(v.size() == 3); - REQUIRE(v.capacity() == initial_capacity); - REQUIRE(v.data() == initial_data); - REQUIRE(v[0] == 1); - REQUIRE(v[2] == 3); - } - } - - WHEN("move assigning to itself") - { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" -#pragma GCC diagnostic ignored "-Wunknown-warning-option" -#pragma GCC diagnostic ignored "-Wself-move" - v = std::move(v); -#pragma GCC diagnostic pop - - THEN("the vector remains unchanged") - { - REQUIRE(v.size() == 3); - REQUIRE(v.capacity() == initial_capacity); - REQUIRE(v.data() == initial_data); - REQUIRE(v[0] == 1); - REQUIRE(v[2] == 3); - } - } - } -} - -SCENARIO("Vector const accessors and copy insertion", "[vector]") -{ - GIVEN("A const populated vector") - { - auto const v = kstd::vector{10, 20, 30}; - - WHEN("calling const accessors") - { - THEN("elements are read correctly as const references") - { - REQUIRE(v.front() == 10); - REQUIRE(v.back() == 30); - REQUIRE(v[1] == 20); - REQUIRE(v.at(1) == 20); - } - } - } - - GIVEN("An empty vector and a const lvalue tracker") - { - auto v = kstd::vector{}; - auto const tracker = kstd::tests::special_member_tracker{42}; - - WHEN("push_back is called with the const lvalue") - { - v.push_back(tracker); - - THEN("the element is gracefully copy-constructed") - { - REQUIRE(v.size() == 1); - REQUIRE(v.back().value == 42); - REQUIRE(v.back().copy_constructed_count == 1); - REQUIRE(v.back().move_constructed_count == 0); - } - } - - WHEN("push_back is called with a const lvalue when capacity is sufficient") - { - v.reserve(10); - auto const current_capacity = v.capacity(); - v.push_back(tracker); - - THEN("the element is copy-constructed without reallocation") - { - REQUIRE(v.size() == 1); - REQUIRE(v.capacity() == current_capacity); - REQUIRE(v.back().value == 42); - REQUIRE(v.back().copy_constructed_count == 1); - REQUIRE(v.back().move_constructed_count == 0); - } - } - } -} diff --git a/libs/kstd/kstd/vector.tests.cpp b/libs/kstd/kstd/vector.tests.cpp new file mode 100644 index 00000000..72c1127a --- /dev/null +++ b/libs/kstd/kstd/vector.tests.cpp @@ -0,0 +1,2003 @@ +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +SCENARIO("Vector initialization and construction", "[vector]") +{ + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto v = kstd::vector{}; + + THEN("the vector is empty") + { + REQUIRE(v.empty()); + } + + THEN("the size and capacity are zero") + { + REQUIRE(v.size() == 0); + REQUIRE(v.capacity() == 0); + } + } + + WHEN("constructing with a specific size") + { + auto v = kstd::vector(10); + + THEN("the vector is not empty") + { + REQUIRE_FALSE(v.empty()); + } + + THEN("the size is and capacity match the specified value") + { + REQUIRE(v.size() == 10); + REQUIRE(v.capacity() == 10); + } + } + + WHEN("constructing from an initializer list") + { + auto v = kstd::vector{1, 2, 3, 4, 5}; + + THEN("the vector is not empty") + { + REQUIRE_FALSE(v.empty()); + } + + THEN("the size is and capacity match the specified value") + { + REQUIRE(v.size() == 5); + REQUIRE(v.capacity() == 5); + } + + THEN("the elements are correctly initialized") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + REQUIRE(v[3] == 4); + REQUIRE(v[4] == 5); + } + } + } + + GIVEN("A non-empty range") + { + auto range = std::array{1, 2, 3}; + + WHEN("constructing from a random-access iterator range") + { + auto v = kstd::vector{std::begin(range), std::end(range)}; + + THEN("the vector is not empty") + { + REQUIRE_FALSE(v.empty()); + } + + THEN("the size and capacity match the range size") + { + REQUIRE(v.size() == std::size(range)); + REQUIRE(v.capacity() == std::size(range)); + } + + THEN("the elements are correctly initialized") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + + WHEN("constructing from a range") + { + auto v = kstd::vector{kstd::from_range, range}; + + THEN("the vector is not empty") + { + REQUIRE_FALSE(v.empty()); + } + + THEN("the size and capacity match the range size") + { + REQUIRE(v.size() == std::ranges::size(range)); + REQUIRE(v.capacity() == std::ranges::size(range)); + } + + THEN("the elements are correctly initialized") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + } + + GIVEN("A populated vector") + { + auto source = kstd::vector{1, 2, 3, 4, 5}; + + WHEN("copy constructing a new vector") + { + auto copy = kstd::vector(source); + + THEN("the copy matches the original") + { + REQUIRE(copy.size() == source.size()); + REQUIRE(copy.capacity() == source.capacity()); + REQUIRE(copy[0] == 1); + REQUIRE(copy[1] == 2); + REQUIRE(copy[2] == 3); + REQUIRE(copy[3] == 4); + REQUIRE(copy[4] == 5); + } + + THEN("the original is left unchanged") + { + REQUIRE(source.size() == 5); + REQUIRE(source.capacity() == 5); + REQUIRE(source[0] == 1); + REQUIRE(source[1] == 2); + REQUIRE(source[2] == 3); + REQUIRE(source[3] == 4); + REQUIRE(source[4] == 5); + } + } + + WHEN("move constructing a new vector") + { + auto moved = kstd::vector(std::move(source)); + + THEN("The new vector has the original elements") + { + REQUIRE(moved.size() == 5); + REQUIRE(moved.capacity() == 5); + REQUIRE(moved[0] == 1); + REQUIRE(moved[1] == 2); + REQUIRE(moved[2] == 3); + REQUIRE(moved[3] == 4); + REQUIRE(moved[4] == 5); + } + + THEN("The original vector is left in a valid but unspecified state") + { + REQUIRE(source.empty()); + REQUIRE(source.size() == 0); + REQUIRE(source.capacity() == 0); + } + } + } +} + +SCENARIO("Vector element access", "[vector]") +{ + GIVEN("A populated vector") + { + auto v = kstd::vector{10, 20, 30}; + + WHEN("accessing elements for reading") + { + THEN("operator[] and at() return the correct elements") + { + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + + REQUIRE(v.at(0) == 10); + REQUIRE(v.at(1) == 20); + REQUIRE(v.at(2) == 30); + } + + THEN("front() and back() return the first and last elements") + { + REQUIRE(v.front() == 10); + REQUIRE(v.back() == 30); + } + + THEN("data() return a pointer to the contiguous storage") + { + auto ptr = v.data(); + REQUIRE(ptr); + REQUIRE(ptr[0] == 10); + REQUIRE(ptr[1] == 20); + REQUIRE(ptr[2] == 30); + } + + THEN("accessing out of bounds elements panics") + { + REQUIRE_THROWS_AS(v.at(3), kstd::tests::os_panic); + } + } + + WHEN("accessing elements for writing") + { + v[0] = 100; + v.at(1) = 200; + v.back() = 300; + + THEN("the elements are correctly modified") + { + REQUIRE(v[0] == 100); + REQUIRE(v[1] == 200); + REQUIRE(v[2] == 300); + } + } + } +} + +SCENARIO("Vector iterators", "[vector]") +{ + GIVEN("A populated vector") + { + auto v = kstd::vector{1, 2, 3}; + + WHEN("using forward iterators") + { + THEN("they navigate the elements in the correct forward order") + { + auto it = v.begin(); + REQUIRE(it != v.end()); + REQUIRE(*it == 1); + + ++it; + REQUIRE(it != v.end()); + REQUIRE(*it == 2); + + ++it; + REQUIRE(it != v.end()); + REQUIRE(*it == 3); + + ++it; + REQUIRE(it == v.end()); + } + + THEN("const forward iterators provide correct access") + { + auto it = v.cbegin(); + REQUIRE(it != v.cend()); + REQUIRE(*it == 1); + + ++it; + REQUIRE(it != v.cend()); + REQUIRE(*it == 2); + + ++it; + REQUIRE(it != v.cend()); + REQUIRE(*it == 3); + + ++it; + REQUIRE(it == v.cend()); + } + } + + WHEN("using reverse iterators") + { + THEN("they navigate the elements in the correct reverse order") + { + auto it = v.rbegin(); + REQUIRE(it != v.rend()); + REQUIRE(*it == 3); + + ++it; + REQUIRE(it != v.rend()); + REQUIRE(*it == 2); + + ++it; + REQUIRE(it != v.rend()); + REQUIRE(*it == 1); + + ++it; + REQUIRE(it == v.rend()); + } + + THEN("const reverse iterators provide correct access") + { + auto it = v.crbegin(); + REQUIRE(it != v.crend()); + REQUIRE(*it == 3); + + ++it; + REQUIRE(it != v.crend()); + REQUIRE(*it == 2); + + ++it; + REQUIRE(it != v.crend()); + REQUIRE(*it == 1); + + ++it; + REQUIRE(it == v.crend()); + } + } + } + + GIVEN("an empty vector") + { + auto v = kstd::vector{}; + + WHEN("getting iterators") + { + THEN("begin() equals end() and cbegin() equals cend()") + { + REQUIRE(v.begin() == v.end()); + REQUIRE(v.cbegin() == v.cend()); + } + + THEN("rbegin() equals rend() and crbegin() equals crend()") + { + REQUIRE(v.rbegin() == v.rend()); + REQUIRE(v.crbegin() == v.crend()); + } + } + } +} + +SCENARIO("Vector capacity management", "[vector]") +{ + GIVEN("An empty vector") + { + auto v = kstd::vector{}; + + WHEN("reserving space") + { + v.reserve(10); + + THEN("the capacity is at least the reserved amount") + { + REQUIRE(v.capacity() >= 10); + } + + THEN("the size is still zero") + { + REQUIRE(v.size() == 0); + } + + THEN("the vector is still empty") + { + REQUIRE(v.empty()); + } + } + + WHEN("reserving space less than or equal to current capacity") + { + v.reserve(10); + auto const current_capacity = v.capacity(); + v.reserve(5); + + THEN("the capacity remains unchanged") + { + REQUIRE(v.capacity() == current_capacity); + } + } + + WHEN("reserving space greater than max_size") + { + THEN("a panic is triggered") + { + REQUIRE_THROWS_AS(v.reserve(v.max_size() + 1), kstd::tests::os_panic); + } + } + } + + GIVEN("A populated vector with excess capacity") + { + auto v = kstd::vector{1, 2, 3}; + v.reserve(10); + + REQUIRE(v.capacity() == 10); + + WHEN("calling shrink_to_fit") + { + v.shrink_to_fit(); + + THEN("the capacity is reduced to match the size") + { + REQUIRE(v.capacity() == 3); + REQUIRE(v.size() == 3); + } + + THEN("the elements remain unchanged") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + } +} + +SCENARIO("Vector modifiers", "[vector]") +{ + GIVEN("An empty vector") + { + auto v = kstd::vector{}; + + WHEN("push_back is called with a value") + { + v.push_back(10); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v.back() == 10); + } + } + + WHEN("emplace_back is called with constructor arguments") + { + v.emplace_back(20); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v.back() == 20); + } + } + + WHEN("elements are added while capacity is sufficient") + { + v.reserve(10); + auto const capacity = v.capacity(); + + v.push_back(10); + v.emplace_back(20); + + THEN("the elements are added without reallocation") + { + REQUIRE(v.size() == 2); + REQUIRE(v.capacity() == capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + } + } + + WHEN("emplace is called with the end iterator and constructor arguments") + { + v.emplace(v.end(), 20); + + THEN("the element is appended and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v.back() == 20); + } + } + + WHEN("emplace is called while capacity is sufficient") + { + v.reserve(10); + auto const capacity = v.capacity(); + + v.emplace(v.end(), 20); + + THEN("the element is appended without reallocation") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() == capacity); + REQUIRE(v.back() == 20); + } + } + + WHEN("inserting an element") + { + auto it = v.insert(v.cbegin(), 40); + + THEN("the size and capacity increase and the element is inserted") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v[0] == 40); + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting an lvalue element") + { + auto const value = 40; + auto it = v.insert(v.cbegin(), value); + + THEN("the size and capacity increase and the element is inserted") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() >= 1); + REQUIRE(v[0] == 40); + REQUIRE(it == v.begin()); + } + } + + WHEN("appending a range") + { + auto const range = std::views::iota(0, 3); + v.append_range(range); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are appended") + { + REQUIRE(v[0] == 0); + REQUIRE(v[1] == 1); + REQUIRE(v[2] == 2); + } + } + + WHEN("appending from an input range") + { + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + + v.append_range(std::ranges::subrange{first, last}); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are appended") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + + WHEN("appending from an input range with sufficient capacity") + { + v.reserve(3); + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + + v.append_range(std::ranges::subrange{first, last}); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity stays the same") + { + REQUIRE(v.capacity() == 3); + } + + THEN("the elements are appended") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + } + + WHEN("resizing the vector to a greater size") + { + v.resize(3); + + THEN("the size and capacity increase and the elements are value initialized") + { + REQUIRE(v.size() == 3); + REQUIRE(v.capacity() >= 3); + REQUIRE(v[0] == 0); + REQUIRE(v[1] == 0); + REQUIRE(v[2] == 0); + } + } + + WHEN("resizing the vector to a greater size with initial value") + { + v.resize(3, 2); + + THEN("the size and capacity increase and the elements are initialized to the given value") + { + REQUIRE(v.size() == 3); + REQUIRE(v.capacity() >= 3); + REQUIRE(v[0] == 2); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 2); + } + } + + WHEN("inserting a range at the beginning") + { + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.begin(), arr); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting a range at the end") + { + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.end(), arr); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting from an input range without sufficient capacity") + { + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last}); + + THEN("the size increases") + { + REQUIRE(v.size() == 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + } + + GIVEN("A populated vector") + { + auto v = kstd::vector{10, 20, 30}; + auto initial_capacity = v.capacity(); + + WHEN("push_back is called") + { + v.push_back(40); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() >= initial_capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 40); + } + } + + WHEN("emplace_back is called with constructor arguments") + { + v.emplace_back(40); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() >= initial_capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 40); + } + } + + WHEN("emplace is called with an iterator and constructor arguments") + { + auto it = v.emplace(v.begin() + 2, 25); + + THEN("the element is inserted and the size and capacity increase") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() >= initial_capacity); + REQUIRE(v.at(2) == 25); + } + + THEN("the returned iterator points to the inserted element") + { + REQUIRE(it == v.cbegin() + 2); + REQUIRE(*it == 25); + } + } + + WHEN("emplace is called with an iterator and sufficient capacity") + { + v.reserve(v.size() + 1); + + auto it = v.emplace(v.begin() + 2, 25); + + THEN("the element is inserted and the size and capacity increase") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() >= initial_capacity); + REQUIRE(v.at(2) == 25); + } + + THEN("the returned iterator points to the inserted element") + { + REQUIRE(it == v.cbegin() + 2); + REQUIRE(*it == 25); + } + } + + WHEN("push_back is called with a reference to an internal element") + { + v.shrink_to_fit(); + auto const original_value = v[0]; + + v.push_back(v[0]); + + THEN("reallocation handles the internal reference safely without dangling") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == original_value); + REQUIRE(v.back() == original_value); + } + } + + WHEN("pop_back is called") + { + v.pop_back(); + + THEN("the last element is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v.capacity() == initial_capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + } + } + + WHEN("clear is called") + { + v.clear(); + + THEN("the vector is empty") + { + REQUIRE(v.empty()); + REQUIRE(v.size() == 0); + REQUIRE(v.capacity() == initial_capacity); + } + } + + WHEN("inserting at the beginning") + { + auto it = v.insert(v.cbegin(), 5); + + THEN("the element is inserted at the front") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 5); + REQUIRE(v[1] == 10); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting in the middle") + { + auto it = v.insert(v.cbegin() + 1, 15); + + THEN("the element is inserted in the middle") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 15); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting at the end") + { + auto it = v.insert(v.cend(), 40); + + THEN("the element is inserted at the back") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 40); + REQUIRE(it == v.begin() + 3); + } + } + + WHEN("inserting an lvalue at the end") + { + auto const value = 40; + auto it = v.insert(v.cend(), value); + + THEN("the element is inserted at the back") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 40); + REQUIRE(it == v.begin() + 3); + } + } + + WHEN("inserting when capacity is sufficient") + { + v.reserve(10); + auto const capacity = v.capacity(); + + auto it = v.insert(v.cbegin() + 1, 15); + + THEN("the element is added without reallocation") + { + REQUIRE(v.size() == 4); + REQUIRE(v.capacity() == capacity); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 15); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting a reference to an existing element with reallocation") + { + v.shrink_to_fit(); + REQUIRE(v.capacity() == v.size()); + auto it = v.insert(v.cbegin() + 1, v[2]); + + THEN("the element is correctly copied and inserted") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting a reference to an existing element without reallocation") + { + v.reserve(10); + REQUIRE(v.capacity() > v.size()); + auto it = v.insert(v.cbegin() + 1, v[2]); + + THEN("the element is correctly copied and inserted") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting an rvalue reference to an existing element with reallocation") + { + v.shrink_to_fit(); + REQUIRE(v.capacity() == v.size()); + auto it = v.insert(v.cbegin() + 1, std::move(v[2])); + + THEN("the element is correctly moved and inserted") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting an rvalue reference to an existing element without reallocation") + { + v.reserve(10); + REQUIRE(v.capacity() > v.size()); + auto it = v.insert(v.cbegin() + 1, std::move(v[2])); + + THEN("the element is correctly moved and inserted") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("erasing the first element") + { + auto it = v.erase(v.cbegin()); + + THEN("the first element is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v[0] == 20); + REQUIRE(v[1] == 30); + REQUIRE(it == v.begin()); + } + } + + WHEN("erasing a middle element") + { + auto it = v.erase(v.cbegin() + 1); + + THEN("the middle element is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("erasing the last element") + { + auto it = v.erase(v.cend() - 1); + + THEN("the last element is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(it == v.end()); + } + } + + WHEN("erasing the end() iterator") + { + THEN("a panic is triggered") + { + REQUIRE_THROWS_AS(v.erase(v.end()), kstd::tests::os_panic); + } + } + + WHEN("erasing a range of elements") + { + auto it = v.erase(v.cbegin() + 1, v.cend() - 1); + + THEN("the specified range is removed and the size decreases") + { + REQUIRE(v.size() == 2); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("erasing an empty range") + { + auto it = v.erase(v.cbegin() + 1, v.cbegin() + 1); + + THEN("the vector is unchanged") + { + REQUIRE(v.size() == 3); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("appending a range") + { + auto initial_size = v.size(); + v.append_range(std::views::iota(0, 3)); + + THEN("capacity is increased") + { + REQUIRE(v.capacity() >= initial_capacity); + } + + THEN("size is increased") + { + REQUIRE(v.size() == initial_size + 3); + } + + THEN("the elements are appended") + { + REQUIRE(v[initial_size + 0] == 0); + REQUIRE(v[initial_size + 1] == 1); + REQUIRE(v[initial_size + 2] == 2); + } + } + + WHEN("resizing the vector to a greater size") + { + auto initial_size = v.size(); + v.resize(initial_size + 3); + + THEN("the size and capacity increase and the elements are value initialized") + { + REQUIRE(v.size() == initial_size + 3); + REQUIRE(v.capacity() >= initial_size + 3); + REQUIRE(v[initial_size + 0] == 0); + REQUIRE(v[initial_size + 1] == 0); + REQUIRE(v[initial_size + 2] == 0); + } + } + + WHEN("resizing the vector to a greater size with initial value") + { + auto initial_size = v.size(); + v.resize(initial_size + 3, 2); + + THEN("the size and capacity increase and the elements are initialized to the given value") + { + REQUIRE(v.size() == initial_size + 3); + REQUIRE(v.capacity() >= initial_size + 3); + REQUIRE(v[initial_size + 0] == 2); + REQUIRE(v[initial_size + 1] == 2); + REQUIRE(v[initial_size + 2] == 2); + } + } + + WHEN("resizing the vector to a smaller size") + { + v.resize(1); + + THEN("the size decreases and the elements are destroyed") + { + REQUIRE(v.size() == 1); + REQUIRE(v[0] == 10); + } + } + + WHEN("inserting an empty range") + { + auto initial_size = v.size(); + auto it = v.insert_range(v.begin(), std::views::empty); + + THEN("the size does not change") + { + REQUIRE(v.size() == initial_size); + } + + THEN("the capacity does not change") + { + REQUIRE(v.capacity() == initial_capacity); + } + + THEN("the content is unchanged") + { + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + } + + THEN("the returned iterator points to the position of insertion") + { + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting a range at the beginning") + { + auto initial_size = v.size(); + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.begin(), arr); + + THEN("the size increases") + { + REQUIRE(v.size() == initial_size + 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= initial_size + 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + REQUIRE(v[3] == 10); + REQUIRE(v[4] == 20); + REQUIRE(v[5] == 30); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + + WHEN("inserting a range at the end") + { + auto initial_size = v.size(); + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.end(), arr); + + THEN("the size increases") + { + REQUIRE(v.size() == initial_size + 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= initial_size + 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 20); + REQUIRE(v[2] == 30); + REQUIRE(v[3] == 1); + REQUIRE(v[4] == 2); + REQUIRE(v[5] == 3); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin() + initial_size); + } + } + + WHEN("inserting a range that causes reallocation") + { + auto initial_size = v.size(); + auto const arr = std::array{1, 2, 3}; + auto it = v.insert_range(v.begin() + 1, arr); + + THEN("the size increases") + { + REQUIRE(v.size() == initial_size + 3); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= initial_size + 3); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 1); + REQUIRE(v[2] == 2); + REQUIRE(v[3] == 3); + REQUIRE(v[4] == 20); + REQUIRE(v[5] == 30); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting fewer elements than the suffix without reallocation") + { + v.reserve(10); + auto const capacity = v.capacity(); + auto const arr = std::array{1}; + auto it = v.insert_range(v.begin() + 1, arr); + + THEN("the elements are correctly placed") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 1); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + } + + THEN("no reallocation occurs") + { + REQUIRE(v.capacity() == capacity); + } + + THEN("the returned iterator points to the inserted element") + { + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting fewer elements than the suffix without sufficient capacity") + { + v.shrink_to_fit(); + auto const arr = std::array{1}; + auto it = v.insert_range(v.begin() + 1, arr); + + THEN("the elements are correctly placed") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0] == 10); + REQUIRE(v[1] == 1); + REQUIRE(v[2] == 20); + REQUIRE(v[3] == 30); + } + + THEN("the returned iterator points to the inserted element") + { + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("inserting from an input range without sufficient capacity") + { + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last}); + + THEN("the size increases") + { + REQUIRE(v.size() == 6); + } + + THEN("the capacity increases") + { + REQUIRE(v.capacity() >= 6); + } + + THEN("the elements are inserted") + { + REQUIRE(v[0] == 1); + REQUIRE(v[1] == 2); + REQUIRE(v[2] == 3); + REQUIRE(v[3] == 10); + REQUIRE(v[4] == 20); + REQUIRE(v[5] == 30); + } + + THEN("the returned iterator points to the beginning of the inserted range") + { + REQUIRE(it == v.begin()); + } + } + } +} + +SCENARIO("Vector comparison", "[vector]") +{ + GIVEN("Two identical vectors") + { + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{1, 2, 3}; + + WHEN("comparing for equality") + { + THEN("the vectors are equal") + { + REQUIRE(v1 == v2); + } + + THEN("the vectors and not not-equal") + { + REQUIRE_FALSE(v1 != v2); + } + } + + WHEN("comparing using the spaceship operator") + { + THEN("the vectors are equivalent") + { + REQUIRE((v1 <=> v2) == 0); + REQUIRE(v1 <= v2); + REQUIRE(v1 >= v2); + } + } + } + + GIVEN("Two vectors of different sizes") + { + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{1, 2, 3, 4}; + + WHEN("comparing for equality") + { + THEN("the vectors are not equal") + { + REQUIRE_FALSE(v1 == v2); + } + + THEN("the vectors are not-equal") + { + REQUIRE(v1 != v2); + } + } + + WHEN("comparing for ordering") + { + THEN("the shorter vector evaluates as less than the longer vector") + { + REQUIRE(v1 < v2); + REQUIRE(v2 > v1); + } + } + } + + GIVEN("Two vectors of the same size but different elements") + { + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{1, 2, 4}; + + WHEN("comparing for ordering") + { + THEN("they are ordered lexicographically") + { + REQUIRE(v1 < v2); + REQUIRE(v2 > v1); + } + } + } +} + +SCENARIO("Vector with non-default-constructible types", "[vector]") +{ + GIVEN("A type without a default constructor") + { + WHEN("constructing an empty vector") + { + auto v = kstd::vector{}; + + THEN("the vector is empty") + { + REQUIRE(v.empty()); + } + } + + WHEN("using emplace_back") + { + auto v = kstd::vector{}; + v.emplace_back(40); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.back() == kstd::tests::non_default_constructible{40}); + } + } + } +} + +SCENARIO("Vector with custom allocator", "[vector]") +{ + GIVEN("a tracking allocator acting as the vector's memory manager") + { + auto allocations = 0; + auto allocator = kstd::tests::tracking_allocator{&allocations}; + + WHEN("a vector uses this allocator to allocate memory") + { + auto v = kstd::vector>(allocator); + REQUIRE(allocations == 0); + + v.reserve(10); + + THEN("the allocator was used to allocate memory") + { + REQUIRE(allocations > 0); + } + } + } +} + +SCENARIO("Vector modifier move semantics", "[vector]") +{ + GIVEN("An empty vector and a move tracker element") + { + auto v = kstd::vector{}; + auto tracker = kstd::tests::special_member_tracker{40}; + + WHEN("push_back is called with the move tracker") + { + v.push_back(std::move(tracker)); + + THEN("the element is added and the size and capacity increase") + { + REQUIRE(v.size() == 1); + REQUIRE(v.back().move_constructed_count == 1); + REQUIRE(v.back().copy_constructed_count == 0); + REQUIRE(v.back().value == 40); + } + + THEN("the original tracker is left in a valid but unspecified state") + { + REQUIRE(tracker.value == -1); + } + } + } + + GIVEN("An empty vector") + { + auto v = kstd::vector{}; + + WHEN("emplace_back is called with constructor arguments") + { + v.emplace_back(40); + + THEN("the element is constructed directly, without moves or copies") + { + REQUIRE(v.size() == 1); + REQUIRE(v.back().move_constructed_count == 0); + REQUIRE(v.back().copy_constructed_count == 0); + REQUIRE(v.back().value == 40); + } + } + } + + GIVEN("A populated vector of move trackers") + { + auto v = kstd::vector{}; + v.reserve(10); + v.emplace_back(10); + v.emplace_back(20); + v.emplace_back(30); + + WHEN("inserting an element in the middle with sufficient capacity") + { + auto const tracker = kstd::tests::special_member_tracker{15}; + v.insert(v.cbegin() + 1, tracker); + + THEN("the shifted elements are move-assigned and the new element is copy-assigned") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + REQUIRE(v[1].value == 15); + REQUIRE(v[1].move_assigned_count == 0); + REQUIRE(v[1].copy_assigned_count == 1); + REQUIRE(tracker.copied_from_count == 1); + REQUIRE(v[2].value == 20); + REQUIRE(v[2].move_assigned_count == 1); + REQUIRE(v[2].copy_assigned_count == 0); + REQUIRE(v[3].value == 30); + REQUIRE(v[3].move_constructed_count == 1); + } + } + + WHEN("inserting an rvalue element in the middle with sufficient capacity") + { + auto tracker = kstd::tests::special_member_tracker{15}; + v.insert(v.cbegin() + 1, std::move(tracker)); + + THEN("the shifted elements are move-assigned and the new element is move-assigned") + { + REQUIRE(v.size() == 4); + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + REQUIRE(v[1].value == 15); + REQUIRE(v[1].move_assigned_count == 1); + REQUIRE(v[1].copy_assigned_count == 0); + REQUIRE(tracker.moved_from_count == 1); + REQUIRE(v[2].value == 20); + REQUIRE(v[2].move_assigned_count == 1); + REQUIRE(v[3].value == 30); + REQUIRE(v[3].move_constructed_count == 1); + } + } + + WHEN("erasing an element in the middle") + { + for (auto & elem : v) + { + elem.reset_counts(); + } + + auto it = v.erase(v.cbegin() + 1); + + THEN("the subsequent elements are move-assigned leftwards") + { + REQUIRE(v.size() == 2); + + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + + REQUIRE(v[1].value == 30); + REQUIRE(v[1].move_assigned_count == 1); + REQUIRE(v[1].copy_assigned_count == 0); + + REQUIRE(v.data()[2].destroyed_count == 1); + REQUIRE(v.data()[2].moved_from_count == 1); + + REQUIRE(it == v.begin() + 1); + } + } + + WHEN("erasing the last element") + { + for (auto & elem : v) + { + elem.reset_counts(); + } + + auto it = v.erase(v.cend() - 1); + + THEN("no elements are moved, just the last element destroyed") + { + REQUIRE(v.size() == 2); + + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_constructed_count == 0); + REQUIRE(v[0].copy_constructed_count == 0); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + + REQUIRE(v[1].value == 20); + REQUIRE(v[1].move_constructed_count == 0); + REQUIRE(v[1].copy_constructed_count == 0); + REQUIRE(v[1].move_assigned_count == 0); + REQUIRE(v[1].copy_assigned_count == 0); + + REQUIRE(v.data()[2].destroyed_count == 1); + REQUIRE(v.data()[2].moved_from_count == 0); + REQUIRE(v.data()[2].copied_from_count == 0); + + REQUIRE(it == v.end()); + } + } + + WHEN("erasing a range of elements in the middle") + { + v.emplace_back(40); + v.emplace_back(50); + + for (auto & elem : v) + { + elem.reset_counts(); + } + + auto it = v.erase(v.cbegin() + 1, v.cbegin() + 3); + + THEN("the specified elements are destroyed and subsequent elements are move-assigned leftwards") + { + REQUIRE(v.size() == 3); + + REQUIRE(v[0].value == 10); + REQUIRE(v[0].move_constructed_count == 0); + REQUIRE(v[0].copy_constructed_count == 0); + REQUIRE(v[0].move_assigned_count == 0); + REQUIRE(v[0].copy_assigned_count == 0); + + REQUIRE(v[1].value == 40); + REQUIRE(v[1].move_constructed_count == 0); + REQUIRE(v[1].copy_constructed_count == 0); + REQUIRE(v[1].move_assigned_count == 1); + REQUIRE(v[1].copy_assigned_count == 0); + + REQUIRE(v[2].value == 50); + REQUIRE(v[2].move_constructed_count == 0); + REQUIRE(v[2].copy_constructed_count == 0); + REQUIRE(v[2].move_assigned_count == 1); + REQUIRE(v[2].copy_assigned_count == 0); + + REQUIRE(v.data()[3].destroyed_count == 1); + REQUIRE(v.data()[3].moved_from_count == 1); + REQUIRE(v.data()[3].copied_from_count == 0); + + REQUIRE(v.data()[4].destroyed_count == 1); + REQUIRE(v.data()[4].moved_from_count == 1); + REQUIRE(v.data()[4].copied_from_count == 0); + + REQUIRE(it == v.begin() + 1); + } + } + } +} + +SCENARIO("Vector advanced construction", "[vector]") +{ + GIVEN("A count and a default value") + { + WHEN("constructing with count and default argument") + { + auto const count = 5uz; + auto const value = 42; + auto v = kstd::vector(count, value); + + THEN("the vector is initialized with count copies of value") + { + REQUIRE(v.size() == 5); + REQUIRE(v.capacity() == 5); + REQUIRE(v.front() == 42); + REQUIRE(v.back() == 42); + } + } + } + + GIVEN("A pure input iterator range") + { + WHEN("constructing from input iterators") + { + auto const arr = std::array{1, 2, 3}; + auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()}; + auto const last = kstd::tests::test_input_iterator{}; + + auto v = kstd::vector(first, last); + + THEN("the vector is generated dynamically and initialized correctly") + { + REQUIRE(v.size() == 3); + REQUIRE(v[0] == 1); + REQUIRE(v[2] == 3); + } + } + } + + GIVEN("A tracking allocator and a source vector") + { + auto allocs = 0; + auto allocator = kstd::tests::tracking_allocator{&allocs}; + auto source = kstd::vector>{allocator}; + source.push_back(1); + source.push_back(2); + source.push_back(3); + + allocs = 0; + + WHEN("copy constructing with an allocator") + { + auto copy = kstd::vector>(source, allocator); + + THEN("the copy succeeds and the allocator is used") + { + REQUIRE(copy.size() == 3); + REQUIRE(allocs > 0); + REQUIRE(copy[0] == 1); + REQUIRE(copy[2] == 3); + } + } + + WHEN("move constructing with an identically comparing allocator") + { + auto moved = kstd::vector>(std::move(source), allocator); + + THEN("the move succeeds and no new allocations are made (memory is stolen)") + { + REQUIRE(moved.size() == 3); + REQUIRE(allocs == 0); + REQUIRE(moved[0] == 1); + REQUIRE(moved[2] == 3); + } + } + + WHEN("move constructing with a non-equal allocator") + { + auto allocs2 = 0; + auto allocator2 = kstd::tests::tracking_allocator{&allocs2}; + + auto moved = kstd::vector>(std::move(source), allocator2); + + THEN("the move allocates new memory and moves elements") + { + REQUIRE(allocs2 > 0); + REQUIRE(moved.size() == 3); + REQUIRE(source.empty()); + } + } + } +} + +SCENARIO("Vector assignment operators", "[vector]") +{ + GIVEN("A source vector and an empty target vector") + { + auto source = kstd::vector{1, 2, 3}; + auto target = kstd::vector{}; + + WHEN("copy assigning") + { + target = source; + + THEN("the target matches the source") + { + REQUIRE(target.size() == 3); + REQUIRE(target[0] == 1); + REQUIRE(target[2] == 3); + REQUIRE(source.size() == 3); + } + } + + WHEN("move assigning") + { + target = std::move(source); + + THEN("the target assumes the source's data") + { + REQUIRE(target.size() == 3); + REQUIRE(target[0] == 1); + REQUIRE(target[2] == 3); + REQUIRE(source.empty()); + } + } + } + + GIVEN("Vectors with propagating copy allocator") + { + auto allocs1 = 0; + auto allocs2 = 0; + auto alloc1 = kstd::tests::propagating_allocator{&allocs1}; + auto alloc2 = kstd::tests::propagating_allocator{&allocs2}; + + auto v1 = kstd::vector>{alloc1}; + v1.push_back(1); + v1.push_back(2); + auto v2 = kstd::vector>{alloc2}; + + WHEN("copy assigning") + { + v2 = v1; + THEN("the allocator propagates") + { + REQUIRE(v2.get_allocator() == v1.get_allocator()); + } + } + } + + GIVEN("Vectors for copy assignment overlap") + { + auto v1 = kstd::vector{1, 2, 3}; + auto v2 = kstd::vector{4, 5}; + v2.reserve(10); + + WHEN("copy assigning a larger vector to a smaller one with enough capacity") + { + v2 = v1; + THEN("elements are copied and size is updated") + { + REQUIRE(v2.size() == 3); + REQUIRE(v2.capacity() >= 10); + REQUIRE(v2[2] == 3); + } + } + + auto v3 = kstd::vector{1, 2, 3, 4}; + v3.reserve(10); + WHEN("copy assigning a smaller vector to a larger one") + { + v3 = v1; + THEN("excess elements are destroyed") + { + REQUIRE(v3.size() == 3); + REQUIRE(v3[0] == 1); + } + } + } + + GIVEN("Vectors with the same tracking allocator") + { + auto allocs = 0; + auto alloc = kstd::tests::tracking_allocator{&allocs}; + auto v1 = kstd::vector>{alloc}; + v1.push_back(1); + auto v2 = kstd::vector>{alloc}; + + WHEN("move assigning") + { + v2 = std::move(v1); + THEN("memory is stolen without allocation") + { + REQUIRE(v2.size() == 1); + REQUIRE(allocs == 1); + } + } + } + + GIVEN("Vectors with different non-propagating tracking allocators") + { + auto allocs1 = 0; + auto allocs2 = 0; + auto alloc1 = kstd::tests::tracking_allocator{&allocs1}; + auto alloc2 = kstd::tests::tracking_allocator{&allocs2}; + + auto v1 = kstd::vector>{alloc1}; + v1.push_back(1); + v1.push_back(2); + v1.push_back(3); + + auto v2 = kstd::vector>{alloc2}; + v2.push_back(4); + v2.push_back(5); + + WHEN("move assigning a larger vector to a smaller one without enough capacity") + { + v2.shrink_to_fit(); + v2 = std::move(v1); + THEN("memory is reallocated and elements are moved") + { + REQUIRE(v2.size() == 3); + REQUIRE(allocs2 > 2); + } + } + + auto v3 = kstd::vector>{alloc2}; + v3.reserve(10); + v3.push_back(4); + v3.push_back(5); + WHEN("move assigning a larger vector to a smaller one with enough capacity") + { + v3 = std::move(v1); + THEN("elements are move-assigned over overlap and move-constructed over remainder") + { + REQUIRE(v3.size() == 3); + } + } + + auto v4 = kstd::vector>{alloc2}; + v4.reserve(10); + v4.push_back(4); + v4.push_back(5); + v4.push_back(6); + v4.push_back(7); + WHEN("move assigning a smaller vector to a larger one with enough capacity") + { + v4 = std::move(v1); + THEN("overlap is moved and excess is destroyed") + { + REQUIRE(v4.size() == 3); + } + } + } +} + +SCENARIO("Vector self-assignment operators", "[vector]") +{ + GIVEN("A populated vector") + { + auto v = kstd::vector{1, 2, 3}; + auto const initial_capacity = v.capacity(); + auto const * initial_data = v.data(); + + WHEN("copy assigning to itself") + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wunknown-warning-option" +#pragma GCC diagnostic ignored "-Wself-assign-overloaded" + v = v; +#pragma GCC diagnostic pop + + THEN("the vector remains unchanged") + { + REQUIRE(v.size() == 3); + REQUIRE(v.capacity() == initial_capacity); + REQUIRE(v.data() == initial_data); + REQUIRE(v[0] == 1); + REQUIRE(v[2] == 3); + } + } + + WHEN("move assigning to itself") + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wunknown-warning-option" +#pragma GCC diagnostic ignored "-Wself-move" + v = std::move(v); +#pragma GCC diagnostic pop + + THEN("the vector remains unchanged") + { + REQUIRE(v.size() == 3); + REQUIRE(v.capacity() == initial_capacity); + REQUIRE(v.data() == initial_data); + REQUIRE(v[0] == 1); + REQUIRE(v[2] == 3); + } + } + } +} + +SCENARIO("Vector const accessors and copy insertion", "[vector]") +{ + GIVEN("A const populated vector") + { + auto const v = kstd::vector{10, 20, 30}; + + WHEN("calling const accessors") + { + THEN("elements are read correctly as const references") + { + REQUIRE(v.front() == 10); + REQUIRE(v.back() == 30); + REQUIRE(v[1] == 20); + REQUIRE(v.at(1) == 20); + } + } + } + + GIVEN("An empty vector and a const lvalue tracker") + { + auto v = kstd::vector{}; + auto const tracker = kstd::tests::special_member_tracker{42}; + + WHEN("push_back is called with the const lvalue") + { + v.push_back(tracker); + + THEN("the element is gracefully copy-constructed") + { + REQUIRE(v.size() == 1); + REQUIRE(v.back().value == 42); + REQUIRE(v.back().copy_constructed_count == 1); + REQUIRE(v.back().move_constructed_count == 0); + } + } + + WHEN("push_back is called with a const lvalue when capacity is sufficient") + { + v.reserve(10); + auto const current_capacity = v.capacity(); + v.push_back(tracker); + + THEN("the element is copy-constructed without reallocation") + { + REQUIRE(v.size() == 1); + REQUIRE(v.capacity() == current_capacity); + REQUIRE(v.back().value == 42); + REQUIRE(v.back().copy_constructed_count == 1); + REQUIRE(v.back().move_constructed_count == 0); + } + } + } +} -- cgit v1.2.3