// Copyright 2023 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h" #include #include #include #include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h" #include "pw_unit_test/framework.h" #pragma clang diagnostic ignored "-Wc99-extensions" namespace bt { namespace { TEST(ByteBufferTest, StaticByteBuffer) { constexpr size_t kBufferSize = 5; StaticByteBuffer buffer; // The buffer is initialized to 0. constexpr std::array kExpectedDefault{ {0x00, 0x00, 0x00, 0x00, 0x00}}; EXPECT_TRUE(ContainersEqual(kExpectedDefault, buffer)); EXPECT_EQ(kBufferSize, buffer.size()); buffer.SetToZeros(); buffer[3] = 3; constexpr std::array kExpected{ {0x00, 0x00, 0x00, 0x03, 0x00}}; EXPECT_TRUE(ContainersEqual(kExpected, buffer)); // Moving will result in a copy. StaticByteBuffer buffer_copy = std::move(buffer); // NOLINT(performance-move-const-arg) EXPECT_EQ(kBufferSize, buffer.size()); // NOLINT(bugprone-use-after-move) EXPECT_EQ(kBufferSize, buffer_copy.size()); EXPECT_TRUE( ContainersEqual(kExpected, buffer)); // NOLINT(bugprone-use-after-move) EXPECT_TRUE(ContainersEqual(kExpected, buffer_copy)); // Copying should also copy. StaticByteBuffer buffer_copy1 = buffer; EXPECT_EQ(kBufferSize, buffer.size()); EXPECT_EQ(kBufferSize, buffer_copy1.size()); EXPECT_TRUE(ContainersEqual(kExpected, buffer)); EXPECT_TRUE(ContainersEqual(kExpected, buffer_copy1)); // Const ByteBuffer should still permit operator[] access. const StaticByteBuffer const_buff(0x10); EXPECT_EQ(0x10, const_buff[0]); } TEST(ByteBufferTest, StaticByteBufferPackConstructor) { constexpr size_t kBufferSize = 3; StaticByteBuffer buffer0; buffer0[0] = 0x01; buffer0[1] = 0x02; buffer0[2] = 0x03; StaticByteBuffer buffer1{0x01, 0x02, 0x03}; StaticByteBuffer buffer2(0x01, 0x02, 0x03); StaticByteBuffer buffer3{0x01, 0x02, 0x03}; EXPECT_TRUE(ContainersEqual(buffer0, buffer1)); EXPECT_TRUE(ContainersEqual(buffer0, buffer2)); EXPECT_TRUE(ContainersEqual(buffer1, buffer2)); EXPECT_TRUE(ContainersEqual(buffer1, buffer3)); // The corresponding check is a BT_DEBUG_ASSERT runtime check EXPECT_DEBUG_DEATH(StaticByteBuffer(-257), "ASSERT"); } TEST(ByteBufferTest, DynamicByteBuffer) { constexpr size_t kBufferSize = 5; DynamicByteBuffer buffer(kBufferSize); EXPECT_EQ(kBufferSize, buffer.size()); // The buffer is initialized to 0. constexpr std::array kExpectedDefault{ {0x00, 0x00, 0x00, 0x00, 0x00}}; EXPECT_TRUE(ContainersEqual(kExpectedDefault, buffer)); buffer[3] = 3; constexpr std::array kExpected{ {0x00, 0x00, 0x00, 0x03, 0x00}}; EXPECT_TRUE(ContainersEqual(kExpected, buffer)); // Moving will invalidate the source buffer. DynamicByteBuffer buffer_moved = std::move(buffer); EXPECT_EQ(0u, buffer.size()); // NOLINT(bugprone-use-after-move) EXPECT_EQ(kBufferSize, buffer_moved.size()); EXPECT_EQ(nullptr, buffer.data()); // NOLINT(bugprone-use-after-move) EXPECT_TRUE(ContainersEqual(kExpected, buffer_moved)); // Const ByteBuffer copied from buffer_moved should permit operator[] access. // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) const DynamicByteBuffer const_buff(buffer_moved); EXPECT_EQ(0x03, const_buff[3]); } TEST(ByteBufferTest, DynamicByteBufferZeroSize) { DynamicByteBuffer buffer; EXPECT_EQ(0u, buffer.size()); EXPECT_EQ(nullptr, buffer.data()); DynamicByteBuffer zerosize(0); EXPECT_EQ(0u, zerosize.size()); EXPECT_EQ(nullptr, buffer.data()); } TEST(ByteBufferTest, DynamicByteBufferConstructFromBuffer) { constexpr size_t kBufferSize = 3; StaticByteBuffer buffer(1, 2, 3); DynamicByteBuffer dyn_buffer(buffer); EXPECT_EQ(kBufferSize, dyn_buffer.size()); EXPECT_TRUE(ContainersEqual(buffer, dyn_buffer)); } TEST(ByteBufferTest, DynamicByteBufferExplicitCopy) { DynamicByteBuffer src(1); src[0] = 'a'; DynamicByteBuffer dst(src); EXPECT_EQ(1u, dst.size()); EXPECT_TRUE(ContainersEqual(src, dst)); } TEST(ByteBufferTest, DynamicByteBufferConstructFromBytes) { constexpr size_t kBufferSize = 3; std::array kExpected{{0, 1, 2}}; auto bytes = std::make_unique(kBufferSize); std::memcpy(bytes.get(), kExpected.data(), kBufferSize); DynamicByteBuffer buffer(kBufferSize, std::move(bytes)); EXPECT_EQ(nullptr, bytes.get()); EXPECT_TRUE(ContainersEqual(kExpected, buffer)); } TEST(ByteBufferTest, DynamicByteBufferConstructFromString) { constexpr size_t kBufferSize = 3; std::array kExpected{{0x41, 0x42, 0x43}}; auto str = std::string("ABC"); DynamicByteBuffer buffer(str); EXPECT_EQ(std::string("ABC"), str); EXPECT_TRUE(ContainersEqual(kExpected, buffer)); } TEST(ByteBufferTest, BufferViewTest) { constexpr size_t kBufferSize = 5; DynamicByteBuffer buffer(kBufferSize); EXPECT_EQ(kBufferSize, buffer.size()); buffer.SetToZeros(); BufferView view(buffer); EXPECT_EQ(0x00, buffer[0]); EXPECT_EQ(0x00, view[0]); EXPECT_EQ(kBufferSize, buffer.size()); EXPECT_EQ(kBufferSize, view.size()); } TEST(ByteBufferTest, BufferViewFromVector) { const std::vector kData{{1, 2, 3}}; BufferView view(kData); EXPECT_TRUE(ContainersEqual(kData, view)); } TEST(ByteBufferTest, MutableBufferViewTest) { constexpr size_t kBufferSize = 5; constexpr size_t kViewSize = 3; DynamicByteBuffer buffer(kBufferSize); EXPECT_EQ(kBufferSize, buffer.size()); buffer.SetToZeros(); MutableBufferView view(buffer.mutable_data(), kViewSize); view[0] = 0x01; EXPECT_EQ(0x01, buffer[0]); EXPECT_EQ(0x01, view[0]); EXPECT_EQ(kBufferSize, buffer.size()); EXPECT_EQ(kViewSize, view.size()); MutableBufferView view2(view); view2[0] = 0x00; EXPECT_EQ(0x00, buffer[0]); EXPECT_EQ(0x00, view[0]); EXPECT_EQ(0x00, view2[0]); EXPECT_EQ(kBufferSize, buffer.size()); EXPECT_EQ(kViewSize, view.size()); } TEST(ByteBufferDeathTest, Copy) { StaticByteBuffer buffer('T', 'e', 's', 't'); BufferView empty_buffer; // Create a large enough buffer. StaticByteBuffer<10> target_buffer; // Copying an empty buffer should copy 0 bytes. empty_buffer.Copy(&target_buffer); // Copy all of |buffer|. The first |buffer.size()| octets of |target_buffer| // should match the contents of |buffer|. buffer.Copy(&target_buffer); EXPECT_TRUE( ContainersEqual(buffer, BufferView(target_buffer, buffer.size()))); // Copy one octet of |buffer| starting at index 2 target_buffer.SetToZeros(); buffer.Copy(&target_buffer, 1, 1); EXPECT_EQ(buffer[1], target_buffer[0]); // Zero the buffer and copy its contents for later comparison. target_buffer.SetToZeros(); auto target_buffer_copy = target_buffer; // Copy all remaining octets starting just past the end of |buffer|. This // should copy zero bytes and |target_buffer| should remain unchanged. buffer.Copy(&target_buffer, buffer.size(), 0); EXPECT_TRUE(ContainersEqual(target_buffer_copy, target_buffer)); // Copied range must remain within buffer limits EXPECT_DEATH_IF_SUPPORTED(buffer.Copy(&target_buffer, buffer.size() + 1, 0), "offset exceeds source range"); EXPECT_DEATH_IF_SUPPORTED(buffer.Copy(&target_buffer, 0, buffer.size() + 1), "end exceeds source range"); EXPECT_DEATH_IF_SUPPORTED( buffer.Copy(&target_buffer, buffer.size() / 2 + 1, buffer.size() / 2 + 1), "end exceeds source range"); StaticByteBuffer<1> insufficient_target_buffer; EXPECT_DEATH_IF_SUPPORTED(buffer.Copy(&insufficient_target_buffer), "destination not large enough"); // Range calculation overflow is fatal rather than silently erroneous MutableBufferView bogus_target_buffer(target_buffer.mutable_data(), std::numeric_limits::max()); EXPECT_DEATH_IF_SUPPORTED( buffer.Copy(&bogus_target_buffer, 1, std::numeric_limits::max()), "end of source range overflows size_t"); } TEST(ByteBufferTest, View) { StaticByteBuffer buffer('T', 'e', 's', 't'); BufferView empty_buffer; BufferView view = empty_buffer.view(); EXPECT_EQ(0u, view.size()); view = empty_buffer.view(0, 200); EXPECT_EQ(0u, view.size()); view = buffer.view(); EXPECT_EQ("Test", view.AsString()); view = buffer.view(4); EXPECT_EQ(0u, view.size()); view = buffer.view(1); EXPECT_EQ("est", view.AsString()); view = buffer.view(1, 1); EXPECT_EQ("e", view.AsString()); view = buffer.view(1, 2); EXPECT_EQ("es", view.AsString()); } TEST(ByteBufferTest, Span) { StaticByteBuffer buffer('T', 'e', 's', 't'); BufferView empty_buffer; pw::span span = empty_buffer.subspan(); EXPECT_EQ(0u, span.size()); span = empty_buffer.subspan(0, 200); EXPECT_EQ(0u, span.size()); span = buffer.subspan(); EXPECT_THAT( span, ::testing::ElementsAreArray( {std::byte{'T'}, std::byte{'e'}, std::byte{'s'}, std::byte{'t'}})); span = buffer.subspan(4); EXPECT_EQ(0u, span.size()); span = buffer.subspan(1); EXPECT_THAT(span, ::testing::ElementsAreArray( {std::byte{'e'}, std::byte{'s'}, std::byte{'t'}})); span = buffer.subspan(1, 1); EXPECT_THAT(span, ::testing::ElementsAreArray({std::byte{'e'}})); span = buffer.subspan(1, 2); EXPECT_THAT(span, ::testing::ElementsAreArray({std::byte{'e'}, std::byte{'s'}})); } TEST(ByteBufferTest, MutableView) { StaticByteBuffer buffer('T', 'e', 's', 't'); MutableBufferView empty_buffer; MutableBufferView view; view = empty_buffer.mutable_view(); EXPECT_EQ(0u, view.size()); view = empty_buffer.mutable_view(0, 200); EXPECT_EQ(0u, view.size()); view = buffer.mutable_view(); EXPECT_EQ("Test", view.AsString()); view = buffer.mutable_view(4); EXPECT_EQ(0u, view.size()); view = buffer.mutable_view(1); EXPECT_EQ("est", view.AsString()); view = buffer.mutable_view(1, 1); EXPECT_EQ("e", view.AsString()); view = buffer.mutable_view(1, 2); EXPECT_EQ("es", view.AsString()); // MutableView can modify the underlying buffer. view[0] = 'E'; EXPECT_EQ("Es", view.AsString()); EXPECT_EQ("TEst", buffer.AsString()); } TEST(ByteBufferTest, ByteBufferEqualityFail) { const StaticByteBuffer kData0('T', 'e', 's', 't'); const StaticByteBuffer kData1('F', 'o', 'o'); EXPECT_FALSE(kData0 == kData1); } TEST(ByteBufferTest, ByteBufferEqualitySuccess) { const StaticByteBuffer kData0('T', 'e', 's', 't'); const StaticByteBuffer kData1('T', 'e', 's', 't'); EXPECT_TRUE(kData0 == kData1); } TEST(ByteBufferDeathTest, ByteBufferWithInsufficientBytesAssertsOnTo) { StaticByteBuffer buffer(0, 0, 0); ASSERT_GT(sizeof(float), buffer.size()); EXPECT_DEATH_IF_SUPPORTED( [=] { [[maybe_unused]] auto _ = buffer.To(); }(), "end exceeds source range"); } template void TestByteBufferRoundtrip(std::initializer_list values) { for (auto value : values) { BufferView value_view(&value, sizeof(value)); auto to_value = value_view.To(); SCOPED_TRACE(value); EXPECT_EQ(0, std::memcmp(&value, &to_value, sizeof(T))); } } TEST(ByteBufferTest, ByteBufferToRoundtripOnPrimitives) { // Test values from absl::bit_cast tests { SCOPED_TRACE("bool"); TestByteBufferRoundtrip({true, false}); } { SCOPED_TRACE("array 1 of const bool"); TestByteBufferRoundtrip({{true}, {false}}); } { SCOPED_TRACE("int32_t"); TestByteBufferRoundtrip( {0, 1, 100, 2147483647, -1, -100, -2147483647, -2147483647 - 1}); } { SCOPED_TRACE("int64_t"); TestByteBufferRoundtrip({0, 1, 1LL << 40, -1, -(1LL << 40)}); } { SCOPED_TRACE("uint64_t"); TestByteBufferRoundtrip({0, 1, 1LLU << 40, 1LLU << 63}); } { SCOPED_TRACE("float"); TestByteBufferRoundtrip({0.0f, 1.0f, -1.0f, 10.0f, -10.0f, 1e10f, 1e20f, 1e-10f, 1e-20f, 2.71828f, 3.14159f}); } { SCOPED_TRACE("double"); TestByteBufferRoundtrip( {0.0, 1.0, -1.0, 10.0, -10.0, 1e10, 1e100, 1e-10, 1e-100, 2.718281828459045, 3.141592653589793238462643383279502884197169399375105820974944}); } } TEST(ByteBufferTest, ByteBufferToStripsCvQualifiers) { auto to_value = StaticByteBuffer(2).To(); static_assert(std::is_same_v); } TEST(ByteBufferTest, ByteBufferWithAdditionalBytesToBasicType) { EXPECT_EQ(191u, StaticByteBuffer(191, 25).To()); } TEST(ByteBufferTest, ByteBufferToStruct) { const StaticByteBuffer data(10, 12); struct [[gnu::packed]] point { uint8_t x; uint8_t y; }; EXPECT_EQ(10, data.To().x); EXPECT_EQ(12, data.To().y); } TEST(ByteBufferTest, ByteBufferToArray) { const StaticByteBuffer buf(191, 25); const auto array = buf.To(); EXPECT_EQ(buf.size(), array.size()); EXPECT_EQ(191, array[0]); EXPECT_EQ(25, array[1]); } TEST(ByteBufferTest, ByteBufferToDoesNotReadUnaligned) { const DynamicByteBuffer buf(2 * sizeof(float) - 1); // Advance past the float alignment boundary const size_t offset = std::distance( buf.begin(), std::find_if(buf.begin(), buf.end(), [](auto& b) { return reinterpret_cast(&b) % alignof(float) != 0; })); BufferView view = buf.view(offset, sizeof(float)); // Prove that the data in the view isn't aligned for a float ASSERT_NE(0U, reinterpret_cast(view.data()) % alignof(float)); // This cref binds to a new object that ByteBuffer::To creates, so the // alignment is correct const float& to_object = view.To(); EXPECT_EQ(0U, reinterpret_cast(&to_object) % alignof(float)); } TEST(ByteBufferTest, ByteBufferReadMember) { struct [[gnu::packed]] Point { uint8_t x; const uint8_t y; const uint8_t array[2]; char multi[2][1]; uint8_t flex[]; }; StaticByteBuffer data(0x01, 0x02, 0x03, 0x37, 0x7f, 0x02, 0x45); auto x = data.ReadMember<&Point::x>(); static_assert(std::is_same_v); EXPECT_EQ(data[offsetof(Point, x)], x); // Top-level const qualifier is removed auto y = data.ReadMember<&Point::y>(); static_assert(std::is_same_v); EXPECT_EQ(data[offsetof(Point, y)], y); // Returned array elements are const just like in the original struct auto array = data.ReadMember<&Point::array>(); static_assert(std::is_same_v, decltype(array)>); EXPECT_THAT(array, ::testing::ElementsAre(uint8_t{0x03}, uint8_t{0x37})); auto multi = data.ReadMember<&Point::multi>(); static_assert( std::is_same_v, 2>, decltype(multi)>); EXPECT_THAT( multi, ::testing::ElementsAre(std::array{char{0x7f}}, std::array{char{0x02}})); } TEST(ByteBufferDeathTest, ByteBufferReadMemberOfFixedArrayType) { struct [[gnu::packed]] Point { float f; int8_t coordinates[3]; char multi[2][1]; }; StaticByteBuffer data( // f 0, 0, 0, 0, // coordinates[3] 0x01, 0x02, 0x03, // multi[2][1] 0x37, 0x45); ASSERT_LE(sizeof(Point), data.size()); EXPECT_DEATH_IF_SUPPORTED(data.ReadMember<&Point::coordinates>(3), "index past array bounds"); auto view = data.view(0, 6); ASSERT_GT(sizeof(Point), view.size()); EXPECT_DEATH_IF_SUPPORTED(view.ReadMember<&Point::coordinates>(0), "insufficient buffer"); EXPECT_EQ(data[offsetof(Point, coordinates)], data.ReadMember<&Point::coordinates>(0)); EXPECT_EQ(data[offsetof(Point, coordinates) + 1], data.ReadMember<&Point::coordinates>(1)); // Elements of a multi-dimensional C array are returned as std::arrays auto inner = data.ReadMember<&Point::multi>(1); EXPECT_EQ(data[offsetof(Point, multi) + 1], inner.at(0)); } TEST(ByteBufferDeathTest, ByteBufferReadMemberOfStdArrayType) { struct [[gnu::packed]] Point { float f; std::array coordinates; }; StaticByteBuffer data(0, 0, 0, 0, 0x01, 0x02, 0x03, 0x37); ASSERT_LE(sizeof(Point), data.size()); EXPECT_DEATH_IF_SUPPORTED(data.ReadMember<&Point::coordinates>(3), "index past array bounds"); EXPECT_EQ(data[offsetof(Point, coordinates)], data.ReadMember<&Point::coordinates>(0)); EXPECT_EQ(data[offsetof(Point, coordinates) + 1], data.ReadMember<&Point::coordinates>(1)); } TEST(ByteBufferDeathTest, ByteBufferReadMemberOfFlexibleArrayType) { struct [[gnu::packed]] Point { uint16_t dimensions; int8_t coordinates[]; }; StaticByteBuffer data(0, 0, 0x01, 0x02); ASSERT_LE(sizeof(Point), data.size()); EXPECT_DEATH_IF_SUPPORTED(data.ReadMember<&Point::coordinates>(2), "end exceeds source range"); EXPECT_EQ(data[offsetof(Point, coordinates)], data.ReadMember<&Point::coordinates>(0)); EXPECT_EQ(data[offsetof(Point, coordinates) + 1], data.ReadMember<&Point::coordinates>(1)); } TEST(ByteBufferTest, ByteBufferReadMemberOfUnalignedArrayType) { struct [[gnu::packed]] Point { int8_t byte; float f[1]; } point; BufferView view(&point, sizeof(point)); static_assert(alignof(decltype(view.ReadMember<&Point::f>(0))) == alignof(float)); // This branch (that the second field of |point| is unaligned) does get taken // in manual testing but there's no way to guarantee it, so make it run-time // conditional. if (reinterpret_cast(&point.f) % alignof(float) != 0) { // Casting (which is UB here) a pointer into the buffer contents yields a // pointer that is not aligned to type requirements const float* unaligned_pointer = reinterpret_cast(view.data())->f; ASSERT_NE(0U, reinterpret_cast(unaligned_pointer) % alignof(float)); } // The same cref binds to a temporary new object that ByteBuffer::ReadMember // creates, so the alignment is correct. const float& through_read_member = view.ReadMember<&Point::f>(0); EXPECT_EQ(0U, reinterpret_cast(&through_read_member) % alignof(float)); } TEST(ByteBufferTest, MutableByteBufferAsMutableFundamental) { StaticByteBuffer data(10, 12); ++*data.AsMutable(); EXPECT_EQ(11, data[0]); } TEST(ByteBufferTest, MutableByteBufferAsMutableStruct) { StaticByteBuffer data(10, 12); struct point { uint8_t x; uint8_t y; }; ++data.AsMutable()->x; ++data.AsMutable()->y; const StaticByteBuffer expected_data(11, 13); EXPECT_EQ(expected_data, data); } TEST(ByteBufferTest, MutableByteBufferAsMutableArray) { StaticByteBuffer buf(10, 12); uint8_t(&array)[2] = *buf.AsMutable(); ++array[0]; ++array[1]; EXPECT_EQ(11, buf[0]); EXPECT_EQ(13, buf[1]); } TEST(ByteBufferDeathTest, MutableByteBufferWrite) { const StaticByteBuffer kData0('T', 'e', 's', 't'); const StaticByteBuffer kData1('F', 'o', 'o'); StaticByteBuffer buffer('X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'); EXPECT_EQ("XXXXXXXX", buffer.AsString()); buffer.Write(kData0); EXPECT_EQ("TestXXXX", buffer.AsString()); // Write from raw pointer. buffer = StaticByteBuffer('X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'); buffer.Write(kData0.data(), kData0.size()); EXPECT_EQ("TestXXXX", buffer.AsString()); // Write at offset. buffer.Write(kData1, 1); EXPECT_EQ("TFooXXXX", buffer.AsString()); // Write at offset from raw pointer buffer.Write(kData1.data(), kData1.size(), 3); EXPECT_EQ("TFoFooXX", buffer.AsString()); // Writing zero bytes should have no effect. buffer = StaticByteBuffer('X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'); buffer.Write(kData1.data(), 0u); buffer.Write(/*data=*/nullptr, 0u); // Passing nullptr is OK when size is 0 EXPECT_EQ("XXXXXXXX", buffer.AsString()); // Writing zero bytes just past the buffer should be accepted (i.e. no // assertion) and have no effect. buffer.Write(kData1.data(), 0u, buffer.size()); EXPECT_EQ("XXXXXXXX", buffer.AsString()); // Buffer limits are strictly enforced EXPECT_DEATH_IF_SUPPORTED(buffer.Write(kData0.data(), buffer.size() + 1, 0), "destination not large enough"); EXPECT_DEATH_IF_SUPPORTED(buffer.Write(kData0.data(), 0, buffer.size() + 1), "offset past buffer"); } TEST(ByteBufferTest, AsString) { StaticByteBuffer buffer('T', 'e', 's', 't'); EXPECT_EQ("Test", buffer.AsString()); } TEST(ByteBufferTest, Fill) { StaticByteBuffer<5> buffer; buffer.Fill('A'); EXPECT_EQ("AAAAA", buffer.AsString()); } TEST(ByteBufferTest, ToVectorEmpty) { BufferView buffer; std::vector vec = buffer.ToVector(); EXPECT_TRUE(vec.empty()); } TEST(ByteBufferTest, ToVector) { StaticByteBuffer buffer('h', 'e', 'l', 'l', 'o'); std::vector vec = buffer.ToVector(); EXPECT_TRUE(ContainersEqual(vec, buffer)); } TEST(ByteBufferTest, PrintableAscii) { StaticByteBuffer buffer('f', 'o', 'o', 'b', 'a', 'r', 'b', 'a', 'z'); std::string result = buffer.Printable(3, 3); EXPECT_EQ("bar", result); } TEST(ByteBufferTest, PrintableUtf8) { StaticByteBuffer buffer( 0xce, 0xba, 0xe1, 0xbd, 0xb9, 0xcf, 0x83, 0xce, 0xbc, 0xce, 0xB5); std::string result = buffer.Printable(0, buffer.size()); EXPECT_EQ("κόσμε", result); } TEST(ByteBufferTest, PrintableInvalidUtf8) { StaticByteBuffer buffer(0xce, 0xba, 0x80); std::string result = buffer.Printable(0, buffer.size()); EXPECT_EQ("...", result); } TEST(ByteBufferDeathTest, PrintableSizeTooLarge) { StaticByteBuffer buffer(0xce, 0xba, 0x80); ASSERT_DEATH({ buffer.Printable(0, buffer.size() + 1); }, ""); ASSERT_DEATH({ buffer.Printable(1, buffer.size()); }, ""); ASSERT_DEATH({ buffer.Printable(1, buffer.size() + 1); }, ""); } } // namespace } // namespace bt