diff --git a/.github/workflows/build_run_unit_test_cmake.yml b/.github/workflows/build_run_unit_test_cmake.yml index 5f68d1a..311762a 100644 --- a/.github/workflows/build_run_unit_test_cmake.yml +++ b/.github/workflows/build_run_unit_test_cmake.yml @@ -12,8 +12,8 @@ jobs: build_matrix: strategy: matrix: - # os: [ubuntu-latest, windows-latest, macos-14] - os: [ubuntu-latest] + os: [ubuntu-latest, windows-latest, macos-14] + # os: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} defaults: run: diff --git a/README.md b/README.md index 93231e2..9a1bbad 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ ![GH Tag](https://img.shields.io/github/v/tag/connectivecpp/shared-buffer?label=GH%20tag) +![License](https://img.shields.io/badge/License-Boost%201.0-blue) + ## Overview The `shared_buffer` classes are reference counted `std::byte` buffer classes useful for asynchronous networking. In particular, the Asio asynchronous networking library requires a buffer to be kept alive and valid until the outstanding IO operation (e.g. a network write) is completed. A straightforward and idiomatic way to achieve this is by using reference counted buffers. @@ -40,7 +42,7 @@ The unit test code uses [Catch2](https://github.com/catchorg/Catch2). If the `SH The unit test uses utilities from Connective C++'s [utility-rack](https://github.com/connectivecpp/utility-rack). -Specific version (or branch) specs for the dependenies are in `test/CMakeLists.txt`. +Specific version (or branch) specs for the dependencies are in the [test/CMakeLists.txt](test/CMakeLists.txt) file, look for the `CPMAddPackage` commands. ## Build and Run Unit Tests diff --git a/include/buffer/shared_buffer.hpp b/include/buffer/shared_buffer.hpp index ad200fc..3a2f095 100644 --- a/include/buffer/shared_buffer.hpp +++ b/include/buffer/shared_buffer.hpp @@ -81,12 +81,12 @@ #include #include // std::move, std::swap -#include // std::memcpy +#include // std::copy -// TODO - add conecepts and / or requires +// TODO - add concepts and / or requires // // modify the templated constructor that takes a buffer of any valid -// byte type, add constraints; this makes the "reinterpret_cast" safe +// byte type, add constraints which makes the casting safer namespace chops { @@ -147,6 +147,34 @@ class mutable_shared_buffer { mutable_shared_buffer() noexcept : m_data{std::make_shared(size_type(0))} { } +/** + * @brief Construct by copying from a @c std::span of @c std::byte. + * + * @param sp @c std::byte span pointing to buffer of data. The data is + * copied into the internal buffer of the @c mutable_shared_buffer. + * + */ + template + explicit mutable_shared_buffer(std::span sp) : + m_data{std::make_shared(sp.data(), sp.data()+sp.size())} { } + +/** + * @brief Construct by copying from a @c std::byte array. + * + * A @c std::span is first created, then the constructor taking + * a @c std::span is called. + * + * @pre Size cannot be greater than the source buffer. + * + * @param buf Non-null pointer to a @c std::byte buffer of data. The + * data is copied into the internal buffer of the @c mutable_shared_buffer. + * + * @param sz Size of buffer. + * + */ + mutable_shared_buffer(const std::byte* buf, std::size_t sz) : + mutable_shared_buffer(std::as_bytes(std::span{buf, sz})) { } + /** * @brief Move construct from a @c std::vector of @c std::bytes. * @@ -161,9 +189,10 @@ class mutable_shared_buffer { m_data{std::make_shared(size_type(0))} { *m_data = std::move(bv); } + /** * @brief Construct a @c mutable_shared_buffer with an initial size, contents - * set to zero. + * of each byte set to zero. * * Allocate zero initialized space which can be overwritten with data as needed. * The @c data method is called to get access to the underlying @c std::byte @@ -171,35 +200,23 @@ class mutable_shared_buffer { * * @param sz Size for internal @c std::byte buffer. */ - explicit mutable_shared_buffer(size_type sz) noexcept : + explicit mutable_shared_buffer(size_type sz) : m_data{std::make_shared(sz)} { } -/** - * @brief Construct by copying from a @c std::span of @c std::byte. - * - * @param sp @c std::byte span pointing to buffer of data. The data is - * copied into the internal buffer of the @c mutable_shared_buffer. - * - */ - mutable_shared_buffer(std::span sp) noexcept : - m_data{std::make_shared(sp.data(), sp.data()+sp.size())} { } /** - * @brief Construct by copying from a @c std::byte array. - * - * A @c std::span is first created, then the constructor taking - * a @c std::span is called. + * @brief Construct by copying bytes from a @c std::span. * - * @pre Size cannot be greater than the source buffer. - * - * @param buf @c std::byte array containing buffer of data. The data is - * copied into the internal buffer of the @c mutable_shared_buffer. + * The type of the span must be convertible to or be layout compatible with + * @c std::byte. * - * @param sz Size of buffer. + * @param sp @c std::span pointing to buffer of data. The @c std::span + * pointer is cast into a @c std::byte pointer and bytes are then copied. * */ - mutable_shared_buffer(const std::byte* buf, size_type sz) noexcept : - mutable_shared_buffer(std::span(buf, sz)) { } + template + mutable_shared_buffer(std::span sp) : + mutable_shared_buffer(std::as_bytes(sp)) { } /** * @brief Construct by copying bytes from an arbitrary pointer. @@ -210,14 +227,13 @@ class mutable_shared_buffer { * * @pre Size cannot be greater than the source buffer. * - * @param buf Pointer to a buffer of data. The pointer must be convertible - * to a @c void pointer and then to a @c std::byte pointer. + * @param buf Non-null pointer to a buffer of data. * * @param sz Size of buffer, in bytes. */ template mutable_shared_buffer(const T* buf, size_type sz) : - mutable_shared_buffer(reinterpret_cast(buf), sz) { } + mutable_shared_buffer(std::as_bytes(std::span{buf, sz})) { } /** * @brief Construct from input iterators. @@ -256,23 +272,23 @@ class mutable_shared_buffer { const std::byte* data() const noexcept { return m_data->data(); } /** - * @brief Return access to underlying @c std::vector. - * - * This can be used to instantiate a dynamic_buffer as defined in the Networking TS. - * Changing the @c std::vector from outside this class works because no state - * data is stored within this object that needs to be consistent with the @c std::vector - * contents. + * @brief Return size (number of bytes) of buffer. * - * @return Reference to @c std::vector. + * @return Size of buffer, which may be zero. */ - byte_vec& get_byte_vec() noexcept { return *m_data; } + size_type size() const noexcept { return m_data->size(); } /** - * @brief Return size (number of bytes) of buffer. + * @brief Return access to underlying @c std::vector. * - * @return Size of buffer, which may be zero. + * This can be used to instantiate a @c dynamic_buffer as defined in the Networking TS + * or Asio API. Changing the @c std::vector from outside this class works because no + * state data is stored within this object that needs to be consistent with the + * @c std::vector contents. + * + * @return Reference to @c std::vector. */ - size_type size() const noexcept { return m_data->size(); } + byte_vec& get_byte_vec() noexcept { return *m_data; } /** * @brief Query to see if size is zero. @@ -315,19 +331,31 @@ class mutable_shared_buffer { /** * @brief Append a @c std::byte buffer to the end of the internal buffer. * - * @param buf @c std::byte array containing buffer of data. + * @param buf Non-null pointer to @c std::byte buffer of data. * * @param sz Size of buffer. * * @return Reference to @c this (to allow method chaining). */ - mutable_shared_buffer& append(const std::byte* buf, size_type sz) { + mutable_shared_buffer& append(const std::byte* buf, std::size_t sz) { size_type old_sz = size(); resize(old_sz + sz); // set up buffer space - std::memcpy(data() + old_sz, buf, sz); + std::copy(buf, buf+sz, data()+old_sz); return *this; } +/** + * @brief Append a @c std::span to the end of the internal buffer. + * + * @param sp @c std::span of @c std::byte data. + * + * @return Reference to @c this (to allow method chaining). + */ + template + mutable_shared_buffer& append(std::span sp) { + return append(sp.data(), sp.size()); + } + /** * @brief Append by copying bytes from an arbitrary pointer. * @@ -335,14 +363,31 @@ class mutable_shared_buffer { * are then copied. In particular, this method can be used for @c char pointers, * @c void pointers, @ unsigned @c char pointers, etc. * - * @param buf Pointer to a buffer of data. The pointer must be convertible - * to a @c void pointer and then to a @c std::byte pointer. + * @param buf Non-null pointer to a buffer of data. * * @param sz Size of buffer, in bytes. */ template - mutable_shared_buffer& append(const T* buf, size_type sz) { - return append(reinterpret_cast(buf), sz); + mutable_shared_buffer& append(const T* buf, std::size_t sz) { + return append(std::as_bytes(std::span{buf, sz})); + } + +/** + * @brief Append a @c std::span that is a non @c std::byte buffer. + * + * The @c std::span passed into this method is performs a cast on the + * data. In particular, this method can be used for @c char pointers, + * @c void pointers, @ unsigned @c char pointers, etc. + * + * The type of the span must be convertible to or be layout compatible with + * @c std::byte. + * + * @param sp @c std::span of arbitrary bytes. + * + */ + template + mutable_shared_buffer& append(std::span sp) { + return append(std::as_bytes(sp)); } /** @@ -463,19 +508,42 @@ class const_shared_buffer { const_shared_buffer& operator=(const const_shared_buffer&) = delete; const_shared_buffer& operator=(const_shared_buffer&&) = delete; +/** + * @brief Construct by copying from a @c std::span of @c std::byte. + * + * @param sp @c std::byte span pointing to buffer of data. The data is + * copied into the internal buffer of the @c const_shared_buffer. + * + */ + template + explicit const_shared_buffer(std::span sp) : + m_data(std::make_shared(sp.data(), sp.data()+sp.size())) { } /** * @brief Construct by copying from a @c std::byte array. * * @pre Size cannot be greater than the source buffer. * - * @param buf @c std::byte array containing buffer of data. The data is + * @param buf Non-null pointer to @c std::byte buffer of data. The data is * copied into the internal buffer of the @c const_shared_buffer. * * @param sz Size of buffer. */ - const_shared_buffer(const std::byte* buf, size_type sz) : - m_data(std::make_shared(buf, buf+sz)) { } + const_shared_buffer(const std::byte* buf, std::size_t sz) : + const_shared_buffer(std::as_bytes(std::span{buf, sz})) { } +/** + * @brief Construct by copying from a @c std::span. + * + * The type of the span must be convertible to or be layout compatible with + * @c std::byte. + * + * @param sp @c std::span pointing to buffer of data. The @c std::span + * pointer is cast into a @c std::byte pointer and bytes are then copied. + * + */ + template + const_shared_buffer(std::span sp) : + const_shared_buffer(std::as_bytes(sp)) { } /** * @brief Construct by copying bytes from an arbitrary pointer. * @@ -483,16 +551,18 @@ class const_shared_buffer { * are then copied. In particular, this method can be used for @c char pointers, * @c void pointers, @c unsigned @c char pointers, etc. * + * The type of the span must be convertible to or be layout compatible with + * @c std::byte. + * * @pre Size cannot be greater than the source buffer. * - * @param buf Pointer to a buffer of data. The pointer must be convertible - * to a @c void pointer and then to a @c std::byte pointer. + * @param buf Non-null pointer to a buffer of data. * * @param sz Size of buffer, in bytes. */ template - const_shared_buffer(const T* buf, size_type sz) : - const_shared_buffer(reinterpret_cast(buf), sz) { } + const_shared_buffer(const T* buf, std::size_t sz) : + const_shared_buffer(std::as_bytes(std::span{buf, sz})) { } /** * @brief Construct by copying from a @c mutable_shared_buffer object. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8f9b073..5a72e14 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,8 +15,8 @@ target_compile_features ( shared_buffer_test PRIVATE cxx_std_20 ) # add dependencies include ( ../cmake/download_cpm.cmake ) -CPMAddPackage ( "gh:catchorg/Catch2@3.6.0" ) -CPMAddPackage ( "gh:connectivecpp/utility-rack@1.0.0" ) +CPMAddPackage ( "gh:catchorg/Catch2@3.7.0" ) +CPMAddPackage ( "gh:connectivecpp/utility-rack@1.0.3" ) # link dependencies target_link_libraries ( shared_buffer_test PRIVATE shared_buffer utility_rack Catch2::Catch2WithMain ) diff --git a/test/shared_buffer_test.cpp b/test/shared_buffer_test.cpp index d1445db..4776df7 100644 --- a/test/shared_buffer_test.cpp +++ b/test/shared_buffer_test.cpp @@ -20,62 +20,125 @@ #include #include #include +#include +#include // std::copy +#include // std::bit_cast #include "buffer/shared_buffer.hpp" #include "utility/repeat.hpp" -#include "utility/make_byte_array.hpp" +#include "utility/byte_array.hpp" -constexpr std::byte Harhar { 42 }; -constexpr int N = 11; +constexpr std::size_t test_data_size { 12u }; +using test_data_type = std::array; +constexpr test_data_type test_data { chops::make_byte_array( 40, 41, 42, 43, 44, 60, 59, 58, 57, 56, 42, 42 ) }; +char test_data_char[test_data_size] { 40, 41, 42, 43, 44, 60, 59, 58, 57, 56, 42, 42 }; +const char* test_data_char_ptr {test_data_char}; +template +bool check_sb_against_test_data(SB sb) { + REQUIRE (sb.size() == test_data_size); + test_data_type buf; + std::copy(sb.data(), sb.data()+sb.size(), buf.begin()); + return chops::compare_byte_arrays(buf, test_data); +} template -void generic_pointer_construction_test() { - auto arr = chops::make_byte_array( 40, 41, 42, 43, 44, 60, 59, 58, 57, 56, 42, 42 ); - const PT* ptr = reinterpret_cast(arr.data()); - SB sb(ptr, arr.size()); +SB generic_pointer_construction_test() { + auto ptr { std::bit_cast(test_data.data()) }; + SB sb(ptr, test_data_size); REQUIRE_FALSE (sb.empty()); - chops::repeat(static_cast(arr.size()), [&sb, arr] (int i) { REQUIRE(*(sb.data()+i) == arr[i]); } ); + REQUIRE (check_sb_against_test_data(sb)); + return sb; +} + +template +void generic_pointer_append_test() { + auto sb { generic_pointer_construction_test() }; + auto sav_sz { sb.size() }; + const PT arr[] { 5, 6, 7 }; + const PT* ptr_arr { arr }; + sb.append (ptr_arr, 3); + REQUIRE (sb.size() == (sav_sz + 3)); + std::span sp { arr }; + sb.append (sp); + REQUIRE (sb.size() == (sav_sz + 6)); } template -void common_methods_test(const std::byte* buf, typename SB::size_type sz) { +void check_sb(SB sb) { + REQUIRE_FALSE (sb.empty()); + REQUIRE (sb.size() == test_data_size); + REQUIRE (check_sb_against_test_data(sb)); +} - REQUIRE (sz > 2); +template +void common_ctor_test() { - SB sb(buf, sz); - REQUIRE_FALSE (sb.empty()); { - SB sb2(buf, sz); - REQUIRE_FALSE (sb2.empty()); - REQUIRE (sb == sb2); + std::span sp { test_data }; + SB sb{sp}; + check_sb(sb); } { - std::list lst (buf, buf+sz); - SB sb2(lst.cbegin(), lst.cend()); - REQUIRE_FALSE (sb2.empty()); - REQUIRE (sb == sb2); + std::span sp { test_data.data(), test_data.size() }; + SB sb{sp}; + check_sb(sb); + } + { + SB sb{test_data.data(), test_data.size()}; + check_sb(sb); + } + { + std::span sp { test_data_char }; + SB sb{sp}; + check_sb(sb); + } + { + std::span sp { test_data_char, test_data_char+test_data_size }; + SB sb{sp}; + check_sb(sb); } { - auto ba = chops::make_byte_array(buf[0], buf[1]); - SB sb2(ba.cbegin(), ba.cend()); - REQUIRE_FALSE (sb2.empty()); - REQUIRE (((sb2 < sb) != 0)); // uses spaceship operator - REQUIRE (sb2 != sb); + SB sb{test_data_char_ptr, test_data_size}; + check_sb(sb); + } + + { + std::list lst {test_data.cbegin(), test_data.cend()}; + SB sb {lst.cbegin(), lst.cend()}; + check_sb(sb); + } + { + SB sb1{test_data.data(), test_data.size()}; + SB sb2{test_data.data(), test_data.size()}; + REQUIRE (sb1 == sb2); } { - auto ba = chops::make_byte_array(0x00, 0x00, 0x00, 0x00, 0x00, 0x00); - SB sb2(ba.cbegin(), ba.cend()); - REQUIRE_FALSE (sb2.empty()); - REQUIRE (sb2 != sb); + SB sb1{test_data.data(), test_data.size()}; + SB sb2{sb1}; + REQUIRE (sb1 == sb2); } + +} + +template +void common_comparison_test() { + auto ba1 { chops::make_byte_array(0x00, 0x00, 0x00) }; + auto ba2 { chops::make_byte_array(0x00, 0x22, 0x33) }; + + SB sb1(ba1.cbegin(), ba1.cend()); + SB sb2(ba2.cbegin(), ba2.cend()); + REQUIRE_FALSE (sb1.empty()); + REQUIRE_FALSE (sb2.empty()); + REQUIRE_FALSE (sb1 == sb2); + REQUIRE (((sb1 < sb2) != 0)); // uses spaceship operator } template void byte_vector_move_test() { - auto arr = chops::make_byte_array (0x01, 0x02, 0x03, 0x04, 0x05); + auto arr { chops::make_byte_array (0x01, 0x02, 0x03, 0x04, 0x05) }; std::vector bv { arr.cbegin(), arr.cend() }; SB sb(std::move(bv)); @@ -83,25 +146,32 @@ void byte_vector_move_test() { REQUIRE_FALSE (bv.size() == sb.size()); } -TEMPLATE_TEST_CASE ( "Checking generic pointer construction", +TEMPLATE_TEST_CASE ( "Generic pointer construction", "[common]", - char, unsigned char, signed char ) { + char, unsigned char, signed char, std::uint8_t ) { generic_pointer_construction_test(); generic_pointer_construction_test(); } -TEMPLATE_TEST_CASE ( "Shared buffer common methods test", - "[const_shared_buffer] [common]", +TEMPLATE_TEST_CASE ( "Shared buffer common ctor methods", + "[const_shared_buffer] [mutable_shared_buffer] [common]", + chops::mutable_shared_buffer, chops::const_shared_buffer ) { + common_ctor_test(); +} + +TEMPLATE_TEST_CASE ( "Shared buffer common comparison methods", + "[const_shared_buffer] [mutable_shared_buffer] [common]", chops::mutable_shared_buffer, chops::const_shared_buffer ) { - auto arr = chops::make_byte_array ( 80, 81, 82, 83, 84, 90, 91, 92 ); - common_methods_test(arr.data(), arr.size()); + common_comparison_test(); } TEST_CASE ( "Mutable shared buffer copy construction and assignment", "[mutable_shared_buffer] [copy]" ) { - auto arr = chops::make_byte_array ( 80, 81, 82, 83, 84, 90, 91, 92 ); + constexpr std::byte Harhar { 42 }; + + auto arr { chops::make_byte_array ( 80, 81, 82, 83, 84, 90, 91, 92 ) }; chops::mutable_shared_buffer sb; REQUIRE (sb.empty()); @@ -122,148 +192,130 @@ TEST_CASE ( "Mutable shared buffer copy construction and assignment", } } -SCENARIO ( "Mutable shared buffer resize and clear", - "[mutable_shared_buffer] [resize_and_clear]" ) { - - GIVEN ("A default constructed mutable shared_buffer") { - chops::mutable_shared_buffer sb; - WHEN ("Resize is called") { - sb.resize(N); - THEN ("the internal buffer will have all zeros") { - REQUIRE (sb.size() == N); - chops::repeat(N, [&sb] (const int& i) { REQUIRE (*(sb.data() + i) == std::byte{0} ); } ); - } - } - AND_WHEN ("Another mutable shared buffer with a size is constructed") { - sb.resize(N); - chops::mutable_shared_buffer sb2(N); - THEN ("the two shared buffers compare equal, with all zeros in the buffer") { - REQUIRE (sb == sb2); - chops::repeat(N, [&sb, &sb2] (const int& i) { - REQUIRE (*(sb.data() + i) == std::byte{0} ); - REQUIRE (*(sb2.data() + i) == std::byte{0} ); - } ); - } - } - AND_WHEN ("The mutable shared buffer is cleared") { - sb.resize(N); - sb.clear(); - THEN ("the size will be zero and the buffer is empty") { - REQUIRE (sb.size() == 0); - REQUIRE (sb.empty()); - } - } +TEST_CASE ( "Mutable shared buffer resize and clear", + "[mutable_shared_buffer] [resize_and_clear]" ) { + + constexpr int N = 11; + + chops::mutable_shared_buffer sb; + REQUIRE (sb.empty()); + REQUIRE (sb.size() == 0); + + sb.resize(N); + REQUIRE (sb.size() == N); + chops::repeat(N, [&sb] (int i) { REQUIRE (std::to_integer(*(sb.data() + i)) == 0 ); } ); + + SECTION ( "Compare two resized mutable shared buffer with same size" ) { + chops::mutable_shared_buffer sb2(N); + REQUIRE (sb == sb2); + chops::repeat(N, [&sb, &sb2] (int i) { + REQUIRE (std::to_integer(*(sb.data() + i)) == 0 ); + REQUIRE (std::to_integer(*(sb2.data() + i)) == 0 ); + } ); + } + SECTION ( "Clear, check size" ) { + sb.clear(); + REQUIRE (sb.size() == 0); + REQUIRE (sb.empty()); } // end given } -SCENARIO ( "Mutable shared buffer swap", - "[mutable_shared_buffer] [swap]" ) { - - GIVEN ("Two mutable shared_buffers") { - auto arr1 = chops::make_byte_array (0xaa, 0xbb, 0xcc); - auto arr2 = chops::make_byte_array (0x01, 0x02, 0x03, 0x04, 0x05); - - chops::mutable_shared_buffer sb1(arr1.cbegin(), arr1.cend()); - chops::mutable_shared_buffer sb2(arr2.cbegin(), arr2.cend()); - - WHEN ("The buffers are swapped") { - chops::swap(sb1, sb2); - THEN ("the sizes and contents will be swapped") { - REQUIRE (sb1.size() == arr2.size()); - REQUIRE (sb2.size() == arr1.size()); - REQUIRE (*(sb1.data()+0) == *(arr2.data()+0)); - REQUIRE (*(sb1.data()+1) == *(arr2.data()+1)); - REQUIRE (*(sb2.data()+0) == *(arr1.data()+0)); - REQUIRE (*(sb2.data()+1) == *(arr1.data()+1)); - } - } - } // end given +TEST_CASE ( "Mutable shared buffer swap", + "[mutable_shared_buffer] [swap]" ) { + + auto arr1 = chops::make_byte_array (0xaa, 0xbb, 0xcc); + auto arr2 = chops::make_byte_array (0x01, 0x02, 0x03, 0x04, 0x05); + + chops::mutable_shared_buffer sb1(arr1.cbegin(), arr1.cend()); + chops::mutable_shared_buffer sb2(arr2.cbegin(), arr2.cend()); + + chops::swap(sb1, sb2); + REQUIRE (sb1.size() == arr2.size()); + REQUIRE (sb2.size() == arr1.size()); + + REQUIRE (std::to_integer(*(sb1.data()+0)) == std::to_integer(*(arr2.data()+0))); + REQUIRE (std::to_integer(*(sb1.data()+1)) == std::to_integer(*(arr2.data()+1))); + REQUIRE (std::to_integer(*(sb1.data()+2)) == std::to_integer(*(arr2.data()+2))); + REQUIRE (std::to_integer(*(sb1.data()+3)) == std::to_integer(*(arr2.data()+3))); + REQUIRE (std::to_integer(*(sb1.data()+4)) == std::to_integer(*(arr2.data()+4))); + + REQUIRE (std::to_integer(*(sb2.data()+0)) == std::to_integer(*(arr1.data()+0))); + REQUIRE (std::to_integer(*(sb2.data()+1)) == std::to_integer(*(arr1.data()+1))); + REQUIRE (std::to_integer(*(sb2.data()+2)) == std::to_integer(*(arr1.data()+2))); } -SCENARIO ( "Mutable shared buffer append", - "[mutable_shared_buffer] [append]" ) { +TEST_CASE ( "Mutable shared buffer append", + "[mutable_shared_buffer] [append]" ) { auto arr = chops::make_byte_array (0xaa, 0xbb, 0xcc); auto arr2 = chops::make_byte_array (0xaa, 0xbb, 0xcc, 0xaa, 0xbb, 0xcc); chops::mutable_shared_buffer ta(arr.cbegin(), arr.cend()); chops::mutable_shared_buffer ta2(arr2.cbegin(), arr2.cend()); - GIVEN ("A default constructed mutable shared_buffer") { - chops::mutable_shared_buffer sb; - WHEN ("Append with a pointer and size is called") { - sb.append(arr.data(), arr.size()); - THEN ("the internal buffer will contain the appended data") { - REQUIRE (sb == ta); - } - } - AND_WHEN ("Append with a mutable shared buffer is called") { - sb.append(ta); - THEN ("the internal buffer will contain the appended data") { - REQUIRE (sb == ta); - } - } - AND_WHEN ("Append is called twice") { - sb.append(ta); - sb.append(ta); - THEN ("the internal buffer will contain twice the appended data") { - REQUIRE (sb == ta2); - } - } - AND_WHEN ("Appending with single bytes") { - sb.append(std::byte(0xaa)); - sb.append(std::byte(0xbb)); - sb += std::byte(0xcc); - THEN ("the internal buffer will contain the appended data") { - REQUIRE (sb == ta); - } - } - AND_WHEN ("Appending with a char* to test templated append") { - std::string_view sv("Haha, Bro!"); - chops::mutable_shared_buffer cb(sv.data(), sv.size()); - sb.append(sv.data(), sv.size()); - THEN ("the internal buffer will contain the appended data") { - REQUIRE (sb == cb); - } - } - } // end given + chops::mutable_shared_buffer sb; + REQUIRE (sb.empty()); + + SECTION ( "Append array to default constructed mutable shared_buffer" ) { + sb.append(arr.data(), arr.size()); + REQUIRE (sb == ta); + } + + SECTION ( "Append mutable shared buffer" ) { + sb.append(ta); + REQUIRE (sb == ta); + } + + SECTION ( "Call append twice" ) { + sb.append(ta); + sb.append(ta); + REQUIRE (sb == ta2); + } + + SECTION ( "Append with single byte" ) { + sb.append(std::byte(0xaa)); + sb.append(std::byte(0xbb)); + sb += std::byte(0xcc); + REQUIRE (sb == ta); + } + + SECTION ( "Append with templated append" ) { + std::string_view sv("Haha, Bro!"); + chops::mutable_shared_buffer cb(sv.data(), sv.size()); + sb.append(sv.data(), sv.size()); + REQUIRE (sb == cb); + } } -SCENARIO ( "Compare a mutable shared_buffer with a const shared buffer", - "[mutable_shared_buffer] [const_shared_buffer] [compare]" ) { - - GIVEN ("An array of bytes") { - auto arr = chops::make_byte_array (0xaa, 0xbb, 0xcc); - WHEN ("A mutable_shared_buffer and a const_shared_buffer are created from the bytes") { - chops::mutable_shared_buffer msb(arr.cbegin(), arr.cend()); - chops::const_shared_buffer csb(arr.cbegin(), arr.cend()); - THEN ("the shared buffers will compare equal") { - REQUIRE (msb == csb); - REQUIRE (csb == msb); - } - } - } // end given +TEMPLATE_TEST_CASE ( "Generic pointer append", + "[mutable_shared_buffer] [pointer] [append]", + char, unsigned char, signed char, std::uint8_t ) { + generic_pointer_append_test(); +} + +TEST_CASE ( "Compare a mutable shared_buffer with a const shared buffer", + "[mutable_shared_buffer] [const_shared_buffer] [compare]" ) { + + auto arr = chops::make_byte_array (0xaa, 0xbb, 0xcc); + chops::mutable_shared_buffer msb(arr.cbegin(), arr.cend()); + chops::const_shared_buffer csb(arr.cbegin(), arr.cend()); + REQUIRE (msb == csb); + REQUIRE (csb == msb); } -SCENARIO ( "Mutable shared buffer move into const shared buffer", - "[mutable_shared_buffer] [const_shared_buffer] [move]" ) { +TEST_CASE ( "Mutable shared buffer move into const shared buffer", + "[mutable_shared_buffer] [const_shared_buffer] [move]" ) { auto arr1 = chops::make_byte_array (0xaa, 0xbb, 0xcc); auto arr2 = chops::make_byte_array (0x01, 0x02, 0x03, 0x04, 0x05); - GIVEN ("A mutable_shared_buffer") { - chops::mutable_shared_buffer msb(arr1.cbegin(), arr1.cend()); - WHEN ("A const_shared_buffer is move constructed from the mutable_shared_buffer") { - chops::const_shared_buffer csb(std::move(msb)); - THEN ("the const_shared_buffer will contain the data and the mutable_shared_buffer will not") { - REQUIRE (csb == chops::const_shared_buffer(arr1.cbegin(), arr1.cend())); - REQUIRE_FALSE (msb == csb); - msb.clear(); - msb.resize(arr2.size()); - msb.append(arr2.data(), arr2.size()); - REQUIRE_FALSE (msb == csb); - } - } - } // end given + chops::mutable_shared_buffer msb(arr1.cbegin(), arr1.cend()); + chops::const_shared_buffer csb(std::move(msb)); + REQUIRE (csb == chops::const_shared_buffer(arr1.cbegin(), arr1.cend())); + REQUIRE_FALSE (msb == csb); + msb.clear(); + msb.resize(arr2.size()); + msb.append(arr2.data(), arr2.size()); + REQUIRE_FALSE (msb == csb); } TEMPLATE_TEST_CASE ( "Move a vector of bytes into a shared buffer", @@ -274,24 +326,21 @@ TEMPLATE_TEST_CASE ( "Move a vector of bytes into a shared buffer", } -SCENARIO ( "Use get_byte_vec for external modification of buffer", - "[mutable_shared_buffer] [get_byte_vec]" ) { +TEST_CASE ( "Use get_byte_vec for external modification of buffer", + "[mutable_shared_buffer] [get_byte_vec]" ) { auto arr = chops::make_byte_array (0xaa, 0xbb, 0xcc); chops::mutable_shared_buffer::byte_vec bv (arr.cbegin(), arr.cend()); - GIVEN ("A mutable_shared_buffer") { - chops::mutable_shared_buffer msb(bv.cbegin(), bv.cend()); - - WHEN ("get_byte_vec is called") { - auto r = msb.get_byte_vec(); - THEN ("the refererence can be used to access and modify data") { - REQUIRE (r == bv); - r[0] = std::byte(0xdd); - REQUIRE_FALSE (r == bv); - } - } - } // end given + chops::mutable_shared_buffer msb(bv.cbegin(), bv.cend()); + auto r = msb.get_byte_vec(); +// REQUIRE (r == bv); // Catch2 build problems on MSVC + std::array arr2 { r[0], r[1], r[2] }; + REQUIRE (chops::compare_byte_arrays(arr, arr2)); + r[0] = std::byte(0xdd); +// REQUIRE_FALSE (r == bv); // Catch2 build problems on MSVC + std::array arr3 { r[0], r[1], r[2] }; + REQUIRE_FALSE (chops::compare_byte_arrays(arr, arr3)); }