| // Copyright 2019 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_string/string_builder.h" |
| |
| #include <cinttypes> |
| #include <cmath> |
| #include <cstdint> |
| #include <cstring> |
| #include <span> |
| #include <string_view> |
| |
| #include "gtest/gtest.h" |
| #include "pw_string/format.h" |
| |
| namespace this_pw_test { |
| |
| struct CustomType { |
| uint32_t a; |
| uint32_t b; |
| |
| static constexpr const char* kToString = "This is a CustomType"; |
| |
| CustomType() = default; |
| |
| // Non-copyable to verify StringBuffer's << operator doesn't copy it. |
| CustomType(const CustomType&) = delete; |
| CustomType& operator=(const CustomType&) = delete; |
| }; |
| |
| } // namespace this_pw_test |
| |
| namespace pw { |
| |
| template <> |
| StatusWithSize ToString<this_pw_test::CustomType>( |
| const this_pw_test::CustomType&, std::span<char> buffer) { |
| return string::Format(buffer, this_pw_test::CustomType::kToString); |
| } |
| |
| } // namespace pw |
| |
| namespace pw { |
| namespace { |
| |
| using this_pw_test::CustomType; |
| |
| TEST(StringBuilder, EmptyBuffer_SizeAndMaxSizeAreCorrect) { |
| StringBuilder sb(std::span<char>{}); |
| |
| EXPECT_TRUE(sb.empty()); |
| EXPECT_EQ(0u, sb.size()); |
| EXPECT_EQ(0u, sb.max_size()); |
| } |
| |
| using namespace std::literals::string_view_literals; |
| |
| constexpr std::string_view kNoTouch = "DO NOT TOUCH\0VALUE SHOULD NOT CHANGE"sv; |
| |
| TEST(StringBuilder, EmptyBuffer_StreamOutput_WritesNothing) { |
| char buffer[kNoTouch.size()]; |
| std::memcpy(buffer, kNoTouch.data(), sizeof(buffer)); |
| |
| StringBuilder sb(std::span(buffer, 0)); |
| |
| sb << CustomType() << " is " << 12345; |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status()); |
| EXPECT_EQ(kNoTouch, std::string_view(buffer, sizeof(buffer))); |
| } |
| |
| TEST(StringBuilder, EmptyBuffer_Append_WritesNothing) { |
| char buffer[kNoTouch.size()]; |
| std::memcpy(buffer, kNoTouch.data(), sizeof(buffer)); |
| |
| StringBuilder sb(std::span(buffer, 0)); |
| |
| EXPECT_FALSE(sb.append("Hello").ok()); |
| EXPECT_EQ(kNoTouch, std::string_view(buffer, sizeof(buffer))); |
| } |
| |
| TEST(StringBuilder, EmptyBuffer_Resize_WritesNothing) { |
| char buffer[kNoTouch.size()]; |
| std::memcpy(buffer, kNoTouch.data(), sizeof(buffer)); |
| |
| StringBuilder sb(std::span(buffer, 0)); |
| |
| sb.resize(0); |
| EXPECT_TRUE(sb.ok()); |
| EXPECT_EQ(kNoTouch, std::string_view(buffer, sizeof(buffer))); |
| } |
| |
| TEST(StringBuilder, EmptyBuffer_AppendEmpty_ResourceExhausted) { |
| StringBuilder sb(std::span<char>{}); |
| EXPECT_EQ(Status::OK, sb.last_status()); |
| EXPECT_EQ(Status::OK, sb.status()); |
| |
| sb << ""; |
| |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.last_status()); |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status()); |
| } |
| |
| TEST(StringBuilder, Status_StartsOk) { |
| StringBuffer<16> sb; |
| EXPECT_EQ(Status::OK, sb.status()); |
| EXPECT_EQ(Status::OK, sb.last_status()); |
| } |
| |
| TEST(StringBuilder, Status_StatusAndLastStatusUpdate) { |
| StringBuffer<16> sb; |
| sb << "Well, if only there were enough room in here for this string"; |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status()); |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.last_status()); |
| |
| sb.resize(1029); |
| EXPECT_EQ(Status::OUT_OF_RANGE, sb.status()); |
| EXPECT_EQ(Status::OUT_OF_RANGE, sb.last_status()); |
| |
| sb << ""; |
| EXPECT_EQ(Status::OUT_OF_RANGE, sb.status()); |
| EXPECT_EQ(Status::OK, sb.last_status()); |
| } |
| |
| TEST(StringBuilder, Status_ClearStatus_SetsStatuesToOk) { |
| StringBuffer<2> sb = MakeString<2>("Won't fit!!!!!"); |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status()); |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.last_status()); |
| |
| sb.clear_status(); |
| EXPECT_EQ(Status::OK, sb.status()); |
| EXPECT_EQ(Status::OK, sb.last_status()); |
| } |
| |
| TEST(StringBuilder, StreamOutput_OutputSelf) { |
| auto sb = MakeString<32>("echo!"); |
| sb << sb; |
| |
| EXPECT_STREQ("echo!echo!", sb.data()); |
| EXPECT_EQ(10u, sb.size()); |
| } |
| |
| TEST(StringBuilder, PushBack) { |
| StringBuffer<12> sb; |
| sb.push_back('?'); |
| EXPECT_EQ(Status::OK, sb.last_status()); |
| EXPECT_EQ(1u, sb.size()); |
| EXPECT_STREQ("?", sb.data()); |
| } |
| |
| TEST(StringBuilder, PushBack_Full) { |
| StringBuffer<1> sb; |
| sb.push_back('!'); |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.last_status()); |
| EXPECT_EQ(0u, sb.size()); |
| } |
| |
| TEST(StringBuilder, PopBack) { |
| auto sb = MakeString<12>("Welcome!"); |
| sb.pop_back(); |
| EXPECT_EQ(Status::OK, sb.last_status()); |
| EXPECT_EQ(7u, sb.size()); |
| EXPECT_STREQ("Welcome", sb.data()); |
| } |
| |
| TEST(StringBuilder, PopBack_Empty) { |
| StringBuffer<12> sb; |
| sb.pop_back(); |
| EXPECT_EQ(Status::OUT_OF_RANGE, sb.last_status()); |
| EXPECT_EQ(0u, sb.size()); |
| } |
| |
| TEST(StringBuilder, Append_NonTerminatedString) { |
| static char bad_string[256]; |
| std::memset(bad_string, '?', sizeof(bad_string)); |
| |
| StringBuffer<6> sb; |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.append(bad_string).last_status()); |
| EXPECT_STREQ("?????", sb.data()); |
| } |
| |
| TEST(StringBuilder, Append_Chars) { |
| StringBuffer<8> sb; |
| |
| EXPECT_TRUE(sb.append(7, '?').ok()); |
| EXPECT_STREQ("???????", sb.data()); |
| } |
| |
| TEST(StringBuilder, Append_Chars_Full) { |
| StringBuffer<8> sb; |
| |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.append(8, '?').last_status()); |
| EXPECT_STREQ("???????", sb.data()); |
| } |
| |
| TEST(StringBuilder, Append_PartialCString) { |
| StringBuffer<12> sb; |
| EXPECT_TRUE(sb.append("123456", 4).ok()); |
| EXPECT_EQ(4u, sb.size()); |
| EXPECT_STREQ("1234", sb.data()); |
| } |
| |
| TEST(StringBuilder, Append_CString) { |
| auto sb = MakeString("hello"); |
| EXPECT_TRUE(sb.append(" goodbye").ok()); |
| EXPECT_STREQ("hello goodbye", sb.data()); |
| EXPECT_EQ(13u, sb.size()); |
| } |
| |
| TEST(StringBuilder, Append_CString_Full) { |
| auto sb = MakeString<6>("hello"); |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.append("890123", 1).last_status()); |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status()); |
| EXPECT_EQ(sb.max_size(), sb.size()); |
| EXPECT_STREQ("hello", sb.data()); |
| } |
| |
| TEST(StringBuilder, Append_StringView) { |
| auto sb = MakeString<32>("hello"); |
| EXPECT_TRUE(sb.append("???"sv).ok()); |
| EXPECT_EQ("hello???"sv, sb); |
| } |
| |
| TEST(StringBuilder, Append_StringView_Substring) { |
| auto sb = MakeString<32>("I like "); |
| EXPECT_TRUE(sb.append("your shoes!!!"sv, 5, 5).ok()); |
| EXPECT_EQ("I like shoes"sv, sb); |
| } |
| |
| TEST(StringBuilder, Append_StringView_RemainingSubstring) { |
| auto sb = MakeString<32>("I like "); |
| EXPECT_TRUE(sb.append("your shoes!!!"sv, 5).ok()); |
| EXPECT_EQ("I like shoes!!!"sv, sb); |
| } |
| |
| TEST(StringBuilder, Resize_Smaller) { |
| auto sb = MakeString<12>("Four"); |
| sb.resize(2); |
| EXPECT_TRUE(sb.ok()); |
| EXPECT_EQ(2u, sb.size()); |
| EXPECT_STREQ("Fo", sb.data()); |
| } |
| |
| TEST(StringBuilder, Resize_Clear) { |
| auto sb = MakeString<12>("Four"); |
| sb.resize(0); |
| EXPECT_TRUE(sb.ok()); |
| EXPECT_EQ(0u, sb.size()); |
| EXPECT_STREQ("", sb.data()); |
| } |
| |
| TEST(StringBuilder, Resize_Larger_Fails) { |
| auto sb = MakeString<12>("Four"); |
| EXPECT_EQ(4u, sb.size()); |
| sb.resize(10); |
| EXPECT_EQ(sb.status(), Status::OUT_OF_RANGE); |
| EXPECT_EQ(4u, sb.size()); |
| } |
| |
| TEST(StringBuilder, Resize_LargerThanCapacity_Fails) { |
| auto sb = MakeString<12>("Four"); |
| sb.resize(1234); |
| EXPECT_EQ(sb.status(), Status::OUT_OF_RANGE); |
| EXPECT_EQ(4u, sb.size()); |
| EXPECT_STREQ("Four", sb.data()); |
| } |
| |
| TEST(StringBuilder, Format_Normal) { |
| std::byte buffer[64]; |
| StringBuilder sb(buffer); |
| EXPECT_TRUE(sb.Format("0x%x", 0xabc).ok()); |
| EXPECT_STREQ("0xabc", sb.data()); |
| |
| sb << "def"; |
| |
| EXPECT_TRUE(sb.Format("GHI").ok()); |
| EXPECT_STREQ("0xabcdefGHI", sb.data()); |
| } |
| |
| TEST(StringBuilder, Format_ExhaustBuffer) { |
| StringBuffer<6> sb; |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.Format("012345").status()); |
| |
| EXPECT_STREQ("01234", sb.data()); |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status()); |
| } |
| |
| TEST(StringBuilder, StreamOutput_MultipleTypes) { |
| constexpr const char* kExpected = "This is -1true example\n of this"; |
| constexpr const char* kExample = "example"; |
| |
| StringBuffer<64> sb; |
| sb << "This is " << -1 << true << ' ' << kExample << '\n' << " of this"; |
| |
| EXPECT_STREQ(kExpected, sb.data()); |
| EXPECT_EQ(std::strlen(kExpected), sb.size()); |
| } |
| |
| TEST(StringBuilder, StreamOutput_FullBufferIgnoresExtraStrings) { |
| StringBuffer<6> sb; |
| EXPECT_EQ(5u, sb.max_size()); // max_size() excludes the null terminator |
| |
| sb << 1 - 1; |
| EXPECT_TRUE(sb.ok()); |
| EXPECT_STREQ("0", sb.data()); |
| |
| sb << true << "Now it's way " << static_cast<unsigned char>(2) << " long"; |
| EXPECT_FALSE(sb.ok()); |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status()); |
| EXPECT_STREQ("0true", sb.data()); |
| } |
| |
| TEST(StringBuilder, StreamOutput_ExhaustBuffer_InOneString) { |
| StringBuffer<9> sb; |
| EXPECT_EQ(8u, sb.max_size()); |
| |
| sb << "0123456789"; // write 10 chars |
| EXPECT_FALSE(sb.ok()); |
| EXPECT_STREQ("01234567", sb.data()); // only can fit 8 |
| EXPECT_EQ(8u, sb.size()); |
| |
| sb << "no" |
| << " more " |
| << "room" << '?'; |
| EXPECT_STREQ("01234567", sb.data()); |
| } |
| |
| TEST(StringBuilder, StreamOutput_ExhaustBuffer_InTwoStrings) { |
| StringBuffer<4> sb; |
| |
| sb << "01"; // fill 3/4 of buffer |
| EXPECT_EQ(2u, sb.size()); |
| sb << "234"; |
| EXPECT_STREQ("012", sb.data()); |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status()); |
| EXPECT_EQ(3u, sb.size()); |
| } |
| |
| TEST(StringBuilder, StreamOutput_NonTerminatedString) { |
| static char bad_string[256]; |
| std::memset(bad_string, '?', sizeof(bad_string)); |
| |
| StringBuffer<6> sb; |
| sb << "hey" << bad_string; |
| |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status()); |
| EXPECT_STREQ("hey??", sb.data()); |
| } |
| |
| TEST(StringBuilder, SteamOutput_StringView) { |
| StringBuffer<6> buffer; |
| constexpr std::string_view hello("hello"); |
| |
| buffer << hello; |
| EXPECT_EQ(Status::OK, buffer.status()); |
| EXPECT_STREQ("hello", buffer.data()); |
| } |
| |
| TEST(StringBuilder, StreamOutput_EmptyStringView) { |
| StringBuffer<4> buffer; |
| buffer << "hi" << std::string_view() << "!"; |
| EXPECT_TRUE(buffer.ok()); |
| EXPECT_STREQ("hi!", buffer.data()); |
| } |
| |
| TEST(StringBuffer, Assign) { |
| StringBuffer<10> one; |
| StringBuffer<10> two; |
| |
| one << "What"; |
| ASSERT_STREQ("What", one.data()); |
| two = one; |
| EXPECT_STREQ("What", two.data()); |
| EXPECT_NE(one.data(), two.data()); |
| one << " the"; |
| two << " heck"; |
| |
| EXPECT_STREQ("What the", one.data()); |
| EXPECT_STREQ("What heck", two.data()); |
| |
| two << "0123456789"; |
| ASSERT_STREQ("What heck", two.data()); |
| ASSERT_EQ(Status::RESOURCE_EXHAUSTED, two.status()); |
| ASSERT_EQ(Status::RESOURCE_EXHAUSTED, two.last_status()); |
| |
| one = two; |
| EXPECT_STREQ("What heck", one.data()); |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, one.status()); |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, one.last_status()); |
| |
| StringBuffer<12> three; |
| three = two; |
| EXPECT_STREQ(three.data(), two.data()); |
| EXPECT_EQ(three.size(), two.size()); |
| } |
| |
| TEST(StringBuffer, CopyConstructFromSameSize) { |
| StringBuffer<10> one; |
| |
| one << "What"; |
| ASSERT_STREQ("What", one.data()); |
| StringBuffer<10> two(one); |
| EXPECT_STREQ("What", two.data()); |
| EXPECT_NE(one.data(), two.data()); |
| one << " the"; |
| two << " heck"; |
| |
| EXPECT_STREQ("What the", one.data()); |
| EXPECT_STREQ("What heck", two.data()); |
| |
| two << "0123456789"; |
| two << ""; |
| ASSERT_STREQ("What heck", two.data()); |
| ASSERT_EQ(Status::RESOURCE_EXHAUSTED, two.status()); |
| ASSERT_EQ(Status::OK, two.last_status()); |
| } |
| |
| TEST(StringBuffer, CopyConstructFromSmaller) { |
| StringBuffer<10> one = MakeString<10>("You are the chosen one."); |
| StringBuffer<12> two(one); |
| |
| EXPECT_STREQ("You are t", two.data()); |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, two.status()); |
| } |
| |
| TEST(StringBuilder, Object) { |
| StringBuffer<64> sb; |
| sb << CustomType(); |
| |
| EXPECT_STREQ(CustomType::kToString, sb.data()); |
| EXPECT_EQ(std::strlen(CustomType::kToString), sb.size()); |
| } |
| |
| TEST(MakeString, Object) { |
| CustomType custom; |
| const auto sb = MakeString<64>(custom); |
| |
| EXPECT_STREQ(CustomType::kToString, sb.data()); |
| EXPECT_EQ(std::strlen(CustomType::kToString), sb.size()); |
| } |
| |
| TEST(MakeString, IntegerTypes) { |
| EXPECT_STREQ("0123-4567", |
| MakeString(0ll, |
| 1u, |
| 2l, |
| 3, |
| -4, |
| static_cast<unsigned short>(5), |
| static_cast<short>(6), |
| static_cast<unsigned char>(7)) |
| .data()); |
| } |
| |
| TEST(MakeString, Char) { |
| EXPECT_STREQ("a b c", MakeString('a', ' ', 'b', ' ', 'c').data()); |
| } |
| |
| TEST(MakeString, Float) { EXPECT_STREQ("-inf", MakeString(-INFINITY).data()); } |
| |
| TEST(MakeString, Pointer_Null) { |
| EXPECT_STREQ("(null)", MakeString(nullptr).data()); |
| EXPECT_STREQ("(null)", MakeString(static_cast<void*>(nullptr)).data()); |
| } |
| |
| TEST(MakeString, Pointer_NonNull) { |
| EXPECT_STREQ("1", MakeString(reinterpret_cast<void*>(0x1)).data()); |
| EXPECT_STREQ("123", MakeString(reinterpret_cast<int*>(0x123)).data()); |
| } |
| |
| TEST(MakeString, Pointer_CustomType) { |
| char expected[32] = {}; |
| |
| CustomType custom; |
| std::snprintf(expected, |
| sizeof(expected), |
| "%" PRIxPTR, |
| reinterpret_cast<uintptr_t>(&custom)); |
| |
| EXPECT_STREQ(expected, MakeString(&custom).data()); |
| } |
| |
| TEST(MakeString, Bool) { |
| EXPECT_STREQ("true", MakeString(true).data()); |
| EXPECT_STREQ("false", MakeString(false).data()); |
| } |
| |
| TEST(MakeString, MutableString) { |
| char chars[] = {'C', 'o', 'o', 'l', '\0'}; |
| EXPECT_STREQ("Cool?", MakeString(chars, "?").data()); |
| } |
| |
| TEST(MakeString, Empty_IsEmpty) { EXPECT_TRUE(MakeString().empty()); } |
| |
| constexpr char kLongestString[] = "18446744073709551615"; // largest uint64_t |
| |
| TEST(MakeString, DefaultSizeString_FitsWholeString) { |
| EXPECT_STREQ( |
| kLongestString, |
| MakeString(184, "467", u'\x04', "40", '7', '3', '7', "0", "", 955ul, 1615) |
| .data()); |
| } |
| |
| TEST(MakeString, LargerThanDefaultSize_Truncates) { |
| auto sb = MakeString("1844674407", 3709551615, 123456); |
| |
| EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status()); |
| EXPECT_STREQ(kLongestString, sb.data()); |
| } |
| |
| TEST(MakeString, StringLiteral_ResizesToFitWholeLiteral) { |
| EXPECT_STREQ("", MakeString().data()); |
| |
| auto normal = MakeString(""); |
| static_assert(normal.max_size() == decltype(MakeString(1))::max_size()); |
| |
| auto resized = MakeString("This string is reeeeeeeeeaaaaallly long!!!!!"); |
| static_assert(resized.max_size() > decltype(MakeString(1))::max_size()); |
| static_assert(resized.max_size() == |
| sizeof("This string is reeeeeeeeeaaaaallly long!!!!!") - 1); |
| } |
| |
| TEST(MakeString, StringLiteral_UsesLongerFixedSize) { |
| auto fixed_size = MakeString<64>(""); |
| static_assert(fixed_size.max_size() == 63u); |
| EXPECT_EQ(fixed_size.max_size(), 63u); |
| EXPECT_STREQ("", fixed_size.data()); |
| } |
| |
| TEST(MakeString, StringLiteral_TruncatesShorterFixedSize) { |
| EXPECT_STREQ("Goo", MakeString<4>("Google").data()); |
| EXPECT_STREQ("Google", MakeString<7>("Google").data()); |
| EXPECT_EQ(MakeString().max_size(), MakeString("Google").max_size()); |
| EXPECT_STREQ("Google", MakeString("Google").data()); |
| } |
| |
| TEST(MakeString, DefaultSize_FitsMaxAndMinInts) { |
| EXPECT_STREQ("-9223372036854775808", |
| MakeString(std::numeric_limits<int64_t>::min()).data()); |
| EXPECT_STREQ("18446744073709551615", |
| MakeString(std::numeric_limits<uint64_t>::max()).data()); |
| } |
| |
| TEST(MakeString, OutputToTemporaryStringBuffer) { |
| EXPECT_STREQ("hello", (MakeString<6>("hello ") << "world").data()); |
| EXPECT_STREQ("hello world", (MakeString("hello ") << "world").data()); |
| } |
| |
| // Test MakeString's default size calculations. |
| template <typename... Args> |
| constexpr size_t DefaultStringBufferSize(Args&&...) { |
| return string_internal::DefaultStringBufferSize<Args...>(); |
| } |
| |
| // Default sizes are rounded up to 24 bytes. |
| static_assert(DefaultStringBufferSize("") == 24); |
| static_assert(DefaultStringBufferSize("123") == 24); |
| static_assert(DefaultStringBufferSize("123", "456", "78901234567890") == 24); |
| static_assert(DefaultStringBufferSize("1234567890", "1234567890", "123") == 24); |
| static_assert(DefaultStringBufferSize(1234, 5678, 9012) == 24); |
| |
| // The buffer is sized to fix strings needing more than 24 bytes. |
| static_assert(DefaultStringBufferSize("1234567890", "1234567890", "1234") == |
| 25); |
| static_assert(DefaultStringBufferSize("1234567890", "1234567890", "12345") == |
| 26); |
| static_assert(DefaultStringBufferSize("1234567890", "1234567890", "12345678") == |
| 29); |
| |
| // Four bytes are allocated for each non-string argument. |
| static_assert(DefaultStringBufferSize(1234, 5678, 9012, 3456, 7890, 1234) == |
| 25); |
| static_assert(DefaultStringBufferSize('a', nullptr, 'b', 4, 5, 6, 7, 8) == 33); |
| |
| } // namespace |
| } // namespace pw |