From fe39e670a51640258cfb2b7d911cddebf9249d14 Mon Sep 17 00:00:00 2001 From: Denys Bielyshev Date: Thu, 21 Aug 2025 16:03:56 +0300 Subject: [PATCH 1/6] implement kf/stl/basic_string --- include/kf/stl/basic_string | 914 ++++++++++++++++++++++++++++++++++++ 1 file changed, 914 insertions(+) create mode 100644 include/kf/stl/basic_string diff --git a/include/kf/stl/basic_string b/include/kf/stl/basic_string new file mode 100644 index 0000000..5d365cb --- /dev/null +++ b/include/kf/stl/basic_string @@ -0,0 +1,914 @@ +#pragma once +#include +#include +#include +#include +#include + +#if _KERNEL_MODE && _ITERATOR_DEBUG_LEVEL > 0 +#error "_ITERATOR_DEBUG_LEVEL must not be greater than 0" +#endif + +namespace kf +{ + ////////////////////////////////////////////////////////////////////////// + // string - the Wrapper for std::string that allows to use it in + // exception-free environments. + + template> + class basic_string + { + public: + using string_type = std::basic_string, Allocator>; + using traits_type = string_type::traits_type; + using allocator_type = string_type::allocator_type; + + using value_type = string_type::value_type; + using size_type = string_type::size_type; + using difference_type = string_type::difference_type; + using pointer = string_type::pointer; + using const_pointer = string_type::const_pointer; + using reference = string_type::reference; + using const_reference = string_type::const_reference; + + using iterator = string_type::iterator; + using const_iterator = string_type::const_iterator; + + using reverse_iterator = string_type::reverse_iterator; + using const_reverse_iterator = string_type::const_reverse_iterator; + + static constexpr size_type npos = string_type::npos; + + // + // Construction and assignment + // + + constexpr basic_string() noexcept = default; + + basic_string(const basic_string&) = delete; + basic_string& operator=(const basic_string&) = delete; + + constexpr basic_string(basic_string&& other) noexcept = default; + constexpr basic_string& operator=(basic_string&& other) noexcept = default; + + constexpr [[nodiscard]] NTSTATUS assign(size_type count, const T& value) noexcept + { + if (auto status = reallocateGrowth(count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.assign(count, value); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS assign(const basic_string& other) noexcept + { + if (auto status = reallocateGrowth(other.size()); !NT_SUCCESS(status)) + { + return status; + } + + m_string.assign(other.m_string); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS assign(const basic_string& other, size_type pos, size_type count = npos) noexcept + { + if (pos > other.size()) + { + return assign(0, T{}); + } + + const auto available = other.size() - pos; + const auto clamped = (count == npos || count > available) ? available : count; + if (auto status = reallocateGrowth(clamped); !NT_SUCCESS(status)) + { + return status; + } + + m_string.assign(other.m_string, pos, clamped); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS assign(const T* s, size_type count) noexcept + { + if (auto status = reallocateGrowth(count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.assign(s, count); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS assign(const T* s) noexcept + { + const auto len = traits_type::length(s); + return assign(s, len); + } + + constexpr operator std::string_view() const noexcept + { + return std::string_view(m_string.data(), m_string.size()); + } + + constexpr void swap(basic_string& other) noexcept + { + m_string.swap(other.m_string); + } + + // + // Element access + // + + constexpr std::optional> at(size_type pos) noexcept + { + return pos < size() ? std::optional(std::ref(m_string[pos])) : std::nullopt; + } + + constexpr std::optional> at(size_type pos) const noexcept + { + return pos < size() ? std::optional(std::ref(m_string[pos])) : std::nullopt; + } + + constexpr T& operator[](size_type pos) noexcept + { + return m_string[pos]; + } + + constexpr const T& operator[](size_type pos) const noexcept + { + return m_string[pos]; + } + + constexpr T& front() noexcept + { + return m_string.front(); + } + + constexpr const T& front() const noexcept + { + return m_string.front(); + } + + constexpr T& back() noexcept + { + return m_string.back(); + } + + constexpr const T& back() const noexcept + { + return m_string.back(); + } + + constexpr T* data() + { + return m_string.data(); + } + + constexpr const T* data() const noexcept + { + return m_string.data(); + } + + constexpr const T* c_str() const noexcept + { + return m_string.c_str(); + } + + // + // Iterators + // + + constexpr iterator begin() noexcept + { + return m_string.begin(); + } + + constexpr const_iterator begin() const noexcept + { + return m_string.begin(); + } + + constexpr const_iterator cbegin() const noexcept + { + return m_string.cbegin(); + } + + constexpr iterator end() noexcept + { + return m_string.end(); + } + + constexpr const_iterator end() const noexcept + { + return m_string.end(); + } + + constexpr const_iterator cend() const noexcept + { + return m_string.cend(); + } + + constexpr reverse_iterator rbegin() noexcept + { + return m_string.rbegin(); + } + + constexpr const_reverse_iterator rbegin() const noexcept + { + return m_string.rbegin(); + } + + constexpr const_reverse_iterator crbegin() const noexcept + { + return m_string.crbegin(); + } + + constexpr reverse_iterator rend() noexcept + { + return m_string.rend(); + } + + constexpr const_reverse_iterator rend() const noexcept + { + return m_string.rend(); + } + + constexpr const_reverse_iterator crend() const noexcept + { + return m_string.crend(); + } + + // + // Size and capacity + // + + constexpr bool empty() const noexcept + { + return m_string.empty(); + } + + constexpr size_type size() const noexcept + { + return m_string.size(); + } + + constexpr size_type length() const noexcept + { + return size(); + } + + constexpr size_type max_size() const noexcept + { + return m_string.max_size(); + } + + constexpr NTSTATUS reserve(size_type newCapacity) noexcept + { + if (newCapacity <= m_string.capacity()) + { + return STATUS_SUCCESS; + } + + return reallocateExactly(newCapacity); + } + + constexpr size_type capacity() const noexcept + { + return m_string.capacity(); + } + + constexpr NTSTATUS shrink_to_fit() noexcept + { + if (m_string.size() == m_string.capacity()) + { + return STATUS_SUCCESS; + } + + if (auto status = reallocateExactly(m_string.size()); !NT_SUCCESS(status)) + { + return status; + } + + m_string.shrink_to_fit(); + return STATUS_SUCCESS; + } + + // + // Comparison + // + + constexpr int compare(const basic_string& str) const noexcept + { + return m_string.compare(str.m_string); + } + + constexpr int compare(size_type pos1, size_type count1, const basic_string& str) const noexcept + { + return m_string.compare(pos1, count1, str.m_string); + } + + constexpr int compare(size_type pos1, size_type count1, const basic_string& str, size_type pos2, size_type count2 = npos) const noexcept + { + return m_string.compare(pos1, count1, str.m_string, pos2, count2); + } + + constexpr int compare(const T* s) const noexcept + { + return m_string.compare(s); + } + + constexpr int compare(size_type pos1, size_type count1, const T* s) const noexcept + { + return m_string.compare(pos1, count1, s); + } + + constexpr int compare(size_type pos1, size_type count1, const T* s, size_type count2) const noexcept + { + return m_string.compare(pos1, count1, s, count2); + } + + constexpr bool operator==(const basic_string& rhs) const noexcept + { + return compare(rhs) == 0; + } + + constexpr bool operator!=(const basic_string& rhs) const noexcept + { + return !(*this == rhs); + } + + constexpr bool operator<(const basic_string& rhs) const noexcept + { + return compare(rhs) < 0; + } + + // Compare with C-style strings (const T*) + constexpr bool operator==(const T* rhs) const noexcept + { + return compare(rhs) == 0; + } + + constexpr bool operator!=(const T* rhs) const noexcept + { + return !(*this == rhs); + } + + constexpr bool operator<(const T* rhs) const noexcept + { + return compare(rhs) < 0; + } + + // + // Modifiers + // + + constexpr void clear() noexcept + { + m_string.clear(); + } + + // insert (iterator-based) + constexpr std::optional insert(const_iterator pos, const T& value) noexcept + { + const auto idx = static_cast(pos - m_string.begin()); + + if (auto status = reallocateGrowth(m_string.size() + 1); !NT_SUCCESS(status)) + { + return std::nullopt; + } + + return m_string.insert(m_string.begin() + idx, value); + } + + constexpr std::optional insert(const_iterator pos, T&& value) noexcept + { + const auto idx = static_cast(pos - m_string.begin()); + + if (auto status = reallocateGrowth(m_string.size() + 1); !NT_SUCCESS(status)) + { + return std::nullopt; + } + + return m_string.insert(m_string.begin() + idx, std::move(value)); + } + + constexpr std::optional insert(const_iterator pos, size_type count, const T& value) noexcept + { + const auto idx = static_cast(pos - m_string.begin()); + + if (auto status = reallocateGrowth(m_string.size() + count); !NT_SUCCESS(status)) + { + return std::nullopt; + } + + return m_string.insert(m_string.begin() + idx, count, value); + } + + template + constexpr std::optional emplace(const_iterator pos, Args&&... args) noexcept + { + const auto idx = static_cast(pos - m_string.begin()); + + if (auto status = reallocateGrowth(m_string.size() + 1); !NT_SUCCESS(status)) + { + return std::nullopt; + } + + return m_string.emplace(m_string.begin() + idx, std::forward(args)...); + } + + // insert (index-based) + constexpr [[nodiscard]] NTSTATUS insert(size_type index, size_type count, T ch) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.insert(index, count, ch); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS insert(size_type index, const T* s, size_type count) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.insert(index, s, count); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS insert(size_type index, const T* s) noexcept + { + return insert(index, s, traits_type::length(s)); + } + + constexpr [[nodiscard]] NTSTATUS insert(size_type index, const basic_string& str) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + str.size()); !NT_SUCCESS(status)) + { + return status; + } + + m_string.insert(index, str.m_string); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS insert(size_type index, const basic_string& str, size_type index_str, size_type count = npos) noexcept + { + if (index_str > str.size()) + { + return STATUS_SUCCESS; + } + + const auto available = str.size() - index_str; + const auto clamped = (count == npos || count > available) ? available : count; + if (auto status = reallocateGrowth(m_string.size() + clamped); !NT_SUCCESS(status)) + { + return status; + } + + m_string.insert(index, str.m_string, index_str, clamped); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS push_back(const T& value) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + 1); !NT_SUCCESS(status)) + { + return status; + } + + m_string.push_back(value); + + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS push_back(T&& value) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + 1); !NT_SUCCESS(status)) + { + return status; + } + + m_string.push_back(std::move(value)); + + return STATUS_SUCCESS; + } + + constexpr void pop_back() noexcept + { + m_string.pop_back(); + } + + template + constexpr std::optional> emplace_back(Args&&... args) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + 1); !NT_SUCCESS(status)) + { + return std::nullopt; + } + + return m_string.emplace_back(std::forward(args)...); + } + + constexpr [[nodiscard]] NTSTATUS append(size_t count, T ch) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.append(count, ch); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS append(const basic_string& str) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + str.size()); !NT_SUCCESS(status)) + { + return status; + } + + m_string.append(str.m_string); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS append(const basic_string& str, size_type pos, size_type count = npos) noexcept + { + if (pos > str.size()) + { + return STATUS_SUCCESS; + } + + const auto available = str.size() - pos; + const auto clamped = (count == npos || count > available) ? available : count; + if (auto status = reallocateGrowth(m_string.size() + clamped); !NT_SUCCESS(status)) + { + return status; + } + + m_string.append(str.m_string, pos, clamped); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS append(const T* s, size_t count) noexcept + { + if (auto status = reallocateGrowth(m_string.size() + count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.append(s, count); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS append(const T* s) noexcept + { + return append(s, traits_type::length(s)); + } + + // Proper usage: auto status = myString += "addition"; + constexpr [[nodiscard]] NTSTATUS operator+=(const basic_string& str) noexcept + { + return append(str); + } + + // Proper usage: auto status = myString += "addition"; + constexpr [[nodiscard]] NTSTATUS operator+=(const T* s) noexcept + { + return append(s); + } + + // Proper usage: auto status = myString += "addition"; + constexpr [[nodiscard]] NTSTATUS operator+=(T ch) noexcept + { + return append(1, ch); + } + + constexpr [[nodiscard]] NTSTATUS resize(size_type count) noexcept + { + if (auto status = reallocateGrowth(count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.resize(count); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS resize(size_type count, const T& value) noexcept + { + if (auto status = reallocateGrowth(count); !NT_SUCCESS(status)) + { + return status; + } + + m_string.resize(count, value); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS erase(size_type index = 0, size_type count = npos) noexcept + { + m_string.erase(index, count); + return STATUS_SUCCESS; + } + + constexpr iterator erase(const_iterator pos) noexcept + { + return m_string.erase(pos); + } + + constexpr iterator erase(const_iterator first, const_iterator last) noexcept + { + return m_string.erase(first, last); + } + + constexpr [[nodiscard]] NTSTATUS replace(size_t pos, size_t count, const basic_string& str) noexcept + { + size_t removed = 0; + if (pos <= size()) + { + const auto available = size() - pos; + removed = (count == npos || count > available) ? available : count; + } + + const auto newSize = size() - removed + str.size(); + if (auto status = reallocateGrowth(newSize); !NT_SUCCESS(status)) + { + return status; + } + + m_string.replace(pos, count, str.m_string); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS replace(size_type pos, size_type count, const T* s, size_type count2) noexcept + { + size_type removed = 0; + if (pos <= size()) + { + const auto available = size() - pos; + removed = (count == npos || count > available) ? available : count; + } + + const auto newSize = size() - removed + count2; + if (auto status = reallocateGrowth(newSize); !NT_SUCCESS(status)) + { + return status; + } + + m_string.replace(pos, count, s, count2); + return STATUS_SUCCESS; + } + + constexpr [[nodiscard]] NTSTATUS replace(size_type pos, size_type count, const T* s) noexcept + { + return replace(pos, count, s, traits_type::length(s)); + } + + constexpr [[nodiscard]] NTSTATUS replace(size_type pos1, size_type count1, const basic_string& str, size_t pos2, size_t count2 = npos) noexcept + { + if (pos2 > str.size()) + { + return replace(pos1, count1, static_cast(nullptr), size_type{0}); + } + + const auto available = str.size() - pos2; + const auto clamped = (count2 == npos || count2 > available) ? available : count2; + return replace(pos1, count1, str.m_string.data() + pos2, clamped); + } + + constexpr [[nodiscard]] NTSTATUS replace(const_iterator first, const_iterator last, const basic_string& str) noexcept + { + const auto idx = static_cast(first - m_string.begin()); + const auto cnt = static_cast(last - first); + return replace(idx, cnt, str); + } + + constexpr [[nodiscard]] NTSTATUS replace(const_iterator first, const_iterator last, size_type count2, T ch) noexcept + { + const auto idx = static_cast(first - m_string.begin()); + const auto cnt = static_cast(last - first); + if (auto status = reallocateGrowth(size() - cnt + count2); !NT_SUCCESS(status)) + { + return status; + } + + m_string.replace(first, last, count2, ch); + return STATUS_SUCCESS; + } + + // + // String operations + // + + constexpr size_type copy(T* dest, size_type count, size_type pos = 0) const noexcept + { + if (pos > size()) + { + return 0; + } + + const auto available = size() - pos; + const auto rcount = (count > available) ? available : count; + traits_type::copy(dest, m_string.data() + pos, rcount); + return rcount; + } + + constexpr basic_string substr(size_type pos = 0, size_type count = npos) const noexcept + { + basic_string result; + if (pos > size()) + { + return result; + } + + const auto available = size() - pos; + const auto rcount = (count == npos || count > available) ? available : count; + if (!NT_SUCCESS(result.reserve(rcount))) + { + return result; // return empty on allocation failure + } + + result.m_string.append(m_string.data() + pos, rcount); + return result; + } + + // find/rfind and friends + constexpr size_type find(const basic_string& str, size_type pos = 0) const noexcept + { + return m_string.find(str.m_string, pos); + } + + constexpr size_type find(const T* s, size_type pos, size_type count) const noexcept + { + return m_string.find(s, pos, count); + } + + constexpr size_type find(const T* s, size_type pos = 0) const noexcept + { + return m_string.find(s, pos); + } + + constexpr size_type find(T ch, size_type pos = 0) const noexcept + { + return m_string.find(ch, pos); + } + + constexpr size_type rfind(const basic_string& str, size_type pos = npos) const noexcept + { + return m_string.rfind(str.m_string, pos); + } + + constexpr size_type rfind(const T* s, size_type pos, size_type count) const noexcept + { + return m_string.rfind(s, pos, count); + } + + constexpr size_type rfind(const T* s, size_type pos = npos) const noexcept + { + return m_string.rfind(s, pos); + } + + constexpr size_type rfind(T ch, size_type pos = npos) const noexcept + { + return m_string.rfind(ch, pos); + } + + constexpr size_type find_first_of(const basic_string& str, size_type pos = 0) const noexcept + { + return m_string.find_first_of(str.m_string, pos); + } + + constexpr size_type find_first_of(const T* s, size_type pos, size_type count) const noexcept + { + return m_string.find_first_of(s, pos, count); + } + + constexpr size_type find_first_of(const T* s, size_type pos = 0) const noexcept + { + return m_string.find_first_of(s, pos); + } + + constexpr size_type find_first_of(T ch, size_type pos = 0) const noexcept + { + return m_string.find_first_of(ch, pos); + } + + constexpr size_type find_first_not_of(const basic_string& str, size_type pos = 0) const noexcept + { + return m_string.find_first_not_of(str.m_string, pos); + } + + constexpr size_type find_first_not_of(const T* s, size_type pos, size_type count) const noexcept + { + return m_string.find_first_not_of(s, pos, count); + } + + constexpr size_type find_first_not_of(const T* s, size_type pos = 0) const noexcept + { + return m_string.find_first_not_of(s, pos); + } + + constexpr size_type find_first_not_of(T ch, size_type pos = 0) const noexcept + { + return m_string.find_first_not_of(ch, pos); + } + + constexpr size_type find_last_of(const basic_string& str, size_type pos = npos) const noexcept + { + return m_string.find_last_of(str.m_string, pos); + } + + constexpr size_type find_last_of(const T* s, size_type pos, size_type count) const noexcept + { + return m_string.find_last_of(s, pos, count); + } + + constexpr size_type find_last_of(const T* s, size_type pos = npos) const noexcept + { + return m_string.find_last_of(s, pos); + } + + constexpr size_type find_last_of(T ch, size_type pos = npos) const noexcept + { + return m_string.find_last_of(ch, pos); + } + + constexpr size_type find_last_not_of(const basic_string& str, size_type pos = npos) const noexcept + { + return m_string.find_last_not_of(str.m_string, pos); + } + + constexpr size_type find_last_not_of(const T* s, size_type pos, size_type count) const noexcept + { + return m_string.find_last_not_of(s, pos, count); + } + + constexpr size_type find_last_not_of(const T* s, size_type pos = npos) const noexcept + { + return m_string.find_last_not_of(s, pos); + } + + constexpr size_type find_last_not_of(T ch, size_type pos = npos) const noexcept + { + return m_string.find_last_not_of(ch, pos); + } + + private: + constexpr NTSTATUS reallocateGrowth(size_type newSize) noexcept + { + if (newSize <= m_string.capacity()) + { + return STATUS_SUCCESS; + } + + return moveInternal(calculateGrowth(newSize)); + } + + constexpr NTSTATUS reallocateExactly(size_type newSize) noexcept + { + return moveInternal(newSize); + } + + constexpr NTSTATUS moveInternal(size_type newCapacity) noexcept + { + string_type newString; + newString.reserve(newCapacity); + if (newString.capacity() < newCapacity) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + m_string.swap(newString); + return STATUS_SUCCESS; + } + + constexpr size_type calculateGrowth(size_type required) noexcept + { + const auto oldCapacity = capacity(); + const auto max = max_size(); + if (oldCapacity > max - oldCapacity / 2) + { + return max; // geometric growth would overflow + } + + const auto geometric = oldCapacity + oldCapacity / 2; + if (geometric < required) + { + return required; // geometric growth would be insufficient + } + + return geometric; // geometric growth is sufficient + } + + private: + string_type m_string; + }; +} From 7b0889bea47af969e6fbf33271792c66ddaec441 Mon Sep 17 00:00:00 2001 From: Denys Bielyshev Date: Thu, 21 Aug 2025 21:20:17 +0300 Subject: [PATCH 2/6] implement tests for basic_string --- include/kf/stl/basic_string | 31 +- test/BasicStringTest.cpp | 1185 +++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 1 + 3 files changed, 1189 insertions(+), 28 deletions(-) create mode 100644 test/BasicStringTest.cpp diff --git a/include/kf/stl/basic_string b/include/kf/stl/basic_string index 5d365cb..99a7247 100644 --- a/include/kf/stl/basic_string +++ b/include/kf/stl/basic_string @@ -407,19 +407,6 @@ namespace kf return m_string.insert(m_string.begin() + idx, count, value); } - template - constexpr std::optional emplace(const_iterator pos, Args&&... args) noexcept - { - const auto idx = static_cast(pos - m_string.begin()); - - if (auto status = reallocateGrowth(m_string.size() + 1); !NT_SUCCESS(status)) - { - return std::nullopt; - } - - return m_string.emplace(m_string.begin() + idx, std::forward(args)...); - } - // insert (index-based) constexpr [[nodiscard]] NTSTATUS insert(size_type index, size_type count, T ch) noexcept { @@ -506,17 +493,6 @@ namespace kf m_string.pop_back(); } - template - constexpr std::optional> emplace_back(Args&&... args) noexcept - { - if (auto status = reallocateGrowth(m_string.size() + 1); !NT_SUCCESS(status)) - { - return std::nullopt; - } - - return m_string.emplace_back(std::forward(args)...); - } - constexpr [[nodiscard]] NTSTATUS append(size_t count, T ch) noexcept { if (auto status = reallocateGrowth(m_string.size() + count); !NT_SUCCESS(status)) @@ -686,14 +662,13 @@ namespace kf constexpr [[nodiscard]] NTSTATUS replace(const_iterator first, const_iterator last, const basic_string& str) noexcept { - const auto idx = static_cast(first - m_string.begin()); - const auto cnt = static_cast(last - first); + const auto idx = static_cast(first - m_string.begin()); + const auto cnt = static_cast(last - first); return replace(idx, cnt, str); } constexpr [[nodiscard]] NTSTATUS replace(const_iterator first, const_iterator last, size_type count2, T ch) noexcept { - const auto idx = static_cast(first - m_string.begin()); const auto cnt = static_cast(last - first); if (auto status = reallocateGrowth(size() - cnt + count2); !NT_SUCCESS(status)) { @@ -909,6 +884,6 @@ namespace kf } private: - string_type m_string; + string_type m_string{}; }; } diff --git a/test/BasicStringTest.cpp b/test/BasicStringTest.cpp new file mode 100644 index 0000000..bbd4bb9 --- /dev/null +++ b/test/BasicStringTest.cpp @@ -0,0 +1,1185 @@ +#include "pch.h" +#include + +SCENARIO("basic_string: ñonstruction and assignment") +{ + GIVEN("a basic_string with content") + { + kf::basic_string str; + REQUIRE_NT_SUCCESS(str.assign("Hello, World!")); + + WHEN("assign(size_type count, const T& value) is called") + { + auto status = str.assign(5, 'A'); + + THEN("the string should contain the repeated character") + { + REQUIRE(NT_SUCCESS(status)); + REQUIRE(str == "AAAAA"); + } + } + + WHEN("assign(const T* s) is called") + { + auto status = str.assign("New Content"); + + THEN("the string should contain the new content") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "New Content"); + } + } + + WHEN("assign(const T* s, size_type count) is called") + { + auto status = str.assign("Partial", 4); + + THEN("the string should contain the partial content") + { + REQUIRE(NT_SUCCESS(status)); + REQUIRE(str == "Part"); + } + } + + WHEN("assign(const basic_string& other) is called") + { + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("Another String")); + auto status = str.assign(other); + + THEN("the string should be assigned the content of the other string") + { + REQUIRE(NT_SUCCESS(status)); + REQUIRE(str == "Another String"); + } + } + + WHEN("operator std::string_view() const is called") + { + std::string_view view(str); + + THEN("the view should reflect the string content") + { + REQUIRE(view == "Hello, World!"); + } + } + + WHEN("swap(basic_string& other) is called") + { + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("Other")); + str.swap(other); + + THEN("the contents should be swapped") + { + REQUIRE(str == "Other"); + REQUIRE(other == "Hello, World!"); + } + } + + WHEN("assign(const T* s) is called on a wide string") + { + kf::basic_string wstr; + auto status = wstr.assign(L"WideText"); + + THEN("the wide string should contain the new content") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(wstr == L"WideText"); + } + } + } + + GIVEN("an empty basic_string") + { + kf::basic_string str; + + WHEN("operator std::string_view() const is called") + { + std::string_view view(str); + + THEN("the view should be empty") + { + REQUIRE(view.empty()); + } + } + + WHEN("assign(const T* s) is called with empty C-string") + { + auto status = str.assign(""); + + THEN("the string remains empty") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str.empty()); + REQUIRE(str.size() == 0); + } + } + + WHEN("assign(size_type count, const T& value) is called with zero count") + { + auto status = str.assign(0, 'Z'); + + THEN("the string remains empty") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str.empty()); + } + } + + WHEN("assign(const T* s, size_type count) is called with zero count") + { + auto status = str.assign("abc", 0); + + THEN("the string remains empty") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str.empty()); + } + } + + WHEN("assign(const basic_string& other) is called with empty other") + { + kf::basic_string other; + auto status = str.assign(other); + + THEN("the string remains empty") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str.empty()); + } + } + + WHEN("swap(basic_string& other) is called with non-empty other") + { + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("X")); + str.swap(other); + + THEN("the empty string takes the content and other becomes empty") + { + REQUIRE(str == "X"); + REQUIRE(other.empty()); + } + } + } +} + +SCENARIO("basic_string: Element access") +{ + GIVEN("a basic_string with content") + { + kf::basic_string str; + REQUIRE_NT_SUCCESS(str.assign("abc")); + + WHEN("at(size_type pos) is called") + { + auto refOpt = str.at(1); + + THEN("it should be 'b' and writable") + { + REQUIRE(refOpt.has_value()); + REQUIRE(refOpt->get() == 'b'); + refOpt->get() = 'B'; + REQUIRE(str == "aBc"); + } + } + + WHEN("at(size_type pos) is called on a const basic_string with out-of-range index") + { + const auto& cstr = str; + auto refOpt = cstr.at(100); + + THEN("it should be empty") + { + REQUIRE(!refOpt.has_value()); + } + } + + WHEN("operator[](size_type) is called") + { + REQUIRE(str[0] == 'a'); + str[2] = 'C'; + + THEN("the characters should reflect updates via operator[]") + { + REQUIRE(str == "abC"); + } + } + + WHEN("front() is called") + { + THEN("front should return the first character") + { + REQUIRE(str.front() == 'a'); + } + } + + WHEN("back() is called") + { + THEN("back should return the last character") + { + REQUIRE(str.back() == 'c'); + } + } + + WHEN("data() is called") + { + THEN("data should not be null") + { + REQUIRE(str.data() != nullptr); + } + } + + WHEN("c_str() is called") + { + THEN("c_str should not be null") + { + REQUIRE(str.c_str() != nullptr); + } + } + + WHEN("at(size_type pos) is called on a wide string") + { + kf::basic_string wstr; + REQUIRE_NT_SUCCESS(wstr.assign(L"xyz")); + auto refOpt = wstr.at(0); + + THEN("wide element should be accessible") + { + REQUIRE(refOpt.has_value()); + REQUIRE(refOpt->get() == L'x'); + } + } + } + + GIVEN("an empty basic_string") + { + kf::basic_string str; + + WHEN("at(size_type pos) is called") + { + auto refOpt = str.at(0); + + THEN("it should be empty") + { + REQUIRE(!refOpt.has_value()); + } + } + + WHEN("data() is called") + { + THEN("data should not be null and point to a null terminator") + { + REQUIRE(str.data() != nullptr); + REQUIRE(*str.data() == '\0'); + } + } + + WHEN("c_str() is called") + { + THEN("c_str should not be null and be an empty string") + { + REQUIRE(str.c_str() != nullptr); + REQUIRE(str.c_str()[0] == '\0'); + } + } + } +} + +SCENARIO("basic_string: Iterators") +{ + GIVEN("a basic_string with content") + { + kf::basic_string str; + REQUIRE_NT_SUCCESS(str.assign("abcd")); + + WHEN("begin() and end() are used to iterate") + { + std::array acc{}; + size_t i = 0; + for (auto it = str.begin(); it != str.end(); ++it) + { + acc[i++] = *it; + } + + THEN("accumulated sequence matches") + { + REQUIRE(acc[0] == 'a'); + REQUIRE(acc[1] == 'b'); + REQUIRE(acc[2] == 'c'); + REQUIRE(acc[3] == 'd'); + } + } + + WHEN("rbegin() and rend() are used to iterate") + { + std::array acc{}; + size_t i = 0; + for (auto it = str.rbegin(); it != str.rend(); ++it) + { + acc[i++] = *it; + } + + THEN("accumulated sequence matches reverse") + { + REQUIRE(acc[0] == 'd'); + REQUIRE(acc[1] == 'c'); + REQUIRE(acc[2] == 'b'); + REQUIRE(acc[3] == 'a'); + } + } + + WHEN("cbegin() is called on a const basic_string") + { + const auto& cstr = str; + auto it = cstr.cbegin(); + + THEN("dereferencing works") + { + REQUIRE(*it == 'a'); + } + } + } + + GIVEN("an empty basic_string") + { + kf::basic_string str; + + WHEN("begin() and end() are used to iterate") + { + THEN("begin equals end for empty string") + { + REQUIRE(str.begin() == str.end()); + } + } + + WHEN("rbegin() and rend() are used to iterate") + { + THEN("rbegin equals rend for empty string") + { + REQUIRE(str.rbegin() == str.rend()); + } + } + + WHEN("cbegin() and cend() are used on a const basic_string") + { + const auto& cstr = str; + THEN("cbegin equals cend for empty string") + { + REQUIRE(cstr.cbegin() == cstr.cend()); + } + } + } +} + +SCENARIO("basic_string: Size and capacity") +{ + GIVEN("an empty basic_string") + { + kf::basic_string str; + REQUIRE(str.empty()); + + WHEN("reserve(size_type newCapacity) is called") + { + auto st1 = str.reserve(32); + const auto cap = str.capacity(); + + THEN("reserve should succeed and capacity grow") + { + REQUIRE_NT_SUCCESS(st1); + REQUIRE(cap >= 32); + } + } + + WHEN("shrink_to_fit() is called") + { + REQUIRE_NT_SUCCESS(str.assign("12345")); + auto status = str.shrink_to_fit(); + const auto capNow = str.capacity(); + const auto sizeNow = str.size(); + + THEN("capacity should be equal to size after shrink") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(capNow == sizeNow); + } + } + + WHEN("length() is called") + { + REQUIRE_NT_SUCCESS(str.assign("xx")); + const auto len = str.length(); + const auto sz = str.size(); + + THEN("length equals size") + { + REQUIRE(len == sz); + } + } + + WHEN("max_size() is called") + { + REQUIRE_NT_SUCCESS(str.assign("x")); + const auto max = str.max_size(); + const auto sz = str.size(); + + THEN("max_size is sane") + { + REQUIRE(max >= sz); + } + } + + WHEN("resize(size_type count, const T& value) is called") + { + REQUIRE_NT_SUCCESS(str.assign("hi")); + auto status = str.resize(5, 'x'); + + THEN("the string should be grown with fill value") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "hixxx"); + } + } + + WHEN("resize(size_type count) is called") + { + REQUIRE_NT_SUCCESS(str.assign("hi")); + auto status = str.resize(2); + + THEN("the string should be shrunk") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "hi"); + } + } + } + + GIVEN("a basic_string with content") + { + kf::basic_string str; + REQUIRE_NT_SUCCESS(str.assign("abcdef")); + + WHEN("size() is called") + { + THEN("size equals number of characters") + { + REQUIRE(str.size() == 6); + } + } + + WHEN("capacity() is called") + { + THEN("capacity is at least size") + { + REQUIRE(str.capacity() >= str.size()); + } + } + + WHEN("reserve(size_type newCapacity) is called") + { + auto stRes = str.reserve(64); + + THEN("reserve succeeds and content preserved") + { + REQUIRE_NT_SUCCESS(stRes); + REQUIRE(str.capacity() >= 64); + REQUIRE(str == "abcdef"); + } + } + + WHEN("shrink_to_fit() is called") + { + REQUIRE_NT_SUCCESS(str.reserve(64)); + auto status = str.shrink_to_fit(); + const auto capNow = str.capacity(); + const auto szNow = str.size(); + + THEN("capacity equals size and content preserved") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(capNow == szNow); + REQUIRE(str == "abcdef"); + } + } + + WHEN("resize(size_type count) is called") + { + auto status = str.resize(3); + + THEN("string should be truncated") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "abc"); + } + } + + WHEN("length() is called") + { + THEN("length equals size") + { + REQUIRE(str.length() == str.size()); + } + } + + WHEN("max_size() is called") + { + THEN("max_size is at least current size") + { + REQUIRE(str.max_size() >= str.size()); + } + } + } +} + +SCENARIO("basic_string: Comparison") +{ + GIVEN("two basic_strings with content") + { + kf::basic_string a, b; + REQUIRE_NT_SUCCESS(a.assign("alpha")); + REQUIRE_NT_SUCCESS(b.assign("beta")); + + WHEN("operator==(const basic_string&) is called") + { + kf::basic_string c; + REQUIRE_NT_SUCCESS(c.assign("alpha")); + + THEN("they are equal when contents match") + { + REQUIRE(a == c); + } + } + + WHEN("operator!=(const basic_string&) is called") + { + THEN("they are not equal when contents differ") + { + REQUIRE(a != b); + } + } + + WHEN("operator<(const basic_string&) is called") + { + THEN("they compare lexicographically") + { + REQUIRE(a < b); + } + } + + WHEN("compare(const basic_string&) is called") + { + const auto cmp = a.compare(b); + + THEN("compare is negative when lhs < rhs") + { + REQUIRE(cmp < 0); + } + } + + WHEN("operator==(const T*) is called") + { + THEN("C-string equality works") + { + REQUIRE(a == "alpha"); + } + } + + WHEN("operator!=(const T*) is called") + { + THEN("C-string inequality works") + { + REQUIRE(a != "alphabet"); + } + } + + WHEN("operator<(const T*) is called") + { + THEN("C-string less-than works") + { + REQUIRE(a < "b"); + } + } + + WHEN("operator==(const T*) is called on a wide string") + { + kf::basic_string w; + REQUIRE_NT_SUCCESS(w.assign(L"zeta")); + + THEN("wide equality works with wide literals") + { + REQUIRE(w == L"zeta"); + } + } + + WHEN("operator!=(const T*) is called on a wide string") + { + kf::basic_string w; + REQUIRE_NT_SUCCESS(w.assign(L"zeta")); + + THEN("wide inequality works with wide literals") + { + REQUIRE(w != L"eta"); + } + } + + WHEN("operator<(const T*) is called on a wide string") + { + kf::basic_string w; + REQUIRE_NT_SUCCESS(w.assign(L"zeta")); + + THEN("wide less-than works with wide literals") + { + REQUIRE(w < L"zzzz"); + } + } + } + + GIVEN("one of basic_strings with content") + { + kf::basic_string a, b; + REQUIRE_NT_SUCCESS(a.assign("alpha")); + // b is empty + + WHEN("operator==(const basic_string&) is called") + { + THEN("content string is not equal to empty string") + { + REQUIRE(a != b); + } + } + + WHEN("operator<(const basic_string&) is called") + { + THEN("empty string is less than content string") + { + REQUIRE(b < a); + } + } + + WHEN("compare(const basic_string&) is called") + { + const auto cmp1 = a.compare(b); + const auto cmp2 = b.compare(a); + + THEN("compare reflects ordering vs empty") + { + REQUIRE(cmp1 > 0); + REQUIRE(cmp2 < 0); + } + } + } +} + +SCENARIO("basic_string: Modifiers") +{ + GIVEN("an empty basic_string") + { + kf::basic_string str; + + WHEN("clear() is called") + { + REQUIRE_NT_SUCCESS(str.assign("core")); + str.clear(); + + THEN("string is empty") + { + REQUIRE(str.empty()); + REQUIRE(str.size() == 0); + } + } + + WHEN("push_back(const T& value) is called") + { + REQUIRE_NT_SUCCESS(str.assign("abc")); + auto status = str.push_back('d'); + + THEN("element is appended") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "abcd"); + } + } + + WHEN("pop_back() is called") + { + REQUIRE_NT_SUCCESS(str.assign("abcd")); + str.pop_back(); + + THEN("last char removed") + { + REQUIRE(str == "abc"); + } + } + + WHEN("insert(const_iterator pos, const T& value) is called") + { + REQUIRE_NT_SUCCESS(str.assign("ABCD")); + auto itOpt = str.insert(str.begin() + 2, 'x'); // ABxCD + + THEN("iterator is returned and string modified") + { + REQUIRE(itOpt.has_value()); + REQUIRE(str == "ABxCD"); + } + } + + WHEN("erase(const_iterator first, const_iterator last) is called") + { + REQUIRE_NT_SUCCESS(str.assign("ABxCD")); + auto first = str.begin() + 1; // B + auto last = str.begin() + 3; // x + auto after = str.erase(first, last); // remove Bx -> ACD + + THEN("erase returns iterator to next element") + { + REQUIRE(*after == 'C'); + REQUIRE(str == "ACD"); + } + } + + WHEN("insert(size_type index, size_type count, T ch) is called") + { + REQUIRE_NT_SUCCESS(str.assign("abc")); + auto status = str.insert(1, 2, 'Y'); // aYYbc + + THEN("characters inserted by count") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "aYYbc"); + } + } + + WHEN("insert(size_type index, const T* s) is called") + { + REQUIRE_NT_SUCCESS(str.assign("aYYbc")); + auto status = str.insert(0, "--"); // --aYYbc + + THEN("C-string inserted") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "--aYYbc"); + } + } + + WHEN("insert(size_type index, const basic_string&) is called") + { + REQUIRE_NT_SUCCESS(str.assign("--aYYbc")); + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("[]")); + auto status = str.insert(2, other); // --[]aYYbc + + THEN("basic_string inserted") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "--[]aYYbc"); + } + } + + WHEN("insert(size_type index, const basic_string&, size_type index_str, size_type count) is called") + { + REQUIRE_NT_SUCCESS(str.assign("--[]aYYbc")); + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("[]")); + auto status = str.insert(2, other, 1, 1); // --] []aYYbc -> --][]aYYbc + + THEN("substring from basic_string inserted") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "--][]aYYbc"); + } + } + + WHEN("append(size_t count, T ch) is called") + { + REQUIRE_NT_SUCCESS(str.assign("foo")); + auto status = str.append(3, '!'); + + THEN("characters appended by count") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "foo!!!"); + } + } + + WHEN("append(const basic_string&) is called") + { + REQUIRE_NT_SUCCESS(str.assign("foo!!!")); + kf::basic_string more; + REQUIRE_NT_SUCCESS(more.assign("bar")); + auto status = str.append(more); + + THEN("basic_string appended") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "foo!!!bar"); + } + } + + WHEN("append(const T* s, size_t count) is called") + { + REQUIRE_NT_SUCCESS(str.assign("foo!!!bar")); + auto status = str.append("XYZ", 2); + + THEN("partial C-string appended") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "foo!!!barXY"); + } + } + + WHEN("operator+=(const T*) is called") + { + REQUIRE_NT_SUCCESS(str.assign("foo!!!barXY")); + auto status = (str += "Z"); + + THEN("C-string concatenated") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "foo!!!barXYZ"); + } + } + + WHEN("operator+=(T) is called") + { + REQUIRE_NT_SUCCESS(str.assign("foo!!!barXYZ")); + auto status = (str += '!'); + + THEN("single character concatenated") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "foo!!!barXYZ!"); + } + } + + WHEN("replace(size_t pos, size_t count, const basic_string&) is called") + { + REQUIRE_NT_SUCCESS(str.assign("0123456789")); + kf::basic_string tmp; + REQUIRE_NT_SUCCESS(tmp.assign("AA")); + auto status = str.replace(2, 3, tmp); // 01 + AA + 56789 + + THEN("substring replaced with basic_string") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str.find("AA") == 2); + } + } + + WHEN("replace(size_type pos, size_type count, const T* s, size_type count2) is called") + { + REQUIRE_NT_SUCCESS(str.assign("01AA56789")); + auto status = str.replace(2, 2, "XX", 2); + + THEN("substring replaced with C-string and count") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str.find("XX") == 2); + } + } + + WHEN("replace(const_iterator first, const_iterator last, size_type count2, T ch) is called") + { + REQUIRE_NT_SUCCESS(str.assign("--XX56789")); + auto first = str.begin() + 2; + auto last = first + 2; + auto status = str.replace(first, last, 3, '*'); + + THEN("replacement should be applied") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str.find("***", 0) != decltype(str)::npos); + } + } + + WHEN("erase(size_type index, size_type count) is called") + { + REQUIRE_NT_SUCCESS(str.assign("erase")); + REQUIRE_NT_SUCCESS(str.erase(1, 2)); // e + se -> ese + + THEN("substring erased by index/count") + { + REQUIRE(str == "ese"); + } + } + + WHEN("erase(const_iterator pos) is called") + { + REQUIRE_NT_SUCCESS(str.assign("ese")); + auto it = str.erase(str.begin()); + + THEN("first char erased") + { + REQUIRE(*it == 's'); + REQUIRE(str == "se"); + } + } + } +} + +SCENARIO("basic_string: String operations") +{ + GIVEN("a basic_string with content") + { + kf::basic_string str; + REQUIRE_NT_SUCCESS(str.assign("copy-me")); + + WHEN("copy(T* dest, size_type count, size_type pos) is called") + { + char buf[16] = {}; + auto n = str.copy(buf, 4, 5); // from index 5: 'm','e' + + THEN("buffer contains copied data and count is correct") + { + REQUIRE(n == 2); + REQUIRE(buf[0] == 'm'); + REQUIRE(buf[1] == 'e'); + } + } + + WHEN("substr(size_type pos, size_type count) is called") + { + auto sub1 = str.substr(0, 4); + auto sub2 = str.substr(100, 2); // out of range -> empty + + THEN("substrings should be as expected") + { + REQUIRE(sub1 == "copy"); + REQUIRE(sub2.empty()); + } + } + } + + GIVEN("an empty basic_string") + { + kf::basic_string str; + + WHEN("copy(T* dest, size_type count, size_type pos) is called") + { + char buf[8] = {}; + auto n = str.copy(buf, 4, 0); + + THEN("nothing is copied from an empty string") + { + REQUIRE(n == 0); + REQUIRE(buf[0] == '\0'); + } + } + + WHEN("substr(size_type pos, size_type count) is called") + { + auto sub = str.substr(0, 10); + + THEN("substr of empty is empty") + { + REQUIRE(sub.empty()); + } + } + } +} + +SCENARIO("basic_string: find/rfind and friends") +{ + GIVEN("a basic_string with content") + { + kf::basic_string str; + REQUIRE_NT_SUCCESS(str.assign("abracadabra")); + + WHEN("find(const T* s, size_type pos) is called") + { + const auto posSub = str.find("abra"); + + THEN("substring position should match") + { + REQUIRE(posSub == 0); + } + } + + WHEN("find(T ch, size_type pos) is called") + { + const auto posCh = str.find('c'); + + THEN("character position should match") + { + REQUIRE(posCh == 4); + } + } + + WHEN("rfind(const T* s, size_type pos) is called") + { + const auto posRSub = str.rfind("abra"); + + THEN("reverse substring position should match") + { + REQUIRE(posRSub == 7); + } + } + + WHEN("rfind(T ch, size_type pos) is called") + { + const auto posRCh = str.rfind('a'); + + THEN("reverse character position should match") + { + REQUIRE(posRCh == 10); + } + } + + WHEN("find_first_of(const T* s, size_type pos) is called") + { + const auto posFirst = str.find_first_of("xyzabc"); + + THEN("it finds any of the given set from the beginning") + { + REQUIRE(posFirst == 0); + } + } + + WHEN("find_last_of(const T* s, size_type pos) is called") + { + const auto posLast = str.find_last_of("xyzabc"); + + THEN("it finds any of the given set from the end") + { + REQUIRE(posLast == 10); + } + } + + WHEN("find_first_not_of(const T* s, size_type pos) is called") + { + const auto posFirstNot = str.find_first_not_of("ab"); + + THEN("it skips the matching set from the beginning") + { + REQUIRE(posFirstNot == 2); // 'r' + } + } + + WHEN("find_last_not_of(const T* s, size_type pos) is called") + { + const auto posLastNot = str.find_last_not_of("ar"); + + THEN("it skips the matching set from the end") + { + REQUIRE(posLastNot == 8); // before 'b' + } + } + + WHEN("find(const T* s, size_type pos) is called on a wide string") + { + kf::basic_string wstr; + REQUIRE_NT_SUCCESS(wstr.assign(L"Hello Wide World")); + const auto posWide = wstr.find(L"Wide"); + + THEN("wide substring search works") + { + REQUIRE(posWide == 6); + } + } + + WHEN("find(T ch, size_type pos) is called on a wide string") + { + kf::basic_string wstr; + REQUIRE_NT_SUCCESS(wstr.assign(L"Hello Wide World")); + const auto posWCh = wstr.find(L'W'); + + THEN("wide character search works") + { + REQUIRE(posWCh == 6); + } + } + + WHEN("find_first_of(const T* s, size_type pos) is called on a wide string") + { + kf::basic_string wstr; + REQUIRE_NT_SUCCESS(wstr.assign(L"Hello Wide World")); + const auto posFirst = wstr.find_first_of(L"HW"); + + THEN("wide find_first_of works") + { + REQUIRE(posFirst == 0); + } + } + + WHEN("find_last_of(const T* s, size_type pos) is called on a wide string") + { + kf::basic_string wstr; + REQUIRE_NT_SUCCESS(wstr.assign(L"Hello Wide World")); + const auto posLast = wstr.find_last_of(L"od"); + + THEN("wide find_last_of works") + { + REQUIRE(posLast == 15); + } + } + } + + GIVEN("an empty basic_string") + { + kf::basic_string str; + + WHEN("find(const T* s, size_type pos) is called") + { + THEN("find on empty returns npos") + { + REQUIRE(str.find("a") == decltype(str)::npos); + } + } + + WHEN("find(T ch, size_type pos) is called") + { + THEN("find on empty returns npos") + { + REQUIRE(str.find('a') == decltype(str)::npos); + } + } + + WHEN("rfind(const T* s, size_type pos) is called") + { + THEN("rfind on empty returns npos") + { + REQUIRE(str.rfind("a") == decltype(str)::npos); + } + } + + WHEN("rfind(T ch, size_type pos) is called") + { + THEN("rfind on empty returns npos") + { + REQUIRE(str.rfind('a') == decltype(str)::npos); + } + } + + WHEN("find_first_of(const T* s, size_type pos) is called") + { + THEN("find_first_of on empty returns npos") + { + REQUIRE(str.find_first_of("abc") == decltype(str)::npos); + } + } + + WHEN("find_last_of(const T* s, size_type pos) is called") + { + THEN("find_last_of on empty returns npos") + { + REQUIRE(str.find_last_of("abc") == decltype(str)::npos); + } + } + + WHEN("find_first_not_of(const T* s, size_type pos) is called") + { + THEN("find_first_not_of on empty returns npos") + { + REQUIRE(str.find_first_not_of("abc") == decltype(str)::npos); + } + } + + WHEN("find_last_not_of(const T* s, size_type pos) is called") + { + THEN("find_last_not_of on empty returns npos") + { + REQUIRE(str.find_last_not_of("abc") == decltype(str)::npos); + } + } + } +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6dccd52..cb926f1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -59,6 +59,7 @@ wdk_add_driver(kf-test WINVER NTDDI_WIN10 STL AutoSpinLockTest.cpp EResourceSharedLockTest.cpp RecursiveAutoSpinLockTest.cpp + BasicStringTest.cpp ) target_link_libraries(kf-test kf::kf kmtest::kmtest) From 491d069b9bac59445eaa13f911189196f79e7b2b Mon Sep 17 00:00:00 2001 From: Vlada Kanivets Date: Thu, 4 Sep 2025 16:06:27 +0200 Subject: [PATCH 3/6] add tests, fix tests --- include/kf/stl/basic_string | 12 +- test/BasicStringTest.cpp | 510 ++++++++++++++++++++++++++++++++++-- 2 files changed, 500 insertions(+), 22 deletions(-) diff --git a/include/kf/stl/basic_string b/include/kf/stl/basic_string index 99a7247..12e92a9 100644 --- a/include/kf/stl/basic_string +++ b/include/kf/stl/basic_string @@ -282,7 +282,7 @@ namespace kf constexpr NTSTATUS shrink_to_fit() noexcept { - if (m_string.size() == m_string.capacity()) + if (m_string.size() == 0 || m_string.size() == m_string.capacity()) { return STATUS_SUCCESS; } @@ -327,7 +327,8 @@ namespace kf constexpr int compare(size_type pos1, size_type count1, const T* s, size_type count2) const noexcept { - return m_string.compare(pos1, count1, s, count2); + auto tempCount2 = strlen(s) < count2 ? strlen(s) : count2; + return m_string.compare(pos1, count1, s, tempCount2); } constexpr bool operator==(const basic_string& rhs) const noexcept @@ -591,6 +592,11 @@ namespace kf constexpr [[nodiscard]] NTSTATUS erase(size_type index = 0, size_type count = npos) noexcept { + if (index > size()) + { + return STATUS_INVALID_PARAMETER; + } + m_string.erase(index, count); return STATUS_SUCCESS; } @@ -855,12 +861,14 @@ namespace kf constexpr NTSTATUS moveInternal(size_type newCapacity) noexcept { string_type newString; + // If newCapacity is less than or equal to the current capacity(), there is no effect. newString.reserve(newCapacity); if (newString.capacity() < newCapacity) { return STATUS_INSUFFICIENT_RESOURCES; } + newString.insert(0, m_string); m_string.swap(newString); return STATUS_SUCCESS; } diff --git a/test/BasicStringTest.cpp b/test/BasicStringTest.cpp index bbd4bb9..b4c4559 100644 --- a/test/BasicStringTest.cpp +++ b/test/BasicStringTest.cpp @@ -1,7 +1,7 @@ #include "pch.h" #include -SCENARIO("basic_string: ñonstruction and assignment") +SCENARIO("basic_string: construction and assignment") { GIVEN("a basic_string with content") { @@ -90,6 +90,48 @@ } } + GIVEN("Two basic_string with content") + { + kf::basic_string str; + REQUIRE_NT_SUCCESS(str.assign("Hello, World!")); + + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("Other str")); + + WHEN("assign(const basic_string&, size_type, size_type) is called with pos > other.size()") + { + auto status = str.assign(other, 20, 5); + + THEN("the string should become empty") + { + REQUIRE(NT_SUCCESS(status)); + REQUIRE(str.empty()); + } + } + + WHEN("assign(const basic_string&, size_type, size_type) is called with pos within range and count = npos") + { + auto status = str.assign(other, 3); + + THEN("the string should contain the suffix starting at pos") + { + REQUIRE(NT_SUCCESS(status)); + REQUIRE(str == "er str"); + } + } + + WHEN("assign(const basic_string&, size_type, size_type) is called with pos within range and count < available") + { + auto status = str.assign(other, 2, 5); + + THEN("the string should contain the substring of given count") + { + REQUIRE(NT_SUCCESS(status)); + REQUIRE(str == "her s"); + } + } + } + GIVEN("an empty basic_string") { kf::basic_string str; @@ -394,12 +436,13 @@ SCENARIO("basic_string: Size and capacity") WHEN("shrink_to_fit() is called") { - REQUIRE_NT_SUCCESS(str.assign("12345")); + // shrink_to_fit has no effect on small strings stored in SSO (size <= 15 chars); + REQUIRE_NT_SUCCESS(str.assign("1-2-3-4-5-6-7-8-9-10-11-12-13-14")); auto status = str.shrink_to_fit(); const auto capNow = str.capacity(); const auto sizeNow = str.size(); - THEN("capacity should be equal to size after shrink") + THEN("capacity should be equal to expected") { REQUIRE_NT_SUCCESS(status); REQUIRE(capNow == sizeNow); @@ -490,16 +533,18 @@ SCENARIO("basic_string: Size and capacity") WHEN("shrink_to_fit() is called") { + // shrink_to_fit has no effect on small strings stored in SSO (size <= 15 chars); + REQUIRE_NT_SUCCESS(str.assign("1-2-3-4-5-6-7-8-9")); REQUIRE_NT_SUCCESS(str.reserve(64)); auto status = str.shrink_to_fit(); const auto capNow = str.capacity(); - const auto szNow = str.size(); + const auto sizeNow = str.size(); - THEN("capacity equals size and content preserved") + THEN("capacity equals expected capacity and content preserved") { REQUIRE_NT_SUCCESS(status); - REQUIRE(capNow == szNow); - REQUIRE(str == "abcdef"); + REQUIRE(capNow == sizeNow); + REQUIRE(str == "1-2-3-4-5-6-7-8-9"); } } @@ -577,6 +622,66 @@ SCENARIO("basic_string: Comparison") } } + WHEN("compare(pos1, count1, const basic_string& str) is called") + { + auto cmp = a.compare(1, 2, b); + + THEN("compare is positive when substring lhs > rhs") + { + REQUIRE(cmp > 0); + } + } + + WHEN("compare(pos1, count1, const basic_string&) with count1 > remaining length is called") + { + auto cmp = a.compare(3, 10, b); + + THEN("compare is positive") + { + REQUIRE(cmp > 0); + } + } + + WHEN("compare(pos1, count1, const basic_string& str) with empty substring of a is called") + { + auto cmp = a.compare(0, 0, b); + + THEN("empty substring is less than non-empty string") + { + REQUIRE(cmp < 0); + } + } + + WHEN("compare(pos1, count1, const basic_string& str, pos2, count2) is called") + { + auto cmp = a.compare(0, 2, b, 1, 2); // "al" vs "et" + + THEN("compare is negative when lhs < rhs") + { + REQUIRE(cmp < 0); + } + } + + WHEN("compare(pos1, count1, const basic_string& str, pos2, count2) for substrings of the same string is called") + { + auto cmp = a.compare(0, 2, a, 0, 2); // "al" vs "al" + + THEN("substrings are equal") + { + REQUIRE(cmp == 0); + } + } + + WHEN("compare(pos1, count1, const basic_string& str, pos2, count2) with overlapping positions is called") + { + auto cmp = a.compare(1, 3, a, 2, 3); + + THEN("comparison is negative") + { + REQUIRE(cmp < 0); + } + } + WHEN("operator==(const T*) is called") { THEN("C-string equality works") @@ -601,6 +706,56 @@ SCENARIO("basic_string: Comparison") } } + WHEN("compare(pos1, count1, const T* s) with C-style string is called") + { + auto cmp = a.compare(0, 2, "al"); // "al" == "al" + + THEN("substring equals C-style string") + { + REQUIRE(cmp == 0); + } + } + + WHEN("compare(pos1, count1, const T* s) with longer C-style string is called") + { + auto cmp = a.compare(0, 2, "alphabet"); // "al" < "alphabet" + + THEN("comparison is negative") + { + REQUIRE(cmp < 0); + } + } + + WHEN("compare(pos1, count1, const T* s, count2) with C-style string is called") + { + auto cmp = a.compare(0, 2, "alphabet", 2); // "al" == "al" + + THEN("substring equals first count2 chars of C-style string") + { + REQUIRE(cmp == 0); + } + } + + WHEN("compare(pos1, count1, const T* s, count2) with C-style string is called") + { + auto cmp = a.compare(2, 3, "alp", 2); // "pha" > "al" + + THEN("comparison is positive") + { + REQUIRE(cmp > 0); + } + } + + WHEN("compare(pos1, count1, const T* s, count2) with count2 > strlen(s)") + { + auto cmp = a.compare(0, 2, "al", 10); + + THEN("they are equal") + { + REQUIRE(cmp == 0); // -1 + } + } + WHEN("operator==(const T*) is called on a wide string") { kf::basic_string w; @@ -668,6 +823,27 @@ SCENARIO("basic_string: Comparison") REQUIRE(cmp2 < 0); } } + + WHEN("compare(pos1, count1, str) is called") + { + auto cmp = a.compare(0, 5, b); + + THEN("non-empty substring is greater than empty string") + { + REQUIRE(cmp > 0); + } + } + + WHEN("compare(pos1, count1, str) is called") + { + auto cmp = b.compare(0, 0, b); + + THEN("empty substrings are equal") + { + REQUIRE(cmp == 0); + } + } + } } @@ -692,7 +868,8 @@ SCENARIO("basic_string: Modifiers") WHEN("push_back(const T& value) is called") { REQUIRE_NT_SUCCESS(str.assign("abc")); - auto status = str.push_back('d'); + const auto ch = 'd'; + auto status = str.push_back(ch); THEN("element is appended") { @@ -701,6 +878,19 @@ SCENARIO("basic_string: Modifiers") } } + WHEN("push_back(T&& value) is called") + { + REQUIRE_NT_SUCCESS(str.assign("abc")); + char ch = 'd'; + auto status = str.push_back(std::move(ch)); + + THEN("element is appended using rvalue reference") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "abcd"); + } + } + WHEN("pop_back() is called") { REQUIRE_NT_SUCCESS(str.assign("abcd")); @@ -724,17 +914,40 @@ SCENARIO("basic_string: Modifiers") } } - WHEN("erase(const_iterator first, const_iterator last) is called") + WHEN("insert(const_iterator pos, T&& value) is called") { - REQUIRE_NT_SUCCESS(str.assign("ABxCD")); - auto first = str.begin() + 1; // B - auto last = str.begin() + 3; // x - auto after = str.erase(first, last); // remove Bx -> ACD + REQUIRE_NT_SUCCESS(str.assign("ABCD")); + char ch = 'x'; + auto itOpt = str.insert(str.begin() + 2, std::move(ch)); // ABxCD - THEN("erase returns iterator to next element") + THEN("iterator is returned and rvalue inserted") { - REQUIRE(*after == 'C'); - REQUIRE(str == "ACD"); + REQUIRE(itOpt.has_value()); + REQUIRE(str == "ABxCD"); + } + } + + WHEN("insert(const_iterator pos, size_type count, const T& value) is called") + { + REQUIRE_NT_SUCCESS(str.assign("ABCD")); + auto itOpt = str.insert(str.begin() + 2, 3, 'y'); // AByyyCD + + THEN("multiple copies inserted at iterator position") + { + REQUIRE(itOpt.has_value()); + REQUIRE(str == "AByyyCD"); + } + } + + WHEN("insert(size_type index, const T* s, size_type count) is called") + { + REQUIRE_NT_SUCCESS(str.assign("abc")); + auto status = str.insert(1, "XYZ", 2); // aXYbc + + THEN("substring of C-string inserted") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "aXYbc"); } } @@ -790,6 +1003,20 @@ SCENARIO("basic_string: Modifiers") } } + WHEN("insert(size_type index, const basic_string&, size_type index_str, size_type count) with index_str > str.size() is called") + { + REQUIRE_NT_SUCCESS(str.assign("base")); + kf::basic_string src; + REQUIRE_NT_SUCCESS(src.assign("xx")); + auto status = str.insert(2, src, 10, 5); + + THEN("operation succeeds and string is unchanged") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "base"); + } + } + WHEN("append(size_t count, T ch) is called") { REQUIRE_NT_SUCCESS(str.assign("foo")); @@ -816,6 +1043,18 @@ SCENARIO("basic_string: Modifiers") } } + WHEN("append(const T* s) is called") + { + REQUIRE_NT_SUCCESS(str.assign("foo")); + auto status = str.append("bar"); + + THEN("entire C-string appended") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "foobar"); + } + } + WHEN("append(const T* s, size_t count) is called") { REQUIRE_NT_SUCCESS(str.assign("foo!!!bar")); @@ -828,6 +1067,34 @@ SCENARIO("basic_string: Modifiers") } } + WHEN("append(const basic_string&, pos, count) is called") + { + REQUIRE_NT_SUCCESS(str.assign("base")); + kf::basic_string more; + REQUIRE_NT_SUCCESS(more.assign("appendix")); + auto status = str.append(more, 0, 3); // "baseapp" + + THEN("substring of basic_string appended") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "baseapp"); + } + } + + WHEN("append(const basic_string& str, size_type pos, size_type count) with pos > str.size() is called") + { + REQUIRE_NT_SUCCESS(str.assign("A")); + kf::basic_string src; + REQUIRE_NT_SUCCESS(src.assign("XYZ")); + auto status = str.append(src, 5, 2); + + THEN("operation succeeds and string is unchanged") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "A"); + } + } + WHEN("operator+=(const T*) is called") { REQUIRE_NT_SUCCESS(str.assign("foo!!!barXY")); @@ -852,6 +1119,20 @@ SCENARIO("basic_string: Modifiers") } } + WHEN("operator+=(const basic_string&) is called") + { + REQUIRE_NT_SUCCESS(str.assign("foo")); + kf::basic_string more; + REQUIRE_NT_SUCCESS(more.assign("bar")); + auto status = (str += more); + + THEN("basic_string concatenated") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "foobar"); + } + } + WHEN("replace(size_t pos, size_t count, const basic_string&) is called") { REQUIRE_NT_SUCCESS(str.assign("0123456789")); @@ -866,6 +1147,18 @@ SCENARIO("basic_string: Modifiers") } } + WHEN("replace(size_type pos, size_type count, const T* s) is called") + { + REQUIRE_NT_SUCCESS(str.assign("abcdef")); + auto status = str.replace(2, 3, "XYZ"); // abXYZf + + THEN("substring replaced with C-string") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "abXYZf"); + } + } + WHEN("replace(size_type pos, size_type count, const T* s, size_type count2) is called") { REQUIRE_NT_SUCCESS(str.assign("01AA56789")); @@ -878,6 +1171,48 @@ SCENARIO("basic_string: Modifiers") } } + WHEN("replace(size_type pos1, size_type count1, const basic_string& str, pos2, count2) is called") + { + REQUIRE_NT_SUCCESS(str.assign("abcdef")); + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("12345")); + auto status = str.replace(1, 3, other, 1, 2); // a + "23" + ef + + THEN("substring replaced with substring of another string") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "a23ef"); + } + } + + WHEN("replace(size_type pos1, size_type count1, const basic_string& str, size_t pos2, size_t count2) with pos2 > other.size()) is called") + { + REQUIRE_NT_SUCCESS(str.assign("abcdef")); + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("12")); + auto status = str.replace(2, 3, other, 5, 7); + + THEN("range is erased") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "abf"); + } + } + + WHEN("replace(const_iterator first, const_iterator last, const basic_string& str) is called") + { + REQUIRE_NT_SUCCESS(str.assign("abcdef")); + kf::basic_string repl; + REQUIRE_NT_SUCCESS(repl.assign("ZZ")); + auto status = str.replace(str.begin() + 2, str.begin() + 4, repl); // abZZef + + THEN("range replaced with another string") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "abZZef"); + } + } + WHEN("replace(const_iterator first, const_iterator last, size_type count2, T ch) is called") { REQUIRE_NT_SUCCESS(str.assign("--XX56789")); @@ -892,6 +1227,55 @@ SCENARIO("basic_string: Modifiers") } } + WHEN("erase(const_iterator first, const_iterator last) is called") + { + REQUIRE_NT_SUCCESS(str.assign("ABxCD")); + auto first = str.begin() + 1; // B + auto last = str.begin() + 3; // x + auto after = str.erase(first, last); // remove Bx -> ACD + + THEN("erase returns iterator to next element") + { + REQUIRE(*after == 'C'); + REQUIRE(str == "ACD"); + } + } + + WHEN("erase(const_iterator first, const_iterator last) is called with first == last") + { + REQUIRE_NT_SUCCESS(str.assign("abcde")); + auto it = str.erase(str.begin() + 2, str.begin() + 2); + + THEN("no characters erased, iterator points to first") + { + REQUIRE(it == str.begin() + 2); + REQUIRE(str == "abcde"); + } + } + + WHEN("erase(const_iterator first, const_iterator last) is called with first == end()") + { + REQUIRE_NT_SUCCESS(str.assign("abcde")); + auto it = str.erase(str.end(), str.end()); + + THEN("no characters erased, iterator points to end") + { + REQUIRE(it == str.end()); + REQUIRE(str == "abcde"); + } + } + + WHEN("erase(const_iterator first, const_iterator last) is called on empty string") + { + auto it = str.erase(str.begin(), str.end()); + + THEN("iterator points to begin/end") + { + REQUIRE(it == str.begin()); + REQUIRE(str.empty()); + } + } + WHEN("erase(size_type index, size_type count) is called") { REQUIRE_NT_SUCCESS(str.assign("erase")); @@ -903,6 +1287,38 @@ SCENARIO("basic_string: Modifiers") } } + WHEN("erase(size_type index, size_type count) is called with index >= size()") + { + REQUIRE_NT_SUCCESS(str.assign("hello")); + + THEN("erase should fail without crush") + { + REQUIRE_NT_SUCCESS(!str.erase(10, 2)); // index beyond size + } + } + + WHEN("erase(size_type index, size_type count) is called with count == 0") + { + REQUIRE_NT_SUCCESS(str.assign("hello")); + REQUIRE_NT_SUCCESS(str.erase(2, 0)); + + THEN("string remains unchanged") + { + REQUIRE(str == "hello"); + } + } + + WHEN("erase(size_type index, size_type count) is called with count > size() - index") + { + REQUIRE_NT_SUCCESS(str.assign("hello")); + REQUIRE_NT_SUCCESS(str.erase(3, 10)); + + THEN("erases until the end") + { + REQUIRE(str == "hel"); + } + } + WHEN("erase(const_iterator pos) is called") { REQUIRE_NT_SUCCESS(str.assign("ese")); @@ -914,6 +1330,30 @@ SCENARIO("basic_string: Modifiers") REQUIRE(str == "se"); } } + + WHEN("erase(const_iterator pos) is called with pos == end()") + { + REQUIRE_NT_SUCCESS(str.assign("abc")); + auto it = str.erase(str.end()); + + THEN("no characters erased, iterator points to end") + { + REQUIRE(it == str.end()); + REQUIRE(str == "abc"); + } + } + + WHEN("erase(const_iterator pos) is called on empty string") + { + str.clear(); + auto it = str.erase(str.begin()); + + THEN("no characters erased, iterator points to begin") + { + REQUIRE(it == str.begin()); + REQUIRE(str.empty()); + } + } } } @@ -937,15 +1377,45 @@ SCENARIO("basic_string: String operations") } } + WHEN("copy(T* dest, size_type count, size_type pos) with pos == size is called") + { + char buf[8] = {}; + auto n = str.copy(buf, 4, str.size()); + + THEN("nothing is copied and count is 0") + { + REQUIRE(n == 0); + REQUIRE(buf[0] == '\0'); + } + } + WHEN("substr(size_type pos, size_type count) is called") { - auto sub1 = str.substr(0, 4); - auto sub2 = str.substr(100, 2); // out of range -> empty + auto sub = str.substr(0, 4); THEN("substrings should be as expected") { - REQUIRE(sub1 == "copy"); - REQUIRE(sub2.empty()); + REQUIRE(sub == "copy"); + } + } + + WHEN("substr(size_type pos, size_type count) with pos > size is called") + { + auto sub = str.substr(100, 2); // out of range -> empty + + THEN("substrings should be empty") + { + REQUIRE(sub.empty()); + } + } + + WHEN("substr(size_type pos, size_type count) with count > available is called") + { + auto sub = str.substr(3, 10); + + THEN("substring is truncated to available characters") + { + REQUIRE(sub == "y-me"); } } } From 69918a03162f465e4f7c447dfbe600ea7c8111df Mon Sep 17 00:00:00 2001 From: Vlada Kanivets Date: Thu, 4 Sep 2025 18:58:30 +0200 Subject: [PATCH 4/6] fix tests --- test/BasicStringTest.cpp | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/test/BasicStringTest.cpp b/test/BasicStringTest.cpp index b4c4559..25e6a6b 100644 --- a/test/BasicStringTest.cpp +++ b/test/BasicStringTest.cpp @@ -434,21 +434,6 @@ SCENARIO("basic_string: Size and capacity") } } - WHEN("shrink_to_fit() is called") - { - // shrink_to_fit has no effect on small strings stored in SSO (size <= 15 chars); - REQUIRE_NT_SUCCESS(str.assign("1-2-3-4-5-6-7-8-9-10-11-12-13-14")); - auto status = str.shrink_to_fit(); - const auto capNow = str.capacity(); - const auto sizeNow = str.size(); - - THEN("capacity should be equal to expected") - { - REQUIRE_NT_SUCCESS(status); - REQUIRE(capNow == sizeNow); - } - } - WHEN("length() is called") { REQUIRE_NT_SUCCESS(str.assign("xx")); @@ -534,16 +519,16 @@ SCENARIO("basic_string: Size and capacity") WHEN("shrink_to_fit() is called") { // shrink_to_fit has no effect on small strings stored in SSO (size <= 15 chars); + constexpr size_t newCap = 64; REQUIRE_NT_SUCCESS(str.assign("1-2-3-4-5-6-7-8-9")); - REQUIRE_NT_SUCCESS(str.reserve(64)); + REQUIRE_NT_SUCCESS(str.reserve(newCap)); auto status = str.shrink_to_fit(); const auto capNow = str.capacity(); - const auto sizeNow = str.size(); - THEN("capacity equals expected capacity and content preserved") + THEN("capacity reduced") { REQUIRE_NT_SUCCESS(status); - REQUIRE(capNow == sizeNow); + REQUIRE(capNow < newCap); REQUIRE(str == "1-2-3-4-5-6-7-8-9"); } } From 665b33deb7ff91938c0e9e629390ee37c6bfb6c0 Mon Sep 17 00:00:00 2001 From: Vlada Kanivets Date: Wed, 10 Sep 2025 17:15:55 +0200 Subject: [PATCH 5/6] refactoring --- include/kf/stl/basic_string | 35 ++- test/BasicStringTest.cpp | 462 ++++++++++++++++++++---------------- 2 files changed, 289 insertions(+), 208 deletions(-) diff --git a/include/kf/stl/basic_string b/include/kf/stl/basic_string index 12e92a9..84e6ae7 100644 --- a/include/kf/stl/basic_string +++ b/include/kf/stl/basic_string @@ -73,6 +73,16 @@ namespace kf return STATUS_SUCCESS; } + constexpr [[nodiscard]] NTSTATUS assign(basic_string&& other) noexcept + { + if (auto status = reallocateGrowth(other.size()); !NT_SUCCESS(status)) + { + return status; + } + m_string = std::move(other.m_string); + return STATUS_SUCCESS; + } + constexpr [[nodiscard]] NTSTATUS assign(const basic_string& other, size_type pos, size_type count = npos) noexcept { if (pos > other.size()) @@ -327,7 +337,7 @@ namespace kf constexpr int compare(size_type pos1, size_type count1, const T* s, size_type count2) const noexcept { - auto tempCount2 = strlen(s) < count2 ? strlen(s) : count2; + auto tempCount2 = traits_type::length(s) < count2 ? traits_type::length(s) : count2; return m_string.compare(pos1, count1, s, tempCount2); } @@ -374,14 +384,7 @@ namespace kf // insert (iterator-based) constexpr std::optional insert(const_iterator pos, const T& value) noexcept { - const auto idx = static_cast(pos - m_string.begin()); - - if (auto status = reallocateGrowth(m_string.size() + 1); !NT_SUCCESS(status)) - { - return std::nullopt; - } - - return m_string.insert(m_string.begin() + idx, value); + return insert(pos, 1, value); } constexpr std::optional insert(const_iterator pos, T&& value) noexcept @@ -702,7 +705,19 @@ namespace kf return rcount; } - constexpr basic_string substr(size_type pos = 0, size_type count = npos) const noexcept + constexpr std::string_view substr(size_type pos = 0, size_type count = npos) const noexcept + { + if (pos > size()) + { + return {}; + } + + const auto available = size() - pos; + const auto rcount = (count == npos || count > available) ? available : count; + return std::string_view(m_string.data() + pos, rcount); + } + + constexpr basic_string substr_copy(size_type pos = 0, size_type count = npos) const noexcept { basic_string result; if (pos > size()) diff --git a/test/BasicStringTest.cpp b/test/BasicStringTest.cpp index 25e6a6b..43d29e0 100644 --- a/test/BasicStringTest.cpp +++ b/test/BasicStringTest.cpp @@ -88,6 +88,22 @@ SCENARIO("basic_string: construction and assignment") REQUIRE(wstr == L"WideText"); } } + + WHEN("assign(T&& s) is called on a wide string") + { + kf::basic_string wstr1; + kf::basic_string wstr2; + REQUIRE_NT_SUCCESS(wstr1.assign(L"WideText")); + + auto status = wstr2.assign(std::move(wstr1)); + + THEN("the wide string should contain the new content") + { + REQUIRE_NT_SUCCESS(status); + REQUIRE(wstr2 == L"WideText"); + REQUIRE(wstr1.empty()); + } + } } GIVEN("Two basic_string with content") @@ -146,18 +162,6 @@ SCENARIO("basic_string: construction and assignment") } } - WHEN("assign(const T* s) is called with empty C-string") - { - auto status = str.assign(""); - - THEN("the string remains empty") - { - REQUIRE_NT_SUCCESS(status); - REQUIRE(str.empty()); - REQUIRE(str.size() == 0); - } - } - WHEN("assign(size_type count, const T& value) is called with zero count") { auto status = str.assign(0, 'Z'); @@ -227,6 +231,16 @@ SCENARIO("basic_string: Element access") } } + WHEN("at(size_type pos) is called on a non-const basic_string with out-of-range index") + { + auto refOpt = str.at(100); + + THEN("it should be empty") + { + REQUIRE(!refOpt.has_value()); + } + } + WHEN("at(size_type pos) is called on a const basic_string with out-of-range index") { const auto& cstr = str; @@ -240,7 +254,16 @@ SCENARIO("basic_string: Element access") WHEN("operator[](size_type) is called") { - REQUIRE(str[0] == 'a'); + THEN("the character is correct") + { + REQUIRE(str[0] == 'a'); + REQUIRE(str[1] == 'b'); + REQUIRE(str[2] == 'c'); + } + } + + WHEN("operator[](size_type) is called for modifying") + { str[2] = 'C'; THEN("the characters should reflect updates via operator[]") @@ -267,9 +290,13 @@ SCENARIO("basic_string: Element access") WHEN("data() is called") { + auto ptr = str.data(); + constexpr std::string_view kExpected = "abc"; + THEN("data should not be null") { - REQUIRE(str.data() != nullptr); + REQUIRE(ptr != nullptr); + REQUIRE(std::string_view(ptr, str.size()) == kExpected); } } @@ -284,13 +311,28 @@ SCENARIO("basic_string: Element access") WHEN("at(size_type pos) is called on a wide string") { kf::basic_string wstr; - REQUIRE_NT_SUCCESS(wstr.assign(L"xyz")); - auto refOpt = wstr.at(0); + REQUIRE_NT_SUCCESS(wstr.assign(L"Some-long-string-for-testing-positions")); + auto refOpt1 = wstr.at(1); + auto refOpt2 = wstr.at(11); + auto refOpt3 = wstr.at(26); + auto refOpt4 = wstr.at(37); THEN("wide element should be accessible") { - REQUIRE(refOpt.has_value()); - REQUIRE(refOpt->get() == L'x'); + THEN("wide elements should be accessible") + { + REQUIRE(refOpt1.has_value()); + REQUIRE(refOpt1->get() == L'o'); + + REQUIRE(refOpt2.has_value()); + REQUIRE(refOpt2->get() == L't'); + + REQUIRE(refOpt3.has_value()); + REQUIRE(refOpt3->get() == L'n'); + + REQUIRE(refOpt4.has_value()); + REQUIRE(refOpt4->get() == L's'); + } } } } @@ -338,7 +380,7 @@ SCENARIO("basic_string: Iterators") WHEN("begin() and end() are used to iterate") { - std::array acc{}; + std::array acc{}; size_t i = 0; for (auto it = str.begin(); it != str.end(); ++it) { @@ -356,7 +398,7 @@ SCENARIO("basic_string: Iterators") WHEN("rbegin() and rend() are used to iterate") { - std::array acc{}; + std::array acc{}; size_t i = 0; for (auto it = str.rbegin(); it != str.rend(); ++it) { @@ -372,14 +414,22 @@ SCENARIO("basic_string: Iterators") } } - WHEN("cbegin() is called on a const basic_string") + WHEN("cbegin() and cend() are used to iterate over const basic_string") { const auto& cstr = str; - auto it = cstr.cbegin(); + std::array acc{}; + size_t i = 0; + for (auto it = cstr.cbegin(); it != cstr.cend(); ++it) + { + acc[i++] = *it; + } - THEN("dereferencing works") + THEN("accumulated sequence matches reverse") { - REQUIRE(*it == 'a'); + REQUIRE(acc[0] == 'a'); + REQUIRE(acc[1] == 'b'); + REQUIRE(acc[2] == 'c'); + REQUIRE(acc[3] == 'd'); } } } @@ -436,19 +486,18 @@ SCENARIO("basic_string: Size and capacity") WHEN("length() is called") { - REQUIRE_NT_SUCCESS(str.assign("xx")); - const auto len = str.length(); - const auto sz = str.size(); + const auto length = str.length(); + const auto size = str.size(); - THEN("length equals size") + THEN("length and size are equal to 0") { - REQUIRE(len == sz); + REQUIRE(length == 0); + REQUIRE(size == 0); } } WHEN("max_size() is called") { - REQUIRE_NT_SUCCESS(str.assign("x")); const auto max = str.max_size(); const auto sz = str.size(); @@ -457,30 +506,6 @@ SCENARIO("basic_string: Size and capacity") REQUIRE(max >= sz); } } - - WHEN("resize(size_type count, const T& value) is called") - { - REQUIRE_NT_SUCCESS(str.assign("hi")); - auto status = str.resize(5, 'x'); - - THEN("the string should be grown with fill value") - { - REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "hixxx"); - } - } - - WHEN("resize(size_type count) is called") - { - REQUIRE_NT_SUCCESS(str.assign("hi")); - auto status = str.resize(2); - - THEN("the string should be shrunk") - { - REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "hi"); - } - } } GIVEN("a basic_string with content") @@ -490,9 +515,21 @@ SCENARIO("basic_string: Size and capacity") WHEN("size() is called") { + constexpr auto kExpectedSize = 6; + THEN("size equals number of characters") { - REQUIRE(str.size() == 6); + REQUIRE(str.size() == kExpectedSize); + } + } + + WHEN("length() is called") + { + constexpr auto kExpectedLength = 6; + + THEN("length equals number of characters") + { + REQUIRE(str.length() == kExpectedLength); } } @@ -544,11 +581,14 @@ SCENARIO("basic_string: Size and capacity") } } - WHEN("length() is called") + WHEN("resize(size_type count, const T& value) is called") { - THEN("length equals size") + auto status = str.resize(9, 'x'); + + THEN("the string should be grown with fill value") { - REQUIRE(str.length() == str.size()); + REQUIRE_NT_SUCCESS(status); + REQUIRE(str == "abcdefxxx"); } } @@ -564,7 +604,7 @@ SCENARIO("basic_string: Size and capacity") SCENARIO("basic_string: Comparison") { - GIVEN("two basic_strings with content") + GIVEN("two basic_strings with content") { kf::basic_string a, b; REQUIRE_NT_SUCCESS(a.assign("alpha")); @@ -740,12 +780,15 @@ SCENARIO("basic_string: Comparison") REQUIRE(cmp == 0); // -1 } } + } + + GIVEN("basic_strings with content") + { + kf::basic_string w; + REQUIRE_NT_SUCCESS(w.assign(L"zeta")); WHEN("operator==(const T*) is called on a wide string") { - kf::basic_string w; - REQUIRE_NT_SUCCESS(w.assign(L"zeta")); - THEN("wide equality works with wide literals") { REQUIRE(w == L"zeta"); @@ -754,9 +797,6 @@ SCENARIO("basic_string: Comparison") WHEN("operator!=(const T*) is called on a wide string") { - kf::basic_string w; - REQUIRE_NT_SUCCESS(w.assign(L"zeta")); - THEN("wide inequality works with wide literals") { REQUIRE(w != L"eta"); @@ -765,9 +805,6 @@ SCENARIO("basic_string: Comparison") WHEN("operator<(const T*) is called on a wide string") { - kf::basic_string w; - REQUIRE_NT_SUCCESS(w.assign(L"zeta")); - THEN("wide less-than works with wide literals") { REQUIRE(w < L"zzzz"); @@ -775,7 +812,7 @@ SCENARIO("basic_string: Comparison") } } - GIVEN("one of basic_strings with content") + GIVEN("one of basic_strings with content and one empty") { kf::basic_string a, b; REQUIRE_NT_SUCCESS(a.assign("alpha")); @@ -828,7 +865,6 @@ SCENARIO("basic_string: Comparison") REQUIRE(cmp == 0); } } - } } @@ -840,7 +876,44 @@ SCENARIO("basic_string: Modifiers") WHEN("clear() is called") { - REQUIRE_NT_SUCCESS(str.assign("core")); + str.clear(); + THEN("string remains empty") + { + REQUIRE(str.empty()); + REQUIRE(str.size() == 0); + } + } + + WHEN("erase(const_iterator first, const_iterator last) is called") + { + auto it = str.erase(str.begin(), str.end()); + + THEN("iterator points to begin/end") + { + REQUIRE(it == str.begin()); + REQUIRE(str.empty()); + } + } + + WHEN("erase(const_iterator pos) is called") + { + auto it = str.erase(str.begin()); + + THEN("no characters erased, iterator points to begin") + { + REQUIRE(it == str.begin()); + REQUIRE(str.empty()); + } + } + } + + GIVEN("basic_string with content") + { + kf::basic_string str; + REQUIRE_NT_SUCCESS(str.assign("example")); + + WHEN("clear() is called") + { str.clear(); THEN("string is empty") @@ -852,327 +925,304 @@ SCENARIO("basic_string: Modifiers") WHEN("push_back(const T& value) is called") { - REQUIRE_NT_SUCCESS(str.assign("abc")); const auto ch = 'd'; auto status = str.push_back(ch); THEN("element is appended") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "abcd"); + REQUIRE(str == "exampled"); } } WHEN("push_back(T&& value) is called") { - REQUIRE_NT_SUCCESS(str.assign("abc")); char ch = 'd'; auto status = str.push_back(std::move(ch)); THEN("element is appended using rvalue reference") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "abcd"); + REQUIRE(str == "exampled"); } } WHEN("pop_back() is called") { - REQUIRE_NT_SUCCESS(str.assign("abcd")); str.pop_back(); THEN("last char removed") { - REQUIRE(str == "abc"); + REQUIRE(str == "exampl"); } } WHEN("insert(const_iterator pos, const T& value) is called") { - REQUIRE_NT_SUCCESS(str.assign("ABCD")); - auto itOpt = str.insert(str.begin() + 2, 'x'); // ABxCD + auto itOpt = str.insert(str.begin() + 2, 'X'); THEN("iterator is returned and string modified") { REQUIRE(itOpt.has_value()); - REQUIRE(str == "ABxCD"); + REQUIRE(str == "exXample"); } } + WHEN("insert(const_iterator pos, T&& value) is called") { - REQUIRE_NT_SUCCESS(str.assign("ABCD")); - char ch = 'x'; - auto itOpt = str.insert(str.begin() + 2, std::move(ch)); // ABxCD + char ch = 'X'; + auto itOpt = str.insert(str.begin() + 2, std::move(ch)); THEN("iterator is returned and rvalue inserted") { REQUIRE(itOpt.has_value()); - REQUIRE(str == "ABxCD"); + REQUIRE(str == "exXample"); } } WHEN("insert(const_iterator pos, size_type count, const T& value) is called") { - REQUIRE_NT_SUCCESS(str.assign("ABCD")); - auto itOpt = str.insert(str.begin() + 2, 3, 'y'); // AByyyCD + auto itOpt = str.insert(str.begin() + 2, 3, 'Y'); THEN("multiple copies inserted at iterator position") { REQUIRE(itOpt.has_value()); - REQUIRE(str == "AByyyCD"); + REQUIRE(str == "exYYYample"); } } WHEN("insert(size_type index, const T* s, size_type count) is called") { - REQUIRE_NT_SUCCESS(str.assign("abc")); - auto status = str.insert(1, "XYZ", 2); // aXYbc + auto status = str.insert(1, "XYZ", 2); THEN("substring of C-string inserted") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "aXYbc"); + REQUIRE(str == "eXYxample"); } } WHEN("insert(size_type index, size_type count, T ch) is called") { - REQUIRE_NT_SUCCESS(str.assign("abc")); - auto status = str.insert(1, 2, 'Y'); // aYYbc + auto status = str.insert(1, 2, 'Y'); THEN("characters inserted by count") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "aYYbc"); + REQUIRE(str == "eYYxample"); } } WHEN("insert(size_type index, const T* s) is called") { - REQUIRE_NT_SUCCESS(str.assign("aYYbc")); - auto status = str.insert(0, "--"); // --aYYbc + auto status = str.insert(0, "--"); THEN("C-string inserted") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "--aYYbc"); + REQUIRE(str == "--example"); } } WHEN("insert(size_type index, const basic_string&) is called") { - REQUIRE_NT_SUCCESS(str.assign("--aYYbc")); kf::basic_string other; REQUIRE_NT_SUCCESS(other.assign("[]")); - auto status = str.insert(2, other); // --[]aYYbc + auto status = str.insert(2, other); THEN("basic_string inserted") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "--[]aYYbc"); + REQUIRE(str == "ex[]ample"); } } WHEN("insert(size_type index, const basic_string&, size_type index_str, size_type count) is called") { - REQUIRE_NT_SUCCESS(str.assign("--[]aYYbc")); kf::basic_string other; REQUIRE_NT_SUCCESS(other.assign("[]")); - auto status = str.insert(2, other, 1, 1); // --] []aYYbc -> --][]aYYbc + auto status = str.insert(2, other, 1, 1); THEN("substring from basic_string inserted") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "--][]aYYbc"); + REQUIRE(str == "ex]ample"); } } WHEN("insert(size_type index, const basic_string&, size_type index_str, size_type count) with index_str > str.size() is called") { - REQUIRE_NT_SUCCESS(str.assign("base")); - kf::basic_string src; - REQUIRE_NT_SUCCESS(src.assign("xx")); - auto status = str.insert(2, src, 10, 5); + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("xx")); + auto status = str.insert(2, other, 10, 5); THEN("operation succeeds and string is unchanged") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "base"); + REQUIRE(str == "example"); } } WHEN("append(size_t count, T ch) is called") { - REQUIRE_NT_SUCCESS(str.assign("foo")); auto status = str.append(3, '!'); THEN("characters appended by count") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "foo!!!"); + REQUIRE(str == "example!!!"); } } WHEN("append(const basic_string&) is called") { - REQUIRE_NT_SUCCESS(str.assign("foo!!!")); - kf::basic_string more; - REQUIRE_NT_SUCCESS(more.assign("bar")); - auto status = str.append(more); + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("bar")); + auto status = str.append(other); THEN("basic_string appended") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "foo!!!bar"); + REQUIRE(str == "examplebar"); } } WHEN("append(const T* s) is called") { - REQUIRE_NT_SUCCESS(str.assign("foo")); auto status = str.append("bar"); THEN("entire C-string appended") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "foobar"); + REQUIRE(str == "examplebar"); } } WHEN("append(const T* s, size_t count) is called") { - REQUIRE_NT_SUCCESS(str.assign("foo!!!bar")); auto status = str.append("XYZ", 2); THEN("partial C-string appended") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "foo!!!barXY"); + REQUIRE(str == "exampleXY"); } } WHEN("append(const basic_string&, pos, count) is called") { - REQUIRE_NT_SUCCESS(str.assign("base")); - kf::basic_string more; - REQUIRE_NT_SUCCESS(more.assign("appendix")); - auto status = str.append(more, 0, 3); // "baseapp" + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("other")); + auto status = str.append(other, 0, 3); THEN("substring of basic_string appended") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "baseapp"); + REQUIRE(str == "exampleoth"); } } WHEN("append(const basic_string& str, size_type pos, size_type count) with pos > str.size() is called") { - REQUIRE_NT_SUCCESS(str.assign("A")); - kf::basic_string src; - REQUIRE_NT_SUCCESS(src.assign("XYZ")); - auto status = str.append(src, 5, 2); + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("XYZ")); + auto status = str.append(other, 5, 2); THEN("operation succeeds and string is unchanged") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "A"); + REQUIRE(str == "example"); } } WHEN("operator+=(const T*) is called") { - REQUIRE_NT_SUCCESS(str.assign("foo!!!barXY")); auto status = (str += "Z"); THEN("C-string concatenated") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "foo!!!barXYZ"); + REQUIRE(str == "exampleZ"); } } WHEN("operator+=(T) is called") { - REQUIRE_NT_SUCCESS(str.assign("foo!!!barXYZ")); auto status = (str += '!'); THEN("single character concatenated") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "foo!!!barXYZ!"); + REQUIRE(str == "example!"); } } WHEN("operator+=(const basic_string&) is called") { - REQUIRE_NT_SUCCESS(str.assign("foo")); - kf::basic_string more; - REQUIRE_NT_SUCCESS(more.assign("bar")); - auto status = (str += more); + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("bar")); + auto status = (str += other); THEN("basic_string concatenated") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "foobar"); + REQUIRE(str == "examplebar"); } } WHEN("replace(size_t pos, size_t count, const basic_string&) is called") { - REQUIRE_NT_SUCCESS(str.assign("0123456789")); - kf::basic_string tmp; - REQUIRE_NT_SUCCESS(tmp.assign("AA")); - auto status = str.replace(2, 3, tmp); // 01 + AA + 56789 + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("AA")); + auto status = str.replace(2, 3, other); THEN("substring replaced with basic_string") { REQUIRE_NT_SUCCESS(status); REQUIRE(str.find("AA") == 2); + REQUIRE(str == "exAAle"); } } WHEN("replace(size_type pos, size_type count, const T* s) is called") { - REQUIRE_NT_SUCCESS(str.assign("abcdef")); - auto status = str.replace(2, 3, "XYZ"); // abXYZf + auto status = str.replace(2, 3, "XYZ"); THEN("substring replaced with C-string") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "abXYZf"); + REQUIRE(str == "exXYZle"); } } WHEN("replace(size_type pos, size_type count, const T* s, size_type count2) is called") { - REQUIRE_NT_SUCCESS(str.assign("01AA56789")); auto status = str.replace(2, 2, "XX", 2); THEN("substring replaced with C-string and count") { REQUIRE_NT_SUCCESS(status); REQUIRE(str.find("XX") == 2); + REQUIRE(str == "exXXple"); } } WHEN("replace(size_type pos1, size_type count1, const basic_string& str, pos2, count2) is called") { - REQUIRE_NT_SUCCESS(str.assign("abcdef")); kf::basic_string other; REQUIRE_NT_SUCCESS(other.assign("12345")); - auto status = str.replace(1, 3, other, 1, 2); // a + "23" + ef + auto status = str.replace(1, 3, other, 1, 2); THEN("substring replaced with substring of another string") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "a23ef"); + REQUIRE(str == "e23ple"); } } WHEN("replace(size_type pos1, size_type count1, const basic_string& str, size_t pos2, size_t count2) with pos2 > other.size()) is called") { - REQUIRE_NT_SUCCESS(str.assign("abcdef")); kf::basic_string other; REQUIRE_NT_SUCCESS(other.assign("12")); auto status = str.replace(2, 3, other, 5, 7); @@ -1180,102 +1230,94 @@ SCENARIO("basic_string: Modifiers") THEN("range is erased") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "abf"); + REQUIRE(str == "exle"); } } WHEN("replace(const_iterator first, const_iterator last, const basic_string& str) is called") { - REQUIRE_NT_SUCCESS(str.assign("abcdef")); - kf::basic_string repl; - REQUIRE_NT_SUCCESS(repl.assign("ZZ")); - auto status = str.replace(str.begin() + 2, str.begin() + 4, repl); // abZZef + kf::basic_string other; + REQUIRE_NT_SUCCESS(other.assign("ZZ")); + auto status = str.replace(str.begin() + 2, str.begin() + 4, other); THEN("range replaced with another string") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "abZZef"); + REQUIRE(str == "exZZple") } } WHEN("replace(const_iterator first, const_iterator last, size_type count2, T ch) is called") { - REQUIRE_NT_SUCCESS(str.assign("--XX56789")); auto first = str.begin() + 2; - auto last = first + 2; + auto last = first + 2; auto status = str.replace(first, last, 3, '*'); THEN("replacement should be applied") { REQUIRE_NT_SUCCESS(status); REQUIRE(str.find("***", 0) != decltype(str)::npos); + REQUIRE(str == "ex***ple"); } } WHEN("erase(const_iterator first, const_iterator last) is called") { - REQUIRE_NT_SUCCESS(str.assign("ABxCD")); - auto first = str.begin() + 1; // B - auto last = str.begin() + 3; // x - auto after = str.erase(first, last); // remove Bx -> ACD + auto first = str.begin() + 1; + auto last = str.begin() + 3; + auto after = str.erase(first, last); THEN("erase returns iterator to next element") { - REQUIRE(*after == 'C'); - REQUIRE(str == "ACD"); + REQUIRE(*after == 'm'); + REQUIRE(str == "emple"); } } WHEN("erase(const_iterator first, const_iterator last) is called with first == last") { - REQUIRE_NT_SUCCESS(str.assign("abcde")); auto it = str.erase(str.begin() + 2, str.begin() + 2); THEN("no characters erased, iterator points to first") { REQUIRE(it == str.begin() + 2); - REQUIRE(str == "abcde"); + REQUIRE(str == "example"); } } WHEN("erase(const_iterator first, const_iterator last) is called with first == end()") { - REQUIRE_NT_SUCCESS(str.assign("abcde")); auto it = str.erase(str.end(), str.end()); THEN("no characters erased, iterator points to end") { REQUIRE(it == str.end()); - REQUIRE(str == "abcde"); + REQUIRE(str == "example"); } } - WHEN("erase(const_iterator first, const_iterator last) is called on empty string") + WHEN("erase(size_type index) with count = npos is called") { - auto it = str.erase(str.begin(), str.end()); + REQUIRE_NT_SUCCESS(str.erase(1)); - THEN("iterator points to begin/end") + THEN("substring is erased") { - REQUIRE(it == str.begin()); - REQUIRE(str.empty()); + REQUIRE(str == "e"); } } WHEN("erase(size_type index, size_type count) is called") { - REQUIRE_NT_SUCCESS(str.assign("erase")); - REQUIRE_NT_SUCCESS(str.erase(1, 2)); // e + se -> ese + REQUIRE_NT_SUCCESS(str.erase(1, 2)); THEN("substring erased by index/count") { - REQUIRE(str == "ese"); + REQUIRE(str == "emple"); } } WHEN("erase(size_type index, size_type count) is called with index >= size()") { - REQUIRE_NT_SUCCESS(str.assign("hello")); - THEN("erase should fail without crush") { REQUIRE_NT_SUCCESS(!str.erase(10, 2)); // index beyond size @@ -1284,59 +1326,43 @@ SCENARIO("basic_string: Modifiers") WHEN("erase(size_type index, size_type count) is called with count == 0") { - REQUIRE_NT_SUCCESS(str.assign("hello")); REQUIRE_NT_SUCCESS(str.erase(2, 0)); THEN("string remains unchanged") { - REQUIRE(str == "hello"); + REQUIRE(str == "example"); } } WHEN("erase(size_type index, size_type count) is called with count > size() - index") { - REQUIRE_NT_SUCCESS(str.assign("hello")); REQUIRE_NT_SUCCESS(str.erase(3, 10)); THEN("erases until the end") { - REQUIRE(str == "hel"); + REQUIRE(str == "exa"); } } WHEN("erase(const_iterator pos) is called") { - REQUIRE_NT_SUCCESS(str.assign("ese")); auto it = str.erase(str.begin()); THEN("first char erased") { - REQUIRE(*it == 's'); - REQUIRE(str == "se"); + REQUIRE(*it == 'x'); + REQUIRE(str == "xample"); } } WHEN("erase(const_iterator pos) is called with pos == end()") { - REQUIRE_NT_SUCCESS(str.assign("abc")); auto it = str.erase(str.end()); THEN("no characters erased, iterator points to end") { REQUIRE(it == str.end()); - REQUIRE(str == "abc"); - } - } - - WHEN("erase(const_iterator pos) is called on empty string") - { - str.clear(); - auto it = str.erase(str.begin()); - - THEN("no characters erased, iterator points to begin") - { - REQUIRE(it == str.begin()); - REQUIRE(str.empty()); + REQUIRE(str == "example"); } } } @@ -1374,6 +1400,36 @@ SCENARIO("basic_string: String operations") } } + WHEN("substr_copy(size_type pos, size_type count) is called") + { + auto sub = str.substr_copy(0, 4); + + THEN("substrings should be as expected") + { + REQUIRE(sub == "copy"); + } + } + + WHEN("substr_copy(size_type pos, size_type count) with pos > size is called") + { + auto sub = str.substr_copy(100, 2); // out of range -> empty + + THEN("substrings should be empty") + { + REQUIRE(sub.empty()); + } + } + + WHEN("substr_copy(size_type pos, size_type count) with count > available is called") + { + auto sub = str.substr_copy(3, 10); + + THEN("substring is truncated to available characters") + { + REQUIRE(sub == "y-me"); + } + } + WHEN("substr(size_type pos, size_type count) is called") { auto sub = str.substr(0, 4); @@ -1421,6 +1477,16 @@ SCENARIO("basic_string: String operations") } } + WHEN("substr_copy(size_type pos, size_type count) is called") + { + auto sub = str.substr_copy(0, 10); + + THEN("substr of empty is empty") + { + REQUIRE(sub.empty()); + } + } + WHEN("substr(size_type pos, size_type count) is called") { auto sub = str.substr(0, 10); From f6869ba5e8e6ad02f1a7dd1da09a4e584f86f56a Mon Sep 17 00:00:00 2001 From: Vlada Kanivets Date: Mon, 15 Sep 2025 15:56:14 +0200 Subject: [PATCH 6/6] refactoring --- include/kf/stl/basic_string | 4 ---- test/BasicStringTest.cpp | 27 ++++++++++++--------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/include/kf/stl/basic_string b/include/kf/stl/basic_string index 84e6ae7..c80332d 100644 --- a/include/kf/stl/basic_string +++ b/include/kf/stl/basic_string @@ -75,10 +75,6 @@ namespace kf constexpr [[nodiscard]] NTSTATUS assign(basic_string&& other) noexcept { - if (auto status = reallocateGrowth(other.size()); !NT_SUCCESS(status)) - { - return status; - } m_string = std::move(other.m_string); return STATUS_SUCCESS; } diff --git a/test/BasicStringTest.cpp b/test/BasicStringTest.cpp index 43d29e0..536b769 100644 --- a/test/BasicStringTest.cpp +++ b/test/BasicStringTest.cpp @@ -296,7 +296,7 @@ SCENARIO("basic_string: Element access") THEN("data should not be null") { REQUIRE(ptr != nullptr); - REQUIRE(std::string_view(ptr, str.size()) == kExpected); + REQUIRE(std::string_view(ptr) == kExpected); } } @@ -319,20 +319,17 @@ SCENARIO("basic_string: Element access") THEN("wide element should be accessible") { - THEN("wide elements should be accessible") - { - REQUIRE(refOpt1.has_value()); - REQUIRE(refOpt1->get() == L'o'); + REQUIRE(refOpt1.has_value()); + REQUIRE(refOpt1->get() == L'o'); - REQUIRE(refOpt2.has_value()); - REQUIRE(refOpt2->get() == L't'); + REQUIRE(refOpt2.has_value()); + REQUIRE(refOpt2->get() == L't'); - REQUIRE(refOpt3.has_value()); - REQUIRE(refOpt3->get() == L'n'); + REQUIRE(refOpt3.has_value()); + REQUIRE(refOpt3->get() == L'n'); - REQUIRE(refOpt4.has_value()); - REQUIRE(refOpt4->get() == L's'); - } + REQUIRE(refOpt4.has_value()); + REQUIRE(refOpt4->get() == L's'); } } } @@ -424,7 +421,7 @@ SCENARIO("basic_string: Iterators") acc[i++] = *it; } - THEN("accumulated sequence matches reverse") + THEN("accumulated sequence matches") { REQUIRE(acc[0] == 'a'); REQUIRE(acc[1] == 'b'); @@ -777,7 +774,7 @@ SCENARIO("basic_string: Comparison") THEN("they are equal") { - REQUIRE(cmp == 0); // -1 + REQUIRE(cmp == 0); } } } @@ -1243,7 +1240,7 @@ SCENARIO("basic_string: Modifiers") THEN("range replaced with another string") { REQUIRE_NT_SUCCESS(status); - REQUIRE(str == "exZZple") + REQUIRE(str == "exZZple"); } }