diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0bb77f..3e5ef47 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,16 +154,16 @@ jobs: files: build/coverage.info memcheck: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v3 - name: Install GCC run: | - sudo apt update && sudo apt install -y gcc-11 g++-11 - echo "CC=gcc-11" >> $GITHUB_ENV - echo "CXX=g++-11" >> $GITHUB_ENV + sudo apt update && sudo apt install -y gcc-14 g++-14 + echo "CC=gcc-14" >> $GITHUB_ENV + echo "CXX=g++-14" >> $GITHUB_ENV - name: Install valgrind run: sudo apt update && sudo apt install -y valgrind @@ -189,16 +189,16 @@ jobs: fi sanitize: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v3 - name: Install GCC run: | - sudo apt update && sudo apt install -y gcc-11 g++-11 - echo "CC=gcc-11" >> $GITHUB_ENV - echo "CXX=g++-11" >> $GITHUB_ENV + sudo apt update && sudo apt install -y gcc-14 g++-14 + echo "CC=gcc-14" >> $GITHUB_ENV + echo "CXX=g++-14" >> $GITHUB_ENV - name: Configure run: cmake --preset=ci-sanitize diff --git a/include/emio/buffer.hpp b/include/emio/buffer.hpp index 2782d32..9075e60 100644 --- a/include/emio/buffer.hpp +++ b/include/emio/buffer.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #if __STDC_HOSTED__ # include @@ -31,10 +32,10 @@ inline constexpr size_t default_cache_size{128}; */ class buffer { public: - buffer(const buffer& other) = delete; - buffer(buffer&& other) = delete; - buffer& operator=(const buffer& other) = delete; - buffer& operator=(buffer&& other) = delete; + constexpr buffer(const buffer& other) = delete; + constexpr buffer(buffer&& other) = delete; + constexpr buffer& operator=(const buffer& other) = delete; + constexpr buffer& operator=(buffer&& other) = delete; virtual constexpr ~buffer() noexcept = default; /** @@ -145,10 +146,40 @@ class memory_buffer final : public buffer { static_cast(request_write_area(0, std::max(vec_.capacity(), capacity))); } - constexpr memory_buffer(const memory_buffer&) = default; - constexpr memory_buffer(memory_buffer&&) noexcept = default; - constexpr memory_buffer& operator=(const memory_buffer&) = default; - constexpr memory_buffer& operator=(memory_buffer&&) noexcept = default; + constexpr memory_buffer(const memory_buffer& other) + : buffer{}, used_{other.used_ + other.get_used_count()}, vec_{other.vec_} { + this->set_write_area({vec_.data() + used_, vec_.data() + vec_.capacity()}); + } + + constexpr memory_buffer(memory_buffer&& other) noexcept + : buffer{}, used_{other.used_ + other.get_used_count()}, vec_{std::move(other).vec_} { + this->set_write_area({vec_.data() + used_, vec_.data() + vec_.capacity()}); + other.reset(); + } + + constexpr memory_buffer& operator=(const memory_buffer& other) { + if (&other == this) { + return *this; + } + + used_ = other.used_ + other.get_used_count(); + vec_ = other.vec_; + this->set_write_area({vec_.data() + used_, vec_.data() + vec_.capacity()}); + return *this; + } + + constexpr memory_buffer& operator=(memory_buffer&& other) noexcept { + if (&other == this) { + return *this; + } + + used_ = other.used_ + other.get_used_count(); + vec_ = std::move(other).vec_; + this->set_write_area({vec_.data() + used_, vec_.data() + vec_.capacity()}); + other.reset(); + return *this; + } + constexpr ~memory_buffer() override = default; /** @@ -219,10 +250,30 @@ class span_buffer : public buffer { this->set_write_area(span_); } - constexpr span_buffer(const span_buffer&) = delete; - constexpr span_buffer(span_buffer&&) noexcept = delete; - constexpr span_buffer& operator=(const span_buffer&) = delete; - constexpr span_buffer& operator=(span_buffer&&) noexcept = delete; + constexpr span_buffer(const span_buffer& other) : buffer{fixed_size::yes}, span_{other.span_} { + this->set_write_area(span_); + get_write_area_of(other.get_used_count()).value(); + } + + // NOLINTNEXTLINE(performance-move-constructor-init): optimized move not possible + constexpr span_buffer(span_buffer&& other) noexcept : span_buffer{std::as_const(other)} {} + + constexpr span_buffer& operator=(const span_buffer& other) { + if (&other == this) { + return *this; + } + + span_ = other.span_; + this->set_write_area(span_); + get_write_area_of(other.get_used_count()).value(); + return *this; + } + + constexpr span_buffer& operator=(span_buffer&& other) noexcept { + *this = std::as_const(other); + return *this; + } + constexpr ~span_buffer() override; /** @@ -277,10 +328,30 @@ class static_buffer final : private std::array, public span_b */ constexpr static_buffer() noexcept : span_buffer{std::span{*this}} {} - constexpr static_buffer(const static_buffer&) = delete; - constexpr static_buffer(static_buffer&&) noexcept = delete; - constexpr static_buffer& operator=(const static_buffer&) = delete; - constexpr static_buffer& operator=(static_buffer&&) noexcept = delete; + constexpr static_buffer(const static_buffer& other) : static_buffer() { + const std::span area = get_write_area_of(other.get_used_count()).value(); + detail::copy_n(other.begin(), area.size(), area.data()); + } + + // NOLINTNEXTLINE(performance-move-constructor-init): optimized move not possible + constexpr static_buffer(static_buffer&& other) noexcept : static_buffer(std::as_const(other)) {} + + constexpr static_buffer& operator=(const static_buffer& other) { + if (&other == this) { + return *this; + } + + set_write_area(std::span{*this}); + const std::span area = get_write_area_of(other.get_used_count()).value(); + detail::copy_n(other.begin(), area.size(), area.data()); + return *this; + } + + constexpr static_buffer& operator=(static_buffer&& other) noexcept { + *this = std::as_const(other); + return *this; + } + constexpr ~static_buffer() override = default; // Note: We inherit from std::array to put the storage lifetime before span_buffer. diff --git a/include/emio/detail/ct_vector.hpp b/include/emio/detail/ct_vector.hpp index e436500..1f4b3eb 100644 --- a/include/emio/detail/ct_vector.hpp +++ b/include/emio/detail/ct_vector.hpp @@ -29,10 +29,61 @@ class ct_vector { } } - ct_vector(const ct_vector&) = delete; - ct_vector(ct_vector&&) = delete; - ct_vector& operator=(const ct_vector&) = delete; - ct_vector& operator=(ct_vector&&) = delete; + constexpr ct_vector(const ct_vector& other) : ct_vector() { + reserve(other.size_); + copy_n(other.data_, other.size_, data_); + } + + constexpr ct_vector(ct_vector&& other) noexcept : ct_vector() { + // Transfer ownership. + if (other.hold_external()) { + data_ = other.data_; + capacity_ = other.capacity_; + } else { + copy_n(other.data_, other.size_, data_); + } + size_ = other.size_; + + // Reset other. + other.data_ = other.storage_.data(); + other.size_ = 0; + other.capacity_ = StorageSize; + } + + constexpr ct_vector& operator=(const ct_vector& other) { + if (&other == this) { + return *this; + } + reserve(other.size_); + copy_n(other.data_, other.size_, data_); + return *this; + } + + constexpr ct_vector& operator=(ct_vector&& other) noexcept { + if (&other == this) { + return *this; + } + + // Free this. + if (hold_external()) { + delete[] data_; // NOLINT(cppcoreguidelines-owning-memory) + } + + // Transfer ownership. + if (other.hold_external()) { + data_ = other.data_; + capacity_ = other.capacity_; + } else { + copy_n(other.data_, other.size_, data_); + } + size_ = other.size_; + + // Reset other. + other.data_ = other.storage_.data(); + other.size_ = 0; + other.capacity_ = StorageSize; + return *this; + } constexpr ~ct_vector() noexcept { if (hold_external()) { diff --git a/test/unit_test/detail/test_ct_vector.cpp b/test/unit_test/detail/test_ct_vector.cpp index 461cabc..c6af317 100644 --- a/test/unit_test/detail/test_ct_vector.cpp +++ b/test/unit_test/detail/test_ct_vector.cpp @@ -3,9 +3,96 @@ // Other includes. #include +#include +#include using namespace std::string_view_literals; +namespace { + +// Copyable & moveable. +template +void check_equality_of_vector(T& expected, T& other) { + const auto compare = [&]() { + CHECK(expected.size() == other.size()); + CHECK(expected.capacity() >= other.capacity()); + const std::string_view sv{expected.data(), expected.size()}; + const std::string_view sv2{other.data(), other.size()}; + CHECK(sv == sv2); + }; + + compare(); + + expected.reserve(expected.size() + 2); + other.reserve(other.size() + 2); + std::fill(expected.data() + expected.size() - 2, expected.data() + expected.size(), '1'); + std::fill(other.data() + other.size() - 2, other.data() + other.size(), '1'); + + compare(); + + expected.reserve(expected.size() + 123); + other.reserve(other.size() + 123); + std::fill(expected.data() + expected.size() - 123, expected.data() + expected.size(), '2'); + std::fill(other.data() + other.size() - 123, other.data() + other.size(), '2'); + + compare(); +} + +template +void check_gang_of_5(T& buf) { + SECTION("copy-construct") { + T buf2{buf}; + check_equality_of_vector(buf, buf2); + } + SECTION("copy-assign") { + T buf2; + buf2 = buf; + check_equality_of_vector(buf, buf2); + } + SECTION("move-construct") { + T buf_tmp{buf}; + T buf2{std::move(buf_tmp)}; + check_equality_of_vector(buf, buf2); + } + SECTION("move-assign") { + T buf_tmp{buf}; + T buf2; + buf2 = std::move(buf_tmp); + check_equality_of_vector(buf, buf2); + } + SECTION("wild") { + T buf2{buf}; + T buf3{std::move(buf2)}; + T buf4; + buf4 = buf3; + T buf5; + buf5 = std::move(buf4); + T buf6{buf}; + buf6 = buf5; + + SECTION("1") { + check_equality_of_vector(buf, buf5); + } + SECTION("2") { + check_equality_of_vector(buf6, buf5); + } + } + SECTION("self-assignment copy") { + T buf2{buf}; + T& buf_tmp = buf2; // Prevent compiler warn about self assignment. + buf2 = buf_tmp; + check_equality_of_vector(buf, buf2); + } + SECTION("self-assignment move") { + T buf2{buf}; + T& buf_tmp = buf2; // Prevent compiler warn about self assignment. + buf2 = std::move(buf_tmp); + check_equality_of_vector(buf, buf2); + } +} + +} // namespace + TEST_CASE("ct_vector") { // Test strategy: // * Construct, reserve, write into a ct_vector. @@ -17,62 +104,110 @@ TEST_CASE("ct_vector") { constexpr bool success = [] { bool result = true; - ct_vector str; - result &= str.size() == 0; - result &= str.capacity() == 0; - - str.reserve(5); - result &= str.size() == 5; - result &= str.capacity() == 5; - std::fill(str.data(), str.data() + str.size(), '\0'); - *(str.data() + 2) = 'x'; - - str.reserve(4); - result &= str.size() == 4; - result &= str.capacity() == 5; - - str.reserve(5); - result &= str.size() == 5; - result &= str.capacity() == 5; - - str.reserve(10); - result &= str.size() == 10; - result &= str.capacity() == 10; - std::fill(str.data() + 5, str.data() + str.size(), '\0'); - *(str.data() + 8) = 'y'; - - str.reserve(15); - result &= str.size() == 15; - result &= str.capacity() >= 15; - std::fill(str.data() + 10, str.data() + str.size(), '\0'); - *(str.data() + 12) = 'z'; - std::string_view sv{str.data(), str.size()}; - result &= sv == "\0\0x\0\0\0\0\0y\0\0\0z\0\0"sv; - - str.reserve(9); - result &= str.size() == 9; - result &= str.capacity() == 15; - sv = std::string_view{str.data(), str.size()}; - result &= sv == "\0\0x\0\0\0\0\0y"sv; - - str.reserve(15); - result &= str.size() == 15; - result &= str.capacity() == 15; - std::fill(str.data() + 10, str.data() + str.size(), '\0'); - sv = std::string_view{str.data(), str.size()}; - result &= sv == "\0\0x\0\0\0\0\0y\0\0\0\0\0\0"sv; - - str.clear(); - result &= str.size() == 0; - result &= str.capacity() == 15; + ct_vector vec; + result &= vec.size() == 0; + result &= vec.capacity() == 1; + + vec.reserve(1); + result &= vec.size() == 1; + result &= vec.capacity() == 1; + *vec.data() = '1'; + + vec.reserve(5); + result &= vec.size() == 5; + result &= vec.capacity() == 5; + std::fill(vec.data() + 1, vec.data() + vec.size(), '\0'); + *(vec.data() + 2) = 'x'; + + vec.reserve(4); + result &= vec.size() == 4; + result &= vec.capacity() == 5; + + vec.reserve(5); + result &= vec.size() == 5; + result &= vec.capacity() == 5; + + vec.reserve(10); + result &= vec.size() == 10; + result &= vec.capacity() == 10; + std::fill(vec.data() + 5, vec.data() + vec.size(), '\0'); + *(vec.data() + 8) = 'y'; + + vec.reserve(15); + result &= vec.size() == 15; + result &= vec.capacity() >= 15; + std::fill(vec.data() + 10, vec.data() + vec.size(), '\0'); + *(vec.data() + 12) = 'z'; + std::string_view sv{vec.data(), vec.size()}; + result &= sv == "1\0x\0\0\0\0\0y\0\0\0z\0\0"sv; + + vec.reserve(9); + result &= vec.size() == 9; + result &= vec.capacity() == 15; + sv = std::string_view{vec.data(), vec.size()}; + result &= sv == "1\0x\0\0\0\0\0y"sv; + + vec.reserve(15); + result &= vec.size() == 15; + result &= vec.capacity() == 15; + std::fill(vec.data() + 10, vec.data() + vec.size(), 'n'); + sv = std::string_view{vec.data(), vec.size()}; + result &= sv == "1\0x\0\0\0\0\0y\0nnnnn"sv; + + vec.clear(); + result &= vec.size() == 0; + result &= vec.capacity() == 15; + + vec.reserve(1); + result &= vec.size() == 1; + result &= vec.capacity() == 15; + + vec.reserve(15); + result &= vec.size() == 15; + result &= vec.capacity() == 15; + sv = std::string_view{vec.data(), vec.size()}; + result &= sv == "1\0x\0\0\0\0\0y\0nnnnn"sv; + + // Copyable & moveable. + { + ct_vector str2{vec}; + result &= str2.size() == 15; + result &= str2.capacity() == 15; + const std::string_view sv2{str2.data(), vec.size()}; + result &= (sv == sv2); + + ct_vector str3; + str3 = str2; + result &= str3.size() == 15; + result &= str3.capacity() == 15; + const std::string_view sv3{str3.data(), vec.size()}; + result &= (sv == sv3); + + ct_vector str4{std::move(str3)}; + result &= str4.size() == 15; + result &= str4.capacity() == 15; + const std::string_view sv4{str4.data(), vec.size()}; + result &= (sv == sv4); + + ct_vector str5; + str5 = std::move(str4); + result &= str5.size() == 15; + result &= str5.capacity() == 15; + const std::string_view sv5{str5.data(), vec.size()}; + result &= (sv == sv5); + } return result; }(); STATIC_CHECK(success); } + SECTION("runtime") { constexpr size_t InternalStorageSize = 5; + const int checkpoint = GENERATE(range(0, 7)); + INFO("Checkpoint: " << checkpoint); + size_t prev_cap{}; const auto get_capacity = [&](size_t reserve) { if (reserve <= InternalStorageSize) { @@ -89,52 +224,90 @@ TEST_CASE("ct_vector") { return reserve; }; - ct_vector str; - CHECK(str.size() == 0); - CHECK(str.capacity() == get_capacity(0)); - - str.reserve(5); - CHECK(str.size() == 5); - CHECK(str.capacity() == get_capacity(5)); - std::fill(str.data(), str.data() + str.size(), '\0'); - *(str.data() + 2) = 'x'; - - str.reserve(4); - CHECK(str.size() == 4); - CHECK(str.capacity() == get_capacity(4)); - - str.reserve(5); - CHECK(str.size() == 5); - CHECK(str.capacity() == get_capacity(5)); - - str.reserve(10); - CHECK(str.size() == 10); - CHECK(str.capacity() == get_capacity(10)); - std::fill(str.data() + 5, str.data() + str.size(), '\0'); - *(str.data() + 8) = 'y'; - - str.reserve(15); - CHECK(str.size() == 15); - CHECK(str.capacity() == get_capacity(15)); - std::fill(str.data() + 10, str.data() + str.size(), '\0'); - *(str.data() + 12) = 'z'; - std::string_view sv{str.data(), str.size()}; - CHECK(sv == "\0\0x\0\0\0\0\0y\0\0\0z\0\0"sv); - - str.reserve(9); - CHECK(str.size() == 9); - CHECK(str.capacity() == get_capacity(9)); - sv = std::string_view{str.data(), str.size()}; + ct_vector vec; + CHECK(vec.size() == 0); + CHECK(vec.capacity() == get_capacity(0)); + + if (checkpoint == 0) { + check_gang_of_5(vec); + return; + } + + vec.reserve(5); + CHECK(vec.size() == 5); + CHECK(vec.capacity() == get_capacity(5)); + std::fill(vec.data(), vec.data() + vec.size(), '\0'); + *(vec.data() + 2) = 'x'; + + if (checkpoint == 1) { + check_gang_of_5(vec); + return; + } + + vec.reserve(4); + CHECK(vec.size() == 4); + CHECK(vec.capacity() == get_capacity(4)); + + vec.reserve(5); + CHECK(vec.size() == 5); + CHECK(vec.capacity() == get_capacity(5)); + + vec.reserve(10); + CHECK(vec.size() == 10); + CHECK(vec.capacity() == get_capacity(10)); + std::fill(vec.data() + 5, vec.data() + vec.size(), '\0'); + *(vec.data() + 8) = 'y'; + + if (checkpoint == 2) { + check_gang_of_5(vec); + return; + } + + vec.reserve(15); + CHECK(vec.size() == 15); + CHECK(vec.capacity() == get_capacity(15)); + std::fill(vec.data() + 10, vec.data() + vec.size(), 'n'); + *(vec.data() + 12) = 'z'; + std::string_view sv{vec.data(), vec.size()}; + CHECK(sv == "\0\0x\0\0\0\0\0y\0nnznn"sv); + + if (checkpoint == 3) { + check_gang_of_5(vec); + return; + } + + vec.reserve(9); + CHECK(vec.size() == 9); + CHECK(vec.capacity() == get_capacity(9)); + sv = std::string_view{vec.data(), vec.size()}; CHECK(sv == "\0\0x\0\0\0\0\0y"sv); - str.reserve(15); - CHECK(str.size() == 15); - CHECK(str.capacity() == get_capacity(15)); - sv = std::string_view{str.data(), str.size()}; - CHECK(sv == "\0\0x\0\0\0\0\0y\0\0\0z\0\0"sv); + if (checkpoint == 4) { + check_gang_of_5(vec); + return; + } + + vec.reserve(15); + CHECK(vec.size() == 15); + CHECK(vec.capacity() == get_capacity(15)); + sv = std::string_view{vec.data(), vec.size()}; + CHECK(sv == "\0\0x\0\0\0\0\0y\0nnznn"sv); + + if (checkpoint == 5) { + check_gang_of_5(vec); + return; + } + + vec.clear(); + CHECK(vec.size() == 0); + CHECK(vec.capacity() == 15); + + vec.reserve(15); + CHECK(vec.size() == 15); + CHECK(vec.capacity() == get_capacity(15)); + sv = std::string_view{vec.data(), vec.size()}; + CHECK(sv == "\0\0x\0\0\0\0\0y\0nnznn"sv); - str.clear(); - CHECK(str.size() == 0); - CHECK(str.capacity() == 15); + check_gang_of_5(vec); } } diff --git a/test/unit_test/test_buffer.cpp b/test/unit_test/test_buffer.cpp index 01e13bb..722ed89 100644 --- a/test/unit_test/test_buffer.cpp +++ b/test/unit_test/test_buffer.cpp @@ -4,6 +4,7 @@ // Other includes. #include #include +#include namespace { @@ -11,6 +12,122 @@ constexpr void fill(const emio::result>& area, char v) { std::fill(area->begin(), area->end(), v); } +template +constexpr bool check_equality_of_buffer(T& expected, T& other, bool data_ptr_is_different) { + bool result = true; + + std::array dummy{0}; + std::array dummy2{0}; + emio::result> area{dummy}; + emio::result> area2{dummy}; + if (data_ptr_is_different) { + area2 = dummy2; + } + + const auto compare = [&]() { + result &= area.has_value(); + result &= area2.has_value(); + if (data_ptr_is_different) { + result &= area->data() != area2->data(); + } else { + result &= area->data() == area2->data(); + } + }; + + compare(); + + area = expected.get_write_area_of(5); + area2 = other.get_write_area_of(5); + + if ((!area && area2) || (area && !area2)) { + return false; + } + if (!area && !area2) { + return result; + } + + compare(); + + fill(area, 'x'); + fill(area2, 'x'); + + result &= expected.view() == other.view(); + + area = expected.get_write_area_of(2); + area2 = other.get_write_area_of(2); + + if ((!area && area2) || (area && !area2)) { + return false; + } + if (!area && !area2) { + return result; + } + + fill(area, 'y'); + fill(area2, 'z'); + + if (data_ptr_is_different) { + result &= expected.view() != other.view(); + } else { + result &= expected.view() == other.view(); + } + + return result; +} + +template +void check_gang_of_5(T& buf, bool data_ptr_is_different) { + SECTION("copy-construct") { + T buf2{buf}; + CHECK(check_equality_of_buffer(buf, buf2, data_ptr_is_different)); + } + SECTION("copy-assign") { + T buf2; + buf2 = buf; + CHECK(check_equality_of_buffer(buf, buf2, data_ptr_is_different)); + } + SECTION("move-construct") { + T buf_tmp{buf}; + T buf2{std::move(buf_tmp)}; + CHECK(check_equality_of_buffer(buf, buf2, data_ptr_is_different)); + } + SECTION("move-assign") { + T buf_tmp{buf}; + T buf2; + buf2 = std::move(buf_tmp); + CHECK(check_equality_of_buffer(buf, buf2, data_ptr_is_different)); + } + SECTION("wild") { + T buf2{buf}; + T buf3{std::move(buf2)}; + T buf4; + buf4 = buf3; + T buf5; + buf5 = std::move(buf4); + T buf6{buf}; + buf6 = buf5; + + SECTION("1") { + CHECK(check_equality_of_buffer(buf, buf5, data_ptr_is_different)); + } + SECTION("2") { + CHECK(check_equality_of_buffer(buf6, buf5, data_ptr_is_different)); + } + } + SECTION("self-assignment copy") { + T buf2{buf}; + T& buf_tmp = buf2; // Prevent compiler warn about self assignment. + buf2 = buf_tmp; + CHECK(check_equality_of_buffer(buf, buf2, data_ptr_is_different)); + } + SECTION("self-assignment move") { + T buf2{buf}; + T& buf_tmp = buf2; // Prevent compiler warn about self assignment. + buf2 = std::move(buf_tmp); + CHECK(check_equality_of_buffer(buf, buf2, data_ptr_is_different)); + } +} + } // namespace TEST_CASE("memory_buffer", "[buffer]") { @@ -29,12 +146,19 @@ TEST_CASE("memory_buffer", "[buffer]") { const std::string expected_str = expected_str_part_1 + expected_str_part_2 + expected_str_part_3; const bool default_constructed = GENERATE(true, false); + const int checkpoint = GENERATE(range(0, 5)); INFO("Default constructed: " << default_constructed); + INFO("Checkpoint: " << checkpoint); emio::memory_buffer<15> buf = default_constructed ? emio::memory_buffer<15>{} : emio::memory_buffer<15>{18}; CHECK(buf.capacity() == (default_constructed ? 15 : 18)); CHECK(buf.view().empty()); + if (checkpoint == 0) { + check_gang_of_5(buf, true); + return; + } + emio::result> area = buf.get_write_area_of(first_size); REQUIRE(area); CHECK(area->size() == first_size); @@ -42,6 +166,11 @@ TEST_CASE("memory_buffer", "[buffer]") { CHECK(buf.view().size() == first_size); CHECK(buf.view() == expected_str_part_1); + if (checkpoint == 1) { + check_gang_of_5(buf, true); + return; + } + area = buf.get_write_area_of(second_size); REQUIRE(area); CHECK(area->size() == second_size); @@ -49,6 +178,11 @@ TEST_CASE("memory_buffer", "[buffer]") { CHECK(buf.view().size() == first_size + second_size); CHECK(buf.view() == expected_str_part_1 + expected_str_part_2); + if (checkpoint == 2) { + check_gang_of_5(buf, true); + return; + } + area = buf.get_write_area_of(third_size); REQUIRE(area); CHECK(area->size() == third_size); @@ -57,6 +191,11 @@ TEST_CASE("memory_buffer", "[buffer]") { CHECK(buf.view().size() == first_size + second_size + third_size); CHECK(buf.view() == expected_str); + if (checkpoint == 3) { + check_gang_of_5(buf, true); + return; + } + const size_t curr_capacity = buf.capacity(); CHECK(curr_capacity >= buf.view().size()); buf.reset(); @@ -65,6 +204,8 @@ TEST_CASE("memory_buffer", "[buffer]") { area = buf.get_write_area_of(first_size); REQUIRE(area); CHECK(area->size() == first_size); + + check_gang_of_5(buf, true); } TEST_CASE("memory_buffer regression bug 1", "[buffer]") { @@ -144,6 +285,24 @@ TEST_CASE("memory_buffer at compile-time", "[buffer]") { result &= buf.view() == "xxxxxxxyyyyyzzzzzzzz"; result &= buf.capacity() >= buf.view().size(); + const size_t curr_capacity = buf.capacity(); + result &= curr_capacity >= buf.view().size(); + buf.reset(); + result &= buf.capacity() == curr_capacity; + + area = buf.get_write_area_of(first_size); + result &= area.has_value(); + result &= (area->size() == first_size); + + emio::memory_buffer<1> buf2{buf}; + emio::memory_buffer<1> buf3{std::move(buf2)}; + emio::memory_buffer<1> buf4; + buf4 = buf3; + emio::memory_buffer<1> buf5; + buf5 = std::move(buf4); + + result &= check_equality_of_buffer(buf, buf5, true); + return result; }(); STATIC_CHECK(success); @@ -159,11 +318,19 @@ TEST_CASE("span_buffer", "[buffer]") { constexpr size_t second_size{55}; constexpr size_t third_size{emio::default_cache_size}; + const int checkpoint = GENERATE(range(0, 6)); + INFO("Checkpoint: " << checkpoint); + std::array storage{}; emio::span_buffer buf{storage}; CHECK(buf.capacity() == storage.size()); CHECK(buf.view().empty()); + if (checkpoint == 0) { + check_gang_of_5(buf, false); + return; + } + emio::result> area = buf.get_write_area_of(first_size); REQUIRE(area); CHECK(area->size() == first_size); @@ -172,6 +339,11 @@ TEST_CASE("span_buffer", "[buffer]") { CHECK(buf.view().size() == first_size); CHECK(buf.view().data() == storage.data()); + if (checkpoint == 1) { + check_gang_of_5(buf, false); + return; + } + area = buf.get_write_area_of(second_size); REQUIRE(area); CHECK(area->size() == second_size); @@ -180,6 +352,11 @@ TEST_CASE("span_buffer", "[buffer]") { CHECK(buf.view().size() == first_size + second_size); CHECK(buf.view().data() == storage.data()); + if (checkpoint == 2) { + check_gang_of_5(buf, false); + return; + } + area = buf.get_write_area_of(third_size); REQUIRE(area); CHECK(area->size() == third_size); @@ -189,6 +366,11 @@ TEST_CASE("span_buffer", "[buffer]") { CHECK(buf.view().data() == storage.data()); CHECK(buf.view() == buf.str()); + if (checkpoint == 3) { + check_gang_of_5(buf, false); + return; + } + area = buf.get_write_area_of(1); REQUIRE(!area); @@ -196,6 +378,11 @@ TEST_CASE("span_buffer", "[buffer]") { REQUIRE(area); CHECK(area->empty()); + if (checkpoint == 4) { + check_gang_of_5(buf, false); + return; + } + CHECK(buf.capacity() == storage.size()); buf.reset(); CHECK(buf.capacity() == storage.size()); @@ -203,10 +390,7 @@ TEST_CASE("span_buffer", "[buffer]") { REQUIRE(area); CHECK(area->size() == first_size); - buf.reset(); - area = buf.get_write_area_of(storage.size()); - REQUIRE(area); - CHECK(area->size() == storage.size()); + check_gang_of_5(buf, false); } TEST_CASE("span_buffer check request_write_area", "[buffer]") { @@ -217,9 +401,8 @@ TEST_CASE("span_buffer check request_write_area", "[buffer]") { class dummy_span_buffer : public emio::span_buffer { public: - using emio::span_buffer::span_buffer; - using emio::span_buffer::request_write_area; + using emio::span_buffer::span_buffer; }; SECTION("default constructed") { @@ -268,8 +451,56 @@ TEST_CASE("static_buffer", "[buffer]") { // * Check max size of the buffer. // Expected: The max size is correct. - emio::static_buffer<542> buffer; - CHECK(buffer.get_write_area_of_max(600)->size() == 542); + const int checkpoint = GENERATE(range(0, 6)); + INFO("Checkpoint: " << checkpoint); + + emio::static_buffer<542> buf; + + if (checkpoint == 0) { + check_gang_of_5(buf, true); + return; + } + + emio::result> area = buf.get_write_area_of_max(500); + REQUIRE(area); + CHECK(area->size() == 500); + fill(area, 'q'); + + if (checkpoint == 1) { + check_gang_of_5(buf, true); + return; + } + + area = buf.get_write_area_of_max(5); + REQUIRE(area); + CHECK(area->size() == 5); + fill(area, 'y'); + + if (checkpoint == 2) { + check_gang_of_5(buf, true); + return; + } + + buf.reset(); + + if (checkpoint == 3) { + check_gang_of_5(buf, true); + return; + } + + area = buf.get_write_area_of_max(600); + REQUIRE(area); + CHECK(area->size() == 542); + fill(area, 'l'); + + if (checkpoint == 4) { + check_gang_of_5(buf, true); + return; + } + + buf.reset(); + + check_gang_of_5(buf, true); } TEST_CASE("counting_buffer", "[buffer]") {