| //===-- Unittests for string ----------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "hdr/func/free.h" |
| #include "src/__support/CPP/string.h" |
| #include "test/UnitTest/Test.h" |
| |
| using LIBC_NAMESPACE::cpp::string; |
| using LIBC_NAMESPACE::cpp::string_view; |
| using LIBC_NAMESPACE::cpp::to_string; |
| |
| TEST(LlvmLibcStringTest, InitializeEmpty) { |
| const string s; |
| ASSERT_EQ(s.size(), size_t(0)); |
| ASSERT_TRUE(s.empty()); |
| ASSERT_STREQ(s.data(), ""); |
| ASSERT_STREQ(s.c_str(), ""); |
| ASSERT_EQ(s.data(), s.c_str()); |
| ASSERT_EQ(s.capacity(), size_t(0)); |
| } |
| |
| TEST(LlvmLibcStringTest, InitializeCString) { |
| const char *const str = "abc"; |
| const string s(str); |
| ASSERT_EQ(s.size(), size_t(3)); |
| ASSERT_FALSE(s.empty()); |
| ASSERT_NE(s.data(), &str[0]); |
| ASSERT_EQ(s[0], 'a'); |
| ASSERT_EQ(s[1], 'b'); |
| ASSERT_EQ(s[2], 'c'); |
| ASSERT_EQ(s.front(), 'a'); |
| ASSERT_EQ(s.back(), 'c'); |
| ASSERT_EQ(s.data(), s.c_str()); |
| } |
| |
| TEST(LlvmLibcStringTest, ToCString) { |
| const char *const str = "abc"; |
| string s(str); |
| const char *cstr = s.c_str(); |
| ASSERT_EQ(s.size(), size_t(3)); |
| ASSERT_STREQ(str, cstr); |
| } |
| |
| TEST(LlvmLibcStringTest, ToStringView) { |
| const char *const str = "abc"; |
| string s(str); |
| string_view view = s; |
| ASSERT_EQ(view, string_view(str)); |
| } |
| |
| TEST(LlvmLibcStringTest, InitializeCStringWithSize) { |
| const char *const str = "abc"; |
| const string s(str, 2); |
| ASSERT_EQ(s.size(), size_t(2)); |
| ASSERT_EQ(s[0], 'a'); |
| ASSERT_EQ(s[1], 'b'); |
| ASSERT_EQ(s.front(), 'a'); |
| ASSERT_EQ(s.back(), 'b'); |
| } |
| |
| TEST(LlvmLibcStringTest, InitializeStringView) { |
| const string_view str = "ab"; |
| const string s(str); |
| ASSERT_EQ(s.size(), size_t(2)); |
| ASSERT_EQ(s[0], 'a'); |
| ASSERT_EQ(s[1], 'b'); |
| ASSERT_EQ(s.front(), 'a'); |
| ASSERT_EQ(s.back(), 'b'); |
| } |
| |
| TEST(LlvmLibcStringTest, InitializeRepeatedChar) { |
| const string s(4, '1'); |
| ASSERT_EQ(string_view(s), string_view("1111")); |
| } |
| |
| TEST(LlvmLibcStringTest, InitializeZeroChar) { |
| const string s(0, '1'); |
| ASSERT_TRUE(s.empty()); |
| } |
| |
| TEST(LlvmLibcStringTest, CopyConstruct) { |
| const char *const str = "abc"; |
| string a(str); |
| string b(a); |
| // Same content |
| ASSERT_STREQ(a.c_str(), str); |
| ASSERT_STREQ(b.c_str(), str); |
| // Different pointers |
| ASSERT_NE(a.data(), b.data()); |
| } |
| |
| string &&move(string &value) { return static_cast<string &&>(value); } |
| |
| TEST(LlvmLibcStringTest, CopyAssign) { |
| const char *const str = "abc"; |
| string a(str); |
| string b; |
| b = a; |
| // Same content |
| ASSERT_STREQ(a.c_str(), str); |
| ASSERT_STREQ(b.c_str(), str); |
| // Different pointers |
| ASSERT_NE(a.data(), b.data()); |
| } |
| |
| TEST(LlvmLibcStringTest, MoveConstruct) { |
| const char *const str = "abc"; |
| string a(str); |
| string b(move(a)); |
| ASSERT_STREQ(b.c_str(), str); |
| ASSERT_STREQ(a.c_str(), ""); |
| } |
| |
| TEST(LlvmLibcStringTest, MoveAssign) { |
| const char *const str = "abc"; |
| string a(str); |
| string b; |
| b = move(a); |
| ASSERT_STREQ(b.c_str(), str); |
| ASSERT_STREQ(a.c_str(), ""); |
| } |
| |
| TEST(LlvmLibcStringTest, StringViewAssign) { |
| const string_view str = "ab"; |
| string s; |
| s = str; |
| ASSERT_EQ(s.size(), size_t(2)); |
| ASSERT_EQ(s[0], 'a'); |
| ASSERT_EQ(s[1], 'b'); |
| ASSERT_EQ(s.front(), 'a'); |
| ASSERT_EQ(s.back(), 'b'); |
| } |
| |
| TEST(LlvmLibcStringTest, CharacterAssign) { |
| string s("abc"); |
| s = 'a'; |
| ASSERT_EQ(s.size(), size_t(1)); |
| ASSERT_STREQ(s.c_str(), "a"); |
| } |
| |
| TEST(LlvmLibcStringTest, Concat) { |
| const char *const str = "abc"; |
| string a(str); |
| string b; |
| b += a; |
| ASSERT_STREQ(b.c_str(), "abc"); |
| b += a; |
| ASSERT_STREQ(b.c_str(), "abcabc"); |
| } |
| |
| TEST(LlvmLibcStringTest, AddChar) { |
| string a; |
| a += 'a'; |
| ASSERT_STREQ(a.c_str(), "a"); |
| a += 'b'; |
| ASSERT_STREQ(a.c_str(), "ab"); |
| } |
| |
| TEST(LlvmLibcStringTest, ResizeCapacityAndNullTermination) { |
| string a; |
| // Empty |
| ASSERT_EQ(a.capacity(), size_t(0)); |
| ASSERT_EQ(a.data()[0], '\0'); |
| // Still empty |
| a.resize(0); |
| ASSERT_EQ(a.capacity(), size_t(0)); |
| ASSERT_EQ(a.data()[0], '\0'); |
| // One char |
| a.resize(1); |
| ASSERT_EQ(a.size(), size_t(1)); |
| ASSERT_GE(a.capacity(), size_t(1)); |
| ASSERT_EQ(a.data()[1], '\0'); |
| // Clear |
| a.resize(0); |
| ASSERT_EQ(a.size(), size_t(0)); |
| ASSERT_GE(a.capacity(), size_t(1)); |
| ASSERT_EQ(a.data()[0], '\0'); |
| // Resize and check zero initialized |
| a.resize(10); |
| ASSERT_EQ(a.size(), size_t(10)); |
| ASSERT_GE(a.capacity(), size_t(10)); |
| for (size_t i = 0; i < 10; ++i) |
| ASSERT_EQ(a[i], '\0'); |
| } |
| |
| TEST(LlvmLibcStringTest, ResizeWithCapacityPlus1) { |
| string a; |
| a.resize(32); |
| |
| size_t previous_capacity = a.capacity(); |
| a.resize(previous_capacity + 1); |
| ASSERT_GT(a.capacity(), previous_capacity); |
| } |
| |
| TEST(LlvmLibcStringTest, ReserveWithSameCapacityIsNop) { |
| string a; |
| a.resize(32); |
| |
| size_t previous_capacity = a.capacity(); |
| // Since C++20, calling reserve with capacity less than or equal |
| // to the current capacity should have no effect. |
| a.reserve(previous_capacity); |
| ASSERT_EQ(a.capacity(), previous_capacity); |
| } |
| |
| TEST(LlvmLibcStringTest, ReserveOnEmptyStringKeepsNullTerminator) { |
| string s; |
| s.reserve(10); |
| ASSERT_EQ(s.size(), size_t(0)); |
| ASSERT_EQ(s[0], '\0'); |
| } |
| |
| TEST(LlvmLibcStringTest, ConcatWithCString) { |
| ASSERT_STREQ((string("a") + string("b")).c_str(), "ab"); |
| ASSERT_STREQ((string("a") + "b").c_str(), "ab"); |
| ASSERT_STREQ(("a" + string("b")).c_str(), "ab"); |
| } |
| |
| TEST(LlvmLibcStringTest, ReleaseCStrResetsString) { |
| string s("abc"); |
| |
| char *cstr = s.release_c_str(); |
| ASSERT_STREQ(cstr, "abc"); |
| ASSERT_STREQ(s.c_str(), ""); |
| free(cstr); |
| } |
| |
| TEST(LlvmLibcStringTest, ReleaseCStrOnDefaultString) { |
| string s; |
| |
| char *cstr = s.release_c_str(); |
| ASSERT_STREQ(cstr, ""); |
| ASSERT_STREQ(s.c_str(), ""); |
| ASSERT_NE(cstr, s.data()); // Different pointers. |
| free(cstr); |
| } |
| |
| TEST(LlvmLibcStringTest, Comparison) { |
| // Here we simply check that comparison of string and string_view have the |
| // same semantic. |
| struct CStringPair { |
| const char *const a; |
| const char *const b; |
| } kTestPairs[] = {{"a", "b"}, {"", "xyz"}}; |
| for (const auto [pa, pb] : kTestPairs) { |
| const string sa(pa); |
| const string sb(pb); |
| const string_view sva(pa); |
| const string_view svb(pb); |
| ASSERT_EQ(sa == sb, sva == svb); |
| ASSERT_EQ(sa != sb, sva != svb); |
| ASSERT_EQ(sa >= sb, sva >= svb); |
| ASSERT_EQ(sa <= sb, sva <= svb); |
| ASSERT_EQ(sa < sb, sva < svb); |
| ASSERT_EQ(sa > sb, sva > svb); |
| } |
| } |
| |
| TEST(LlvmLibcStringTest, ToString) { |
| struct CStringPair { |
| const int value; |
| const string str; |
| } kTestPairs[] = {{123, "123"}, {0, "0"}, {-321, "-321"}}; |
| for (const auto &[value, str] : kTestPairs) { |
| ASSERT_EQ(to_string((int)(value)), str); |
| ASSERT_EQ(to_string((long)(value)), str); |
| ASSERT_EQ(to_string((long long)(value)), str); |
| if (value >= 0) { |
| ASSERT_EQ(to_string((unsigned int)(value)), str); |
| ASSERT_EQ(to_string((unsigned long)(value)), str); |
| ASSERT_EQ(to_string((unsigned long long)(value)), str); |
| } |
| } |
| } |
| |
| TEST(LlvmLibcStringTest, SelfAssignTest) { |
| string_view alphabet("abcdefghijklmnopqrstuvwxyz"); |
| |
| // Test with a string long enough to where memcpy'ing bytes internal |
| // to the string may fail. |
| string complicated_string; |
| for (size_t i = 0; i < 100; i++) |
| complicated_string += alphabet[i % alphabet.size()]; |
| |
| string s(complicated_string); |
| |
| s = string_view(s).substr(1); |
| ASSERT_EQ(string_view(s), string_view(complicated_string).substr(1)); |
| } |
| |
| TEST(LlvmLibcStringTest, SelfAssignAtCapacityTest) { |
| string s("aaa"); |
| |
| // Append until the string is at its capacity |
| // to exercise assigning past capacity. |
| while (s.size() < s.capacity()) |
| s += 'a'; |
| ASSERT_EQ(s.capacity(), s.size()); |
| size_t cap_before_append = s.capacity(); |
| |
| // Force a resize by assigning to a longer string. |
| string longer_string(s.size() + 1, 'b'); |
| s = string_view(longer_string); |
| |
| size_t cap_after_append = s.capacity(); |
| ASSERT_EQ(s, longer_string); |
| ASSERT_GT(cap_after_append, cap_before_append); |
| } |
| |
| TEST(LlvmLibcStringTest, SelfMoveAssign) { |
| string s("aaa"); |
| s = move(s); |
| |
| ASSERT_STREQ(s.c_str(), "aaa"); |
| } |
| |
| TEST(LlvmLibcStringTest, SelfAppendAtCapacityTest) { |
| string s("aaa"); |
| |
| // Append until the string is at its capacity |
| // to exercise self-appending past capacity. |
| while (s.size() < s.capacity()) |
| s += 'a'; |
| ASSERT_EQ(s.capacity(), s.size()); |
| |
| string_view view = string_view(s).substr(0, 3); |
| size_t expected_size = s.size() + view.size(); |
| size_t cap_before_append = s.capacity(); |
| |
| s += view; |
| |
| size_t cap_after_append = s.capacity(); |
| string expected(expected_size, 'a'); |
| ASSERT_EQ(s, expected); |
| ASSERT_GT(cap_after_append, cap_before_append); |
| } |
| |
| TEST(LlvmLibcStringTest, ReplaceWithSmallerString) { |
| string s("Hello world"); |
| |
| s.replace(3, 7, "orl"); // Replace "lo worl" with "orl" |
| |
| EXPECT_STREQ(s.c_str(), "Helorld"); |
| } |
| |
| TEST(LlvmLibcStringTest, ReplaceWithSmallerSelfReferentialString) { |
| string s("Hello world"); |
| string_view view = string_view(s).substr(7, 3); // "orl" |
| |
| s.replace(3, 7, view); // Replace "lo worl" with "orl" |
| |
| EXPECT_STREQ(s.c_str(), "Helorld"); |
| } |
| |
| TEST(LlvmLibcStringTest, ReplaceWithEqualLengthString) { |
| string s("Hello world"); |
| |
| s.replace(6, 3, " wo"); // Replace "wor" with " wo" |
| |
| EXPECT_STREQ(s.c_str(), "Hello wold"); |
| } |
| |
| TEST(LlvmLibcStringTest, ReplaceWithEqualLengthSelfReferentialString) { |
| string s("Hello world"); |
| string_view view = string_view(s).substr(5, 3); // " wo" |
| |
| s.replace(6, 3, view); // Replace "wor" with " wo" |
| |
| EXPECT_STREQ(s.c_str(), "Hello wold"); |
| } |
| |
| TEST(LlvmLibcStringTest, ReplaceWithLongerStringNoGrowth) { |
| string s("Hello world"); |
| s.reserve(32); // Reserve enough space to where replace will not grow. |
| size_t capacity_before = s.capacity(); |
| |
| s.replace(6, 3, " worl"); // Replace "wor" with " worl" |
| |
| EXPECT_STREQ(s.c_str(), "Hello worlld"); |
| EXPECT_EQ(s.capacity(), capacity_before); |
| } |
| |
| TEST(LlvmLibcStringTest, ReplaceWithLongerSelfReferentialStringNoGrowth) { |
| string s = "Hello world"; |
| s.reserve(32); // Reserve enough space to where replace will not grow. |
| size_t capacity_before = s.capacity(); |
| string_view view = string_view(s).substr(5, 5); // " worl" |
| |
| s.replace(6, 3, view); // Replace "wor" with " worl" |
| |
| EXPECT_STREQ(s.c_str(), "Hello worlld"); |
| EXPECT_EQ(s.capacity(), capacity_before); |
| } |
| |
| TEST(LlvmLibcStringTest, ReplaceWithLongerStringTriggeringGrow) { |
| string s("Hello placeholder world"); |
| string_view placeholder = "placeholder"; |
| size_t capacity_before = s.capacity(); |
| string insert(s.capacity() - s.size() + placeholder.size() + 1, 'a'); |
| |
| s.replace(6, placeholder.size(), insert); // Replace "placeholder" |
| |
| string expected = "Hello "; |
| expected += insert; |
| expected += " world"; |
| EXPECT_EQ(s, expected); |
| EXPECT_GT(s.capacity(), capacity_before); |
| } |
| |
| TEST(LlvmLibcStringTest, ReplaceFromEndOfStringNoGrowth) { |
| string s; |
| s.reserve(32); |
| size_t capacity_before = s.capacity(); |
| |
| s.replace(0, 0, "Hello world"); |
| |
| EXPECT_STREQ(s.c_str(), "Hello world"); |
| EXPECT_EQ(s.capacity(), capacity_before); |
| } |
| |
| TEST(LlvmLibcStringTest, ReplaceFromEndOfStringTriggeringGrowth) { |
| string s; |
| size_t capacity_before = s.capacity(); |
| |
| s.replace(0, 0, "Hello world"); |
| |
| EXPECT_STREQ(s.c_str(), "Hello world"); |
| EXPECT_GT(s.capacity(), capacity_before); |
| } |
| |
| TEST(LlvmLibcStringTest, ReplaceWithEmptyString) { |
| string s = "Hello world"; |
| s.replace(2, 7, ""); |
| EXPECT_STREQ(s.c_str(), "Held"); |
| } |
| |
| TEST(LlvmLibcStringTest, ReplaceAtEndOfString) { |
| string s = "Hello "; |
| s.replace(6, 0, "world"); |
| EXPECT_STREQ(s.c_str(), "Hello world"); |
| } |
| |
| TEST(LlvmLibcStringTest, ReplaceWithLargeCountClampsSize) { |
| string s = "Hello world"; |
| s.replace(0, 100, "goodbye"); |
| EXPECT_STREQ(s.c_str(), "goodbye"); |
| } |