/* * Copyright (c) 2018 - 2025 Ember * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ember; TEST(BinaryStream, MessageReadLimit) { std::array ping { 0x00, 0x0C, 0xDC, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xF4, 0x01, 0x00, 0x00 }; // write the ping packet data twice to the buffer spark::io::DynamicBuffer<32> buffer; buffer.write(ping); buffer.write(ping); // read one packet back out (reuse the ping array) spark::io::BinaryStream stream(buffer, ping.size()); ASSERT_EQ(stream.read_limit(), ping.size()); ASSERT_NO_THROW(stream.get(ping)) << "Failed to read packet back from stream"; // attempt to read past the stream message bound ASSERT_THROW(stream.get(ping.data(), ping.size()), spark::io::stream_read_limit) << "Message boundary was not respected"; ASSERT_EQ(stream.state(), spark::io::StreamState::read_limit_error) << "Unexpected stream state"; } TEST(BinaryStream, BufferLimit) { std::array ping { 0x00, 0x0C, 0xDC, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xF4, 0x01, 0x00, 0x00 }; // write the ping packet data to the buffer spark::io::DynamicBuffer<32> buffer; buffer.write(ping); // read all data back out spark::io::BinaryStream stream(buffer); ASSERT_NO_THROW(stream.get(ping)) << "Failed to read packet back from stream"; // attempt to read past the buffer bound ASSERT_THROW(stream.get(ping), spark::io::buffer_underrun) << "Message boundary was not respected"; ASSERT_EQ(stream.state(), spark::io::StreamState::buffer_limit_error) << "Unexpected stream state"; } TEST(BinaryStream, ReadWriteInts) { spark::io::DynamicBuffer<32> buffer; spark::io::BinaryStream stream(buffer); const std::uint16_t in { 100 }; stream << in; ASSERT_EQ(stream.size(), sizeof(in)); ASSERT_EQ(stream.size(), buffer.size()); std::uint16_t out; stream >> out; ASSERT_EQ(in, out); ASSERT_TRUE(stream.empty()); ASSERT_TRUE(buffer.empty()); ASSERT_EQ(stream.state(), spark::io::StreamState::ok) << "Unexpected stream state"; } TEST(BinaryStream, ReadWriteStdString) { spark::io::DynamicBuffer<32> buffer; spark::io::BinaryStream stream(buffer); const std::string in { "The quick brown fox jumped over the lazy dog" }; stream << spark::io::null_terminated(in); ASSERT_EQ(stream.size(), in.size() + 1); // +1, account for the terminator std::string out; stream >> spark::io::null_terminated(out); ASSERT_TRUE(stream.empty()); ASSERT_EQ(in, out); ASSERT_EQ(stream.state(), spark::io::StreamState::ok) << "Unexpected stream state"; } TEST(BinaryStream, ReadWriteCString) { spark::io::DynamicBuffer<32> buffer; spark::io::BinaryStream stream(buffer); const char* in { "The quick brown fox jumped over the lazy dog" }; stream << in; ASSERT_EQ(stream.size(), strlen(in) + 1); std::string out; stream >> spark::io::null_terminated(out); ASSERT_TRUE(stream.empty()); ASSERT_EQ(0, strcmp(in, out.c_str())); ASSERT_EQ(stream.state(), spark::io::StreamState::ok) << "Unexpected stream state"; } TEST(BinaryStream, ReadWriteVector) { spark::io::DynamicBuffer<32> buffer; spark::io::BinaryStream stream(buffer); const auto time = std::chrono::system_clock::now().time_since_epoch(); const unsigned int seed = gsl::narrow_cast(time.count()); std::srand(seed); std::vector in(std::rand() % 200); std::ranges::iota(in, std::rand() % 100); std::ranges::shuffle(in, std::default_random_engine(seed)); stream.put(in); ASSERT_EQ(stream.size(), in.size() * sizeof(int)); // read the integers back one by one, making sure they // match the expected value for(auto value : in) { int output; stream >> output; ASSERT_EQ(output, value); } stream.put(in); std::vector out(in.size()); // read the integers to an output buffer and compare both stream.get(out); ASSERT_EQ(in, out); ASSERT_EQ(stream.state(), spark::io::StreamState::ok) << "Unexpected stream state"; } TEST(BinaryStream, Clear) { spark::io::DynamicBuffer<32> buffer; spark::io::BinaryStream stream(buffer); stream << 0xBADF00D; ASSERT_TRUE(!stream.empty()); ASSERT_TRUE(!buffer.empty()); stream.skip(stream.size()); ASSERT_TRUE(stream.empty()); ASSERT_TRUE(buffer.empty()); } TEST(BinaryStream, Skip) { spark::io::DynamicBuffer<32> buffer; spark::io::BinaryStream stream(buffer); const std::uint64_t in {0xBADF00D}; stream << in << in; stream.skip(sizeof(in)); ASSERT_EQ(stream.size(), sizeof(in)); ASSERT_EQ(stream.size(), buffer.size()); std::uint64_t out; stream >> out; ASSERT_TRUE(stream.empty()); ASSERT_EQ(in, out); } TEST(BinaryStream, CanWriteSeek) { spark::io::DynamicBuffer<32> buffer; spark::io::BinaryStream stream(buffer); ASSERT_EQ(buffer.can_write_seek(), stream.can_write_seek()); } TEST(BinaryStream, GetPut) { spark::io::DynamicBuffer<32> buffer; spark::io::BinaryStream stream(buffer); std::vector in { 1, 2, 3, 4, 5 }; std::vector out(in.size()); stream.put(in); stream.get(out); ASSERT_EQ(stream.total_read(), out.size()); ASSERT_EQ(stream.total_write(), in.size()); ASSERT_EQ(in, out); } TEST(BinaryStream, Fill) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); stream.fill<30>(128); ASSERT_EQ(buffer.size(), 30); ASSERT_EQ(stream.total_write(), 30); stream.fill<2>(128); ASSERT_EQ(buffer.size(), 32); ASSERT_EQ(stream.total_write(), 32); auto it = std::ranges::find_if(buffer, [](auto i) { return i != 128; }); ASSERT_EQ(it, buffer.end()); } TEST(BinaryStream, NoCopyStringRead) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); const std::string input { "The quick brown fox jumped over the lazy dog" }; const std::uint32_t trailing { 0x0DDBA11 }; stream << spark::io::null_terminated(input) << trailing; // check this stream uses a contiguous buffer const auto contig = std::is_same_v; ASSERT_TRUE(contig); // find the end of the string within the buffer const auto stream_buf = stream.buffer(); const auto pos = stream_buf->find_first_of('\0'); ASSERT_NE(pos, adaptor.npos); // create a view into the buffer & skip ahead so the next read continues as normal std::string_view output(stream_buf->read_ptr(), pos); ASSERT_EQ(input, output); // ensure we can still read subsequent data as normal stream.skip(pos + 1); // +1 to skip terminator std::uint32_t trailing_output = 0; stream >> trailing_output; ASSERT_EQ(trailing, trailing_output); } TEST(BinaryStream, StringviewRead) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); const std::string input { "The quick brown fox jumped over the lazy dog" }; const std::uint32_t trailing { 0x0DDBA11 }; stream << spark::io::null_terminated(input) << trailing; auto view = stream.view(); ASSERT_EQ(input, view); // ensure we can still read subsequent data as normal std::uint32_t trailing_output = 0; stream >> trailing_output; ASSERT_EQ(trailing, trailing_output); } TEST(BinaryStream, PartialStringviewRead) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); const std::string input { "The quick brown fox jumped over the lazy dog" }; stream << spark::io::null_terminated(input); auto span = stream.span(20); std::string_view view(span); ASSERT_EQ("The quick brown fox ", view); // read the rest of the string view = stream.view(); ASSERT_EQ("jumped over the lazy dog", view); ASSERT_TRUE(stream.empty()); } TEST(BinaryStream, StringviewStream) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); const std::string input { "The quick brown fox jumped over the lazy dog" }; const std::uint32_t trailing { 0xDEFECA7E }; stream << spark::io::null_terminated(input) << trailing; std::string_view output; stream >> spark::io::null_terminated(output); ASSERT_EQ(input, output); // ensure we can still read subsequent data as normal std::uint32_t trailing_output = 0; stream >> trailing_output; ASSERT_EQ(trailing, trailing_output); // make a sly modification to the buffer and check the string_view matches ASSERT_FALSE(buffer.empty()); buffer[0] = 'A'; ASSERT_EQ(buffer[0], output[0]); ASSERT_NE(input, output); } TEST(BinaryStream, Array) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); const int arr[] = { 1, 2, 3 }; stream << arr; int val = 0; stream >> val; ASSERT_EQ(val, 1); stream >> val; ASSERT_EQ(val, 2); stream >> val; ASSERT_EQ(val, 3); } TEST(BinaryStream, Span) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); const int arr[] = { 4, 9, 2, 1 }; // chosen by fair dice roll stream << arr; auto span = stream.span(4); ASSERT_TRUE(stream.empty()); // not checking directly against the array in case it somehow gets clobbered, // which would mean the span would get clobbered and succeed where it shouldn't ASSERT_EQ(span[0], 4); ASSERT_EQ(span[1], 9); ASSERT_EQ(span[2], 2); ASSERT_EQ(span[3], 1); } TEST(BinaryStream, StaticBufferWrite) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); stream << 'a' << 'b' << 'c' << 'd'; ASSERT_EQ(buffer[0], 'a'); ASSERT_EQ(buffer[1], 'b'); ASSERT_EQ(buffer[2], 'c'); ASSERT_EQ(buffer[3], 'd'); ASSERT_TRUE(stream); } TEST(BinaryStream, StaticBufferDirectWrite) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); std::uint32_t input = 0xBEE5BEE5; std::uint32_t output = 0; buffer.write(&input, sizeof(input)); stream >> output; ASSERT_EQ(input, output); ASSERT_TRUE(stream); } TEST(BinaryStream, StaticBufferOverflow) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); ASSERT_THROW(stream << std::uint64_t(1), spark::io::buffer_overflow); ASSERT_FALSE(stream); } TEST(BinaryStream, StaticBufferRead) { spark::io::StaticBuffer buffer; const std::uint32_t input = 0x11223344; buffer.write(&input, sizeof(input)); spark::io::BinaryStream stream(buffer); std::uint32_t output = 0; stream >> output; ASSERT_EQ(input, output); ASSERT_TRUE(stream); } TEST(BinaryStream, StaticBufferUnderrun) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); std::uint32_t input = 0xBEEFBEEF; std::uint32_t output = 0; stream << input; stream >> output; ASSERT_THROW(stream >> output, spark::io::buffer_underrun); ASSERT_FALSE(stream); ASSERT_EQ(input, output); } TEST(BinaryStream, StaticBufferUnderrunNoExcept) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer, spark::io::no_throw); std::uint32_t output = 0; stream << output; stream >> output; ASSERT_NO_THROW(stream >> output); ASSERT_FALSE(stream); ASSERT_EQ(output, 0); } TEST(BinaryStream, static_buffer_adaptor_regression) { spark::io::StaticBuffer buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(buffer); stream << 1 << 2 << 3; std::string foo { "foo " }; stream << foo; std::string_view sv; stream.skip(sizeof(int) * 3); stream >> sv; ASSERT_TRUE(stream); } TEST(BinaryStream, static_buffer_adaptor_exception_regression) { spark::io::StaticBuffer buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(buffer); std::string_view str { "This is a string that is longer than the size of the buffer..." }; ASSERT_THROW(stream << str, spark::exception); ASSERT_FALSE(stream); } TEST(BinaryStream, StaticBufferAdaptor_NoExceptionRegression) { spark::io::StaticBuffer buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(buffer, spark::io::no_throw); std::string_view str { "This is a string that is longer than the size of the buffer..." }; ASSERT_NO_THROW(stream << str); ASSERT_FALSE(stream); } TEST(BinaryStream, PutIntegralLiterals) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); stream.put(std::numeric_limits::max()); stream.put(std::numeric_limits::max()); stream.put(std::numeric_limits::max()); stream.put(std::numeric_limits::max()); stream.put(std::numeric_limits::max()); stream.put(std::numeric_limits::max()); stream.put(std::numeric_limits::max()); stream.put(std::numeric_limits::max()); stream.put(1.5f); stream.put(3.0); std::uint64_t resultu64 = 0; std::uint32_t resultu32 = 0; std::uint16_t resultu16 = 0; std::uint8_t resultu8 = 0; std::int64_t result64 = 0; std::int32_t result32 = 0; std::int16_t result16 = 0; std::int8_t result8 = 0; float resultf = 0.0f; double resultd = 0.0; stream >> resultu64 >> resultu32 >> resultu16 >> resultu8; stream >> result64 >> result32 >> result16 >> result8; stream >> resultf >> resultd; ASSERT_FLOAT_EQ(1.5f, resultf); ASSERT_DOUBLE_EQ(3.0, resultd); ASSERT_EQ(resultu8, std::numeric_limits::max()); ASSERT_EQ(resultu16, std::numeric_limits::max()); ASSERT_EQ(resultu32, std::numeric_limits::max()); ASSERT_EQ(resultu64, std::numeric_limits::max()); ASSERT_EQ(result8, std::numeric_limits::max()); ASSERT_EQ(result16, std::numeric_limits::max()); ASSERT_EQ(result32, std::numeric_limits::max()); ASSERT_EQ(result64, std::numeric_limits::max()); ASSERT_TRUE(stream); } TEST(BinaryStream, FileBufferRead) { std::filesystem::path path("test_data/filebuffer"); ASSERT_TRUE(std::filesystem::exists(path)); spark::io::FileBuffer buffer(path); ASSERT_TRUE(buffer) << "File open failed"; spark::io::BinaryStream stream(buffer); std::uint8_t w = 0; std::uint16_t x = 0; std::uint32_t y = 0; std::uint64_t z = 0; std::string_view strcmp { "The quick brown fox jumped over the lazy dog." }; std::string str_out; stream >> w >> x >> y >> z >> spark::io::null_terminated(str_out); ASSERT_TRUE(buffer) << "File read error occurred"; boost::endian::little_to_native_inplace(x); boost::endian::little_to_native_inplace(y); boost::endian::little_to_native_inplace(z); ASSERT_EQ(w, 47) << "Wrong uint8 value"; ASSERT_EQ(x, 49197) << "Wrong uint16 value"; ASSERT_EQ(y, 2173709693) << "Wrong uint32 value"; ASSERT_EQ(z, 1438110846748337907) << "Wrong uint64 value"; ASSERT_EQ(str_out, strcmp); } TEST(BinaryStream, FileBufferWrite) { std::filesystem::path path { "tmp_unittest_BinaryStream_FileBufferWrite" }; gsl::final_action fa([path] { std::filesystem::remove(path); }); // in case previous clean-up failed std::filesystem::remove(path); spark::io::FileBuffer buffer(path); ASSERT_TRUE(buffer) << "File open failed"; spark::io::BinaryStream stream(buffer); std::uint8_t w = 47; std::uint16_t x = 49197; std::uint32_t y = 2173709693; std::uint64_t z = 1438110846748337907; std::string str { "The quick brown fox jumped over the lazy dog."}; boost::endian::native_to_little_inplace(x); boost::endian::native_to_little_inplace(y); boost::endian::native_to_little_inplace(z); stream << w << x << y << z << spark::io::null_terminated(str); buffer.flush(); // ensure data is written before following read const auto md5_1 = utility::generate_md5("test_data/filebuffer"); const auto md5_2 = utility::generate_md5(path); ASSERT_EQ(md5_1, md5_2); } TEST(BinaryStream, SetErrorState) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); ASSERT_TRUE(stream); ASSERT_TRUE(stream.good()); ASSERT_TRUE(stream.state() == spark::io::StreamState::ok); stream.set_error_state(); ASSERT_FALSE(stream); ASSERT_FALSE(stream.good()); ASSERT_TRUE(stream.state() == spark::io::StreamState::user_defined_error); } TEST(BinaryStream, StringAdaptor_PrefixedVarint_Long) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); std::string input; // encode varint requiring three bytes input.resize_and_overwrite(80'000, [&](char* buffer, const std::size_t size) { for(std::size_t i = 0; i < size; ++i) { buffer[i] = (rand() % 127) + 32; // ASCII a-z } return size; }); stream << spark::io::prefixed_varint(input); std::string output; stream >> spark::io::prefixed_varint(output); ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); ASSERT_TRUE(stream); } TEST(BinaryStream, StringAdaptor_PrefixedVarint_Medium) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); std::string input; // encode varint requiring two bytes input.resize_and_overwrite(5'000, [&](char* buffer, const std::size_t size) { for(std::size_t i = 0; i < size; ++i) { buffer[i] = (rand() % 127) + 32; // ASCII a-z } return size; }); stream << spark::io::prefixed_varint(input); std::string output; stream >> spark::io::prefixed_varint(output); ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); ASSERT_TRUE(stream); } TEST(BinaryStream, StringAdaptor_PrefixedVarint_Short) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); std::string input; // encode varint requiring one byte input.resize_and_overwrite(127, [&](char* buffer, const std::size_t size) { for(std::size_t i = 0; i < size; ++i) { buffer[i] = (rand() % 127) + 32; // ASCII a-z } return size; }); stream << spark::io::prefixed_varint(input); std::string output; stream >> spark::io::prefixed_varint(output); ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); ASSERT_TRUE(stream); } TEST(BinaryStream, StringAdaptor_Prefixed) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); const std::string input { "The quick brown fox jumped over the lazy dog" }; stream << spark::io::prefixed(input); std::string output; stream >> spark::io::prefixed(output); ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); } TEST(BinaryStream, StringAdaptor_Default) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); const std::string input { "The quick brown fox jumped over the lazy dog" }; stream << input; std::string output; stream >> output; ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); } TEST(BinaryStream, StringAdaptor_Raw) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); const auto input = std::format("String with {} embedded null", '\0'); stream << spark::io::raw(input); ASSERT_EQ(input.size(), buffer.size()); std::string output; stream >> spark::io::null_terminated(output); ASSERT_EQ(output, "String with "); ASSERT_FALSE(stream.empty()); } TEST(BinaryStream, StringAdaptor_NullTerminated) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); const std::string input { "We're just normal strings. Innocent strings."}; stream << spark::io::null_terminated(input); std::string output; stream >> spark::io::null_terminated(output); ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); } TEST(BinaryStream, StringviewAdaptor_PrefixedVarint_Long) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); std::string input; // encode varint requiring three bytes input.resize_and_overwrite(80'000, [&](char* buffer, const std::size_t size) { for(std::size_t i = 0; i < size; ++i) { buffer[i] = (rand() % 127) + 32; // ASCII a-z } return size; }); stream << spark::io::prefixed_varint(input); std::string_view output; stream >> spark::io::prefixed_varint(output); ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); ASSERT_TRUE(stream); } TEST(BinaryStream, StringviewAdaptor_PrefixedVarint_Medium) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); std::string input; // encode varint requiring two bytes input.resize_and_overwrite(5'000, [&](char* buffer, const std::size_t size) { for(std::size_t i = 0; i < size; ++i) { buffer[i] = (rand() % 127) + 32; // ASCII a-z } return size; }); stream << spark::io::prefixed_varint(input); std::string_view output; stream >> spark::io::prefixed_varint(output); ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); ASSERT_TRUE(stream); } TEST(BinaryStream, StringviewAdaptor_PrefixedVarint_Short) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); std::string input; // encode varint requiring one byte input.resize_and_overwrite(127, [&](char* buffer, const std::size_t size) { for(std::size_t i = 0; i < size; ++i) { buffer[i] = (rand() % 127) + 32; // ASCII a-z } return size; }); stream << spark::io::prefixed_varint(input); std::string_view output; stream >> spark::io::prefixed_varint(output); ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); ASSERT_TRUE(stream); } TEST(BinaryStream, StringviewAdaptor_Prefixed) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); std::string_view input { "The quick brown fox jumped over the lazy dog" }; stream << spark::io::prefixed(input); std::string_view output; stream >> spark::io::prefixed(output); ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); } TEST(BinaryStream, StringviewAdaptor_Default) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); std::string_view input { "The quick brown fox jumped over the lazy dog" }; stream << input; std::string_view output; stream >> output; ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); } TEST(BinaryStream, StringviewAdaptor_Raw) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); const auto input = std::format("String with {} embedded null", '\0'); stream << spark::io::raw(input); ASSERT_EQ(input.size(), buffer.size()); std::string_view output; stream >> spark::io::null_terminated(output); ASSERT_EQ(output, "String with "); ASSERT_FALSE(stream.empty()); } TEST(BinaryStream, StringviewAdaptor_NullTerminated) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); std::string_view input { "We're just normal strings. Innocent strings."}; stream << spark::io::null_terminated(input); ASSERT_EQ(stream.size(), input.size() + 1); // +1, account for the terminator std::string_view output; stream >> spark::io::null_terminated(output); ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); } TEST(BinaryStream, StdArray) { std::array buffer{}; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); std::string_view input { "We're just normal strings. Innocent strings."}; // array is considered full by default as size == capacity ASSERT_THROW(stream << input, spark::io::buffer_overflow); adaptor.clear(); stream.clear_error_state(); // try again now we've reset the state std::string_view output; stream << input; stream >> output; ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); } TEST(BinaryStream, TotalWriteConsistency) { std::array buffer; spark::io::BufferAdaptor adaptor(buffer, spark::io::init_empty); spark::io::BinaryStream stream(adaptor); ASSERT_EQ(stream.total_write(), 0); stream << std::uint8_t(0); ASSERT_EQ(stream.total_write(), 1); stream << std::uint16_t(0); ASSERT_EQ(stream.total_write(), 3); stream << std::uint32_t(0); ASSERT_EQ(stream.total_write(), 7); stream << std::uint64_t(0); ASSERT_EQ(stream.total_write(), 15); std::string_view str { "hello, world!" }; stream << spark::io::raw(str); ASSERT_EQ(stream.total_write(), 28); stream << spark::io::prefixed(str); ASSERT_EQ(stream.total_write(), 45); stream << spark::io::prefixed_varint(str); ASSERT_EQ(stream.total_write(), 59); stream << spark::io::null_terminated(str); ASSERT_EQ(stream.total_write(), 73); stream.put(0); ASSERT_EQ(stream.total_write(), 74); stream.put(0); ASSERT_EQ(stream.total_write(), 76); stream.put(0); ASSERT_EQ(stream.total_write(), 80); stream.put(0); ASSERT_EQ(stream.total_write(), 88); std::array data {}; stream.put(data); ASSERT_EQ(stream.total_write(), 104); stream.put(data); ASSERT_EQ(stream.total_write(), 120); stream.put(data); ASSERT_EQ(stream.total_write(), 136); } TEST(BinaryStream, EndianessBigMatch) { std::array buffer{}; spark::io::BufferAdaptor adaptor(buffer, spark::io::init_empty); spark::io::BinaryStream stream(adaptor, spark::io::endian::big); std::uint64_t input = 100, output = 0; stream << std::uint64_t(100); stream >> output; ASSERT_EQ(input, output); } TEST(BinaryStream, EndiannessLittleMatch) { std::array buffer{}; spark::io::BufferAdaptor adaptor(buffer, spark::io::init_empty); spark::io::BinaryStream stream(adaptor, spark::io::endian::little); std::uint64_t input = 100, output = 0; stream << std::uint64_t(100); stream >> output; ASSERT_EQ(input, output); } TEST(BinaryStream, EndiannessNativeMatch) { std::array buffer{}; spark::io::BufferAdaptor adaptor(buffer, spark::io::init_empty); spark::io::BinaryStream stream(adaptor, spark::io::endian::native); std::uint64_t input = 100, output = 0; stream << std::uint64_t(100); stream >> output; ASSERT_EQ(input, output); } TEST(BinaryStream, EndiannessBigExplicitMatch) { std::array buffer{}; spark::io::BufferAdaptor adaptor(buffer, spark::io::init_empty); spark::io::BinaryStream stream(adaptor, spark::io::endian::big); std::uint64_t input = 100, output = 0; stream << spark::io::endian::le(input); stream >> spark::io::endian::le(output); ASSERT_EQ(input, output); stream << spark::io::endian::be(input); stream >> spark::io::endian::be(output); ASSERT_EQ(input, output); } TEST(BinaryStream, EndiannessLittleExplicitMatch) { std::array buffer{}; spark::io::BufferAdaptor adaptor(buffer, spark::io::init_empty); spark::io::BinaryStream stream(adaptor, spark::io::endian::little); std::uint64_t input = 100, output = 0; stream << spark::io::endian::be(input); stream >> spark::io::endian::be(output); ASSERT_EQ(input, output); stream << spark::io::endian::le(input); stream >> spark::io::endian::le(output); ASSERT_EQ(input, output); } TEST(BinaryStream, EndiannessNativeExplicitMatch) { std::array buffer{}; spark::io::BufferAdaptor adaptor(buffer, spark::io::init_empty); spark::io::BinaryStream stream(adaptor, spark::io::endian::native); std::uint64_t input = 100, output = 0; stream << spark::io::endian::be(input); stream >> spark::io::endian::be(output); ASSERT_EQ(input, output); stream << spark::io::endian::be(input); stream >> spark::io::endian::be(output); ASSERT_EQ(input, output); } TEST(BinaryStream, EndiannessBigExplicitMismatch) { std::array buffer{}; spark::io::BufferAdaptor adaptor(buffer, spark::io::init_empty); spark::io::BinaryStream stream(adaptor, spark::io::endian::big); std::uint64_t input = 100, output = 0; stream << spark::io::endian::le(input); stream >> spark::io::endian::be(output); ASSERT_NE(input, output); stream << spark::io::endian::be(input); stream >> spark::io::endian::le(output); ASSERT_NE(input, output); } TEST(BinaryStream, EndiannessLittleExplicitMismatch) { std::array buffer{}; spark::io::BufferAdaptor adaptor(buffer, spark::io::init_empty); spark::io::BinaryStream stream(adaptor, spark::io::endian::little); std::uint64_t input = 100, output = 0; stream << spark::io::endian::le(input); stream >> spark::io::endian::be(output); ASSERT_NE(input, output); stream << spark::io::endian::be(input); stream >> spark::io::endian::le(output); ASSERT_NE(input, output); } TEST(BinaryStream, EndiannessNativeExplicitMismatch) { std::array buffer{}; spark::io::BufferAdaptor adaptor(buffer, spark::io::init_empty); spark::io::BinaryStream stream(adaptor, spark::io::endian::native); std::uint64_t input = 100, output = 0; stream << spark::io::endian::le(input); stream >> spark::io::endian::be(output); ASSERT_NE(input, output); stream << spark::io::endian::be(input); stream >> spark::io::endian::le(output); ASSERT_NE(input, output); } namespace { struct Foo { std::uint16_t x; std::uint32_t y; std::uint64_t z; std::string_view str; void serialise(auto& stream) { stream(x, y, z, spark::io::null_terminated(str)); stream & spark::io::endian::be(x); } bool operator==(const Foo& rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z && str == rhs.str; } }; } TEST(BinaryStream, ExperimentalSerialise) { std::array buffer{}; spark::io::BufferAdaptor adaptor(buffer, spark::io::init_empty); spark::io::BinaryStream stream(adaptor); Foo input { .x = 100, .y = 200, .z = 300, .str = { "It's a fake!" } }; stream.serialise(input); ASSERT_EQ(stream.total_write(), 29); Foo output{}; ASSERT_NE(input, output); stream.deserialise(output); ASSERT_EQ(stream.total_read(), 29); ASSERT_TRUE(stream.empty()); ASSERT_TRUE(stream); ASSERT_EQ(input, output); } namespace { struct Complex { std::string str = "Hello, world!"; std::vector vec { 1, 2, 3, 4, 5 }; std::list list { 6, 7, 8, 9, 10 }; std::set set { 11, 12, 13, 14, 15 }; void serialise(auto& stream) { stream(str, vec, list, set); } }; } TEST(BinaryStream, IterableContainers) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); std::vector primitives { 1, 2, 3, 4, 5 }; stream << primitives; ASSERT_EQ(primitives.size() * sizeof(int), stream.total_write()); ASSERT_EQ(primitives.size() * sizeof(int), adaptor.size()); ASSERT_EQ(primitives.size() * sizeof(int), buffer.size()); std::vector strings { "hello, ", "world!" }; stream << strings; ASSERT_EQ(stream.get(), 1); ASSERT_EQ(stream.get(), 2); ASSERT_EQ(stream.get(), 3); ASSERT_EQ(stream.get(), 4); ASSERT_EQ(stream.get(), 5); std::string hello, world; stream >> hello >> world; ASSERT_EQ(hello, "hello, "); ASSERT_EQ(world, "world!"); Complex obj_in; stream << obj_in; std::string out; stream >> out; ASSERT_EQ(obj_in.str, out); ASSERT_EQ(stream.get(), 1); ASSERT_EQ(stream.get(), 2); ASSERT_EQ(stream.get(), 3); ASSERT_EQ(stream.get(), 4); ASSERT_EQ(stream.get(), 5); ASSERT_EQ(stream.get(), 6); ASSERT_EQ(stream.get(), 7); ASSERT_EQ(stream.get(), 8); ASSERT_EQ(stream.get(), 9); ASSERT_EQ(stream.get(), 10); ASSERT_EQ(stream.get(), 11); ASSERT_EQ(stream.get(), 12); ASSERT_EQ(stream.get(), 13); ASSERT_EQ(stream.get(), 14); ASSERT_EQ(stream.get(), 15); } namespace { struct PrefixedContainers { std::vector vec; std::list list; void serialise(auto& stream) { stream & spark::io::prefixed(vec); stream & spark::io::prefixed(list); } bool operator==(const PrefixedContainers& rhs) const { return vec == rhs.vec && list == rhs.list; } }; } TEST(BinaryStream, PrefixedContainers) { std::vector buffer; spark::io::BufferAdaptor adaptor(buffer); spark::io::BinaryStream stream(adaptor); std::vector primitives { 1, 2, 3, 4, 5 }; stream << spark::io::prefixed(primitives); std::vector output; stream >> spark::io::prefixed(output); ASSERT_TRUE(std::ranges::equal(primitives, output)); std::vector objects; for(int i = 0u; i < 5; ++i) { objects.emplace_back(PrefixedContainers{ { 1 + i, 2 + i }, { 3 + i, 4 + i} }); } stream << spark::io::prefixed(objects); std::vector output_objs; stream >> spark::io::prefixed(output_objs); EXPECT_EQ(objects.size(), output_objs.size()); ASSERT_EQ(objects, output_objs); } TEST(BinaryStream, StdArrayTest) { std::array buffer; spark::io::BufferAdaptor adaptor(buffer, spark::io::init_empty); spark::io::BinaryStream stream(adaptor); ASSERT_TRUE(adaptor.empty()); ASSERT_EQ(adaptor.size(), 0); ASSERT_EQ(stream.size(), 0); } TEST(BinaryStream, StringAdaptor_Prefixed1b) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); const std::string input { "The quick brown fox jumped over the lazy dog" }; stream << spark::io::prefixed(input); std::string output; stream >> spark::io::prefixed(output); ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); } TEST(BinaryStream, StringAdaptor_Prefixed2b) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); const std::string input { "The quick brown fox jumped over the lazy dog" }; stream << spark::io::prefixed(input); std::string output; stream >> spark::io::prefixed(output); ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); } TEST(BinaryStream, StringAdaptor_Prefixed4b) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); const std::string input { "The quick brown fox jumped over the lazy dog" }; stream << spark::io::prefixed(input); std::string output; stream >> spark::io::prefixed(output); ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); } TEST(BinaryStream, StringAdaptor_Prefixed8b) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); const std::string input { "The quick brown fox jumped over the lazy dog" }; stream << spark::io::prefixed(input); std::string output; stream >> spark::io::prefixed(output); ASSERT_EQ(input, output); ASSERT_TRUE(stream.empty()); } TEST(BinaryStream, StringAdaptor_PrefixedBigEndian) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); const std::string input { "The quick brown fox jumped over the lazy dog" }; stream << spark::io::prefixed(input); std::uint32_t read{}; stream >> spark::io::endian::be(read); ASSERT_EQ(input.size(), read); } TEST(BinaryStream, StringAdaptor_PrefixedLittleEndian) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); const std::string input { "The quick brown fox jumped over the lazy dog" }; stream << spark::io::prefixed(input); std::uint32_t read{}; stream >> spark::io::endian::le(read); ASSERT_EQ(input.size(), read); } TEST(BinaryStream, StringAdaptor_PrefixedNativeEndian) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); const std::string input { "The quick brown fox jumped over the lazy dog" }; stream << spark::io::prefixed(input); std::uint32_t read{}; stream >> read; ASSERT_EQ(input.size(), read); } TEST(BinaryStream, StringAdaptor_PrefixedEndianMismatch) { spark::io::StaticBuffer buffer; spark::io::BinaryStream stream(buffer); const std::string input { "The quick brown fox jumped over the lazy dog" }; stream << spark::io::prefixed(input); std::uint32_t read{}; stream >> spark::io::endian::le(read); ASSERT_NE(input.size(), read); }