From 125e82863d66cf2a25fdcba967ce5a6fd9a55d12 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 10 Jun 2026 17:23:07 +0200 Subject: kstd: add basic char_traits implementation --- libs/kstd/CMakeLists.txt | 1 + libs/kstd/kstd/bits/char_traits.hpp | 119 ++++++++++++++++++++++++++++++ libs/kstd/kstd/bits/char_traits.test.cpp | 120 +++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 libs/kstd/kstd/bits/char_traits.hpp create mode 100644 libs/kstd/kstd/bits/char_traits.test.cpp diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 0f647614..36bacfcd 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -77,6 +77,7 @@ if(BUILD_TESTING) "kstd/flat_map.test.cpp" "kstd/format.test.cpp" "kstd/vector.test.cpp" + "kstd/bits/char_traits.test.cpp" "kstd/bits/observer_ptr.test.cpp" "kstd/test_support/os_panic.test.cpp" "kstd/string.test.cpp" diff --git a/libs/kstd/kstd/bits/char_traits.hpp b/libs/kstd/kstd/bits/char_traits.hpp new file mode 100644 index 00000000..b9a4a038 --- /dev/null +++ b/libs/kstd/kstd/bits/char_traits.hpp @@ -0,0 +1,119 @@ +#ifndef KSTD_BITS_CHAR_TRAITS_HPP +#define KSTD_BITS_CHAR_TRAITS_HPP + +// IWYU pragma: private, include + +#include + +#include +#include + +namespace kstd +{ + + template + struct char_traits; + + template<> + struct char_traits + { + using char_type = char; + using int_type = int; + using off_type = long long; + + constexpr auto static assign(char_type & lhs, char_type const & rhs) noexcept -> void + { + lhs = rhs; + } + + constexpr auto static assign(char_type * destination, std::size_t count, char_type value) noexcept -> char_type * + { + std::ranges::fill_n(destination, static_cast(count), value); + return destination; + } + + constexpr auto static eq(char_type lhs, char_type rhs) noexcept -> bool + { + return static_cast(lhs) == static_cast(rhs); + } + + constexpr auto static lt(char_type lhs, char_type rhs) noexcept -> bool + { + return static_cast(lhs) < static_cast(rhs); + } + + constexpr auto static move(char_type * destination, char_type const * source, std::size_t count) noexcept + -> char_type * + { + std::ranges::move_backward(source, source + count, destination); + return destination; + } + + constexpr auto static copy(char_type * destination, char_type const * source, std::size_t count) noexcept + -> char_type * + { + std::ranges::copy(source, source + count, destination); + return destination; + } + + constexpr auto static compare(char_type const * lhs, char_type const * rhs, std::size_t count) noexcept -> int + { + for (auto i = 0uz; i < count; ++i) + { + auto comparison_result = static_cast(lhs[i]) - static_cast(rhs[i]); + if (comparison_result) + { + return comparison_result; + } + } + return 0; + } + + constexpr auto static length(char_type const * string) noexcept -> std::size_t + { + auto string_length = 0uz; + for (; string[string_length] != char_type{}; ++string_length) + ; + return string_length; + } + + constexpr auto static find(char_type const * string, std::size_t count, char_type const & needle) noexcept + -> char_type const * + { + auto found = std::ranges::find(string, string + count, needle); + if (found == string + count) + { + return nullptr; + } + return found; + } + + constexpr auto static to_char_type(int_type value) noexcept -> char_type + { + return static_cast(value); + } + + constexpr auto static to_int_type(char_type value) noexcept -> int_type + { + return static_cast(value); + } + + constexpr auto static eq_int_type(int_type lhs, int_type rhs) noexcept -> bool + { + return lhs == rhs; + } + + constexpr auto static eof() noexcept -> int_type + { + return -1; + } + + constexpr auto static not_eof(int_type value) noexcept -> int_type + { + return value == eof() ? 0 : value; + } + }; + +} // namespace kstd + +#endif diff --git a/libs/kstd/kstd/bits/char_traits.test.cpp b/libs/kstd/kstd/bits/char_traits.test.cpp new file mode 100644 index 00000000..4864c378 --- /dev/null +++ b/libs/kstd/kstd/bits/char_traits.test.cpp @@ -0,0 +1,120 @@ +#include + +#include + +#include + +SCENARIO("Character traits for char", "[string]") +{ + using traits = kstd::char_traits; + + THEN("Type aliases") + { + REQUIRE(std::is_same_v::char_type>); + REQUIRE(std::is_same_v::int_type>); + REQUIRE(std::is_same_v::off_type>); + } + + GIVEN("two unequal characters 'a' and 'B'") + { + auto const a = 'a'; + auto const B = 'B'; + + WHEN("comparing the two") + { + THEN("eq(a, a) returns true") + { + REQUIRE(traits::eq(a, a)); + } + + THEN("eq(a, B) returns false") + { + REQUIRE_FALSE(traits::eq(a, B)); + } + + THEN("eq(B, a) returns false") + { + REQUIRE_FALSE(traits::eq(B, a)); + } + + THEN("lt(a,a) returns false") + { + REQUIRE_FALSE(traits::lt(a, a)); + } + + THEN("lt(a,B) returns false") + { + REQUIRE_FALSE(traits::lt(a, B)); + } + + THEN("lt(B,a) returns true") + { + REQUIRE(traits::lt(B, a)); + } + + THEN("eq_int_type(a, a) returns true") + { + REQUIRE(traits::eq_int_type(a, a)); + } + + THEN("eq_int_type(a, B) returns false") + { + REQUIRE_FALSE(traits::eq_int_type(a, B)); + } + + THEN("eq_int_type(B, a) returns false") + { + REQUIRE_FALSE(traits::eq_int_type(B, a)); + } + } + } + + GIVEN("two unequal strings 'abc', 'DEFG'") + { + auto const abc = "abc"; + auto const abDe = "abDe"; + + WHEN("comparing the two") + { + THEN("compare(abc, abc, 3) returns 0") + { + REQUIRE(traits::compare(abc, abc, 3) == 0); + } + + THEN("compare(abc, abDe, 2) returns 0") + { + REQUIRE(traits::compare(abc, abDe, 2) == 0); + } + + THEN("compare(abc, abDe, 3) returns >0") + { + REQUIRE(traits::compare(abc, abDe, 3) > 0); + } + + THEN("compare(abDe, abc, 3) returns <0") + { + REQUIRE(traits::compare(abDe, abc, 3) < 0); + } + } + } + + GIVEN("The string 'abcd'") + { + auto const abcd = "abcd"; + + THEN("length(abcd) returns 4") + { + REQUIRE(traits::length(abcd) == 4); + } + + THEN("find(abcd, 4, b) returns a pointer to b") + { + REQUIRE(*traits::find(abcd, 4, 'b') == 'b'); + } + + THEN("find(abcd, 4, q) returns a null pointer") + { + REQUIRE(traits::find(abcd, 4, 'q') == nullptr); + } + } +} -- cgit v1.2.3 From abaaf61af598c32906a94b3db81865352e77ef70 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 11 Jun 2026 19:24:54 +0200 Subject: kstd: add SSO string replacement draft --- libs/kstd/CMakeLists.txt | 2 +- libs/kstd/kstd/bits/basic_string.hpp | 1336 ++++++++++++++++++++ libs/kstd/kstd/bits/basic_string.test.cpp | 1876 +++++++++++++++++++++++++++++ libs/kstd/kstd/bits/char_traits.hpp | 119 -- libs/kstd/kstd/bits/char_traits.test.cpp | 120 -- libs/kstd/kstd/string | 320 +---- 6 files changed, 3218 insertions(+), 555 deletions(-) create mode 100644 libs/kstd/kstd/bits/basic_string.hpp create mode 100644 libs/kstd/kstd/bits/basic_string.test.cpp delete mode 100644 libs/kstd/kstd/bits/char_traits.hpp delete mode 100644 libs/kstd/kstd/bits/char_traits.test.cpp diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 36bacfcd..99ce0c81 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -77,7 +77,7 @@ if(BUILD_TESTING) "kstd/flat_map.test.cpp" "kstd/format.test.cpp" "kstd/vector.test.cpp" - "kstd/bits/char_traits.test.cpp" + "kstd/bits/basic_string.test.cpp" "kstd/bits/observer_ptr.test.cpp" "kstd/test_support/os_panic.test.cpp" "kstd/string.test.cpp" diff --git a/libs/kstd/kstd/bits/basic_string.hpp b/libs/kstd/kstd/bits/basic_string.hpp new file mode 100644 index 00000000..4b32e312 --- /dev/null +++ b/libs/kstd/kstd/bits/basic_string.hpp @@ -0,0 +1,1336 @@ +#ifndef KSTD_BITS_BASIC_STRING_HPP +#define KSTD_BITS_BASIC_STRING_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace kstd +{ + + template + using char_traits = std::char_traits; // NOLINT + + template, + typename Allocator = kstd::allocator> + struct basic_string + + { + //! The character traits of this string. + using traits_type = Traits; + //! The type of character stored in this string. + using value_type = CharacterType; + //! The allocator type used for memory allocations by this string. + using allocator_type = Allocator; + //! The type of all sizes in this string. + using size_type = std::allocator_traits::size_type; + //! The integer type used to represent differences in positions in this string. + using difference_type = std::allocator_traits::difference_type; + //! The type of a reference to an element of this string. + using reference = value_type &; + //! The type of a reference to a constant element of this string. + using const_reference = value_type const &; + //! The type of a pointer to an element of this string. + using pointer = std::allocator_traits::pointer; + //! The type of a pointer to a constant element of this string. + using const_pointer = std::allocator_traits::const_pointer; + //! The type of an iterator to elements of this string. + using iterator = pointer; + //! The type of an iterator to constant elements of this string. + using const_iterator = const_pointer; + //! The type of an iterator to elements of this string, preseting the in reverse order. + using reverse_iterator = std::reverse_iterator; + //! The type of an iterator to constant elements of this string, preseting the in reverse order. + using const_reverse_iterator = std::reverse_iterator; + + //! A constant representing a non-existant position in this string. + constexpr auto static inline npos = static_cast(-1); + + //! Construct an empty string. + basic_string() noexcept(noexcept(Allocator{})) + : basic_string{Allocator{}} + {} + + //! Construct an empty basic string with the given allocator + //! + //! @param allocator The allocator to use for memory allocations in this string. + explicit basic_string(Allocator const & allocator) + : m_allocator{allocator} + , m_data{m_sso_buffer} + { + update_length(0); + } + + //! Construct a string with a single character. + //! + //! @param character The character to initialize the string with. + explicit constexpr basic_string(value_type character) + : basic_string{Allocator{}} + { + m_data[0] = character; + update_length(1); + } + + //! Construct a string with a given number of copies of a given character. + //! + //! @param count The number of characters. + //! @param value The value to use. + //! @param allocator The allocator to use for memory allocations in this string. + constexpr basic_string(size_type count, CharacterType value, Allocator const & allocator = Allocator{}) + : basic_string{allocator} + { + if (count > capacity()) + { + update_data(allocate_buffer(count, true)); + } + + std::ranges::fill_n(m_data, count, value); + update_length(count); + } + + //! Construct a string with the elements in the range denoted by two given iterators. + //! + //! @param first The iterator to the first element. + //! @param last The iterator beyond the last element. + //! @param allocator The allocator to use for memory allocation in this string. + //! @tparam InputIterator an iterator type satisfying the InputIterator concept. + template + constexpr basic_string(InputIterator first, InputIterator last, Allocator const & allocator = Allocator{}) + : basic_string{allocator} + { + forward_from(first, last); + } + + //! Construct a string with the contents of a given range. + //! + //! @param range The range to use elements from. + //! @param allocator The allocator to use for memory allocation in this string. + template Range> + constexpr basic_string(kstd::from_range_t, Range && range, Allocator const & allocator = Allocator{}) + : basic_string{allocator} + { + forward_from(std::ranges::begin(std::forward(range)), std::ranges::end(std::forward(range))); + } + + //! Construct a string with the contents of a given range. + //! + //! @param source The start of the source range. + //! @param count The number of elements to copy. + //! @param allocator The allocator to use for memory allocation in this string. + constexpr basic_string(CharacterType const * source, size_type count, Allocator const & allocator = Allocator{}) + : basic_string{allocator} + { + if (!source && count) + { + os::panic("Tried to construct string from null pointer!"); + } + + if (count > capacity()) + { + update_data(allocate_buffer(count, true)); + } + + traits_type::copy(m_data, source, count); + update_length(count); + } + + //! Construct a string from a given null-terminated, C-style string. + constexpr basic_string(CharacterType const * string, Allocator const & allocator = Allocator{}) + : basic_string{string, traits_type::length(string), allocator} + {} + + //! Construction from nullptr is prohibited. + constexpr basic_string(std::nullptr_t) = delete; + + //! Construct a string from a string view like object. + //! + //! @param view_like An object that is implicitly convertible to a string view. + //! @param allocator The allocator to use for memory allocation in this string. + template + requires(std::is_convertible_v> && + !std::is_convertible_v) + explicit constexpr basic_string(StringViewLike const & view_like, Allocator const & allocator = Allocator{}) + : basic_string{allocator} + { + auto converted = std::basic_string_view{view_like}; + forward_from(std::ranges::begin(converted), std::ranges::end(converted)); + } + + //! Construct a string from a substring of a string view like object. + //! + //! @param view_like An object that is implicitly convertible to a string view. + //! @param from The starting position in the string view like object. + //! @param count The maximum number of character to copy from the string view like object. + //! @param allocator The allocator to use for memory allocation in this string. + template + requires std::is_convertible_v> + constexpr basic_string(StringViewLike const & view_like, size_type from, size_type count, + Allocator const & allocator = Allocator{}) + : basic_string{std::basic_string_view{view_like}.substr(from, count), allocator} + {} + + //! Construct a new string as a copy of an existing one. + //! + //! @param other The string to copy from. + constexpr basic_string(basic_string const & other) + : basic_string{other.data(), other.size(), + std::allocator_traits::select_on_container_copy_construction(other.m_allocator)} + {} + + //! Construct a new string by moving from an existing one. + //! + //! @param other The string to move from. + constexpr basic_string(basic_string && other) noexcept + : m_allocator{std::move(other.m_allocator)} + , m_length{other.m_length} + { + if (other.in_sso_state()) + { + m_data = m_sso_buffer; + traits_type::copy(m_data, other.m_data, m_length + 1); + } + else + { + m_data = other.m_data; + other.m_data = other.m_sso_buffer; + m_heap_capacity = other.m_heap_capacity; + } + + other.update_length(0); + } + + //! Destroy a string, releasing all resources. + constexpr ~basic_string() + { + release_buffer(); + } + + //! Replace the contents of this string with a copy of the contents of another one. + //! + //! @param other The string to copy from. + //! @return A reference to this string. + constexpr auto operator=(basic_string const & other) -> basic_string & + { + if (this == &other) + { + return *this; + } + + if constexpr (std::allocator_traits::propagate_on_container_copy_assignment::value) + { + if (m_allocator != other.m_allocator) + { + release_buffer(); + m_data = m_sso_buffer; + } + m_allocator = other.m_allocator; + } + + if (other.in_sso_state()) + { + if (!in_sso_state()) + { + release_buffer(); + m_data = m_sso_buffer; + } + traits_type::copy(m_sso_buffer, other.m_sso_buffer, other.m_length + 1); + } + else + { + if (in_sso_state() || m_heap_capacity < other.m_length) + { + update_data(allocate_buffer(other.m_length, true)); + traits_type::copy(m_data, other.m_data, other.m_length + 1); + } + else + { + traits_type::copy(m_data, other.m_data, other.m_length + 1); + } + } + + update_length(other.m_length); + return *this; + } + + //! Replace the contents of this string by moving from another one. + //! + //! @param other The string to move from. + //! @return A reference to this string. + constexpr auto operator=(basic_string && other) noexcept( + std::allocator_traits::propagate_on_container_move_assignment::value || + std::allocator_traits::is_always_equal::value) -> basic_string & + { + if (this == &other) + { + return *this; + } + + if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) + { + if (m_allocator != other.m_allocator) + { + release_buffer(); + m_data = m_sso_buffer; + } + m_allocator = std::move(other.m_allocator); + } + + if (other.in_sso_state()) + { + if (!in_sso_state()) + { + release_buffer(); + m_data = m_sso_buffer; + } + traits_type::copy(m_sso_buffer, other.m_sso_buffer, other.m_length + 1); + } + else if (std::allocator_traits::propagate_on_container_move_assignment::value || + m_allocator == other.m_allocator) + { + release_buffer(); + m_data = std::exchange(other.m_data, other.m_sso_buffer); + m_heap_capacity = other.m_heap_capacity; + } + else + { + if (in_sso_state() || m_heap_capacity < other.m_length) + { + update_data(allocate_buffer(other.m_length, true)); + traits_type::copy(m_data, other.m_data, other.m_length + 1); + } + else + { + traits_type::copy(m_data, other.m_data, other.m_length + 1); + } + } + + update_length(other.m_length); + other.update_length(0); + + return *this; + } + + //! Replace the contents of this string with a copy of a given C-style string. + //! + //! @param string The C-style string to copy from. + //! @return A reference to this string. + constexpr auto operator=(CharacterType const * string) -> basic_string & + { + if (string == nullptr) + { + os::panic("Tried to construct string from null pointer!"); + } + + auto incoming_length = traits_type::length(string); + + if (capacity() < incoming_length) + { + auto new_buffer = allocate_buffer(incoming_length, true); + traits_type::copy(new_buffer.data(), string, incoming_length + 1); + update_data(new_buffer); + } + else + { + traits_type::move(m_data, string, incoming_length); + } + + update_length(incoming_length); + return *this; + } + + //! Replace the contents of this string with a given character. + //! + //! @param character The character to use to replace the contents of this string. + //! @return A reference to this string. + constexpr auto operator=(CharacterType character) -> basic_string & + { + m_data[0] = character; + update_length(1); + return *this; + } + + //! Replace the contents of this string with the elements of a given initializer list. + //! + //! @param list The initializer list to copy from. + //! @return A reference to this string. + constexpr auto operator=(std::initializer_list list) -> basic_string & + { + if (list.size() > capacity()) + { + update_data(allocate_buffer(list.size(), true)); + } + + for (auto i = 0uz; auto elem : list) + { + traits_type::assign(m_data[i], elem); + } + + update_length(list.size()); + return *this; + } + + //! Replace the contents of this string with the one from a given strinv view like object. + //! + //! @param view_like The string view like object to copy from. + //! @return A reference to this string. + template + requires(std::is_convertible_v> && + !std::is_convertible_v) + constexpr auto operator=(StringViewLike const & view_like) -> basic_string + { + auto converted = std::basic_string_view{view_like}; + + if (converted.size() > capacity()) + { + update_data(allocate_buffer(converted.size(), true)); + } + + traits_type::copy(m_data, converted.data(), converted.size()); + update_length(converted.size()); + return *this; + } + + //! Assignment from nullptr is prohibited. + constexpr auto operator=(std::nullptr_t) = delete; + + //! Get the allocator used by this string. + //! + //! @return A copy of the allocator used by this string. + [[nodiscard]] constexpr auto get_allocator() const noexcept(noexcept(Allocator{Allocator{}})) -> allocator_type + { + return m_allocator; + } + + //! Get the element at the given position in this string. + //! + //! @param position The position from which to retrieve the element. + //! @return A reference to the character at the given position. + [[nodiscard]] constexpr auto at(size_type position) -> reference + { + if (position >= size()) + { + os::panic("Invalid index in string element access"); + } + + return m_data[position]; + } + + //! Get the element at the given position in this string. + //! + //! @param position The position from which to retrieve the element. + //! @return A reference to the character at the given position. + [[nodiscard]] constexpr auto at(size_type position) const -> const_reference + { + if (position >= size()) + { + os::panic("Invalid index in string element access"); + } + + return m_data[position]; + } + + //! Get the element at the given position in this string. + //! + //! @param position The position from which to retrieve the element. + //! @return A reference to the character at the given position. + [[nodiscard]] constexpr auto operator[](size_type position) noexcept -> reference + { + return m_data[position]; + } + + //! Get the element at the given position in this string. + //! + //! @param position The position from which to retrieve the element. + //! @return A reference to the character at the given position. + [[nodiscard]] constexpr auto operator[](size_type position) const noexcept -> const_reference + { + return m_data[position]; + } + + //! Get the first character in this string. + //! + //! @return A reference to first character. + [[nodiscard]] constexpr auto front() noexcept -> reference + { + return (*this)[0]; + } + + //! Get the first character in this string. + //! + //! @return A reference to first character. + [[nodiscard]] constexpr auto front() const noexcept -> const_reference + { + return (*this)[0]; + } + + //! Get the last character in this string. + //! + //! @return A reference to last character. + [[nodiscard]] constexpr auto back() noexcept -> reference + { + return (*this)[size() - 1]; + } + + //! Get the last character in this string. + //! + //! @return A reference to last character. + [[nodiscard]] constexpr auto back() const noexcept -> const_reference + { + return (*this)[size() - 1]; + } + + //! Get a pointer to the underlying data of this string. + //! + //! @return A pointer to the underlying data of this string. + [[nodiscard]] constexpr auto data() noexcept -> pointer + { + return m_data; + } + + //! Get a pointer to the underlying data of this string. + //! + //! @return A pointer to the underlying data of this string. + [[nodiscard]] constexpr auto data() const noexcept -> pointer + { + return m_data; + } + + //! Get a pointer to the null-terminated underlying data of this string. + //! + //! @return A pointer to the null-terminated underlying data of this string. + [[nodiscard]] constexpr auto c_str() noexcept -> pointer + { + return m_data; + } + + //! Get a pointer to the null-terminated underlying data of this string. + //! + //! @return A pointer to the null-terminated underlying data of this string. + [[nodiscard]] constexpr auto c_str() const noexcept -> pointer + { + return m_data; + } + + //! Get a non-modifiable view of this string. + operator std::basic_string_view() const noexcept + { + return {data(), size()}; + } + + //! Get an iterator to the beginning of this string. + [[nodiscard]] constexpr auto begin() noexcept -> iterator + { + return data(); + } + + //! Get a const iterator to the beginning of this string. + [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator + { + return data(); + } + + //! Get a const iterator to the beginning of this string. + [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator + { + return data(); + } + + //! Get an iterator to the end of this string. + [[nodiscard]] constexpr auto end() noexcept -> iterator + { + return data() + size(); + } + + //! Get a const iterator to the end of this string. + [[nodiscard]] constexpr auto end() const noexcept -> const_iterator + { + return data() + size(); + } + + //! Get a const iterator to the end of this string. + [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator + { + return data() + size(); + } + + //! Get a reverse iterator to the beginning of this string. + [[nodiscard]] constexpr auto rbegin() noexcept -> reverse_iterator + { + return end(); + } + + //! Get a const reverse iterator to the beginning of this string. + [[nodiscard]] constexpr auto rbegin() const noexcept -> const_reverse_iterator + { + return end(); + } + + //! Get a const reverse iterator to the beginning of this string. + [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator + { + return end(); + } + + //! Get a reverse iterator to the end of this string. + [[nodiscard]] constexpr auto rend() noexcept -> reverse_iterator + { + return begin(); + } + + //! Get a const reverse iterator to the end of this string. + [[nodiscard]] constexpr auto rend() const noexcept -> const_reverse_iterator + { + return begin(); + } + + //! Get a const reverse iterator to the end of this string. + [[nodiscard]] constexpr auto crend() const noexcept -> const_reverse_iterator + { + return begin(); + } + + //! Check if this string is empty. + //! + //! @return @p true iff. the string is empty, @p false otherwise. + [[nodiscard]] constexpr auto empty() const noexcept -> bool + { + return size() == 0uz; + } + + //! Get the size of this string. + //! + //! @return the number of characters in this string. + [[nodiscard]] constexpr auto size() const noexcept -> size_type + { + return m_length; + } + + //! Get the length of this string. + //! + //! @return the number of characters in this string. + [[nodiscard]] constexpr auto length() const noexcept -> size_type + { + return size(); + } + + //! Get the maximum possible size for this string. + //! + //! @return the maximum number of elements this string can hold. + [[nodiscard]] constexpr auto max_size() const noexcept -> size_type + { + return std::numeric_limits::max() / sizeof(value_type) - 1; + } + + //! Get the currently allocated capacity of this string + //! + //! @return the number of elements that can be stored in this string without causing a reallocation. + [[nodiscard]] constexpr auto capacity() const noexcept -> size_type + { + return (in_sso_state() ? std::size(m_sso_buffer) : m_heap_capacity) - 1; + } + + //! Clear the content of this string. + constexpr auto clear() -> void + { + update_length(0); + } + + //! Insert a sequence of identical characters at the given index. + //! + //! @param index The position to insert the characters at. + //! @param count The number of characters to insert. + //! @param character The character to insert. + //! @return A reference to this string. + constexpr auto insert(size_type index, size_type count, CharacterType character) -> basic_string & + { + if (index > size()) + { + os::panic("Index out of bounds while inserting into string"); + } + + if (count == 0uz) + { + return *this; + } + + auto new_length = size() + count; + + if (capacity() < new_length) + { + auto new_buffer = allocate_buffer(new_length, true); + traits_type::copy(new_buffer.data(), m_data, index); + std::ranges::fill_n(new_buffer.data() + index, count, character); + traits_type::copy(new_buffer.data() + index + count, m_data + index, size() - index); + update_data(new_buffer); + } + else + { + traits_type::move(m_data + index + count, m_data + index, size() - index); + std::ranges::fill_n(m_data + index, count, character); + } + + update_length(new_length); + return *this; + } + + //! Insert a C-style string into this string at a given position. + //! + //! @param index The position to insert the data at. + //! @param string The string to insert. + constexpr auto insert(size_type index, CharacterType const * string) -> basic_string & + { + if (string == nullptr) + { + os::panic("Tried to insert nullptr string"); + } + + return do_insert(index, std::basic_string_view{string}); + } + + //! Append a character to this string. + //! + //! @param character The character to append. + constexpr auto push_back(CharacterType character) -> void + { + auto new_length = m_length + 1; + + if (capacity() < new_length) + { + auto new_buffer = allocate_buffer(new_length); + traits_type::copy(new_buffer.data(), m_data, m_length); + update_data(new_buffer); + } + + m_data[m_length] = character; + update_length(new_length); + } + + //! Remove the last character of this string. + constexpr auto pop_back() -> void + { + if (m_length > 0) + { + update_length(m_length - 1); + } + } + + //! Append a number of copies of a given character to this string. + //! + //! @param count The number of copies to append. + //! @param character The character to append. + //! @return A reference to this string. + constexpr auto append(size_type count, CharacterType character) -> basic_string & + { + if (count == 0) + { + return *this; + } + + auto new_length = m_length + count; + if (capacity() < new_length) + { + auto new_buffer = allocate_buffer(new_length); + traits_type::copy(new_buffer.data(), m_data, m_length); + update_data(new_buffer); + } + + std::ranges::fill_n(m_data + m_length, count, character); + update_length(new_length); + + return *this; + } + + //! Append a substring of a given C-style string to this string. + //! + //! @param string The string to append. + //! @param count The number of characters of that string to append. + //! @return A reference to this string. + constexpr auto append(CharacterType const * string, std::size_t count) -> basic_string & + { + if (string == nullptr) + { + os::panic("Attempted to append nullptr string"); + } + + if (count == 0) + { + return *this; + } + + auto new_length = m_length + count; + if (capacity() < new_length) + { + auto new_buffer = allocate_buffer(new_length); + traits_type::copy(new_buffer.data(), m_data, m_length); + traits_type::copy(new_buffer.data() + m_length, string, count); + update_data(new_buffer); + } + else + { + traits_type::copy(m_data + m_length, string, count); + } + + update_length(new_length); + + return *this; + } + + //! Append a C-style string to this string. + //! + //! @param string The string to append. + //! @return A reference to this string. + constexpr auto append(CharacterType const * string) -> basic_string & + { + return append(string, traits_type::length(string)); + } + + //! Append to this string by copying from a given string view like object + //! + //! @param view_like The string view like object to copy from. + //! @return A reference to this string. + template + requires(std::is_convertible_v> && + !std::is_convertible_v) + constexpr auto append(StringViewLike const & view_like) -> basic_string & + { + auto converted = std::basic_string_view{view_like}; + return append(converted.data(), converted.length()); + } + + //! Append a string to this string. + //! + //! @param string The string to append. + //! @return A reference to this string. + constexpr auto append(basic_string const & string) -> basic_string & + { + return append(string.c_str(), string.length()); + } + + //! Append a string to this string + //! + //! @param string The string to append. + //! @return A reference to this string. + constexpr auto operator+=(basic_string const & string) -> basic_string & + { + return append(string); + } + + //! Append a character to this string + //! + //! @param character The character to append. + //! @return A reference to this string. + constexpr auto operator+=(value_type character) -> basic_string & + { + push_back(character); + return *this; + } + + //! Swap the contents of this string with another one. + //! + //! @param other The string to swap contents with. + constexpr auto + swap(basic_string & other) noexcept(std::allocator_traits::propagate_on_container_swap::value || + std::allocator_traits::is_always_equal::value) -> void + { + using std::swap; + + if (this == &other) + { + return; + } + + if constexpr (std::allocator_traits::propagate_on_container_swap::value) + { + swap(m_allocator, other.m_allocator); + } + + if (!in_sso_state() && !other.in_sso_state()) + { + swap(m_data, other.m_data); + swap(m_heap_capacity, other.m_heap_capacity); + } + else if (in_sso_state() && !other.in_sso_state()) + { + auto new_data = std::exchange(other.m_data, other.m_sso_buffer); + auto new_heap_capacity = other.m_heap_capacity; + + traits_type::copy(other.m_data, m_data, m_length + 1); + + m_data = new_data; + m_heap_capacity = new_heap_capacity; + } + else if (!in_sso_state() && other.in_sso_state()) + { + auto new_data = std::exchange(m_data, m_sso_buffer); + auto new_heap_capacity = m_heap_capacity; + + traits_type::copy(m_data, other.m_data, other.m_length + 1); + + other.m_data = new_data; + other.m_heap_capacity = new_heap_capacity; + } + else + { + auto temporary_buffer = std::array{}; + + traits_type::copy(temporary_buffer.data(), m_data, m_length + 1); + traits_type::copy(m_data, other.m_data, other.m_length + 1); + traits_type::copy(other.m_data, temporary_buffer.data(), m_length + 1); + } + + swap(m_length, other.m_length); + } + + //! Concatenate two string. + //! + //! @param lhs The left hand side string. + //! @param rhs The right hand side string. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(basic_string const & lhs, basic_string const & rhs) -> basic_string + { + auto result = lhs; + result.append(rhs); + return result; + } + + //! Concatenate a string an a C-style string. + //! + //! @param lhs The right hand side string. + //! @param rhs The left hand side C-style string. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(basic_string const & lhs, CharacterType const * rhs) -> basic_string + { + auto result = lhs; + result.append(rhs); + return result; + } + + //! Create a new string by appending a character to a string. + //! + //! @param lhs The string. + //! @param rhs The character to append. + //! @return A new string with the character appended to the string. + [[nodiscard]] constexpr friend auto operator+(basic_string const & lhs, CharacterType rhs) -> basic_string + { + auto result = lhs; + result.push_back(rhs); + return result; + } + + //! Contcatenate a C-style string and a string. + //! + //! @param lhs The left hand side C-style string. + //! @param rhs The right hand side string. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(const_pointer lhs, basic_string const & rhs) -> basic_string + { + auto result = rhs; + result.insert(0, lhs); + return result; + } + + //! Create a new string by prepending a character to a string. + //! + //! @param lhs The character to prepend. + //! @param rhs The string + //! @return A new string with the character prepended to the string. + [[nodiscard]] constexpr friend auto operator+(CharacterType lhs, basic_string const & rhs) -> basic_string + { + auto result = rhs; + result.insert(0, lhs); + return result; + } + + //! Contcatenate two strings. + //! + //! @param lhs The left hand side string. + //! @param rhs The right hand side string. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(basic_string && lhs, basic_string && rhs) -> basic_string + { + lhs.append(rhs); + return std::move(lhs); + } + + //! Contcatenate two strings. + //! + //! @param lhs The left hand side string. + //! @param rhs The right hand side string. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(basic_string && lhs, basic_string const & rhs) -> basic_string + { + lhs.append(rhs); + return std::move(lhs); + } + + //! Contcatenate a string and a C-style string. + //! + //! @param lhs The left hand side string. + //! @param rhs The right hand side C-style string. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(basic_string && lhs, const_pointer rhs) -> basic_string + { + lhs.append(rhs); + return std::move(lhs); + } + + //! Append a character to a string. + //! + //! @param lhs The string to append to. + //! @param rhs The character to append. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(basic_string && lhs, value_type rhs) -> basic_string + { + lhs.push_back(rhs); + return std::move(lhs); + } + + //! Contcatenate two strings + //! + //! @param lhs The string to prepend. + //! @param rhs The string to append to. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(basic_string const & lhs, basic_string && rhs) -> basic_string + { + rhs.insert(0, lhs); + return std::move(rhs); + } + + //! Contcatenate a C-style string and a string. + //! + //! @param lhs The C-style string to prepend. + //! @param rhs The string to prepend to. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(const_pointer lhs, basic_string && rhs) -> basic_string + { + rhs.insert(0, lhs); + return std::move(rhs); + } + + //! Prepend a character to a string. + //! + //! @param lhs The character to prepend. + //! @param rhs The string to prepend to. + //! @return The concatenation of the two strings. + [[nodiscard]] constexpr friend auto operator+(value_type lhs, basic_string && rhs) -> basic_string + { + rhs.insert(0, 1, lhs); + return std::move(rhs); + } + + [[nodiscard]] constexpr friend auto operator==(basic_string const & lhs, basic_string const & rhs) noexcept -> bool + { + if (lhs.size() != rhs.size()) + { + return false; + } + + if (lhs.size() == 0uz) + { + return true; + } + + return traits_type::compare(lhs.data(), rhs.data(), lhs.size()) == 0; + } + + [[nodiscard]] constexpr friend auto operator==(basic_string const & lhs, + std::basic_string_view const & rhs) noexcept + -> bool + { + if (lhs.size() != rhs.size()) + { + return false; + } + + if (lhs.size() == 0uz) + { + return true; + } + + return traits_type::compare(lhs.data(), rhs.data(), rhs.size()) == 0; + } + + [[nodiscard]] constexpr friend auto operator==(std::basic_string_view const & lhs, + basic_string const & rhs) noexcept -> bool + { + if (lhs.size() != rhs.size()) + { + return false; + } + + if (lhs.size() == 0uz) + { + return true; + } + + return traits_type::compare(lhs.data(), rhs.data(), lhs.size()) == 0; + } + + [[nodiscard]] constexpr friend auto operator==(basic_string const & lhs, const_pointer rhs) noexcept -> bool + { + return lhs == std::basic_string_view{rhs}; + } + + [[nodiscard]] constexpr friend auto operator==(const_pointer lhs, basic_string const & rhs) noexcept -> bool + { + return std::basic_string_view{lhs} == rhs; + } + + [[nodiscard]] constexpr friend auto operator<=>(basic_string const & lhs, basic_string const & rhs) noexcept + { + return std::lexicographical_compare_three_way(std::cbegin(lhs), std::cend(lhs), std::cbegin(rhs), std::cend(rhs)); + } + + [[nodiscard]] constexpr friend auto + operator<=>(basic_string const & lhs, std::basic_string_view const & rhs) noexcept + { + return std::lexicographical_compare_three_way(std::cbegin(lhs), std::cend(lhs), std::cbegin(rhs), std::cend(rhs)); + } + + [[nodiscard]] constexpr friend auto operator<=>(std::basic_string_view const & lhs, + basic_string const & rhs) noexcept -> std::strong_ordering + { + return std::lexicographical_compare_three_way(std::cbegin(lhs), std::cend(lhs), std::cbegin(rhs), std::cend(rhs)); + } + + [[nodiscard]] constexpr friend auto operator<=>(basic_string const & lhs, const_pointer rhs) noexcept + { + return lhs <=> std::basic_string_view{rhs}; + } + + [[nodiscard]] constexpr friend auto operator<=>(const_pointer lhs, basic_string const & rhs) noexcept + { + return std::basic_string_view{lhs} <=> rhs; + } + + private: + //! Allocate a buffer for the data. + //! + //! @param count The of character to reserve space for, excluding the termininating null. + //! @param exact Allocate space for the exact number of characters requested. + //! @return A span describing the newly allocated buffer. + [[nodiscard]] constexpr auto allocate_buffer(size_type count, bool exact = false) -> std::span + { + if (count > max_size()) + { + os::panic("Tried to allocate more memory than possible"); + } + + auto to_allocate = (exact ? count : std::min(std::max(count, 2 * capacity()), max_size())) + 1; + return {std::allocator_traits::allocate(m_allocator, to_allocate), to_allocate}; + } + + //! Insert a string view into this string at a given position. + //! + //! @param index The position to insert the data at. + //! @param view The string view to insert. + constexpr auto do_insert(size_type index, std::basic_string_view const & view) + -> basic_string & + { + if (index > size()) + { + os::panic("Index out of bounds while inserting into string"); + } + + if (view.size() == 0uz) + { + return *this; + } + + auto new_length = size() + view.size(); + + if (capacity() < new_length) + { + auto new_buffer = allocate_buffer(new_length); + traits_type::copy(new_buffer.data(), m_data, index); + traits_type::copy(new_buffer.data() + index, view.data(), view.size()); + traits_type::copy(new_buffer.data() + index + view.size(), m_data + index, size() - index + 1); + update_data(new_buffer); + } + else + { + traits_type::move(m_data + index + view.size(), m_data + index, size() - index); + + if (is_aliased(view.data())) // NOLINT(bugprone-suspicious-stringview-data-usage) + { + auto offset = view.data() - m_data; + + if (offset >= index) + { + traits_type::copy(m_data + index, m_data + offset + view.size(), view.size()); + } + else + { + traits_type::copy(m_data + index, m_data + offset, view.size()); + } + } + else + { + traits_type::copy(m_data + index, view.data(), view.size()); + } + } + + update_length(new_length); + return *this; + } + + //! Copy elements from a range described by an input iterator pair. + //! + //! @param first An iterator pointing to the first element to copy. + //! @param last An iterator pointing beyond the last elemento copy. + template + requires std::input_iterator> + constexpr auto forward_from(InputIterator && first, Sentinel last) -> void + { + auto input_size = 0uz; + + for (; first != last && input_size < capacity(); ++first, ++input_size) + { + traits_type::assign(m_data[input_size], *std::forward(first)); + } + + for (; first != last; ++first, ++input_size) + { + if (input_size == capacity()) + { + auto new_buffer = allocate_buffer(input_size + 1); + traits_type::move(new_buffer.data(), m_data, input_size + 1); + update_data(new_buffer); + } + + traits_type::assign(m_data[input_size], *std::forward(first)); + } + + update_length(input_size); + } + + //! Copy elements from a range described by a forward iterator pair. + //! + //! @param first An iterator pointing to the first element to copy. + //! @param last An iterator pointing beyond the last elemento copy. + template + requires std::forward_iterator> + constexpr auto forward_from(ForwardIterator && first, Sentinel last) -> void + { + auto input_size = std::ranges::distance(std::forward(first), last); + + if (input_size > capacity()) + { + auto new_buffer = allocate_buffer(input_size, true); + update_data(new_buffer); + } + + for (auto i = 0uz; first != last; ++first, ++i) + { + traits_type::assign(m_data[i], *std::forward(first)); + } + + update_length(input_size); + } + + //! Check if this string is currently in its SSO state. + //! + //! @return @p true iff. this string is in its SSO state, @p false otherwise. + [[nodiscard]] constexpr auto in_sso_state() const noexcept -> bool + { + return m_data == m_sso_buffer; + } + + //! Check if the a given pointer aliases into the internal string buffer. + //! + //! @param pointer The pointer to check for aliasing. + //! @return @p true iff. the given pointer aliases into the internal string buffer, @p false otherwise. + [[nodiscard]] constexpr auto is_aliased(const_pointer pointer) const noexcept -> bool + { + if consteval + { + for (auto i = 0uz; i < m_length; ++i) + { + if (pointer == m_data + i) + { + return true; + } + } + return false; + } + else + { + return pointer >= m_data && pointer <= m_data + m_length; + } + } + + //! Release the current buffer of this string. + //! + //! This function can be called even if this string is currently in tis SSO state. + [[nodiscard]] constexpr auto release_buffer() + { + if (!in_sso_state()) + { + std::allocator_traits::deallocate(m_allocator, m_data, m_heap_capacity); + } + } + + //! Update the data pointer + //! + //! @param new_buffer The new buffer for this string. + constexpr auto update_data(std::span new_buffer) -> void + { + release_buffer(); + m_data = new_buffer.data(); + m_heap_capacity = new_buffer.size(); + } + + //! Update the recorded length of this string and ensure null-termination. + //! + //! @param new_length The new length of this string. + constexpr auto update_length(size_type new_length) noexcept -> void + { + m_length = new_length; + traits_type::assign(m_data[m_length], value_type{}); + } + + //! The allocator used for heap allocations. + [[no_unique_address]] allocator_type m_allocator; + + //! The pointer to the start of the string data. + //! + //! When the SSO is active, this points to the internal SSO buffer m_sso_buffer. Otherwise, this points to the + //! heap allocation backing this string. + pointer m_data; + + //! The length of this string. + size_type m_length; + + //! The SSO buffer, or the capacity of this string if SSO is not active. + union + { + value_type m_sso_buffer[15 / sizeof(value_type) + 1]; // NOLINT(modernize-avoid-c-arrays) + size_type m_heap_capacity; + }; + }; + + template + constexpr auto swap(basic_string & lhs, + basic_string & rhs) -> void + { + lhs.swap(rhs); + } + +} // namespace kstd + +#endif diff --git a/libs/kstd/kstd/bits/basic_string.test.cpp b/libs/kstd/kstd/bits/basic_string.test.cpp new file mode 100644 index 00000000..dba0ea0c --- /dev/null +++ b/libs/kstd/kstd/bits/basic_string.test.cpp @@ -0,0 +1,1876 @@ +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using char_string = kstd::basic_string; + +SCENARIO("Basic string of char initialization and construction", "[string]") +{ + THEN("All type aliases are present") + { + 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>); + } + + THEN("npos is present") + { + REQUIRE(char_string::npos == char_string::size_type(-1)); + } + + GIVEN("An empty context") + { + WHEN("constructing by default") + { + auto s = char_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 = char_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 = char_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 = char_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 = char_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 = char_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 = char_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 = char_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((char_string{static_cast(nullptr), 1}), kstd::tests::os_panic); + } + } + + WHEN("constructing from a non-null pointer and zero size") + { + auto ptr = "abcd"; + auto s = char_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 = char_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 = char_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 = char_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 = char_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 = char_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 = char_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 = char_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 = char_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 = char_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 = char_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("Basic string assignment", "[string]") +{ + GIVEN("Two short strings") + { + auto s1 = char_string{"abcd"}; + auto s2 = char_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 = char_string{"abcd"}; + auto s2 = char_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 = char_string{"abcdefghijABCDEFGH"}; + auto s2 = char_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 = char_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 = char_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); + } + } + } +} diff --git a/libs/kstd/kstd/bits/char_traits.hpp b/libs/kstd/kstd/bits/char_traits.hpp deleted file mode 100644 index b9a4a038..00000000 --- a/libs/kstd/kstd/bits/char_traits.hpp +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef KSTD_BITS_CHAR_TRAITS_HPP -#define KSTD_BITS_CHAR_TRAITS_HPP - -// IWYU pragma: private, include - -#include - -#include -#include - -namespace kstd -{ - - template - struct char_traits; - - template<> - struct char_traits - { - using char_type = char; - using int_type = int; - using off_type = long long; - - constexpr auto static assign(char_type & lhs, char_type const & rhs) noexcept -> void - { - lhs = rhs; - } - - constexpr auto static assign(char_type * destination, std::size_t count, char_type value) noexcept -> char_type * - { - std::ranges::fill_n(destination, static_cast(count), value); - return destination; - } - - constexpr auto static eq(char_type lhs, char_type rhs) noexcept -> bool - { - return static_cast(lhs) == static_cast(rhs); - } - - constexpr auto static lt(char_type lhs, char_type rhs) noexcept -> bool - { - return static_cast(lhs) < static_cast(rhs); - } - - constexpr auto static move(char_type * destination, char_type const * source, std::size_t count) noexcept - -> char_type * - { - std::ranges::move_backward(source, source + count, destination); - return destination; - } - - constexpr auto static copy(char_type * destination, char_type const * source, std::size_t count) noexcept - -> char_type * - { - std::ranges::copy(source, source + count, destination); - return destination; - } - - constexpr auto static compare(char_type const * lhs, char_type const * rhs, std::size_t count) noexcept -> int - { - for (auto i = 0uz; i < count; ++i) - { - auto comparison_result = static_cast(lhs[i]) - static_cast(rhs[i]); - if (comparison_result) - { - return comparison_result; - } - } - return 0; - } - - constexpr auto static length(char_type const * string) noexcept -> std::size_t - { - auto string_length = 0uz; - for (; string[string_length] != char_type{}; ++string_length) - ; - return string_length; - } - - constexpr auto static find(char_type const * string, std::size_t count, char_type const & needle) noexcept - -> char_type const * - { - auto found = std::ranges::find(string, string + count, needle); - if (found == string + count) - { - return nullptr; - } - return found; - } - - constexpr auto static to_char_type(int_type value) noexcept -> char_type - { - return static_cast(value); - } - - constexpr auto static to_int_type(char_type value) noexcept -> int_type - { - return static_cast(value); - } - - constexpr auto static eq_int_type(int_type lhs, int_type rhs) noexcept -> bool - { - return lhs == rhs; - } - - constexpr auto static eof() noexcept -> int_type - { - return -1; - } - - constexpr auto static not_eof(int_type value) noexcept -> int_type - { - return value == eof() ? 0 : value; - } - }; - -} // namespace kstd - -#endif diff --git a/libs/kstd/kstd/bits/char_traits.test.cpp b/libs/kstd/kstd/bits/char_traits.test.cpp deleted file mode 100644 index 4864c378..00000000 --- a/libs/kstd/kstd/bits/char_traits.test.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include - -#include - -#include - -SCENARIO("Character traits for char", "[string]") -{ - using traits = kstd::char_traits; - - THEN("Type aliases") - { - REQUIRE(std::is_same_v::char_type>); - REQUIRE(std::is_same_v::int_type>); - REQUIRE(std::is_same_v::off_type>); - } - - GIVEN("two unequal characters 'a' and 'B'") - { - auto const a = 'a'; - auto const B = 'B'; - - WHEN("comparing the two") - { - THEN("eq(a, a) returns true") - { - REQUIRE(traits::eq(a, a)); - } - - THEN("eq(a, B) returns false") - { - REQUIRE_FALSE(traits::eq(a, B)); - } - - THEN("eq(B, a) returns false") - { - REQUIRE_FALSE(traits::eq(B, a)); - } - - THEN("lt(a,a) returns false") - { - REQUIRE_FALSE(traits::lt(a, a)); - } - - THEN("lt(a,B) returns false") - { - REQUIRE_FALSE(traits::lt(a, B)); - } - - THEN("lt(B,a) returns true") - { - REQUIRE(traits::lt(B, a)); - } - - THEN("eq_int_type(a, a) returns true") - { - REQUIRE(traits::eq_int_type(a, a)); - } - - THEN("eq_int_type(a, B) returns false") - { - REQUIRE_FALSE(traits::eq_int_type(a, B)); - } - - THEN("eq_int_type(B, a) returns false") - { - REQUIRE_FALSE(traits::eq_int_type(B, a)); - } - } - } - - GIVEN("two unequal strings 'abc', 'DEFG'") - { - auto const abc = "abc"; - auto const abDe = "abDe"; - - WHEN("comparing the two") - { - THEN("compare(abc, abc, 3) returns 0") - { - REQUIRE(traits::compare(abc, abc, 3) == 0); - } - - THEN("compare(abc, abDe, 2) returns 0") - { - REQUIRE(traits::compare(abc, abDe, 2) == 0); - } - - THEN("compare(abc, abDe, 3) returns >0") - { - REQUIRE(traits::compare(abc, abDe, 3) > 0); - } - - THEN("compare(abDe, abc, 3) returns <0") - { - REQUIRE(traits::compare(abDe, abc, 3) < 0); - } - } - } - - GIVEN("The string 'abcd'") - { - auto const abcd = "abcd"; - - THEN("length(abcd) returns 4") - { - REQUIRE(traits::length(abcd) == 4); - } - - THEN("find(abcd, 4, b) returns a pointer to b") - { - REQUIRE(*traits::find(abcd, 4, 'b') == 'b'); - } - - THEN("find(abcd, 4, q) returns a null pointer") - { - REQUIRE(traits::find(abcd, 4, 'q') == nullptr); - } - } -} diff --git a/libs/kstd/kstd/string b/libs/kstd/kstd/string index 9343b428..de254a1d 100644 --- a/libs/kstd/kstd/string +++ b/libs/kstd/kstd/string @@ -1,330 +1,20 @@ #ifndef KSTD_STRING_HPP #define KSTD_STRING_HPP +#include // IWYU pragma: export #include #include -#include +#include #include #include #include #include -#include #include -#include -#include namespace kstd { - /** - * @brief A simple string implementation that owns its data and provides basic operations. - */ - struct string - { - //! The type of the characters contained in this string. - using value_type = char; - //! The type of the underlying storage used by this string. - using storage_type = kstd::vector; - //! The type of all sizes used in and with this string. - using size_type = std::size_t; - //! The type of the difference between two iterators. - using difference_type = std::ptrdiff_t; - //! The type of references to single values in this string. - using reference = value_type &; - //! The type of references to constant single values in this string. - using const_reference = value_type const &; - //! The type of pointers to single values in this string. - using pointer = value_type *; - //! The type of pointers to constant single values in this string. - using const_pointer = value_type const *; - //! The type of iterators into this string. - using iterator = pointer; - //! The type of constant iterators into this string. - using const_iterator = const_pointer; - - /** - * @brief Constructs an empty null-terminated string. - */ - string() - : m_storage{value_type{'\0'}} - {} - - /** - * @brief Constructs a string from a string view by copying the characters into owned storage. - * @param view The string view to copy the characters from. - */ - explicit string(std::string_view view) - : string() - { - append(view); - } - - /** - * @brief Constructs a string from a null-terminated C-style string by copying the characters into owned storage. - * @param c_str The null-terminated C-style string to copy. - */ - string(char const * c_str) - : string() - { - if (c_str != nullptr) - { - append(std::string_view{c_str}); - } - } - - /** - * @brief Constructs a string containing a single character. - * @param c The character to copy. - */ - string(value_type c) - : string() - { - push_back(c); - } - - /** - * @brief Constructs a string by copying another string. - * @param other The string to copy. - */ - constexpr string(string const & other) - : m_storage{other.m_storage} - {} - - /** - * @brief Destructs the string. - */ - constexpr ~string() = default; - - /** - * @brief Assigns the value of another string to this string. - * @param other The string to assign from. - * @return A reference to this string. - */ - constexpr auto operator=(string const & other) -> string & = default; - - constexpr auto operator=(std::string_view const & other) -> string & - { - clear(); - append(other); - return *this; - } - - constexpr auto operator=(char const * other) -> string & - { - clear(); - append(std::string_view{other}); - return *this; - } - - //! Create a string view from this string. - constexpr operator std::string_view() const noexcept - { - return {data(), size()}; - } - - /** - * @brief Returns the number of characters in this string, not including the null terminator. - */ - [[nodiscard]] constexpr auto size() const noexcept -> size_type - { - return m_storage.empty() ? 0 : m_storage.size() - 1; - } - - /** - * @brief Checks if this string is empty, not including the null terminator. - */ - [[nodiscard]] constexpr auto empty() const noexcept -> bool - { - return size() == 0; - } - - /** - * @brief Clears the content of the string, resulting in an empty string. - * The string remains null-terminated after this operation. - */ - constexpr auto clear() -> void - { - m_storage.clear(); - m_storage.push_back(value_type{'\0'}); - } - - //! Get a pointer to the underlying storage of the string - [[nodiscard]] constexpr auto data() noexcept -> pointer - { - return m_storage.data(); - } - - //! Get a const pointer to the underlying storage of the string - [[nodiscard]] constexpr auto data() const noexcept -> const_pointer - { - return m_storage.data(); - } - - //! Get a const pointer to the underlying storage of the string - [[nodiscard]] constexpr auto c_str() const noexcept -> const_pointer - { - return data(); - } - - //! Get an iterator to the beginning of the string - [[nodiscard]] constexpr auto begin() noexcept -> iterator - { - return data(); - } - - //! Get an const iterator to the beginning of the string - [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator - { - return data(); - } - - //! Get an const iterator to the beginning of the string - [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator - { - return begin(); - } - - //! Get an iterator to the end of the string - [[nodiscard]] constexpr auto end() noexcept -> iterator - { - return data() + size(); - } - - //! Get an const iterator to the end of the string - [[nodiscard]] constexpr auto end() const noexcept -> const_iterator - { - return data() + size(); - } - - //! Get an const iterator to the end of the string - [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator - { - return end(); - } - - //! Get a reference to the first character of the string - [[nodiscard]] constexpr auto front() -> reference - { - return m_storage.front(); - } - - //! Get a const reference to the first character of the string - [[nodiscard]] constexpr auto front() const -> const_reference - { - return m_storage.front(); - } - - //! Get a reference to the last character of the string - [[nodiscard]] constexpr auto back() -> reference - { - return m_storage[size() - 1]; - } - - //! Get a const reference to the last character of the string - [[nodiscard]] constexpr auto back() const -> const_reference - { - return m_storage[size() - 1]; - } - - /** - * @brief Appends a character to the end of the string. - * @param ch The character to append. - */ - constexpr auto push_back(value_type ch) -> void - { - m_storage.back() = ch; - m_storage.push_back(value_type{'\0'}); - } - - /** - * @brief Appends a string view to the end of the string by copying the characters into owned storage. - * @param view The string view to append. - * @return A reference to this string. - */ - constexpr auto append(std::string_view view) -> string & - { - if (!view.empty()) - { - m_storage.reserve(size() + view.size() + 1); - std::ranges::for_each(view, [this](auto const ch) { push_back(ch); }); - } - - return *this; - } - - /** - * @brief Appends another string to the end of this string by copying the characters into owned storage. - * @param other The string to append. - * @return A reference to this string. - */ - constexpr auto append(string const & other) -> string & - { - return append(static_cast(other)); - } - - /** - * @brief Appends another string to the end of this string by copying the characters into owned storage. - * @param other The string to append. - * @return A reference to this string. - */ - constexpr auto operator+=(string const & other) -> string & - { - return append(other); - } - - /** - * @brief Appends a character to the end of the string. - * @param ch The character to append. - * @return A reference to this string. - */ - constexpr auto operator+=(value_type ch) -> string & - { - push_back(ch); - return *this; - } - - //! Compare this string lexicographically to another string. - //! - //! @param other The string to compare to this one. - [[nodiscard]] constexpr auto operator<=>(string const & other) const noexcept -> std::strong_ordering = default; - - [[nodiscard]] constexpr auto operator==(string const & other) const noexcept -> bool = default; - - //! Compare this string lexicographically to a C-style string. - //! - //! @param other The C-style string to compare to this one. - //! @return The result of the comparison. - [[nodiscard]] constexpr auto operator<=>(char const * other) const noexcept - { - return static_cast(*this) <=> other; - } - - //! Check if this string compares equal to a C-style string. - //! - //! @param other The C-style string to compare to this one. - //! @return @p true iff. this string compares equal to @p other, @p false otherwise. - [[nodiscard]] constexpr auto operator==(char const * other) const noexcept -> bool - { - return static_cast(*this) == other; - } - - private: - //! The underlying storage of the string, which owns the characters and ensures null-termination. - storage_type m_storage{}; - }; - - /** - * @brief Concatenates a strings and a character and returns the result as a new string. - * @param lhs The string to concatenate. - * @param rhs The string to concatenate. - * @return A new string that is the result of concatenating @p lhs and @p rhs. - */ - [[nodiscard]] constexpr auto inline operator+(string const & lhs, string const & rhs) -> string - { - string result{lhs}; - result += rhs; - return result; - } + using string = basic_string; /** * @brief Converts an unsigned integer to a string by converting each digit to the corresponding character and @@ -356,11 +46,11 @@ namespace kstd } template<> - struct formatter : formatter + struct formatter : formatter { auto format(string const & str, format_context & context) const -> void { - formatter::format(static_cast(str), context); + formatter::format(str.data(), context); } }; -- cgit v1.2.3 From 04ce1cbebc8e45bda7c2ada79c917f5dfa9c6525 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 11 Jun 2026 19:26:55 +0200 Subject: kstd: fix stray std::string include --- libs/kstd/kstd/bits/basic_string.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/kstd/kstd/bits/basic_string.hpp b/libs/kstd/kstd/bits/basic_string.hpp index 4b32e312..2ba6aea3 100644 --- a/libs/kstd/kstd/bits/basic_string.hpp +++ b/libs/kstd/kstd/bits/basic_string.hpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include -- cgit v1.2.3 From 11423980972f3c3f92ab6b08abde94e1a8d66ba4 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 11 Jun 2026 19:49:46 +0200 Subject: bht: improve test resporting --- .gitlab-ci.yml | 2 +- kernel/CMakeLists.txt | 9 ++++++++- libs/acpi/CMakeLists.txt | 8 +++++++- libs/kstd/CMakeLists.txt | 8 +++++++- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 80219e11..7b0d2a6c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -58,7 +58,7 @@ test:bht: coverage_report: coverage_format: cobertura path: coverage/cobertura-coverage.xml - junit: build/bht/**/junit.xml + junit: build/bht/**/bht_results/*.xml license_check: stage: .pre diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 23883705..88506ff5 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -225,5 +225,12 @@ if(BUILD_TESTING) ) enable_coverage("kernel_tests") - catch_discover_tests("kernel_tests" ${CATCH_TEST_ARGS}) + + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bht_results") + + catch_discover_tests("kernel::tests" + RERPORTER junit + OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/bht_results" + OUTPUT_SUFFIX ".xml" + ) endif() diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index 1d03bb54..d48922f1 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -114,5 +114,11 @@ if(BUILD_TESTING) EXCLUDE_FROM_ALL NO ) - catch_discover_tests("acpi::tests" ${CATCH_TEST_ARGS}) + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bht_results") + + catch_discover_tests("acpi::tests" + RERPORTER junit + OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/bht_results" + OUTPUT_SUFFIX ".xml" + ) endif() diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 99ce0c81..9bf91b6e 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -97,5 +97,11 @@ if(BUILD_TESTING) enable_coverage("kstd_tests") endif() - catch_discover_tests("kstd::tests" ${CATCH_TEST_ARGS}) + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bht_results") + + catch_discover_tests("kstd::tests" + RERPORTER junit + OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/bht_results" + OUTPUT_SUFFIX ".xml" + ) endif() \ No newline at end of file -- cgit v1.2.3 From 726ef581a9a355a3e2752ebb953baedbc0217edc Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 11 Jun 2026 19:51:06 +0200 Subject: build: remove unused variable --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f7c7fe87..40648859 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,6 @@ if (BUILD_TESTING) FetchContent_MakeAvailable("Catch2") add_compile_definitions("CATCH_CONFIG_NO_COUNTER") - set(CATCH_TEST_ARGS "EXTRA_ARGS" "--reporter" "junit::out=junit.xml" "--reporter" "console::out=-::colour-mode=ansi") endif() #[============================================================================[ -- cgit v1.2.3 From 72ece8137684e7dfa3c24d204db6af67040b5098 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 11 Jun 2026 19:54:51 +0200 Subject: kstd: fix signature of basic_string::operator= --- libs/kstd/kstd/bits/basic_string.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/kstd/kstd/bits/basic_string.hpp b/libs/kstd/kstd/bits/basic_string.hpp index 2ba6aea3..c13b8216 100644 --- a/libs/kstd/kstd/bits/basic_string.hpp +++ b/libs/kstd/kstd/bits/basic_string.hpp @@ -390,7 +390,7 @@ namespace kstd template requires(std::is_convertible_v> && !std::is_convertible_v) - constexpr auto operator=(StringViewLike const & view_like) -> basic_string + constexpr auto operator=(StringViewLike const & view_like) -> basic_string & { auto converted = std::basic_string_view{view_like}; -- cgit v1.2.3 From 8221c170391b9e0888d19be82cb3790a640d67ce Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 11 Jun 2026 19:56:19 +0200 Subject: kstd: improve basic_string null checks --- libs/kstd/kstd/bits/basic_string.hpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/libs/kstd/kstd/bits/basic_string.hpp b/libs/kstd/kstd/bits/basic_string.hpp index c13b8216..e55bfbb3 100644 --- a/libs/kstd/kstd/bits/basic_string.hpp +++ b/libs/kstd/kstd/bits/basic_string.hpp @@ -1082,11 +1082,21 @@ namespace kstd [[nodiscard]] constexpr friend auto operator==(basic_string const & lhs, const_pointer rhs) noexcept -> bool { + if (rhs == nullptr) + { + os::panic("Tried to compare a string with nullptr"); + } + return lhs == std::basic_string_view{rhs}; } [[nodiscard]] constexpr friend auto operator==(const_pointer lhs, basic_string const & rhs) noexcept -> bool { + if (rhs == nullptr) + { + os::panic("Tried to compare a string with nullptr"); + } + return std::basic_string_view{lhs} == rhs; } @@ -1109,11 +1119,21 @@ namespace kstd [[nodiscard]] constexpr friend auto operator<=>(basic_string const & lhs, const_pointer rhs) noexcept { + if (rhs == nullptr) + { + os::panic("Tried to compare a string with nullptr"); + } + return lhs <=> std::basic_string_view{rhs}; } [[nodiscard]] constexpr friend auto operator<=>(const_pointer lhs, basic_string const & rhs) noexcept { + if (rhs == nullptr) + { + os::panic("Tried to compare a string with nullptr"); + } + return std::basic_string_view{lhs} <=> rhs; } -- cgit v1.2.3 From 5d7c42ab3e296a979e31ab7842b0a9b7e32041f8 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 11 Jun 2026 19:59:41 +0200 Subject: build: fix test reporter configuration --- kernel/CMakeLists.txt | 2 +- libs/acpi/CMakeLists.txt | 2 +- libs/kstd/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 88506ff5..b12f9f27 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -229,7 +229,7 @@ if(BUILD_TESTING) file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bht_results") catch_discover_tests("kernel::tests" - RERPORTER junit + REPORTER junit OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/bht_results" OUTPUT_SUFFIX ".xml" ) diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt index d48922f1..58661c5e 100644 --- a/libs/acpi/CMakeLists.txt +++ b/libs/acpi/CMakeLists.txt @@ -117,7 +117,7 @@ if(BUILD_TESTING) file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bht_results") catch_discover_tests("acpi::tests" - RERPORTER junit + REPORTER junit OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/bht_results" OUTPUT_SUFFIX ".xml" ) diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index 9bf91b6e..d85f0031 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -100,7 +100,7 @@ if(BUILD_TESTING) file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bht_results") catch_discover_tests("kstd::tests" - RERPORTER junit + REPORTER junit OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/bht_results" OUTPUT_SUFFIX ".xml" ) -- cgit v1.2.3 From cc915bee52fd2434cd68bbd1a8ab98b3d022fd52 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Sun, 14 Jun 2026 14:24:06 +0200 Subject: kstd: enable sanitizers for tests --- libs/kstd/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index d85f0031..aba33abf 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -88,6 +88,14 @@ if(BUILD_TESTING) "kstd::lib" ) + target_compile_options("kstd_tests" PRIVATE + "-fsanitize=undefined,address" + ) + + target_link_options("kstd_tests" PRIVATE + "-fsanitize=undefined,address" + ) + set_target_properties("kstd_tests" PROPERTIES EXCLUDE_FROM_ALL NO ) -- cgit v1.2.3 From 0095db932cec54cd2735f595450fc5131efc0ffb Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Sun, 14 Jun 2026 15:48:49 +0200 Subject: tests: globally enable sanitizers --- CMakeLists.txt | 3 +++ libs/kstd/CMakeLists.txt | 8 -------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 40648859..394f5bfe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,6 +105,9 @@ if(BUILD_TESTING) CXX_CLANG_TIDY "" ) endif() + + add_compile_options("$<$:-fsanitize=undefined,address>") + add_link_options("$<$:-fsanitize=undefined,address>") endif() #[============================================================================[ diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index aba33abf..d85f0031 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -88,14 +88,6 @@ if(BUILD_TESTING) "kstd::lib" ) - target_compile_options("kstd_tests" PRIVATE - "-fsanitize=undefined,address" - ) - - target_link_options("kstd_tests" PRIVATE - "-fsanitize=undefined,address" - ) - set_target_properties("kstd_tests" PROPERTIES EXCLUDE_FROM_ALL NO ) -- cgit v1.2.3 From 02add366d9886dff631de35f64b2a907b6f0f4d2 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Sun, 14 Jun 2026 21:48:08 +0200 Subject: kstd/string: implement reserve and shrink_to_fit --- libs/kstd/kstd/bits/basic_string.hpp | 53 ++++++++++++++++++- libs/kstd/kstd/bits/basic_string.test.cpp | 86 +++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 2 deletions(-) diff --git a/libs/kstd/kstd/bits/basic_string.hpp b/libs/kstd/kstd/bits/basic_string.hpp index e55bfbb3..5d2eecd7 100644 --- a/libs/kstd/kstd/bits/basic_string.hpp +++ b/libs/kstd/kstd/bits/basic_string.hpp @@ -635,6 +635,49 @@ namespace kstd return std::numeric_limits::max() / sizeof(value_type) - 1; } + //! Reserve at least the given amount of memory. + //! + //! @param new_capacity The new, minimum, target capacity. + constexpr auto reserve(size_type new_capacity) -> void + { + if (new_capacity > max_size()) + { + os::panic("Tried to allocate more memory than possible"); + } + + if (new_capacity <= capacity()) + { + return; + } + + auto new_buffer = allocate_buffer(new_capacity); + traits_type::copy(new_buffer.data(), data(), length() + 1); + update_data(new_buffer); + } + + //! Shrink the buffer of this vector to fit the current content. + constexpr auto shrink_to_fit() -> void + { + if (in_sso_state()) + { + return; + } + else if (length() <= sso_length) + { + auto old_capacity = m_heap_capacity; + traits_type::copy(m_sso_buffer, m_data, m_length + 1); + std::allocator_traits::deallocate(m_allocator, m_data, old_capacity); + m_data = m_sso_buffer; + } + else + { + auto new_buffer = allocate_buffer(m_length + 1); + traits_type::copy(new_buffer.data(), m_data, m_length + 1); + release_buffer(); + update_data(new_buffer); + } + } + //! Get the currently allocated capacity of this string //! //! @return the number of elements that can be stored in this string without causing a reallocation. @@ -885,7 +928,7 @@ namespace kstd } else { - auto temporary_buffer = std::array{}; + auto temporary_buffer = std::array{}; traits_type::copy(temporary_buffer.data(), m_data, m_length + 1); traits_type::copy(m_data, other.m_data, other.m_length + 1); @@ -1138,6 +1181,12 @@ namespace kstd } private: + //! The size of the SSO buffer. + constexpr auto static inline sso_capacity = 15 / sizeof(value_type) + 1; + + //! The maximum string length that fits in the SSO buffer. + constexpr auto static inline sso_length = sso_capacity - 1; + //! Allocate a buffer for the data. //! //! @param count The of character to reserve space for, excluding the termininating null. @@ -1338,7 +1387,7 @@ namespace kstd //! The SSO buffer, or the capacity of this string if SSO is not active. union { - value_type m_sso_buffer[15 / sizeof(value_type) + 1]; // NOLINT(modernize-avoid-c-arrays) + value_type m_sso_buffer[sso_capacity]; // NOLINT(modernize-avoid-c-arrays) size_type m_heap_capacity; }; }; diff --git a/libs/kstd/kstd/bits/basic_string.test.cpp b/libs/kstd/kstd/bits/basic_string.test.cpp index dba0ea0c..8f3a00df 100644 --- a/libs/kstd/kstd/bits/basic_string.test.cpp +++ b/libs/kstd/kstd/bits/basic_string.test.cpp @@ -1874,3 +1874,89 @@ SCENARIO("Basic string assignment", "[string]") } } } + +SCENARIO("Basic string of char modifiers", "[string]") +{ + GIVEN("A short string") + { + auto s = char_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); + } + } + } +} -- cgit v1.2.3 From 32fa8e40c845755b3b9645fb01b4790e989bf9a9 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Sun, 14 Jun 2026 21:48:43 +0200 Subject: build: allow disabling of sanitizers --- CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 394f5bfe..5f2ae141 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ endif() option(TEACHOS_ENABLE_LINTING "Enable linting during build" ON) option(TEACHOS_GENERATE_DOCS "Generate documentation during build" ON) +option(TEACHOS_ENABLE_TEST_SANITIZERS "Enable sanitizers for test executables" ON) #[============================================================================[ # Global Build System Configuration @@ -106,8 +107,10 @@ if(BUILD_TESTING) ) endif() - add_compile_options("$<$:-fsanitize=undefined,address>") - add_link_options("$<$:-fsanitize=undefined,address>") + if(TEACHOS_ENABLE_TEST_SANITIZERS) + add_compile_options("$<$:-fsanitize=undefined,address>") + add_link_options("$<$:-fsanitize=undefined,address,leak>") + endif() endif() #[============================================================================[ -- cgit v1.2.3 From 9fb4af5d56fcd0568137c5bf431f2f72807938a5 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 15 Jun 2026 10:22:57 +0200 Subject: kstd/debug: fix string pretty printer --- libs/kstd/gdb/__init__.py | 2 +- libs/kstd/gdb/string.py | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/libs/kstd/gdb/__init__.py b/libs/kstd/gdb/__init__.py index c5d1e539..4c4b3415 100644 --- a/libs/kstd/gdb/__init__.py +++ b/libs/kstd/gdb/__init__.py @@ -12,7 +12,7 @@ from .smart_pointers import ( def build_pretty_printers(): pp = gdb.printing.RegexpCollectionPrettyPrinter("kstd") pp.add_printer("vector", "^kstd::vector<.*>$", KstdVectorPrinter) - pp.add_printer("string", "^kstd::string$", KstdStringPrinter) + pp.add_printer("string", "^kstd::basic_string<.*>$", KstdStringPrinter) pp.add_printer("unique_ptr", "^kstd::unique_ptr<.*>$", KstdUniquePtrPrinter) pp.add_printer("shared_ptr", "^kstd::shared_ptr<.*>$", KstdSharedPtrPrinter) pp.add_printer("observer_ptr", "^kstd::observer_ptr<.*>$", KstdObserverPtrPrinter) diff --git a/libs/kstd/gdb/string.py b/libs/kstd/gdb/string.py index 73c22d64..b60cbe67 100644 --- a/libs/kstd/gdb/string.py +++ b/libs/kstd/gdb/string.py @@ -5,21 +5,17 @@ class KstdStringPrinter(gdb.ValuePrinter): def __init__(self, val): self.__val = val + self.__data = val["m_data"] + self.__length = val["m_length"] def to_string(self): - storage = self.__val["m_storage"] - storage_size = int(storage["m_size"]) - - if storage_size <= 0: + if int(self.__length) <= 0: return '""' - data_pointer = storage["m_data"] - string_length = storage_size - 1 - try: - if hasattr(data_pointer, "lazy_string"): - return data_pointer.lazy_string(encoding="utf-8", length=string_length) - return data_pointer.string(encoding="utf-8", length=string_length) + if hasattr(self.__data, "lazy_string"): + return self.__data.lazy_string(encoding="utf-8", length=self.__length) + return self.__data.string(encoding="utf-8", length=self.__length) except gdb.error: return "" -- cgit v1.2.3 From 073efba1940d6f8d8c3f8e4675c56a3b6c5cb75b Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 15 Jun 2026 10:36:58 +0200 Subject: kstd: move string tests --- libs/kstd/CMakeLists.txt | 1 - libs/kstd/kstd/bits/basic_string.test.cpp | 1962 --------------------------- libs/kstd/kstd/string.test.cpp | 2035 +++++++++++++++++++++++++++-- 3 files changed, 1921 insertions(+), 2077 deletions(-) delete mode 100644 libs/kstd/kstd/bits/basic_string.test.cpp diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt index d85f0031..5a957573 100644 --- a/libs/kstd/CMakeLists.txt +++ b/libs/kstd/CMakeLists.txt @@ -77,7 +77,6 @@ if(BUILD_TESTING) "kstd/flat_map.test.cpp" "kstd/format.test.cpp" "kstd/vector.test.cpp" - "kstd/bits/basic_string.test.cpp" "kstd/bits/observer_ptr.test.cpp" "kstd/test_support/os_panic.test.cpp" "kstd/string.test.cpp" diff --git a/libs/kstd/kstd/bits/basic_string.test.cpp b/libs/kstd/kstd/bits/basic_string.test.cpp deleted file mode 100644 index 8f3a00df..00000000 --- a/libs/kstd/kstd/bits/basic_string.test.cpp +++ /dev/null @@ -1,1962 +0,0 @@ -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using char_string = kstd::basic_string; - -SCENARIO("Basic string of char initialization and construction", "[string]") -{ - THEN("All type aliases are present") - { - 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>); - } - - THEN("npos is present") - { - REQUIRE(char_string::npos == char_string::size_type(-1)); - } - - GIVEN("An empty context") - { - WHEN("constructing by default") - { - auto s = char_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 = char_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 = char_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 = char_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 = char_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 = char_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 = char_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 = char_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((char_string{static_cast(nullptr), 1}), kstd::tests::os_panic); - } - } - - WHEN("constructing from a non-null pointer and zero size") - { - auto ptr = "abcd"; - auto s = char_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 = char_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 = char_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 = char_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 = char_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 = char_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 = char_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 = char_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 = char_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 = char_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 = char_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("Basic string assignment", "[string]") -{ - GIVEN("Two short strings") - { - auto s1 = char_string{"abcd"}; - auto s2 = char_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 = char_string{"abcd"}; - auto s2 = char_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 = char_string{"abcdefghijABCDEFGH"}; - auto s2 = char_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 = char_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 = char_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("Basic string of char modifiers", "[string]") -{ - GIVEN("A short string") - { - auto s = char_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); - } - } - } -} diff --git a/libs/kstd/kstd/string.test.cpp b/libs/kstd/kstd/string.test.cpp index b81cd3a6..e5925aa5 100644 --- a/libs/kstd/kstd/string.test.cpp +++ b/libs/kstd/kstd/string.test.cpp @@ -1,158 +1,1959 @@ #include +#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); + } + } + } -SCENARIO("String initialization and construction", "[string]") -{ - GIVEN("Nothing") + GIVEN("Two long strings") { - WHEN("constructing an empty string") + auto s1 = kstd::string{"abcdefghijABCDEFGH"}; + auto s2 = kstd::string{"ABCDEFGHIJabcdefghij"}; + + WHEN("copy assigning the shorter to the longer one") { - auto str = kstd::string{}; + s2 = s1; - THEN("the size is zero and therefore the string is empty") + THEN("the new length of the longer one is equal to the length of the shorter one") { - REQUIRE(str.empty()); - REQUIRE(str.size() == 0); + REQUIRE(s2.length() == s1.length()); } - THEN("the string is empty") + THEN("the new capacity of the longer one is still 20") { - REQUIRE(str == std::string_view{}); + REQUIRE(s2.capacity() == 20); + } + + THEN("the underlying data of the two is identical") + { + REQUIRE_FALSE(std::strcmp(s1.data(), s2.data())); } } - } - GIVEN("A string view") - { - auto view = std::string_view{"Blub Blub"}; + 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("constructing a string from string_view") + WHEN("move assigning the longer to the shorter one") { - auto str = kstd::string{view}; + s1 = std::move(s2); - THEN("the string is not empty and has the same size as the view") + THEN("the new length of the shorter one is equal to the original length of the longer one") { - REQUIRE_FALSE(str.empty()); - REQUIRE(str.size() == view.size()); + REQUIRE(s1.length() == 20); } - THEN("the string contains the same characters as the view") + THEN("the length of the moved from one is 0") { - REQUIRE(str == view); + 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("A C-style string") + GIVEN("An short string") { - auto c_str = "Blub Blub"; + auto s = kstd::string{"abcd"}; - WHEN("constructing a string from the C-style string") + WHEN("assigning a new, shorter c-style string of length 3") { - auto str = kstd::string{c_str}; + s = "def"; - THEN("the string is not empty and has the same size as the C-style string") + THEN("the length is equal to 3") { - REQUIRE_FALSE(str.empty()); - REQUIRE(str.size() == std::strlen(c_str)); + REQUIRE(s.length() == 3); } + } + + WHEN("assigning a new, longer, c-style string of length 5") + { + s = "defgh"; - THEN("the string contains the same characters as the C-style string") + THEN("the length is equal to 5") { - REQUIRE(str == c_str); + REQUIRE(s.length() == 5); } } - } - GIVEN("A character") - { - auto ch = 'x'; + 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("constructing a string from the character") + WHEN("assigning a single character") { - auto str = kstd::string{ch}; + s = 'z'; - THEN("the string is not empty and has size 1") + THEN("the length is equal to 1") { - REQUIRE_FALSE(str.empty()); - REQUIRE(str.size() == 1); + REQUIRE(s.length() == 1); } + } + + WHEN("assigning an shorter initializer list of length 3") + { + s = {'d', 'e', 'f'}; - THEN("the string contains the same character as the given character") + THEN("the length is equal to 3") { - REQUIRE(str == std::string_view{&ch, 1}); + REQUIRE(s.length() == 3); } } - } - GIVEN("Another string") - { - auto other = kstd::string{"Blub Blub"}; + 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("copy constructing a new string") + WHEN("assigning an longer initializer list of length 20") { - auto str = kstd::string{other}; + s = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; - THEN("the new string contains the same characters as the original") + THEN("the length is equal to 20") { - REQUIRE(static_cast(str) == static_cast(other)); + REQUIRE(s.length() == 20); } } - auto str = kstd::string{"Blub"}; + 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("copy assigning another string") + WHEN("assigning from a longer string view of length 20") { - auto other = kstd::string{"Blub Blub"}; - str = other; + using namespace std::string_view_literals; - THEN("the string contains the same characters as the assigned string") + s = "ABCDEFGHIJabcdefghij"sv; + + THEN("the length is equal to 20") { - REQUIRE(static_cast(str) == static_cast(other)); + REQUIRE(s.length() == 20); } } } - GIVEN("A string") + GIVEN("An long string") { - auto str = kstd::string{"Hello"}; + auto s = kstd::string{"abcdefghijABCDEFGHIJ"}; - WHEN("copy assigning a string view") + WHEN("assigning a new, shorter c-style string of length 3") { - auto view = std::string_view{"Hello, world!"}; - str = view; + s = "def"; - THEN("the string contains the same characters as the assigned view") + THEN("the length is equal to 3") { - REQUIRE(str == view); + REQUIRE(s.length() == 3); } } - } - GIVEN("A string") - { - auto str = kstd::string{"Hello"}; + 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'}; - WHEN("copy assigning a C-style string") + 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") { - auto c_str = "Hello, world!"; - str = c_str; + using namespace std::string_view_literals; + + s = "ABCDEFGHIJabcdefghijkl"sv; - THEN("the string contains the same characters as the assigned C-style string") + THEN("the length is equal to 22") { - REQUIRE(str == c_str); + REQUIRE(s.length() == 22); } } } } -SCENARIO("String concatenation", "[string]") +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); + } + } + } + GIVEN("Two strings") { auto str1 = kstd::string{"Blub"}; @@ -187,21 +1988,6 @@ SCENARIO("String concatenation", "[string]") REQUIRE(str1.size() == str2.size() + 4); } } - - 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()); - } - } } GIVEN("A string and a string view") @@ -260,6 +2046,51 @@ SCENARIO("String concatenation", "[string]") } } } + + 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 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]") @@ -317,30 +2148,6 @@ SCENARIO("String conversion and comparison", "[string]") } } -SCENARIO("String clearing", "[string]") -{ - 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 iteration", "[string]") { GIVEN("A string") -- cgit v1.2.3 From fb0a56d575e8a28822c9bf9e426601acf09384ac Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 15 Jun 2026 10:42:44 +0200 Subject: kstd: add more long string tests --- libs/kstd/kstd/string.test.cpp | 83 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/libs/kstd/kstd/string.test.cpp b/libs/kstd/kstd/string.test.cpp index e5925aa5..19345e09 100644 --- a/libs/kstd/kstd/string.test.cpp +++ b/libs/kstd/kstd/string.test.cpp @@ -1954,6 +1954,89 @@ SCENARIO("String modifiers", "[string]") } } + 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); + } + } + } + GIVEN("Two strings") { auto str1 = kstd::string{"Blub"}; -- cgit v1.2.3