// Copyright 2020 The Abseil 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 "absl/strings/cord.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/internal/endian.h" #include "absl/base/macros.h" #include "absl/base/no_destructor.h" #include "absl/base/options.h" #include "absl/container/fixed_array.h" #include "absl/functional/function_ref.h" #include "absl/hash/hash.h" #include "absl/hash/hash_testing.h" #include "absl/log/check.h" #include "absl/log/log.h" #include "absl/random/random.h" #include "absl/strings/cord_buffer.h" #include "absl/strings/cord_test_helpers.h" #include "absl/strings/cordz_test_helpers.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_rep_crc.h" #include "absl/strings/internal/cord_rep_flat.h" #include "absl/strings/internal/cordz_statistics.h" #include "absl/strings/internal/cordz_update_tracker.h" #include "absl/strings/internal/string_constant.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/types/compare.h" #include "absl/types/optional.h" // convenience local constants static constexpr auto FLAT = absl::cord_internal::FLAT; static constexpr auto MAX_FLAT_TAG = absl::cord_internal::MAX_FLAT_TAG; typedef std::mt19937_64 RandomEngine; using absl::cord_internal::CordRep; using absl::cord_internal::CordRepBtree; using absl::cord_internal::CordRepConcat; using absl::cord_internal::CordRepCrc; using absl::cord_internal::CordRepExternal; using absl::cord_internal::CordRepFlat; using absl::cord_internal::CordRepSubstring; using absl::cord_internal::CordzUpdateTracker; using absl::cord_internal::kFlatOverhead; using absl::cord_internal::kMaxFlatLength; using ::testing::ElementsAre; using ::testing::Le; static std::string RandomLowercaseString(RandomEngine* rng); static std::string RandomLowercaseString(RandomEngine* rng, size_t length); static int GetUniformRandomUpTo(RandomEngine* rng, int upper_bound) { if (upper_bound > 0) { std::uniform_int_distribution uniform(0, upper_bound - 1); return uniform(*rng); } else { return 0; } } static size_t GetUniformRandomUpTo(RandomEngine* rng, size_t upper_bound) { if (upper_bound > 0) { std::uniform_int_distribution uniform(0, upper_bound - 1); return uniform(*rng); } else { return 0; } } static int32_t GenerateSkewedRandom(RandomEngine* rng, int max_log) { const uint32_t base = (*rng)() % (max_log + 1); const uint32_t mask = ((base < 32) ? (1u << base) : 0u) - 1u; return (*rng)() & mask; } static std::string RandomLowercaseString(RandomEngine* rng) { int length; std::bernoulli_distribution one_in_1k(0.001); std::bernoulli_distribution one_in_10k(0.0001); // With low probability, make a large fragment if (one_in_10k(*rng)) { length = GetUniformRandomUpTo(rng, 1048576); } else if (one_in_1k(*rng)) { length = GetUniformRandomUpTo(rng, 10000); } else { length = GenerateSkewedRandom(rng, 10); } return RandomLowercaseString(rng, length); } static std::string RandomLowercaseString(RandomEngine* rng, size_t length) { std::string result(length, '\0'); std::uniform_int_distribution chars('a', 'z'); std::generate(result.begin(), result.end(), [&]() { return static_cast(chars(*rng)); }); return result; } static void DoNothing(absl::string_view /* data */, void* /* arg */) {} static void DeleteExternalString(absl::string_view data, void* arg) { std::string* s = reinterpret_cast(arg); EXPECT_EQ(data, *s); delete s; } // Add "s" to *dst via `MakeCordFromExternal` static void AddExternalMemory(absl::string_view s, absl::Cord* dst) { std::string* str = new std::string(s.data(), s.size()); dst->Append(absl::MakeCordFromExternal(*str, [str](absl::string_view data) { DeleteExternalString(data, str); })); } static void DumpGrowth() { absl::Cord str; for (int i = 0; i < 1000; i++) { char c = 'a' + i % 26; str.Append(absl::string_view(&c, 1)); } } // Make a Cord with some number of fragments. Return the size (in bytes) // of the smallest fragment. static size_t AppendWithFragments(const std::string& s, RandomEngine* rng, absl::Cord* cord) { size_t j = 0; const size_t max_size = s.size() / 5; // Make approx. 10 fragments size_t min_size = max_size; // size of smallest fragment while (j < s.size()) { size_t N = 1 + GetUniformRandomUpTo(rng, max_size); if (N > (s.size() - j)) { N = s.size() - j; } if (N < min_size) { min_size = N; } std::bernoulli_distribution coin_flip(0.5); if (coin_flip(*rng)) { // Grow by adding an external-memory. AddExternalMemory(absl::string_view(s.data() + j, N), cord); } else { cord->Append(absl::string_view(s.data() + j, N)); } j += N; } return min_size; } // Add an external memory that contains the specified std::string to cord static void AddNewStringBlock(const std::string& str, absl::Cord* dst) { char* data = new char[str.size()]; memcpy(data, str.data(), str.size()); dst->Append(absl::MakeCordFromExternal( absl::string_view(data, str.size()), [](absl::string_view s) { delete[] s.data(); })); } // Make a Cord out of many different types of nodes. static absl::Cord MakeComposite() { absl::Cord cord; cord.Append("the"); AddExternalMemory(" quick brown", &cord); AddExternalMemory(" fox jumped", &cord); absl::Cord full(" over"); AddExternalMemory(" the lazy", &full); AddNewStringBlock(" dog slept the whole day away", &full); absl::Cord substring = full.Subcord(0, 18); // Make substring long enough to defeat the copying fast path in Append. substring.Append(std::string(1000, '.')); cord.Append(substring); cord = cord.Subcord(0, cord.size() - 998); // Remove most of extra junk return cord; } namespace absl { ABSL_NAMESPACE_BEGIN class CordTestPeer { public: static void ForEachChunk( const Cord& c, absl::FunctionRef callback) { c.ForEachChunk(callback); } static bool IsTree(const Cord& c) { return c.contents_.is_tree(); } static CordRep* Tree(const Cord& c) { return c.contents_.tree(); } static cord_internal::CordzInfo* GetCordzInfo(const Cord& c) { return c.contents_.cordz_info(); } static Cord MakeSubstring(Cord src, size_t offset, size_t length) { CHECK(src.contents_.is_tree()) << "Can not be inlined"; CHECK(!src.ExpectedChecksum().has_value()) << "Can not be hardened"; Cord cord; auto* tree = cord_internal::SkipCrcNode(src.contents_.tree()); auto* rep = CordRepSubstring::Create(CordRep::Ref(tree), offset, length); cord.contents_.EmplaceTree(rep, CordzUpdateTracker::kSubCord); return cord; } }; ABSL_NAMESPACE_END } // namespace absl // The CordTest fixture runs all tests with and without expected CRCs being set // on the subject Cords. class CordTest : public testing::TestWithParam { public: // Returns true if test is running with Crc enabled. bool UseCrc() const { return GetParam(); } void MaybeHarden(absl::Cord& c) { if (UseCrc()) { c.SetExpectedChecksum(1); } } absl::Cord MaybeHardened(absl::Cord c) { MaybeHarden(c); return c; } // Returns human readable string representation of the test parameter. static std::string ToString(testing::TestParamInfo useCrc) { if (useCrc.param) { return "BtreeHardened"; } else { return "Btree"; } } }; INSTANTIATE_TEST_SUITE_P(WithParam, CordTest, testing::Bool(), CordTest::ToString); TEST(CordRepFlat, AllFlatCapacities) { // Explicitly and redundantly assert built-in min/max limits static_assert(absl::cord_internal::kFlatOverhead < 32, ""); static_assert(absl::cord_internal::kMinFlatSize == 32, ""); static_assert(absl::cord_internal::kMaxLargeFlatSize == 256 << 10, ""); EXPECT_EQ(absl::cord_internal::TagToAllocatedSize(FLAT), 32); EXPECT_EQ(absl::cord_internal::TagToAllocatedSize(MAX_FLAT_TAG), 256 << 10); // Verify all tags to map perfectly back and forth, and // that sizes are monotonically increasing. size_t last_size = 0; for (int tag = FLAT; tag <= MAX_FLAT_TAG; ++tag) { size_t size = absl::cord_internal::TagToAllocatedSize(tag); ASSERT_GT(size, last_size); ASSERT_EQ(absl::cord_internal::TagToAllocatedSize(tag), size); last_size = size; } // All flat size from 32 - 512 are 8 byte granularity for (size_t size = 32; size <= 512; size += 8) { ASSERT_EQ(absl::cord_internal::RoundUpForTag(size), size); uint8_t tag = absl::cord_internal::AllocatedSizeToTag(size); ASSERT_EQ(absl::cord_internal::TagToAllocatedSize(tag), size); } // All flat sizes from 512 - 8192 are 64 byte granularity for (size_t size = 512; size <= 8192; size += 64) { ASSERT_EQ(absl::cord_internal::RoundUpForTag(size), size); uint8_t tag = absl::cord_internal::AllocatedSizeToTag(size); ASSERT_EQ(absl::cord_internal::TagToAllocatedSize(tag), size); } // All flat sizes from 8KB to 256KB are 4KB granularity for (size_t size = 8192; size <= 256 * 1024; size += 4 * 1024) { ASSERT_EQ(absl::cord_internal::RoundUpForTag(size), size); uint8_t tag = absl::cord_internal::AllocatedSizeToTag(size); ASSERT_EQ(absl::cord_internal::TagToAllocatedSize(tag), size); } } TEST(CordRepFlat, MaxFlatSize) { CordRepFlat* flat = CordRepFlat::New(kMaxFlatLength); EXPECT_EQ(flat->Capacity(), kMaxFlatLength); CordRep::Unref(flat); flat = CordRepFlat::New(kMaxFlatLength * 4); EXPECT_EQ(flat->Capacity(), kMaxFlatLength); CordRep::Unref(flat); } TEST(CordRepFlat, MaxLargeFlatSize) { const size_t size = 256 * 1024 - kFlatOverhead; CordRepFlat* flat = CordRepFlat::New(CordRepFlat::Large(), size); EXPECT_GE(flat->Capacity(), size); CordRep::Unref(flat); } TEST(CordRepFlat, AllFlatSizes) { const size_t kMaxSize = 256 * 1024; for (size_t size = 32; size <= kMaxSize; size *=2) { const size_t length = size - kFlatOverhead - 1; CordRepFlat* flat = CordRepFlat::New(CordRepFlat::Large(), length); EXPECT_GE(flat->Capacity(), length); memset(flat->Data(), 0xCD, flat->Capacity()); CordRep::Unref(flat); } } TEST_P(CordTest, AllFlatSizes) { using absl::strings_internal::CordTestAccess; for (size_t s = 0; s < CordTestAccess::MaxFlatLength(); s++) { // Make a string of length s. std::string src; while (src.size() < s) { src.push_back('a' + (src.size() % 26)); } absl::Cord dst(src); MaybeHarden(dst); EXPECT_EQ(std::string(dst), src) << s; } } // We create a Cord at least 128GB in size using the fact that Cords can // internally reference-count; thus the Cord is enormous without actually // consuming very much memory. TEST_P(CordTest, GigabyteCordFromExternal) { const size_t one_gig = 1024U * 1024U * 1024U; size_t max_size = 2 * one_gig; if (sizeof(max_size) > 4) max_size = 128 * one_gig; size_t length = 128 * 1024; char* data = new char[length]; absl::Cord from = absl::MakeCordFromExternal( absl::string_view(data, length), [](absl::string_view sv) { delete[] sv.data(); }); // This loop may seem odd due to its combination of exponential doubling of // size and incremental size increases. We do it incrementally to be sure the // Cord will need rebalancing and will exercise code that, in the past, has // caused crashes in production. We grow exponentially so that the code will // execute in a reasonable amount of time. absl::Cord c; c.Append(from); while (c.size() < max_size) { c.Append(c); c.Append(from); c.Append(from); c.Append(from); c.Append(from); MaybeHarden(c); } for (int i = 0; i < 1024; ++i) { c.Append(from); } LOG(INFO) << "Made a Cord with " << c.size() << " bytes!"; // Note: on a 32-bit build, this comes out to 2,818,048,000 bytes. // Note: on a 64-bit build, this comes out to 171,932,385,280 bytes. } static absl::Cord MakeExternalCord(int size) { char* buffer = new char[size]; memset(buffer, 'x', size); absl::Cord cord; cord.Append(absl::MakeCordFromExternal( absl::string_view(buffer, size), [](absl::string_view s) { delete[] s.data(); })); return cord; } // Extern to fool clang that this is not constant. Needed to suppress // a warning of unsafe code we want to test. extern bool my_unique_true_boolean; bool my_unique_true_boolean = true; TEST_P(CordTest, Assignment) { absl::Cord x(absl::string_view("hi there")); absl::Cord y(x); MaybeHarden(y); ASSERT_EQ(x.ExpectedChecksum(), absl::nullopt); ASSERT_EQ(std::string(x), "hi there"); ASSERT_EQ(std::string(y), "hi there"); ASSERT_TRUE(x == y); ASSERT_TRUE(x <= y); ASSERT_TRUE(y <= x); x = absl::string_view("foo"); ASSERT_EQ(std::string(x), "foo"); ASSERT_EQ(std::string(y), "hi there"); ASSERT_TRUE(x < y); ASSERT_TRUE(y > x); ASSERT_TRUE(x != y); ASSERT_TRUE(x <= y); ASSERT_TRUE(y >= x); x = "foo"; ASSERT_EQ(x, "foo"); // Test that going from inline rep to tree we don't leak memory. std::vector> test_string_pairs = {{"hi there", "foo"}, {"loooooong coooooord", "short cord"}, {"short cord", "loooooong coooooord"}, {"loooooong coooooord1", "loooooong coooooord2"}}; for (std::pair test_strings : test_string_pairs) { absl::Cord tmp(test_strings.first); absl::Cord z(std::move(tmp)); ASSERT_EQ(std::string(z), test_strings.first); tmp = test_strings.second; z = std::move(tmp); ASSERT_EQ(std::string(z), test_strings.second); } { // Test that self-move assignment doesn't crash/leak. // Do not write such code! absl::Cord my_small_cord("foo"); absl::Cord my_big_cord("loooooong coooooord"); // Bypass clang's warning on self move-assignment. absl::Cord* my_small_alias = my_unique_true_boolean ? &my_small_cord : &my_big_cord; absl::Cord* my_big_alias = !my_unique_true_boolean ? &my_small_cord : &my_big_cord; *my_small_alias = std::move(my_small_cord); *my_big_alias = std::move(my_big_cord); // my_small_cord and my_big_cord are in an unspecified but valid // state, and will be correctly destroyed here. } } TEST_P(CordTest, StartsEndsWith) { absl::Cord x(absl::string_view("abcde")); MaybeHarden(x); absl::Cord empty(""); ASSERT_TRUE(x.StartsWith(absl::Cord("abcde"))); ASSERT_TRUE(x.StartsWith(absl::Cord("abc"))); ASSERT_TRUE(x.StartsWith(absl::Cord(""))); ASSERT_TRUE(empty.StartsWith(absl::Cord(""))); ASSERT_TRUE(x.EndsWith(absl::Cord("abcde"))); ASSERT_TRUE(x.EndsWith(absl::Cord("cde"))); ASSERT_TRUE(x.EndsWith(absl::Cord(""))); ASSERT_TRUE(empty.EndsWith(absl::Cord(""))); ASSERT_TRUE(!x.StartsWith(absl::Cord("xyz"))); ASSERT_TRUE(!empty.StartsWith(absl::Cord("xyz"))); ASSERT_TRUE(!x.EndsWith(absl::Cord("xyz"))); ASSERT_TRUE(!empty.EndsWith(absl::Cord("xyz"))); ASSERT_TRUE(x.StartsWith("abcde")); ASSERT_TRUE(x.StartsWith("abc")); ASSERT_TRUE(x.StartsWith("")); ASSERT_TRUE(empty.StartsWith("")); ASSERT_TRUE(x.EndsWith("abcde")); ASSERT_TRUE(x.EndsWith("cde")); ASSERT_TRUE(x.EndsWith("")); ASSERT_TRUE(empty.EndsWith("")); ASSERT_TRUE(!x.StartsWith("xyz")); ASSERT_TRUE(!empty.StartsWith("xyz")); ASSERT_TRUE(!x.EndsWith("xyz")); ASSERT_TRUE(!empty.EndsWith("xyz")); } TEST_P(CordTest, Contains) { auto flat_haystack = absl::Cord("this is a flat cord"); auto fragmented_haystack = absl::MakeFragmentedCord( {"this", " ", "is", " ", "a", " ", "fragmented", " ", "cord"}); EXPECT_TRUE(flat_haystack.Contains("")); EXPECT_TRUE(fragmented_haystack.Contains("")); EXPECT_TRUE(flat_haystack.Contains(absl::Cord(""))); EXPECT_TRUE(fragmented_haystack.Contains(absl::Cord(""))); EXPECT_TRUE(absl::Cord("").Contains("")); EXPECT_TRUE(absl::Cord("").Contains(absl::Cord(""))); EXPECT_FALSE(absl::Cord("").Contains(flat_haystack)); EXPECT_FALSE(absl::Cord("").Contains(fragmented_haystack)); EXPECT_FALSE(flat_haystack.Contains("z")); EXPECT_FALSE(fragmented_haystack.Contains("z")); EXPECT_FALSE(flat_haystack.Contains(absl::Cord("z"))); EXPECT_FALSE(fragmented_haystack.Contains(absl::Cord("z"))); EXPECT_FALSE(flat_haystack.Contains("is an")); EXPECT_FALSE(fragmented_haystack.Contains("is an")); EXPECT_FALSE(flat_haystack.Contains(absl::Cord("is an"))); EXPECT_FALSE(fragmented_haystack.Contains(absl::Cord("is an"))); EXPECT_FALSE( flat_haystack.Contains(absl::MakeFragmentedCord({"is", " ", "an"}))); EXPECT_FALSE(fragmented_haystack.Contains( absl::MakeFragmentedCord({"is", " ", "an"}))); EXPECT_TRUE(flat_haystack.Contains("is a")); EXPECT_TRUE(fragmented_haystack.Contains("is a")); EXPECT_TRUE(flat_haystack.Contains(absl::Cord("is a"))); EXPECT_TRUE(fragmented_haystack.Contains(absl::Cord("is a"))); EXPECT_TRUE( flat_haystack.Contains(absl::MakeFragmentedCord({"is", " ", "a"}))); EXPECT_TRUE( fragmented_haystack.Contains(absl::MakeFragmentedCord({"is", " ", "a"}))); } TEST_P(CordTest, Find) { auto flat_haystack = absl::Cord("this is a flat cord"); auto fragmented_haystack = absl::MakeFragmentedCord( {"this", " ", "is", " ", "a", " ", "fragmented", " ", "cord"}); auto empty_haystack = absl::Cord(""); EXPECT_EQ(flat_haystack.Find(""), flat_haystack.char_begin()); EXPECT_EQ(fragmented_haystack.Find(""), fragmented_haystack.char_begin()); EXPECT_EQ(flat_haystack.Find(absl::Cord("")), flat_haystack.char_begin()); EXPECT_EQ(fragmented_haystack.Find(absl::Cord("")), fragmented_haystack.char_begin()); EXPECT_EQ(empty_haystack.Find(""), empty_haystack.char_begin()); EXPECT_EQ(empty_haystack.Find(absl::Cord("")), empty_haystack.char_begin()); EXPECT_EQ(empty_haystack.Find(flat_haystack), empty_haystack.char_end()); EXPECT_EQ(empty_haystack.Find(fragmented_haystack), empty_haystack.char_end()); EXPECT_EQ(flat_haystack.Find("z"), flat_haystack.char_end()); EXPECT_EQ(fragmented_haystack.Find("z"), fragmented_haystack.char_end()); EXPECT_EQ(flat_haystack.Find(absl::Cord("z")), flat_haystack.char_end()); EXPECT_EQ(fragmented_haystack.Find(absl::Cord("z")), fragmented_haystack.char_end()); EXPECT_EQ(flat_haystack.Find("is an"), flat_haystack.char_end()); EXPECT_EQ(fragmented_haystack.Find("is an"), fragmented_haystack.char_end()); EXPECT_EQ(flat_haystack.Find(absl::Cord("is an")), flat_haystack.char_end()); EXPECT_EQ(fragmented_haystack.Find(absl::Cord("is an")), fragmented_haystack.char_end()); EXPECT_EQ(flat_haystack.Find(absl::MakeFragmentedCord({"is", " ", "an"})), flat_haystack.char_end()); EXPECT_EQ( fragmented_haystack.Find(absl::MakeFragmentedCord({"is", " ", "an"})), fragmented_haystack.char_end()); EXPECT_EQ(flat_haystack.Find("is a"), std::next(flat_haystack.char_begin(), 5)); EXPECT_EQ(fragmented_haystack.Find("is a"), std::next(fragmented_haystack.char_begin(), 5)); EXPECT_EQ(flat_haystack.Find(absl::Cord("is a")), std::next(flat_haystack.char_begin(), 5)); EXPECT_EQ(fragmented_haystack.Find(absl::Cord("is a")), std::next(fragmented_haystack.char_begin(), 5)); EXPECT_EQ(flat_haystack.Find(absl::MakeFragmentedCord({"is", " ", "a"})), std::next(flat_haystack.char_begin(), 5)); EXPECT_EQ( fragmented_haystack.Find(absl::MakeFragmentedCord({"is", " ", "a"})), std::next(fragmented_haystack.char_begin(), 5)); } TEST_P(CordTest, Subcord) { RandomEngine rng(GTEST_FLAG_GET(random_seed)); const std::string s = RandomLowercaseString(&rng, 1024); absl::Cord a; AppendWithFragments(s, &rng, &a); MaybeHarden(a); ASSERT_EQ(s, std::string(a)); // Check subcords of a, from a variety of interesting points. std::set positions; for (int i = 0; i <= 32; ++i) { positions.insert(i); positions.insert(i * 32 - 1); positions.insert(i * 32); positions.insert(i * 32 + 1); positions.insert(a.size() - i); } positions.insert(237); positions.insert(732); for (size_t pos : positions) { if (pos > a.size()) continue; for (size_t end_pos : positions) { if (end_pos < pos || end_pos > a.size()) continue; absl::Cord sa = a.Subcord(pos, end_pos - pos); ASSERT_EQ(absl::string_view(s).substr(pos, end_pos - pos), std::string(sa)) << a; if (pos != 0 || end_pos != a.size()) { ASSERT_EQ(sa.ExpectedChecksum(), absl::nullopt); } } } // Do the same thing for an inline cord. const std::string sh = "short"; absl::Cord c(sh); for (size_t pos = 0; pos <= sh.size(); ++pos) { for (size_t n = 0; n <= sh.size() - pos; ++n) { absl::Cord sc = c.Subcord(pos, n); ASSERT_EQ(sh.substr(pos, n), std::string(sc)) << c; } } // Check subcords of subcords. absl::Cord sa = a.Subcord(0, a.size()); std::string ss = s.substr(0, s.size()); while (sa.size() > 1) { sa = sa.Subcord(1, sa.size() - 2); ss = ss.substr(1, ss.size() - 2); ASSERT_EQ(ss, std::string(sa)) << a; if (HasFailure()) break; // halt cascade } // It is OK to ask for too much. sa = a.Subcord(0, a.size() + 1); EXPECT_EQ(s, std::string(sa)); // It is OK to ask for something beyond the end. sa = a.Subcord(a.size() + 1, 0); EXPECT_TRUE(sa.empty()); sa = a.Subcord(a.size() + 1, 1); EXPECT_TRUE(sa.empty()); } TEST_P(CordTest, Swap) { absl::string_view a("Dexter"); absl::string_view b("Mandark"); absl::Cord x(a); absl::Cord y(b); MaybeHarden(x); swap(x, y); if (UseCrc()) { ASSERT_EQ(x.ExpectedChecksum(), absl::nullopt); ASSERT_EQ(y.ExpectedChecksum(), 1); } ASSERT_EQ(x, absl::Cord(b)); ASSERT_EQ(y, absl::Cord(a)); x.swap(y); if (UseCrc()) { ASSERT_EQ(x.ExpectedChecksum(), 1); ASSERT_EQ(y.ExpectedChecksum(), absl::nullopt); } ASSERT_EQ(x, absl::Cord(a)); ASSERT_EQ(y, absl::Cord(b)); } static void VerifyCopyToString(const absl::Cord& cord) { std::string initially_empty; absl::CopyCordToString(cord, &initially_empty); EXPECT_EQ(initially_empty, cord); constexpr size_t kInitialLength = 1024; std::string has_initial_contents(kInitialLength, 'x'); const char* address_before_copy = has_initial_contents.data(); absl::CopyCordToString(cord, &has_initial_contents); EXPECT_EQ(has_initial_contents, cord); if (cord.size() <= kInitialLength) { EXPECT_EQ(has_initial_contents.data(), address_before_copy) << "CopyCordToString allocated new string storage; " "has_initial_contents = \"" << has_initial_contents << "\""; } } TEST_P(CordTest, CopyToString) { VerifyCopyToString(absl::Cord()); // empty cords cannot carry CRCs VerifyCopyToString(MaybeHardened(absl::Cord("small cord"))); VerifyCopyToString(MaybeHardened( absl::MakeFragmentedCord({"fragmented ", "cord ", "to ", "test ", "copying ", "to ", "a ", "string."}))); } static void VerifyAppendCordToString(const absl::Cord& cord) { std::string initially_empty; absl::AppendCordToString(cord, &initially_empty); EXPECT_EQ(initially_empty, cord); const absl::string_view kInitialContents = "initial contents."; std::string expected_after_append = absl::StrCat(kInitialContents, std::string(cord)); std::string no_reserve(kInitialContents); absl::AppendCordToString(cord, &no_reserve); EXPECT_EQ(no_reserve, expected_after_append); std::string has_reserved_capacity(kInitialContents); has_reserved_capacity.reserve(has_reserved_capacity.size() + cord.size()); const char* address_before_copy = has_reserved_capacity.data(); absl::AppendCordToString(cord, &has_reserved_capacity); EXPECT_EQ(has_reserved_capacity, expected_after_append); EXPECT_EQ(has_reserved_capacity.data(), address_before_copy) << "AppendCordToString allocated new string storage; " "has_reserved_capacity = \"" << has_reserved_capacity << "\""; } TEST_P(CordTest, AppendToString) { VerifyAppendCordToString(absl::Cord()); // empty cords cannot carry CRCs VerifyAppendCordToString(MaybeHardened(absl::Cord("small cord"))); VerifyAppendCordToString(MaybeHardened( absl::MakeFragmentedCord({"fragmented ", "cord ", "to ", "test ", "appending ", "to ", "a ", "string."}))); } TEST_P(CordTest, AppendEmptyBuffer) { absl::Cord cord; cord.Append(absl::CordBuffer()); cord.Append(absl::CordBuffer::CreateWithDefaultLimit(2000)); } TEST_P(CordTest, AppendEmptyBufferToFlat) { absl::Cord cord(std::string(2000, 'x')); cord.Append(absl::CordBuffer()); cord.Append(absl::CordBuffer::CreateWithDefaultLimit(2000)); } TEST_P(CordTest, AppendEmptyBufferToTree) { absl::Cord cord(std::string(2000, 'x')); cord.Append(std::string(2000, 'y')); cord.Append(absl::CordBuffer()); cord.Append(absl::CordBuffer::CreateWithDefaultLimit(2000)); } TEST_P(CordTest, AppendSmallBuffer) { absl::Cord cord; absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(3); ASSERT_THAT(buffer.capacity(), Le(15)); memcpy(buffer.data(), "Abc", 3); buffer.SetLength(3); cord.Append(std::move(buffer)); EXPECT_EQ(buffer.length(), 0); // NOLINT EXPECT_GT(buffer.capacity(), 0); // NOLINT buffer = absl::CordBuffer::CreateWithDefaultLimit(3); memcpy(buffer.data(), "defgh", 5); buffer.SetLength(5); cord.Append(std::move(buffer)); EXPECT_EQ(buffer.length(), 0); // NOLINT EXPECT_GT(buffer.capacity(), 0); // NOLINT EXPECT_THAT(cord.Chunks(), ElementsAre("Abcdefgh")); } TEST_P(CordTest, AppendAndPrependBufferArePrecise) { // Create a cord large enough to force 40KB flats. std::string test_data(absl::cord_internal::kMaxFlatLength * 10, 'x'); absl::Cord cord1(test_data); absl::Cord cord2(test_data); const size_t size1 = cord1.EstimatedMemoryUsage(); const size_t size2 = cord2.EstimatedMemoryUsage(); absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(3); memcpy(buffer.data(), "Abc", 3); buffer.SetLength(3); cord1.Append(std::move(buffer)); buffer = absl::CordBuffer::CreateWithDefaultLimit(3); memcpy(buffer.data(), "Abc", 3); buffer.SetLength(3); cord2.Prepend(std::move(buffer)); #ifndef NDEBUG // Allow 32 bytes new CordRepFlat, and 128 bytes for 'glue nodes' constexpr size_t kMaxDelta = 128 + 32; #else // Allow 256 bytes extra for 'allocation debug overhead' constexpr size_t kMaxDelta = 128 + 32 + 256; #endif EXPECT_LE(cord1.EstimatedMemoryUsage() - size1, kMaxDelta); EXPECT_LE(cord2.EstimatedMemoryUsage() - size2, kMaxDelta); EXPECT_EQ(cord1, absl::StrCat(test_data, "Abc")); EXPECT_EQ(cord2, absl::StrCat("Abc", test_data)); } TEST_P(CordTest, PrependSmallBuffer) { absl::Cord cord; absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(3); ASSERT_THAT(buffer.capacity(), Le(15)); memcpy(buffer.data(), "Abc", 3); buffer.SetLength(3); cord.Prepend(std::move(buffer)); EXPECT_EQ(buffer.length(), 0); // NOLINT EXPECT_GT(buffer.capacity(), 0); // NOLINT buffer = absl::CordBuffer::CreateWithDefaultLimit(3); memcpy(buffer.data(), "defgh", 5); buffer.SetLength(5); cord.Prepend(std::move(buffer)); EXPECT_EQ(buffer.length(), 0); // NOLINT EXPECT_GT(buffer.capacity(), 0); // NOLINT EXPECT_THAT(cord.Chunks(), ElementsAre("defghAbc")); } TEST_P(CordTest, AppendLargeBuffer) { absl::Cord cord; std::string s1(700, '1'); absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(s1.size()); memcpy(buffer.data(), s1.data(), s1.size()); buffer.SetLength(s1.size()); cord.Append(std::move(buffer)); EXPECT_EQ(buffer.length(), 0); // NOLINT EXPECT_GT(buffer.capacity(), 0); // NOLINT std::string s2(1000, '2'); buffer = absl::CordBuffer::CreateWithDefaultLimit(s2.size()); memcpy(buffer.data(), s2.data(), s2.size()); buffer.SetLength(s2.size()); cord.Append(std::move(buffer)); EXPECT_EQ(buffer.length(), 0); // NOLINT EXPECT_GT(buffer.capacity(), 0); // NOLINT EXPECT_THAT(cord.Chunks(), ElementsAre(s1, s2)); } TEST_P(CordTest, PrependLargeBuffer) { absl::Cord cord; std::string s1(700, '1'); absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(s1.size()); memcpy(buffer.data(), s1.data(), s1.size()); buffer.SetLength(s1.size()); cord.Prepend(std::move(buffer)); EXPECT_EQ(buffer.length(), 0); // NOLINT EXPECT_GT(buffer.capacity(), 0); // NOLINT std::string s2(1000, '2'); buffer = absl::CordBuffer::CreateWithDefaultLimit(s2.size()); memcpy(buffer.data(), s2.data(), s2.size()); buffer.SetLength(s2.size()); cord.Prepend(std::move(buffer)); EXPECT_EQ(buffer.length(), 0); // NOLINT EXPECT_GT(buffer.capacity(), 0); // NOLINT EXPECT_THAT(cord.Chunks(), ElementsAre(s2, s1)); } class CordAppendBufferTest : public testing::TestWithParam { public: size_t is_default() const { return GetParam(); } // Returns human readable string representation of the test parameter. static std::string ToString(testing::TestParamInfo param) { return param.param ? "DefaultLimit" : "CustomLimit"; } size_t limit() const { return is_default() ? absl::CordBuffer::kDefaultLimit : absl::CordBuffer::kCustomLimit; } size_t maximum_payload() const { return is_default() ? absl::CordBuffer::MaximumPayload() : absl::CordBuffer::MaximumPayload(limit()); } absl::CordBuffer GetAppendBuffer(absl::Cord& cord, size_t capacity, size_t min_capacity = 16) { return is_default() ? cord.GetAppendBuffer(capacity, min_capacity) : cord.GetCustomAppendBuffer(limit(), capacity, min_capacity); } }; INSTANTIATE_TEST_SUITE_P(WithParam, CordAppendBufferTest, testing::Bool(), CordAppendBufferTest::ToString); TEST_P(CordAppendBufferTest, GetAppendBufferOnEmptyCord) { absl::Cord cord; absl::CordBuffer buffer = GetAppendBuffer(cord, 1000); EXPECT_GE(buffer.capacity(), 1000); EXPECT_EQ(buffer.length(), 0); } TEST_P(CordAppendBufferTest, GetAppendBufferOnInlinedCord) { static constexpr int kInlinedSize = sizeof(absl::CordBuffer) - 1; for (int size : {6, kInlinedSize - 3, kInlinedSize - 2, 1000}) { absl::Cord cord("Abc"); absl::CordBuffer buffer = GetAppendBuffer(cord, size, 1); EXPECT_GE(buffer.capacity(), 3 + size); EXPECT_EQ(buffer.length(), 3); EXPECT_EQ(absl::string_view(buffer.data(), buffer.length()), "Abc"); EXPECT_TRUE(cord.empty()); } } TEST_P(CordAppendBufferTest, GetAppendBufferOnInlinedCordCapacityCloseToMax) { // Cover the use case where we have a non empty inlined cord with some size // 'n', and ask for something like 'uint64_max - k', assuming internal logic // could overflow on 'uint64_max - k + size', and return a valid, but // inefficiently smaller buffer if it would provide is the max allowed size. for (size_t dist_from_max = 0; dist_from_max <= 4; ++dist_from_max) { absl::Cord cord("Abc"); size_t size = std::numeric_limits::max() - dist_from_max; absl::CordBuffer buffer = GetAppendBuffer(cord, size, 1); EXPECT_GE(buffer.capacity(), maximum_payload()); EXPECT_EQ(buffer.length(), 3); EXPECT_EQ(absl::string_view(buffer.data(), buffer.length()), "Abc"); EXPECT_TRUE(cord.empty()); } } TEST_P(CordAppendBufferTest, GetAppendBufferOnFlat) { // Create a cord with a single flat and extra capacity absl::Cord cord; absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); const size_t expected_capacity = buffer.capacity(); buffer.SetLength(3); memcpy(buffer.data(), "Abc", 3); cord.Append(std::move(buffer)); buffer = GetAppendBuffer(cord, 6); EXPECT_EQ(buffer.capacity(), expected_capacity); EXPECT_EQ(buffer.length(), 3); EXPECT_EQ(absl::string_view(buffer.data(), buffer.length()), "Abc"); EXPECT_TRUE(cord.empty()); } TEST_P(CordAppendBufferTest, GetAppendBufferOnFlatWithoutMinCapacity) { // Create a cord with a single flat and extra capacity absl::Cord cord; absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); buffer.SetLength(30); memset(buffer.data(), 'x', 30); cord.Append(std::move(buffer)); buffer = GetAppendBuffer(cord, 1000, 900); EXPECT_GE(buffer.capacity(), 1000); EXPECT_EQ(buffer.length(), 0); EXPECT_EQ(cord, std::string(30, 'x')); } TEST_P(CordAppendBufferTest, GetAppendBufferOnTree) { RandomEngine rng; for (int num_flats : {2, 3, 100}) { // Create a cord with `num_flats` flats and extra capacity absl::Cord cord; std::string prefix; std::string last; for (int i = 0; i < num_flats - 1; ++i) { prefix += last; last = RandomLowercaseString(&rng, 10); absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); buffer.SetLength(10); memcpy(buffer.data(), last.data(), 10); cord.Append(std::move(buffer)); } absl::CordBuffer buffer = GetAppendBuffer(cord, 6); EXPECT_GE(buffer.capacity(), 500); EXPECT_EQ(buffer.length(), 10); EXPECT_EQ(absl::string_view(buffer.data(), buffer.length()), last); EXPECT_EQ(cord, prefix); } } TEST_P(CordAppendBufferTest, GetAppendBufferOnTreeWithoutMinCapacity) { absl::Cord cord; for (int i = 0; i < 2; ++i) { absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); buffer.SetLength(3); memcpy(buffer.data(), i ? "def" : "Abc", 3); cord.Append(std::move(buffer)); } absl::CordBuffer buffer = GetAppendBuffer(cord, 1000, 900); EXPECT_GE(buffer.capacity(), 1000); EXPECT_EQ(buffer.length(), 0); EXPECT_EQ(cord, "Abcdef"); } TEST_P(CordAppendBufferTest, GetAppendBufferOnSubstring) { // Create a large cord with a single flat and some extra capacity absl::Cord cord; absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); buffer.SetLength(450); memset(buffer.data(), 'x', 450); cord.Append(std::move(buffer)); cord.RemovePrefix(1); // Deny on substring buffer = GetAppendBuffer(cord, 6); EXPECT_EQ(buffer.length(), 0); EXPECT_EQ(cord, std::string(449, 'x')); } TEST_P(CordAppendBufferTest, GetAppendBufferOnSharedCord) { // Create a shared cord with a single flat and extra capacity absl::Cord cord; absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); buffer.SetLength(3); memcpy(buffer.data(), "Abc", 3); cord.Append(std::move(buffer)); absl::Cord shared_cord = cord; // Deny on flat buffer = GetAppendBuffer(cord, 6); EXPECT_EQ(buffer.length(), 0); EXPECT_EQ(cord, "Abc"); buffer = absl::CordBuffer::CreateWithDefaultLimit(500); buffer.SetLength(3); memcpy(buffer.data(), "def", 3); cord.Append(std::move(buffer)); shared_cord = cord; // Deny on tree buffer = GetAppendBuffer(cord, 6); EXPECT_EQ(buffer.length(), 0); EXPECT_EQ(cord, "Abcdef"); } TEST_P(CordTest, TryFlatEmpty) { absl::Cord c; EXPECT_EQ(c.TryFlat(), ""); } TEST_P(CordTest, TryFlatFlat) { absl::Cord c("hello"); MaybeHarden(c); EXPECT_EQ(c.TryFlat(), "hello"); } TEST_P(CordTest, TryFlatSubstrInlined) { absl::Cord c("hello"); c.RemovePrefix(1); MaybeHarden(c); EXPECT_EQ(c.TryFlat(), "ello"); } TEST_P(CordTest, TryFlatSubstrFlat) { absl::Cord c("longer than 15 bytes"); absl::Cord sub = absl::CordTestPeer::MakeSubstring(c, 1, c.size() - 1); MaybeHarden(sub); EXPECT_EQ(sub.TryFlat(), "onger than 15 bytes"); } TEST_P(CordTest, TryFlatConcat) { absl::Cord c = absl::MakeFragmentedCord({"hel", "lo"}); MaybeHarden(c); EXPECT_EQ(c.TryFlat(), absl::nullopt); } TEST_P(CordTest, TryFlatExternal) { absl::Cord c = absl::MakeCordFromExternal("hell", [](absl::string_view) {}); MaybeHarden(c); EXPECT_EQ(c.TryFlat(), "hell"); } TEST_P(CordTest, TryFlatSubstrExternal) { absl::Cord c = absl::MakeCordFromExternal("hell", [](absl::string_view) {}); absl::Cord sub = absl::CordTestPeer::MakeSubstring(c, 1, c.size() - 1); MaybeHarden(sub); EXPECT_EQ(sub.TryFlat(), "ell"); } TEST_P(CordTest, TryFlatCommonlyAssumedInvariants) { // The behavior tested below is not part of the API contract of Cord, but it's // something we intend to be true in our current implementation. This test // exists to detect and prevent accidental breakage of the implementation. absl::string_view fragments[] = {"A fragmented test", " cord", " to test subcords", " of ", "a", " cord for", " each chunk " "returned by the ", "iterator"}; absl::Cord c = absl::MakeFragmentedCord(fragments); MaybeHarden(c); int fragment = 0; int offset = 0; absl::Cord::CharIterator itc = c.char_begin(); for (absl::string_view sv : c.Chunks()) { absl::string_view expected = fragments[fragment]; absl::Cord subcord1 = c.Subcord(offset, sv.length()); absl::Cord subcord2 = absl::Cord::AdvanceAndRead(&itc, sv.size()); EXPECT_EQ(subcord1.TryFlat(), expected); EXPECT_EQ(subcord2.TryFlat(), expected); ++fragment; offset += sv.length(); } } static bool IsFlat(const absl::Cord& c) { return c.chunk_begin() == c.chunk_end() || ++c.chunk_begin() == c.chunk_end(); } static void VerifyFlatten(absl::Cord c) { std::string old_contents(c); absl::string_view old_flat; bool already_flat_and_non_empty = IsFlat(c) && !c.empty(); if (already_flat_and_non_empty) { old_flat = *c.chunk_begin(); } absl::string_view new_flat = c.Flatten(); // Verify that the contents of the flattened Cord are correct. EXPECT_EQ(new_flat, old_contents); EXPECT_EQ(std::string(c), old_contents); // If the Cord contained data and was already flat, verify that the data // wasn't copied. if (already_flat_and_non_empty) { EXPECT_EQ(old_flat.data(), new_flat.data()) << "Allocated new memory even though the Cord was already flat."; } // Verify that the flattened Cord is in fact flat. EXPECT_TRUE(IsFlat(c)); } TEST_P(CordTest, Flatten) { VerifyFlatten(absl::Cord()); VerifyFlatten(MaybeHardened(absl::Cord("small cord"))); VerifyFlatten( MaybeHardened(absl::Cord("larger than small buffer optimization"))); VerifyFlatten(MaybeHardened( absl::MakeFragmentedCord({"small ", "fragmented ", "cord"}))); // Test with a cord that is longer than the largest flat buffer RandomEngine rng(GTEST_FLAG_GET(random_seed)); VerifyFlatten(MaybeHardened(absl::Cord(RandomLowercaseString(&rng, 8192)))); } // Test data namespace { class TestData { private: std::vector data_; // Return a std::string of the specified length. static std::string MakeString(int length) { std::string result; char buf[30]; snprintf(buf, sizeof(buf), "(%d)", length); while (result.size() < length) { result += buf; } result.resize(length); return result; } public: TestData() { // short strings increasing in length by one for (int i = 0; i < 30; i++) { data_.push_back(MakeString(i)); } // strings around half kMaxFlatLength static const int kMaxFlatLength = 4096 - 9; static const int kHalf = kMaxFlatLength / 2; for (int i = -10; i <= +10; i++) { data_.push_back(MakeString(kHalf + i)); } for (int i = -10; i <= +10; i++) { data_.push_back(MakeString(kMaxFlatLength + i)); } } size_t size() const { return data_.size(); } const std::string& data(size_t i) const { return data_[i]; } }; } // namespace TEST_P(CordTest, MultipleLengths) { TestData d; for (size_t i = 0; i < d.size(); i++) { std::string a = d.data(i); { // Construct from Cord absl::Cord tmp(a); absl::Cord x(tmp); MaybeHarden(x); EXPECT_EQ(a, std::string(x)) << "'" << a << "'"; } { // Construct from absl::string_view absl::Cord x(a); MaybeHarden(x); EXPECT_EQ(a, std::string(x)) << "'" << a << "'"; } { // Append cord to self absl::Cord self(a); MaybeHarden(self); self.Append(self); EXPECT_EQ(a + a, std::string(self)) << "'" << a << "' + '" << a << "'"; } { // Prepend cord to self absl::Cord self(a); MaybeHarden(self); self.Prepend(self); EXPECT_EQ(a + a, std::string(self)) << "'" << a << "' + '" << a << "'"; } // Try to append/prepend others for (size_t j = 0; j < d.size(); j++) { std::string b = d.data(j); { // CopyFrom Cord absl::Cord x(a); absl::Cord y(b); MaybeHarden(x); x = y; EXPECT_EQ(b, std::string(x)) << "'" << a << "' + '" << b << "'"; } { // CopyFrom absl::string_view absl::Cord x(a); MaybeHarden(x); x = b; EXPECT_EQ(b, std::string(x)) << "'" << a << "' + '" << b << "'"; } { // Cord::Append(Cord) absl::Cord x(a); absl::Cord y(b); MaybeHarden(x); x.Append(y); EXPECT_EQ(a + b, std::string(x)) << "'" << a << "' + '" << b << "'"; } { // Cord::Append(absl::string_view) absl::Cord x(a); MaybeHarden(x); x.Append(b); EXPECT_EQ(a + b, std::string(x)) << "'" << a << "' + '" << b << "'"; } { // Cord::Prepend(Cord) absl::Cord x(a); absl::Cord y(b); MaybeHarden(x); x.Prepend(y); EXPECT_EQ(b + a, std::string(x)) << "'" << b << "' + '" << a << "'"; } { // Cord::Prepend(absl::string_view) absl::Cord x(a); MaybeHarden(x); x.Prepend(b); EXPECT_EQ(b + a, std::string(x)) << "'" << b << "' + '" << a << "'"; } } } } namespace { TEST_P(CordTest, RemoveSuffixWithExternalOrSubstring) { absl::Cord cord = absl::MakeCordFromExternal( "foo bar baz", [](absl::string_view s) { DoNothing(s, nullptr); }); EXPECT_EQ("foo bar baz", std::string(cord)); MaybeHarden(cord); // This RemoveSuffix() will wrap the EXTERNAL node in a SUBSTRING node. cord.RemoveSuffix(4); EXPECT_EQ("foo bar", std::string(cord)); MaybeHarden(cord); // This RemoveSuffix() will adjust the SUBSTRING node in-place. cord.RemoveSuffix(4); EXPECT_EQ("foo", std::string(cord)); } TEST_P(CordTest, RemoveSuffixMakesZeroLengthNode) { absl::Cord c; c.Append(absl::Cord(std::string(100, 'x'))); absl::Cord other_ref = c; // Prevent inplace appends MaybeHarden(c); c.Append(absl::Cord(std::string(200, 'y'))); c.RemoveSuffix(200); EXPECT_EQ(std::string(100, 'x'), std::string(c)); } } // namespace // CordSpliceTest contributed by hendrie. namespace { // Create a cord with an external memory block filled with 'z' absl::Cord CordWithZedBlock(size_t size) { char* data = new char[size]; if (size > 0) { memset(data, 'z', size); } absl::Cord cord = absl::MakeCordFromExternal( absl::string_view(data, size), [](absl::string_view s) { delete[] s.data(); }); return cord; } // Establish that ZedBlock does what we think it does. TEST_P(CordTest, CordSpliceTestZedBlock) { absl::Cord blob = CordWithZedBlock(10); MaybeHarden(blob); EXPECT_EQ(10, blob.size()); std::string s; absl::CopyCordToString(blob, &s); EXPECT_EQ("zzzzzzzzzz", s); } TEST_P(CordTest, CordSpliceTestZedBlock0) { absl::Cord blob = CordWithZedBlock(0); MaybeHarden(blob); EXPECT_EQ(0, blob.size()); std::string s; absl::CopyCordToString(blob, &s); EXPECT_EQ("", s); } TEST_P(CordTest, CordSpliceTestZedBlockSuffix1) { absl::Cord blob = CordWithZedBlock(10); MaybeHarden(blob); EXPECT_EQ(10, blob.size()); absl::Cord suffix(blob); suffix.RemovePrefix(9); EXPECT_EQ(1, suffix.size()); std::string s; absl::CopyCordToString(suffix, &s); EXPECT_EQ("z", s); } // Remove all of a prefix block TEST_P(CordTest, CordSpliceTestZedBlockSuffix0) { absl::Cord blob = CordWithZedBlock(10); MaybeHarden(blob); EXPECT_EQ(10, blob.size()); absl::Cord suffix(blob); suffix.RemovePrefix(10); EXPECT_EQ(0, suffix.size()); std::string s; absl::CopyCordToString(suffix, &s); EXPECT_EQ("", s); } absl::Cord BigCord(size_t len, char v) { std::string s(len, v); return absl::Cord(s); } // Splice block into cord. absl::Cord SpliceCord(const absl::Cord& blob, int64_t offset, const absl::Cord& block) { CHECK_GE(offset, 0); CHECK_LE(static_cast(offset) + block.size(), blob.size()); absl::Cord result(blob); result.RemoveSuffix(blob.size() - offset); result.Append(block); absl::Cord suffix(blob); suffix.RemovePrefix(offset + block.size()); result.Append(suffix); CHECK_EQ(blob.size(), result.size()); return result; } // Taking an empty suffix of a block breaks appending. TEST_P(CordTest, CordSpliceTestRemoveEntireBlock1) { absl::Cord zero = CordWithZedBlock(10); MaybeHarden(zero); absl::Cord suffix(zero); suffix.RemovePrefix(10); absl::Cord result; result.Append(suffix); } TEST_P(CordTest, CordSpliceTestRemoveEntireBlock2) { absl::Cord zero = CordWithZedBlock(10); MaybeHarden(zero); absl::Cord prefix(zero); prefix.RemoveSuffix(10); absl::Cord suffix(zero); suffix.RemovePrefix(10); absl::Cord result(prefix); result.Append(suffix); } TEST_P(CordTest, CordSpliceTestRemoveEntireBlock3) { absl::Cord blob = CordWithZedBlock(10); absl::Cord block = BigCord(10, 'b'); MaybeHarden(blob); MaybeHarden(block); blob = SpliceCord(blob, 0, block); } struct CordCompareTestCase { template CordCompareTestCase(const LHS& lhs, const RHS& rhs, bool use_crc) : lhs_cord(lhs), rhs_cord(rhs) { if (use_crc) { lhs_cord.SetExpectedChecksum(1); } } absl::Cord lhs_cord; absl::Cord rhs_cord; }; const auto sign = [](int x) { return x == 0 ? 0 : (x > 0 ? 1 : -1); }; void VerifyComparison(const CordCompareTestCase& test_case) { std::string lhs_string(test_case.lhs_cord); std::string rhs_string(test_case.rhs_cord); int expected = sign(lhs_string.compare(rhs_string)); EXPECT_EQ(expected, test_case.lhs_cord.Compare(test_case.rhs_cord)) << "LHS=" << lhs_string << "; RHS=" << rhs_string; EXPECT_EQ(expected, test_case.lhs_cord.Compare(rhs_string)) << "LHS=" << lhs_string << "; RHS=" << rhs_string; EXPECT_EQ(-expected, test_case.rhs_cord.Compare(test_case.lhs_cord)) << "LHS=" << rhs_string << "; RHS=" << lhs_string; EXPECT_EQ(-expected, test_case.rhs_cord.Compare(lhs_string)) << "LHS=" << rhs_string << "; RHS=" << lhs_string; } TEST_P(CordTest, Compare) { absl::Cord subcord("aaaaaBBBBBcccccDDDDD"); subcord = subcord.Subcord(3, 10); absl::Cord tmp("aaaaaaaaaaaaaaaa"); tmp.Append("BBBBBBBBBBBBBBBB"); absl::Cord concat = absl::Cord("cccccccccccccccc"); concat.Append("DDDDDDDDDDDDDDDD"); concat.Prepend(tmp); absl::Cord concat2("aaaaaaaaaaaaa"); concat2.Append("aaaBBBBBBBBBBBBBBBBccccc"); concat2.Append("cccccccccccDDDDDDDDDDDDDD"); concat2.Append("DD"); const bool use_crc = UseCrc(); std::vector test_cases = {{ // Inline cords {"abcdef", "abcdef", use_crc}, {"abcdef", "abcdee", use_crc}, {"abcdef", "abcdeg", use_crc}, {"bbcdef", "abcdef", use_crc}, {"bbcdef", "abcdeg", use_crc}, {"abcdefa", "abcdef", use_crc}, {"abcdef", "abcdefa", use_crc}, // Small flat cords {"aaaaaBBBBBcccccDDDDD", "aaaaaBBBBBcccccDDDDD", use_crc}, {"aaaaaBBBBBcccccDDDDD", "aaaaaBBBBBxccccDDDDD", use_crc}, {"aaaaaBBBBBcxcccDDDDD", "aaaaaBBBBBcccccDDDDD", use_crc}, {"aaaaaBBBBBxccccDDDDD", "aaaaaBBBBBcccccDDDDX", use_crc}, {"aaaaaBBBBBcccccDDDDDa", "aaaaaBBBBBcccccDDDDD", use_crc}, {"aaaaaBBBBBcccccDDDDD", "aaaaaBBBBBcccccDDDDDa", use_crc}, // Subcords {subcord, subcord, use_crc}, {subcord, "aaBBBBBccc", use_crc}, {subcord, "aaBBBBBccd", use_crc}, {subcord, "aaBBBBBccb", use_crc}, {subcord, "aaBBBBBxcb", use_crc}, {subcord, "aaBBBBBccca", use_crc}, {subcord, "aaBBBBBcc", use_crc}, // Concats {concat, concat, use_crc}, {concat, "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBccccccccccccccccDDDDDDDDDDDDDDDD", use_crc}, {concat, "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBcccccccccccccccxDDDDDDDDDDDDDDDD", use_crc}, {concat, "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBacccccccccccccccDDDDDDDDDDDDDDDD", use_crc}, {concat, "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBccccccccccccccccDDDDDDDDDDDDDDD", use_crc}, {concat, "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBccccccccccccccccDDDDDDDDDDDDDDDDe", use_crc}, {concat, concat2, use_crc}, }}; for (const auto& tc : test_cases) { VerifyComparison(tc); } } TEST_P(CordTest, CompareAfterAssign) { absl::Cord a("aaaaaa1111111"); absl::Cord b("aaaaaa2222222"); MaybeHarden(a); a = "cccccc"; b = "cccccc"; EXPECT_EQ(a, b); EXPECT_FALSE(a < b); a = "aaaa"; b = "bbbbb"; a = ""; b = ""; EXPECT_EQ(a, b); EXPECT_FALSE(a < b); } // Test CompareTo() and ComparePrefix() against string and substring // comparison methods from basic_string. static void TestCompare(const absl::Cord& c, const absl::Cord& d, RandomEngine* rng) { // char_traits::lt is guaranteed to do an unsigned comparison: // https://en.cppreference.com/w/cpp/string/char_traits/cmp. We also expect // Cord comparisons to be based on unsigned byte comparisons regardless of // whether char is signed. int expected = sign(std::string(c).compare(std::string(d))); EXPECT_EQ(expected, sign(c.Compare(d))) << c << ", " << d; } TEST_P(CordTest, CompareComparisonIsUnsigned) { RandomEngine rng(GTEST_FLAG_GET(random_seed)); std::uniform_int_distribution uniform_uint8(0, 255); char x = static_cast(uniform_uint8(rng)); TestCompare( absl::Cord(std::string(GetUniformRandomUpTo(&rng, 100), x)), absl::Cord(std::string(GetUniformRandomUpTo(&rng, 100), x ^ 0x80)), &rng); } TEST_P(CordTest, CompareRandomComparisons) { const int kIters = 5000; RandomEngine rng(GTEST_FLAG_GET(random_seed)); int n = GetUniformRandomUpTo(&rng, 5000); absl::Cord a[] = {MakeExternalCord(n), absl::Cord("ant"), absl::Cord("elephant"), absl::Cord("giraffe"), absl::Cord(std::string(GetUniformRandomUpTo(&rng, 100), GetUniformRandomUpTo(&rng, 100))), absl::Cord(""), absl::Cord("x"), absl::Cord("A"), absl::Cord("B"), absl::Cord("C")}; for (int i = 0; i < kIters; i++) { absl::Cord c, d; for (int j = 0; j < (i % 7) + 1; j++) { c.Append(a[GetUniformRandomUpTo(&rng, ABSL_ARRAYSIZE(a))]); d.Append(a[GetUniformRandomUpTo(&rng, ABSL_ARRAYSIZE(a))]); } std::bernoulli_distribution coin_flip(0.5); MaybeHarden(c); MaybeHarden(d); TestCompare(coin_flip(rng) ? c : absl::Cord(std::string(c)), coin_flip(rng) ? d : absl::Cord(std::string(d)), &rng); } } template void CompareOperators() { const T1 a("a"); const T2 b("b"); EXPECT_TRUE(a == a); // For pointer type (i.e. `const char*`), operator== compares the address // instead of the string, so `a == const char*("a")` isn't necessarily true. EXPECT_TRUE(std::is_pointer::value || a == T1("a")); EXPECT_TRUE(std::is_pointer::value || a == T2("a")); EXPECT_FALSE(a == b); EXPECT_TRUE(a != b); EXPECT_FALSE(a != a); EXPECT_TRUE(a < b); EXPECT_FALSE(b < a); EXPECT_TRUE(b > a); EXPECT_FALSE(a > b); EXPECT_TRUE(a >= a); EXPECT_TRUE(b >= a); EXPECT_FALSE(a >= b); EXPECT_TRUE(a <= a); EXPECT_TRUE(a <= b); EXPECT_FALSE(b <= a); } TEST_P(CordTest, ComparisonOperators_Cord_Cord) { CompareOperators(); } TEST_P(CordTest, ComparisonOperators_Cord_StringPiece) { CompareOperators(); } TEST_P(CordTest, ComparisonOperators_StringPiece_Cord) { CompareOperators(); } TEST_P(CordTest, ComparisonOperators_Cord_string) { CompareOperators(); } TEST_P(CordTest, ComparisonOperators_string_Cord) { CompareOperators(); } TEST_P(CordTest, ComparisonOperators_stdstring_Cord) { CompareOperators(); } TEST_P(CordTest, ComparisonOperators_Cord_stdstring) { CompareOperators(); } TEST_P(CordTest, ComparisonOperators_charstar_Cord) { CompareOperators(); } TEST_P(CordTest, ComparisonOperators_Cord_charstar) { CompareOperators(); } TEST_P(CordTest, ConstructFromExternalReleaserInvoked) { // Empty external memory means the releaser should be called immediately. { bool invoked = false; auto releaser = [&invoked](absl::string_view) { invoked = true; }; { auto c = absl::MakeCordFromExternal("", releaser); EXPECT_TRUE(invoked); } } // If the size of the data is small enough, a future constructor // implementation may copy the bytes and immediately invoke the releaser // instead of creating an external node. We make a large dummy std::string to // make this test independent of such an optimization. std::string large_dummy(2048, 'c'); { bool invoked = false; auto releaser = [&invoked](absl::string_view) { invoked = true; }; { auto c = absl::MakeCordFromExternal(large_dummy, releaser); EXPECT_FALSE(invoked); } EXPECT_TRUE(invoked); } { bool invoked = false; auto releaser = [&invoked](absl::string_view) { invoked = true; }; { absl::Cord copy; { auto c = absl::MakeCordFromExternal(large_dummy, releaser); copy = c; EXPECT_FALSE(invoked); } EXPECT_FALSE(invoked); } EXPECT_TRUE(invoked); } } TEST_P(CordTest, ConstructFromExternalCompareContents) { RandomEngine rng(GTEST_FLAG_GET(random_seed)); for (int length = 1; length <= 2048; length *= 2) { std::string data = RandomLowercaseString(&rng, length); auto* external = new std::string(data); auto cord = absl::MakeCordFromExternal(*external, [external](absl::string_view sv) { EXPECT_EQ(external->data(), sv.data()); EXPECT_EQ(external->size(), sv.size()); delete external; }); MaybeHarden(cord); EXPECT_EQ(data, cord); } } TEST_P(CordTest, ConstructFromExternalLargeReleaser) { RandomEngine rng(GTEST_FLAG_GET(random_seed)); constexpr size_t kLength = 256; std::string data = RandomLowercaseString(&rng, kLength); std::array data_array; for (size_t i = 0; i < kLength; ++i) data_array[i] = data[i]; bool invoked = false; auto releaser = [data_array, &invoked](absl::string_view data) { EXPECT_EQ(data, absl::string_view(data_array.data(), data_array.size())); invoked = true; }; (void)MaybeHardened(absl::MakeCordFromExternal(data, releaser)); EXPECT_TRUE(invoked); } TEST_P(CordTest, ConstructFromExternalFunctionPointerReleaser) { static absl::string_view data("hello world"); static bool invoked; auto* releaser = static_cast([](absl::string_view sv) { EXPECT_EQ(data, sv); invoked = true; }); invoked = false; (void)MaybeHardened(absl::MakeCordFromExternal(data, releaser)); EXPECT_TRUE(invoked); invoked = false; (void)MaybeHardened(absl::MakeCordFromExternal(data, *releaser)); EXPECT_TRUE(invoked); } TEST_P(CordTest, ConstructFromExternalMoveOnlyReleaser) { struct Releaser { explicit Releaser(bool* invoked) : invoked(invoked) {} Releaser(Releaser&& other) noexcept : invoked(other.invoked) {} void operator()(absl::string_view) const { *invoked = true; } bool* invoked; }; bool invoked = false; (void)MaybeHardened(absl::MakeCordFromExternal("dummy", Releaser(&invoked))); EXPECT_TRUE(invoked); } TEST_P(CordTest, ConstructFromExternalNoArgLambda) { bool invoked = false; (void)MaybeHardened( absl::MakeCordFromExternal("dummy", [&invoked]() { invoked = true; })); EXPECT_TRUE(invoked); } TEST_P(CordTest, ConstructFromExternalStringViewArgLambda) { bool invoked = false; (void)MaybeHardened(absl::MakeCordFromExternal( "dummy", [&invoked](absl::string_view) { invoked = true; })); EXPECT_TRUE(invoked); } TEST_P(CordTest, ConstructFromExternalNonTrivialReleaserDestructor) { struct Releaser { explicit Releaser(bool* destroyed) : destroyed(destroyed) {} ~Releaser() { *destroyed = true; } void operator()(absl::string_view) const {} bool* destroyed; }; bool destroyed = false; Releaser releaser(&destroyed); (void)MaybeHardened(absl::MakeCordFromExternal("dummy", releaser)); EXPECT_TRUE(destroyed); } TEST_P(CordTest, ConstructFromExternalReferenceQualifierOverloads) { enum InvokedAs { kMissing, kLValue, kRValue }; enum CopiedAs { kNone, kMove, kCopy }; struct Tracker { CopiedAs copied_as = kNone; InvokedAs invoked_as = kMissing; void Record(InvokedAs rhs) { ASSERT_EQ(invoked_as, kMissing); invoked_as = rhs; } void Record(CopiedAs rhs) { if (copied_as == kNone || rhs == kCopy) copied_as = rhs; } } tracker; class Releaser { public: explicit Releaser(Tracker* tracker) : tr_(tracker) { *tracker = Tracker(); } Releaser(Releaser&& rhs) : tr_(rhs.tr_) { tr_->Record(kMove); } Releaser(const Releaser& rhs) : tr_(rhs.tr_) { tr_->Record(kCopy); } void operator()(absl::string_view) & { tr_->Record(kLValue); } void operator()(absl::string_view) && { tr_->Record(kRValue); } private: Tracker* tr_; }; const Releaser releaser1(&tracker); (void)MaybeHardened(absl::MakeCordFromExternal("", releaser1)); EXPECT_EQ(tracker.copied_as, kCopy); EXPECT_EQ(tracker.invoked_as, kRValue); const Releaser releaser2(&tracker); (void)MaybeHardened(absl::MakeCordFromExternal("", releaser2)); EXPECT_EQ(tracker.copied_as, kCopy); EXPECT_EQ(tracker.invoked_as, kRValue); Releaser releaser3(&tracker); (void)MaybeHardened(absl::MakeCordFromExternal("", std::move(releaser3))); EXPECT_EQ(tracker.copied_as, kMove); EXPECT_EQ(tracker.invoked_as, kRValue); Releaser releaser4(&tracker); (void)MaybeHardened(absl::MakeCordFromExternal("dummy", releaser4)); EXPECT_EQ(tracker.copied_as, kCopy); EXPECT_EQ(tracker.invoked_as, kRValue); const Releaser releaser5(&tracker); (void)MaybeHardened(absl::MakeCordFromExternal("dummy", releaser5)); EXPECT_EQ(tracker.copied_as, kCopy); EXPECT_EQ(tracker.invoked_as, kRValue); Releaser releaser6(&tracker); (void)MaybeHardened(absl::MakeCordFromExternal("foo", std::move(releaser6))); EXPECT_EQ(tracker.copied_as, kMove); EXPECT_EQ(tracker.invoked_as, kRValue); } TEST_P(CordTest, ExternalMemoryBasicUsage) { static const char* strings[] = {"", "hello", "there"}; for (const char* str : strings) { absl::Cord dst("(prefix)"); MaybeHarden(dst); AddExternalMemory(str, &dst); MaybeHarden(dst); dst.Append("(suffix)"); EXPECT_EQ((std::string("(prefix)") + str + std::string("(suffix)")), std::string(dst)); } } TEST_P(CordTest, ExternalMemoryRemovePrefixSuffix) { // Exhaustively try all sub-strings. absl::Cord cord = MakeComposite(); std::string s = std::string(cord); for (int offset = 0; offset <= s.size(); offset++) { for (int length = 0; length <= s.size() - offset; length++) { absl::Cord result(cord); MaybeHarden(result); result.RemovePrefix(offset); MaybeHarden(result); result.RemoveSuffix(result.size() - length); EXPECT_EQ(s.substr(offset, length), std::string(result)) << offset << " " << length; } } } TEST_P(CordTest, ExternalMemoryGet) { absl::Cord cord("hello"); AddExternalMemory(" world!", &cord); MaybeHarden(cord); AddExternalMemory(" how are ", &cord); cord.Append(" you?"); MaybeHarden(cord); std::string s = std::string(cord); for (int i = 0; i < s.size(); i++) { EXPECT_EQ(s[i], cord[i]); } } // CordMemoryUsage tests verify the correctness of the EstimatedMemoryUsage() // We use whiteboxed expectations based on our knowledge of the layout and size // of empty and inlined cords, and flat nodes. constexpr auto kFairShare = absl::CordMemoryAccounting::kFairShare; constexpr auto kTotalMorePrecise = absl::CordMemoryAccounting::kTotalMorePrecise; // Creates a cord of `n` `c` values, making sure no string stealing occurs. absl::Cord MakeCord(size_t n, char c) { const std::string s(n, c); return absl::Cord(s); } TEST(CordTest, CordMemoryUsageEmpty) { absl::Cord cord; EXPECT_EQ(sizeof(absl::Cord), cord.EstimatedMemoryUsage()); EXPECT_EQ(sizeof(absl::Cord), cord.EstimatedMemoryUsage(kFairShare)); EXPECT_EQ(sizeof(absl::Cord), cord.EstimatedMemoryUsage(kTotalMorePrecise)); } TEST(CordTest, CordMemoryUsageInlined) { absl::Cord a("hello"); EXPECT_EQ(a.EstimatedMemoryUsage(), sizeof(absl::Cord)); EXPECT_EQ(a.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord)); EXPECT_EQ(a.EstimatedMemoryUsage(kTotalMorePrecise), sizeof(absl::Cord)); } TEST(CordTest, CordMemoryUsageExternalMemory) { absl::Cord cord; AddExternalMemory(std::string(1000, 'x'), &cord); const size_t expected = sizeof(absl::Cord) + 1000 + sizeof(CordRepExternal) + sizeof(intptr_t); EXPECT_EQ(cord.EstimatedMemoryUsage(), expected); EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare), expected); EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise), expected); } TEST(CordTest, CordMemoryUsageFlat) { absl::Cord cord = MakeCord(1000, 'a'); const size_t flat_size = absl::CordTestPeer::Tree(cord)->flat()->AllocatedSize(); EXPECT_EQ(cord.EstimatedMemoryUsage(), sizeof(absl::Cord) + flat_size); EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + flat_size); EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise), sizeof(absl::Cord) + flat_size); } TEST(CordTest, CordMemoryUsageSubStringSharedFlat) { absl::Cord flat = MakeCord(2000, 'a'); const size_t flat_size = absl::CordTestPeer::Tree(flat)->flat()->AllocatedSize(); absl::Cord cord = flat.Subcord(500, 1000); EXPECT_EQ(cord.EstimatedMemoryUsage(), sizeof(absl::Cord) + sizeof(CordRepSubstring) + flat_size); EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise), sizeof(absl::Cord) + sizeof(CordRepSubstring) + flat_size); EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + sizeof(CordRepSubstring) + flat_size / 2); } TEST(CordTest, CordMemoryUsageFlatShared) { absl::Cord shared = MakeCord(1000, 'a'); absl::Cord cord(shared); const size_t flat_size = absl::CordTestPeer::Tree(cord)->flat()->AllocatedSize(); EXPECT_EQ(cord.EstimatedMemoryUsage(), sizeof(absl::Cord) + flat_size); EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise), sizeof(absl::Cord) + flat_size); EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + flat_size / 2); } TEST(CordTest, CordMemoryUsageFlatHardenedAndShared) { absl::Cord shared = MakeCord(1000, 'a'); absl::Cord cord(shared); const size_t flat_size = absl::CordTestPeer::Tree(cord)->flat()->AllocatedSize(); cord.SetExpectedChecksum(1); EXPECT_EQ(cord.EstimatedMemoryUsage(), sizeof(absl::Cord) + sizeof(CordRepCrc) + flat_size); EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + sizeof(CordRepCrc) + flat_size / 2); absl::Cord cord2(cord); EXPECT_EQ(cord2.EstimatedMemoryUsage(), sizeof(absl::Cord) + sizeof(CordRepCrc) + flat_size); EXPECT_EQ(cord2.EstimatedMemoryUsage(kTotalMorePrecise), sizeof(absl::Cord) + sizeof(CordRepCrc) + flat_size); EXPECT_EQ(cord2.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + (sizeof(CordRepCrc) + flat_size / 2) / 2); } TEST(CordTest, CordMemoryUsageBTree) { absl::Cord cord1; size_t flats1_size = 0; absl::Cord flats1[4] = {MakeCord(1000, 'a'), MakeCord(1100, 'a'), MakeCord(1200, 'a'), MakeCord(1300, 'a')}; for (absl::Cord flat : flats1) { flats1_size += absl::CordTestPeer::Tree(flat)->flat()->AllocatedSize(); cord1.Append(std::move(flat)); } // Make sure the created cord is a BTREE tree. Under some builds such as // windows DLL, we may have ODR like effects on the flag, meaning the DLL // code will run with the picked up default. if (!absl::CordTestPeer::Tree(cord1)->IsBtree()) { LOG(WARNING) << "Cord library code not respecting btree flag"; return; } size_t rep1_size = sizeof(CordRepBtree) + flats1_size; size_t rep1_shared_size = sizeof(CordRepBtree) + flats1_size / 2; EXPECT_EQ(cord1.EstimatedMemoryUsage(), sizeof(absl::Cord) + rep1_size); EXPECT_EQ(cord1.EstimatedMemoryUsage(kTotalMorePrecise), sizeof(absl::Cord) + rep1_size); EXPECT_EQ(cord1.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + rep1_shared_size); absl::Cord cord2; size_t flats2_size = 0; absl::Cord flats2[4] = {MakeCord(600, 'a'), MakeCord(700, 'a'), MakeCord(800, 'a'), MakeCord(900, 'a')}; for (absl::Cord& flat : flats2) { flats2_size += absl::CordTestPeer::Tree(flat)->flat()->AllocatedSize(); cord2.Append(std::move(flat)); } size_t rep2_size = sizeof(CordRepBtree) + flats2_size; EXPECT_EQ(cord2.EstimatedMemoryUsage(), sizeof(absl::Cord) + rep2_size); EXPECT_EQ(cord2.EstimatedMemoryUsage(kTotalMorePrecise), sizeof(absl::Cord) + rep2_size); EXPECT_EQ(cord2.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + rep2_size); absl::Cord cord(cord1); cord.Append(std::move(cord2)); EXPECT_EQ(cord.EstimatedMemoryUsage(), sizeof(absl::Cord) + sizeof(CordRepBtree) + rep1_size + rep2_size); EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise), sizeof(absl::Cord) + sizeof(CordRepBtree) + rep1_size + rep2_size); EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + sizeof(CordRepBtree) + rep1_shared_size / 2 + rep2_size); } TEST(CordTest, TestHashFragmentation) { // Make sure we hit these boundary cases precisely. EXPECT_EQ(1024, absl::hash_internal::PiecewiseChunkSize()); EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({ absl::Cord(), absl::MakeFragmentedCord({std::string(600, 'a'), std::string(600, 'a')}), absl::MakeFragmentedCord({std::string(1200, 'a')}), absl::MakeFragmentedCord({std::string(900, 'b'), std::string(900, 'b')}), absl::MakeFragmentedCord({std::string(1800, 'b')}), absl::MakeFragmentedCord( {std::string(2000, 'c'), std::string(2000, 'c')}), absl::MakeFragmentedCord({std::string(4000, 'c')}), absl::MakeFragmentedCord({std::string(1024, 'd')}), absl::MakeFragmentedCord({std::string(1023, 'd'), "d"}), absl::MakeFragmentedCord({std::string(1025, 'e')}), absl::MakeFragmentedCord({std::string(1024, 'e'), "e"}), absl::MakeFragmentedCord({std::string(1023, 'e'), "e", "e"}), })); } // Regtest for a change that had to be rolled back because it expanded out // of the InlineRep too soon, which was observable through MemoryUsage(). TEST_P(CordTest, CordMemoryUsageInlineRep) { constexpr size_t kMaxInline = 15; // Cord::InlineRep::N const std::string small_string(kMaxInline, 'x'); absl::Cord c1(small_string); absl::Cord c2; c2.Append(small_string); EXPECT_EQ(c1, c2); EXPECT_EQ(c1.EstimatedMemoryUsage(), c2.EstimatedMemoryUsage()); } TEST_P(CordTest, CordMemoryUsageTotalMorePreciseMode) { constexpr size_t kChunkSize = 2000; std::string tmp_str(kChunkSize, 'x'); const absl::Cord flat(std::move(tmp_str)); // Construct `fragmented` with two references into the same // underlying buffer shared with `flat`: absl::Cord fragmented(flat); fragmented.Append(flat); // Memory usage of `flat`, minus the top-level Cord object: const size_t flat_internal_usage = flat.EstimatedMemoryUsage() - sizeof(absl::Cord); // `fragmented` holds a Cord and a CordRepBtree. That tree points to two // copies of flat's internals, which we expect to dedup: EXPECT_EQ(fragmented.EstimatedMemoryUsage(kTotalMorePrecise), sizeof(absl::Cord) + sizeof(CordRepBtree) + flat_internal_usage); // This is a case where kTotal produces an overestimate: EXPECT_EQ(fragmented.EstimatedMemoryUsage(), sizeof(absl::Cord) + sizeof(CordRepBtree) + 2 * flat_internal_usage); } TEST_P(CordTest, CordMemoryUsageTotalMorePreciseModeWithSubstring) { constexpr size_t kChunkSize = 2000; std::string tmp_str(kChunkSize, 'x'); const absl::Cord flat(std::move(tmp_str)); // Construct `fragmented` with two references into the same // underlying buffer shared with `flat`. // // This time, each reference is through a Subcord(): absl::Cord fragmented; fragmented.Append(flat.Subcord(1, kChunkSize - 2)); fragmented.Append(flat.Subcord(1, kChunkSize - 2)); // Memory usage of `flat`, minus the top-level Cord object: const size_t flat_internal_usage = flat.EstimatedMemoryUsage() - sizeof(absl::Cord); // `fragmented` holds a Cord and a CordRepBtree. That tree points to two // CordRepSubstrings, each pointing at flat's internals. EXPECT_EQ(fragmented.EstimatedMemoryUsage(kTotalMorePrecise), sizeof(absl::Cord) + sizeof(CordRepBtree) + 2 * sizeof(CordRepSubstring) + flat_internal_usage); // This is a case where kTotal produces an overestimate: EXPECT_EQ(fragmented.EstimatedMemoryUsage(), sizeof(absl::Cord) + sizeof(CordRepBtree) + 2 * sizeof(CordRepSubstring) + 2 * flat_internal_usage); } } // namespace // Regtest for 7510292 (fix a bug introduced by 7465150) TEST_P(CordTest, Concat_Append) { // Create a rep of type CONCAT absl::Cord s1("foobarbarbarbarbar"); MaybeHarden(s1); s1.Append("abcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefg"); size_t size = s1.size(); // Create a copy of s1 and append to it. absl::Cord s2 = s1; MaybeHarden(s2); s2.Append("x"); // 7465150 modifies s1 when it shouldn't. EXPECT_EQ(s1.size(), size); EXPECT_EQ(s2.size(), size + 1); } TEST_P(CordTest, DiabolicalGrowth) { // This test exercises a diabolical Append() on a cord, making the // cord shared before each Append call resulting in a terribly fragmented // resulting cord. RandomEngine rng(GTEST_FLAG_GET(random_seed)); const std::string expected = RandomLowercaseString(&rng, 5000); absl::Cord cord; for (char c : expected) { absl::Cord shared(cord); cord.Append(absl::string_view(&c, 1)); MaybeHarden(cord); } std::string value; absl::CopyCordToString(cord, &value); EXPECT_EQ(value, expected); LOG(INFO) << "Diabolical size allocated = " << cord.EstimatedMemoryUsage(); } // The following tests check support for >4GB cords in 64-bit binaries, and // 2GB-4GB cords in 32-bit binaries. This function returns the large cord size // that's appropriate for the binary. // Construct a huge cord with the specified valid prefix. static absl::Cord MakeHuge(absl::string_view prefix) { absl::Cord cord; if (sizeof(size_t) > 4) { // In 64-bit binaries, test 64-bit Cord support. const size_t size = static_cast(std::numeric_limits::max()) + 314; cord.Append(absl::MakeCordFromExternal( absl::string_view(prefix.data(), size), [](absl::string_view s) { DoNothing(s, nullptr); })); } else { // Cords are limited to 32-bit lengths in 32-bit binaries. The following // tests check for use of "signed int" to represent Cord length/offset. // However absl::string_view does not allow lengths >= (1u<<31), so we need // to append in two parts; const size_t s1 = (1u << 31) - 1; // For shorter cord, `Append` copies the data rather than allocating a new // node. The threshold is currently set to 511, so `s2` needs to be bigger // to not trigger the copy. const size_t s2 = 600; cord.Append(absl::MakeCordFromExternal( absl::string_view(prefix.data(), s1), [](absl::string_view s) { DoNothing(s, nullptr); })); cord.Append(absl::MakeCordFromExternal( absl::string_view("", s2), [](absl::string_view s) { DoNothing(s, nullptr); })); } return cord; } TEST_P(CordTest, HugeCord) { absl::Cord cord = MakeHuge("huge cord"); MaybeHarden(cord); const size_t acceptable_delta = 100 + (UseCrc() ? sizeof(absl::cord_internal::CordRepCrc) : 0); EXPECT_LE(cord.size(), cord.EstimatedMemoryUsage()); EXPECT_GE(cord.size() + acceptable_delta, cord.EstimatedMemoryUsage()); } // Tests that Append() works ok when handed a self reference TEST_P(CordTest, AppendSelf) { // Test the empty case. absl::Cord empty; MaybeHarden(empty); empty.Append(empty); ASSERT_EQ(empty, ""); // We run the test until data is ~16K // This guarantees it covers small, medium and large data. std::string control_data = "Abc"; absl::Cord data(control_data); while (control_data.length() < 0x4000) { MaybeHarden(data); data.Append(data); control_data.append(control_data); ASSERT_EQ(control_data, data); } } TEST_P(CordTest, MakeFragmentedCordFromInitializerList) { absl::Cord fragmented = absl::MakeFragmentedCord({"A ", "fragmented ", "Cord"}); MaybeHarden(fragmented); EXPECT_EQ("A fragmented Cord", fragmented); auto chunk_it = fragmented.chunk_begin(); ASSERT_TRUE(chunk_it != fragmented.chunk_end()); EXPECT_EQ("A ", *chunk_it); ASSERT_TRUE(++chunk_it != fragmented.chunk_end()); EXPECT_EQ("fragmented ", *chunk_it); ASSERT_TRUE(++chunk_it != fragmented.chunk_end()); EXPECT_EQ("Cord", *chunk_it); ASSERT_TRUE(++chunk_it == fragmented.chunk_end()); } TEST_P(CordTest, MakeFragmentedCordFromVector) { std::vector chunks = {"A ", "fragmented ", "Cord"}; absl::Cord fragmented = absl::MakeFragmentedCord(chunks); MaybeHarden(fragmented); EXPECT_EQ("A fragmented Cord", fragmented); auto chunk_it = fragmented.chunk_begin(); ASSERT_TRUE(chunk_it != fragmented.chunk_end()); EXPECT_EQ("A ", *chunk_it); ASSERT_TRUE(++chunk_it != fragmented.chunk_end()); EXPECT_EQ("fragmented ", *chunk_it); ASSERT_TRUE(++chunk_it != fragmented.chunk_end()); EXPECT_EQ("Cord", *chunk_it); ASSERT_TRUE(++chunk_it == fragmented.chunk_end()); } TEST_P(CordTest, CordChunkIteratorTraits) { static_assert(std::is_copy_constructible::value, ""); static_assert(std::is_copy_assignable::value, ""); // Move semantics to satisfy swappable via std::swap static_assert(std::is_move_constructible::value, ""); static_assert(std::is_move_assignable::value, ""); static_assert( std::is_same< std::iterator_traits::iterator_category, std::input_iterator_tag>::value, ""); static_assert( std::is_same::value_type, absl::string_view>::value, ""); static_assert( std::is_same< std::iterator_traits::difference_type, ptrdiff_t>::value, ""); static_assert( std::is_same::pointer, const absl::string_view*>::value, ""); static_assert( std::is_same::reference, absl::string_view>::value, ""); } static void VerifyChunkIterator(const absl::Cord& cord, size_t expected_chunks) { EXPECT_EQ(cord.chunk_begin() == cord.chunk_end(), cord.empty()) << cord; EXPECT_EQ(cord.chunk_begin() != cord.chunk_end(), !cord.empty()); absl::Cord::ChunkRange range = cord.Chunks(); EXPECT_EQ(range.begin() == range.end(), cord.empty()); EXPECT_EQ(range.begin() != range.end(), !cord.empty()); std::string content(cord); size_t pos = 0; auto pre_iter = cord.chunk_begin(), post_iter = cord.chunk_begin(); size_t n_chunks = 0; while (pre_iter != cord.chunk_end() && post_iter != cord.chunk_end()) { EXPECT_FALSE(pre_iter == cord.chunk_end()); // NOLINT: explicitly test == EXPECT_FALSE(post_iter == cord.chunk_end()); // NOLINT EXPECT_EQ(pre_iter, post_iter); EXPECT_EQ(*pre_iter, *post_iter); EXPECT_EQ(pre_iter->data(), (*pre_iter).data()); EXPECT_EQ(pre_iter->size(), (*pre_iter).size()); absl::string_view chunk = *pre_iter; EXPECT_FALSE(chunk.empty()); EXPECT_LE(pos + chunk.size(), content.size()); EXPECT_EQ(absl::string_view(content.c_str() + pos, chunk.size()), chunk); int n_equal_iterators = 0; for (absl::Cord::ChunkIterator it = range.begin(); it != range.end(); ++it) { n_equal_iterators += static_cast(it == pre_iter); } EXPECT_EQ(n_equal_iterators, 1); ++pre_iter; EXPECT_EQ(*post_iter++, chunk); pos += chunk.size(); ++n_chunks; } EXPECT_EQ(expected_chunks, n_chunks); EXPECT_EQ(pos, content.size()); EXPECT_TRUE(pre_iter == cord.chunk_end()); // NOLINT: explicitly test == EXPECT_TRUE(post_iter == cord.chunk_end()); // NOLINT } TEST_P(CordTest, CordChunkIteratorOperations) { absl::Cord empty_cord; VerifyChunkIterator(empty_cord, 0); absl::Cord small_buffer_cord("small cord"); MaybeHarden(small_buffer_cord); VerifyChunkIterator(small_buffer_cord, 1); absl::Cord flat_node_cord("larger than small buffer optimization"); MaybeHarden(flat_node_cord); VerifyChunkIterator(flat_node_cord, 1); VerifyChunkIterator(MaybeHardened(absl::MakeFragmentedCord( {"a ", "small ", "fragmented ", "cord ", "for ", "testing ", "chunk ", "iterations."})), 8); absl::Cord reused_nodes_cord(std::string(40, 'c')); reused_nodes_cord.Prepend(absl::Cord(std::string(40, 'b'))); MaybeHarden(reused_nodes_cord); reused_nodes_cord.Prepend(absl::Cord(std::string(40, 'a'))); size_t expected_chunks = 3; for (int i = 0; i < 8; ++i) { reused_nodes_cord.Prepend(reused_nodes_cord); MaybeHarden(reused_nodes_cord); expected_chunks *= 2; VerifyChunkIterator(reused_nodes_cord, expected_chunks); } RandomEngine rng(GTEST_FLAG_GET(random_seed)); absl::Cord flat_cord(RandomLowercaseString(&rng, 256)); absl::Cord subcords; for (int i = 0; i < 128; ++i) subcords.Prepend(flat_cord.Subcord(i, 128)); VerifyChunkIterator(subcords, 128); } TEST_P(CordTest, AdvanceAndReadOnDataEdge) { RandomEngine rng(GTEST_FLAG_GET(random_seed)); const std::string data = RandomLowercaseString(&rng, 2000); for (bool as_flat : {true, false}) { SCOPED_TRACE(as_flat ? "Flat" : "External"); absl::Cord cord = as_flat ? absl::Cord(data) : absl::MakeCordFromExternal(data, [](absl::string_view) {}); auto it = cord.Chars().begin(); #if !defined(NDEBUG) || ABSL_OPTION_HARDENED EXPECT_DEATH_IF_SUPPORTED(cord.AdvanceAndRead(&it, 2001), ".*"); #endif it = cord.Chars().begin(); absl::Cord frag = cord.AdvanceAndRead(&it, 2000); EXPECT_EQ(frag, data); EXPECT_TRUE(it == cord.Chars().end()); it = cord.Chars().begin(); frag = cord.AdvanceAndRead(&it, 200); EXPECT_EQ(frag, data.substr(0, 200)); EXPECT_FALSE(it == cord.Chars().end()); frag = cord.AdvanceAndRead(&it, 1500); EXPECT_EQ(frag, data.substr(200, 1500)); EXPECT_FALSE(it == cord.Chars().end()); frag = cord.AdvanceAndRead(&it, 300); EXPECT_EQ(frag, data.substr(1700, 300)); EXPECT_TRUE(it == cord.Chars().end()); } } TEST_P(CordTest, AdvanceAndReadOnSubstringDataEdge) { RandomEngine rng(GTEST_FLAG_GET(random_seed)); const std::string data = RandomLowercaseString(&rng, 2500); for (bool as_flat : {true, false}) { SCOPED_TRACE(as_flat ? "Flat" : "External"); absl::Cord cord = as_flat ? absl::Cord(data) : absl::MakeCordFromExternal(data, [](absl::string_view) {}); cord = cord.Subcord(200, 2000); const std::string substr = data.substr(200, 2000); auto it = cord.Chars().begin(); #if !defined(NDEBUG) || ABSL_OPTION_HARDENED EXPECT_DEATH_IF_SUPPORTED(cord.AdvanceAndRead(&it, 2001), ".*"); #endif it = cord.Chars().begin(); absl::Cord frag = cord.AdvanceAndRead(&it, 2000); EXPECT_EQ(frag, substr); EXPECT_TRUE(it == cord.Chars().end()); it = cord.Chars().begin(); frag = cord.AdvanceAndRead(&it, 200); EXPECT_EQ(frag, substr.substr(0, 200)); EXPECT_FALSE(it == cord.Chars().end()); frag = cord.AdvanceAndRead(&it, 1500); EXPECT_EQ(frag, substr.substr(200, 1500)); EXPECT_FALSE(it == cord.Chars().end()); frag = cord.AdvanceAndRead(&it, 300); EXPECT_EQ(frag, substr.substr(1700, 300)); EXPECT_TRUE(it == cord.Chars().end()); } } TEST_P(CordTest, CharIteratorTraits) { static_assert(std::is_copy_constructible::value, ""); static_assert(std::is_copy_assignable::value, ""); // Move semantics to satisfy swappable via std::swap static_assert(std::is_move_constructible::value, ""); static_assert(std::is_move_assignable::value, ""); static_assert( std::is_same< std::iterator_traits::iterator_category, std::input_iterator_tag>::value, ""); static_assert( std::is_same::value_type, char>::value, ""); static_assert( std::is_same< std::iterator_traits::difference_type, ptrdiff_t>::value, ""); static_assert( std::is_same::pointer, const char*>::value, ""); static_assert( std::is_same::reference, const char&>::value, ""); } static void VerifyCharIterator(const absl::Cord& cord) { EXPECT_EQ(cord.char_begin() == cord.char_end(), cord.empty()); EXPECT_EQ(cord.char_begin() != cord.char_end(), !cord.empty()); absl::Cord::CharRange range = cord.Chars(); EXPECT_EQ(range.begin() == range.end(), cord.empty()); EXPECT_EQ(range.begin() != range.end(), !cord.empty()); size_t i = 0; absl::Cord::CharIterator pre_iter = cord.char_begin(); absl::Cord::CharIterator post_iter = cord.char_begin(); std::string content(cord); while (pre_iter != cord.char_end() && post_iter != cord.char_end()) { EXPECT_FALSE(pre_iter == cord.char_end()); // NOLINT: explicitly test == EXPECT_FALSE(post_iter == cord.char_end()); // NOLINT EXPECT_LT(i, cord.size()); EXPECT_EQ(content[i], *pre_iter); EXPECT_EQ(pre_iter, post_iter); EXPECT_EQ(*pre_iter, *post_iter); EXPECT_EQ(&*pre_iter, &*post_iter); EXPECT_EQ(&*pre_iter, pre_iter.operator->()); const char* character_address = &*pre_iter; absl::Cord::CharIterator copy = pre_iter; ++copy; EXPECT_EQ(character_address, &*pre_iter); int n_equal_iterators = 0; for (absl::Cord::CharIterator it = range.begin(); it != range.end(); ++it) { n_equal_iterators += static_cast(it == pre_iter); } EXPECT_EQ(n_equal_iterators, 1); absl::Cord::CharIterator advance_iter = range.begin(); absl::Cord::Advance(&advance_iter, i); EXPECT_EQ(pre_iter, advance_iter); advance_iter = range.begin(); EXPECT_EQ(absl::Cord::AdvanceAndRead(&advance_iter, i), cord.Subcord(0, i)); EXPECT_EQ(pre_iter, advance_iter); advance_iter = pre_iter; absl::Cord::Advance(&advance_iter, cord.size() - i); EXPECT_EQ(range.end(), advance_iter); advance_iter = pre_iter; EXPECT_EQ(absl::Cord::AdvanceAndRead(&advance_iter, cord.size() - i), cord.Subcord(i, cord.size() - i)); EXPECT_EQ(range.end(), advance_iter); ++i; ++pre_iter; post_iter++; } EXPECT_EQ(i, cord.size()); EXPECT_TRUE(pre_iter == cord.char_end()); // NOLINT: explicitly test == EXPECT_TRUE(post_iter == cord.char_end()); // NOLINT absl::Cord::CharIterator zero_advanced_end = cord.char_end(); absl::Cord::Advance(&zero_advanced_end, 0); EXPECT_EQ(zero_advanced_end, cord.char_end()); absl::Cord::CharIterator it = cord.char_begin(); for (absl::string_view chunk : cord.Chunks()) { while (!chunk.empty()) { EXPECT_EQ(absl::Cord::ChunkRemaining(it), chunk); chunk.remove_prefix(1); ++it; } } } TEST_P(CordTest, CharIteratorOperations) { absl::Cord empty_cord; VerifyCharIterator(empty_cord); absl::Cord small_buffer_cord("small cord"); MaybeHarden(small_buffer_cord); VerifyCharIterator(small_buffer_cord); absl::Cord flat_node_cord("larger than small buffer optimization"); MaybeHarden(flat_node_cord); VerifyCharIterator(flat_node_cord); VerifyCharIterator(MaybeHardened( absl::MakeFragmentedCord({"a ", "small ", "fragmented ", "cord ", "for ", "testing ", "character ", "iteration."}))); absl::Cord reused_nodes_cord("ghi"); reused_nodes_cord.Prepend(absl::Cord("def")); reused_nodes_cord.Prepend(absl::Cord("abc")); for (int i = 0; i < 4; ++i) { reused_nodes_cord.Prepend(reused_nodes_cord); MaybeHarden(reused_nodes_cord); VerifyCharIterator(reused_nodes_cord); } RandomEngine rng(GTEST_FLAG_GET(random_seed)); absl::Cord flat_cord(RandomLowercaseString(&rng, 256)); absl::Cord subcords; for (int i = 0; i < 4; ++i) { subcords.Prepend(flat_cord.Subcord(16 * i, 128)); MaybeHarden(subcords); } VerifyCharIterator(subcords); } TEST_P(CordTest, CharIteratorAdvanceAndRead) { // Create a Cord holding 6 flats of 2500 bytes each, and then iterate over it // reading 150, 1500, 2500 and 3000 bytes. This will result in all possible // partial, full and straddled read combinations including reads below // kMaxBytesToCopy. b/197776822 surfaced a bug for a specific partial, small // read 'at end' on Cord which caused a failure on attempting to read past the // end in CordRepBtreeReader which was not covered by any existing test. constexpr int kBlocks = 6; constexpr size_t kBlockSize = 2500; constexpr size_t kChunkSize1 = 1500; constexpr size_t kChunkSize2 = 2500; constexpr size_t kChunkSize3 = 3000; constexpr size_t kChunkSize4 = 150; RandomEngine rng; std::string data = RandomLowercaseString(&rng, kBlocks * kBlockSize); absl::Cord cord; for (int i = 0; i < kBlocks; ++i) { const std::string block = data.substr(i * kBlockSize, kBlockSize); cord.Append(absl::Cord(block)); } MaybeHarden(cord); for (size_t chunk_size : {kChunkSize1, kChunkSize2, kChunkSize3, kChunkSize4}) { absl::Cord::CharIterator it = cord.char_begin(); size_t offset = 0; while (offset < data.length()) { const size_t n = std::min(data.length() - offset, chunk_size); absl::Cord chunk = cord.AdvanceAndRead(&it, n); ASSERT_EQ(chunk.size(), n); ASSERT_EQ(chunk.Compare(data.substr(offset, n)), 0); offset += n; } } } TEST_P(CordTest, StreamingOutput) { absl::Cord c = absl::MakeFragmentedCord({"A ", "small ", "fragmented ", "Cord", "."}); MaybeHarden(c); std::stringstream output; output << c; EXPECT_EQ("A small fragmented Cord.", output.str()); } TEST_P(CordTest, ForEachChunk) { for (int num_elements : {1, 10, 200}) { SCOPED_TRACE(num_elements); std::vector cord_chunks; for (int i = 0; i < num_elements; ++i) { cord_chunks.push_back(absl::StrCat("[", i, "]")); } absl::Cord c = absl::MakeFragmentedCord(cord_chunks); MaybeHarden(c); std::vector iterated_chunks; absl::CordTestPeer::ForEachChunk(c, [&iterated_chunks](absl::string_view sv) { iterated_chunks.emplace_back(sv); }); EXPECT_EQ(iterated_chunks, cord_chunks); } } TEST_P(CordTest, SmallBufferAssignFromOwnData) { constexpr size_t kMaxInline = 15; std::string contents = "small buff cord"; EXPECT_EQ(contents.size(), kMaxInline); for (size_t pos = 0; pos < contents.size(); ++pos) { for (size_t count = contents.size() - pos; count > 0; --count) { absl::Cord c(contents); MaybeHarden(c); absl::string_view flat = c.Flatten(); c = flat.substr(pos, count); EXPECT_EQ(c, contents.substr(pos, count)) << "pos = " << pos << "; count = " << count; } } } TEST_P(CordTest, Format) { absl::Cord c; absl::Format(&c, "There were %04d little %s.", 3, "pigs"); EXPECT_EQ(c, "There were 0003 little pigs."); MaybeHarden(c); absl::Format(&c, "And %-3llx bad wolf!", 1); MaybeHarden(c); EXPECT_EQ(c, "There were 0003 little pigs.And 1 bad wolf!"); } TEST_P(CordTest, Stringify) { absl::Cord c = absl::MakeFragmentedCord({"A ", "small ", "fragmented ", "Cord", "."}); MaybeHarden(c); EXPECT_EQ(absl::StrCat(c), "A small fragmented Cord."); } TEST_P(CordTest, Hardening) { absl::Cord cord("hello"); MaybeHarden(cord); // These statement should abort the program in all builds modes. EXPECT_DEATH_IF_SUPPORTED(cord.RemovePrefix(6), ""); EXPECT_DEATH_IF_SUPPORTED(cord.RemoveSuffix(6), ""); bool test_hardening = false; ABSL_HARDENING_ASSERT([&]() { // This only runs when ABSL_HARDENING_ASSERT is active. test_hardening = true; return true; }()); if (!test_hardening) return; EXPECT_DEATH_IF_SUPPORTED(cord[5], ""); EXPECT_DEATH_IF_SUPPORTED(*cord.chunk_end(), ""); EXPECT_DEATH_IF_SUPPORTED(static_cast(cord.chunk_end()->empty()), ""); EXPECT_DEATH_IF_SUPPORTED(++cord.chunk_end(), ""); } // This test mimics a specific (and rare) application repeatedly splitting a // cord, inserting (overwriting) a string value, and composing a new cord from // the three pieces. This is hostile towards a Btree implementation: A split of // a node at any level is likely to have the right-most edge of the left split, // and the left-most edge of the right split shared. For example, splitting a // leaf node with 6 edges will result likely in a 1-6, 2-5, 3-4, etc. split, // sharing the 'split node'. When recomposing such nodes, we 'injected' an edge // in that node. As this happens with some probability on each level of the // tree, this will quickly grow the tree until it reaches maximum height. TEST_P(CordTest, BtreeHostileSplitInsertJoin) { absl::BitGen bitgen; // Start with about 1GB of data std::string data(1 << 10, 'x'); absl::Cord buffer(data); absl::Cord cord; for (int i = 0; i < 1000000; ++i) { cord.Append(buffer); } for (int j = 0; j < 1000; ++j) { MaybeHarden(cord); size_t offset = absl::Uniform(bitgen, 0u, cord.size()); size_t length = absl::Uniform(bitgen, 100u, data.size()); if (cord.size() == offset) { cord.Append(absl::string_view(data.data(), length)); } else { absl::Cord suffix; if (offset + length < cord.size()) { suffix = cord; suffix.RemovePrefix(offset + length); } if (cord.size() > offset) { cord.RemoveSuffix(cord.size() - offset); } cord.Append(absl::string_view(data.data(), length)); if (!suffix.empty()) { cord.Append(suffix); } } } } class AfterExitCordTester { public: bool Set(absl::Cord* cord, absl::string_view expected) { cord_ = cord; expected_ = expected; return true; } ~AfterExitCordTester() { EXPECT_EQ(*cord_, expected_); } private: absl::Cord* cord_; absl::string_view expected_; }; template void TestAfterExit(Str) { const auto expected = Str::value; // Defined before `cord` to be destroyed after it. static AfterExitCordTester exit_tester; // NOLINT static absl::NoDestructor cord_leaker(Str{}); // cord_leaker is static, so this reference will remain valid through the end // of program execution. static absl::Cord& cord = *cord_leaker; static bool init_exit_tester = exit_tester.Set(&cord, expected); (void)init_exit_tester; EXPECT_EQ(cord, expected); // Copy the object and test the copy, and the original. { absl::Cord copy = cord; EXPECT_EQ(copy, expected); } // The original still works EXPECT_EQ(cord, expected); // Try making adding more structure to the tree. { absl::Cord copy = cord; std::string expected_copy(expected); for (int i = 0; i < 10; ++i) { copy.Append(cord); absl::StrAppend(&expected_copy, expected); EXPECT_EQ(copy, expected_copy); } } // Make sure we are using the right branch during constant evaluation. EXPECT_EQ(absl::CordTestPeer::IsTree(cord), cord.size() >= 16); for (int i = 0; i < 10; ++i) { // Make a few more Cords from the same global rep. // This tests what happens when the refcount for it gets below 1. EXPECT_EQ(expected, absl::Cord(Str{})); } } constexpr int SimpleStrlen(const char* p) { return *p ? 1 + SimpleStrlen(p + 1) : 0; } struct ShortView { constexpr absl::string_view operator()() const { return absl::string_view("SSO string", SimpleStrlen("SSO string")); } }; struct LongView { constexpr absl::string_view operator()() const { return absl::string_view("String that does not fit SSO.", SimpleStrlen("String that does not fit SSO.")); } }; TEST_P(CordTest, AfterExit) { TestAfterExit(absl::strings_internal::MakeStringConstant(ShortView{})); TestAfterExit(absl::strings_internal::MakeStringConstant(LongView{})); } namespace { // Test helper that generates a populated cord for future manipulation. // // By test convention, all generated cords begin with the characters "abcde" at // the start of the first chunk. class PopulatedCordFactory { public: constexpr PopulatedCordFactory(absl::string_view name, absl::Cord (*generator)()) : name_(name), generator_(generator) {} absl::string_view Name() const { return name_; } absl::Cord Generate() const { return generator_(); } private: absl::string_view name_; absl::Cord (*generator_)(); }; // clang-format off // This array is constant-initialized in conformant compilers. PopulatedCordFactory cord_factories[] = { {"sso", [] { return absl::Cord("abcde"); }}, {"flat", [] { // Too large to live in SSO space, but small enough to be a simple FLAT. absl::Cord flat(absl::StrCat("abcde", std::string(1000, 'x'))); flat.Flatten(); return flat; }}, {"external", [] { // A cheat: we are using a string literal as the external storage, so a // no-op releaser is correct here. return absl::MakeCordFromExternal("abcde External!", []{}); }}, {"external substring", [] { // A cheat: we are using a string literal as the external storage, so a // no-op releaser is correct here. absl::Cord ext = absl::MakeCordFromExternal("-abcde External!", []{}); return absl::CordTestPeer::MakeSubstring(ext, 1, ext.size() - 1); }}, {"substring", [] { absl::Cord flat(absl::StrCat("-abcde", std::string(1000, 'x'))); flat.Flatten(); return flat.Subcord(1, 998); }}, {"fragmented", [] { std::string fragment = absl::StrCat("abcde", std::string(195, 'x')); std::vector fragments(200, fragment); absl::Cord cord = absl::MakeFragmentedCord(fragments); assert(cord.size() == 40000); return cord; }}, }; // clang-format on // Test helper that can mutate a cord, and possibly undo the mutation, for // testing. class CordMutator { public: constexpr CordMutator(absl::string_view name, void (*mutate)(absl::Cord&), void (*undo)(absl::Cord&) = nullptr) : name_(name), mutate_(mutate), undo_(undo) {} absl::string_view Name() const { return name_; } void Mutate(absl::Cord& cord) const { mutate_(cord); } bool CanUndo() const { return undo_ != nullptr; } void Undo(absl::Cord& cord) const { undo_(cord); } private: absl::string_view name_; void (*mutate_)(absl::Cord&); void (*undo_)(absl::Cord&); }; // clang-format off // This array is constant-initialized in conformant compilers. CordMutator cord_mutators[] = { {"clear", [](absl::Cord& c) { c.Clear(); }}, {"overwrite", [](absl::Cord& c) { c = "overwritten"; }}, { "append string", [](absl::Cord& c) { c.Append("0123456789"); }, [](absl::Cord& c) { c.RemoveSuffix(10); } }, { "append cord", [](absl::Cord& c) { c.Append(absl::MakeFragmentedCord({"12345", "67890"})); }, [](absl::Cord& c) { c.RemoveSuffix(10); } }, { "append checksummed cord", [](absl::Cord& c) { absl::Cord to_append = absl::MakeFragmentedCord({"12345", "67890"}); to_append.SetExpectedChecksum(999); c.Append(to_append); }, [](absl::Cord& c) { c.RemoveSuffix(10); } }, { "append self", [](absl::Cord& c) { c.Append(c); }, [](absl::Cord& c) { c.RemoveSuffix(c.size() / 2); } }, { "append empty string", [](absl::Cord& c) { c.Append(""); }, [](absl::Cord& c) { } }, { "append empty cord", [](absl::Cord& c) { c.Append(absl::Cord()); }, [](absl::Cord& c) { } }, { "append empty checksummed cord", [](absl::Cord& c) { absl::Cord to_append; to_append.SetExpectedChecksum(999); c.Append(to_append); }, [](absl::Cord& c) { } }, { "prepend string", [](absl::Cord& c) { c.Prepend("9876543210"); }, [](absl::Cord& c) { c.RemovePrefix(10); } }, { "prepend cord", [](absl::Cord& c) { c.Prepend(absl::MakeFragmentedCord({"98765", "43210"})); }, [](absl::Cord& c) { c.RemovePrefix(10); } }, { "prepend checksummed cord", [](absl::Cord& c) { absl::Cord to_prepend = absl::MakeFragmentedCord({"98765", "43210"}); to_prepend.SetExpectedChecksum(999); c.Prepend(to_prepend); }, [](absl::Cord& c) { c.RemovePrefix(10); } }, { "prepend empty string", [](absl::Cord& c) { c.Prepend(""); }, [](absl::Cord& c) { } }, { "prepend empty cord", [](absl::Cord& c) { c.Prepend(absl::Cord()); }, [](absl::Cord& c) { } }, { "prepend empty checksummed cord", [](absl::Cord& c) { absl::Cord to_prepend; to_prepend.SetExpectedChecksum(999); c.Prepend(to_prepend); }, [](absl::Cord& c) { } }, { "prepend self", [](absl::Cord& c) { c.Prepend(c); }, [](absl::Cord& c) { c.RemovePrefix(c.size() / 2); } }, {"remove prefix", [](absl::Cord& c) { c.RemovePrefix(c.size() / 2); }}, {"remove suffix", [](absl::Cord& c) { c.RemoveSuffix(c.size() / 2); }}, {"remove 0-prefix", [](absl::Cord& c) { c.RemovePrefix(0); }}, {"remove 0-suffix", [](absl::Cord& c) { c.RemoveSuffix(0); }}, {"subcord", [](absl::Cord& c) { c = c.Subcord(1, c.size() - 2); }}, { "swap inline", [](absl::Cord& c) { absl::Cord other("swap"); c.swap(other); } }, { "swap tree", [](absl::Cord& c) { absl::Cord other(std::string(10000, 'x')); c.swap(other); } }, }; // clang-format on } // namespace TEST_P(CordTest, ExpectedChecksum) { for (const PopulatedCordFactory& factory : cord_factories) { SCOPED_TRACE(factory.Name()); for (bool shared : {false, true}) { SCOPED_TRACE(shared); absl::Cord shared_cord_source = factory.Generate(); auto make_instance = [=] { return shared ? shared_cord_source : factory.Generate(); }; const absl::Cord base_value = factory.Generate(); const std::string base_value_as_string(factory.Generate().Flatten()); absl::Cord c1 = make_instance(); EXPECT_FALSE(c1.ExpectedChecksum().has_value()); // Setting an expected checksum works, and retains the cord's bytes c1.SetExpectedChecksum(12345); EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); EXPECT_EQ(c1, base_value); // Test that setting an expected checksum again doesn't crash or leak // memory. c1.SetExpectedChecksum(12345); EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); EXPECT_EQ(c1, base_value); // CRC persists through copies, assignments, and moves: absl::Cord c1_copy_construct = c1; EXPECT_EQ(c1_copy_construct.ExpectedChecksum().value_or(0), 12345); absl::Cord c1_copy_assign; c1_copy_assign = c1; EXPECT_EQ(c1_copy_assign.ExpectedChecksum().value_or(0), 12345); absl::Cord c1_move(std::move(c1_copy_assign)); EXPECT_EQ(c1_move.ExpectedChecksum().value_or(0), 12345); EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); // A CRC Cord compares equal to its non-CRC value. EXPECT_EQ(c1, make_instance()); for (const CordMutator& mutator : cord_mutators) { SCOPED_TRACE(mutator.Name()); // Test that mutating a cord removes its stored checksum absl::Cord c2 = make_instance(); c2.SetExpectedChecksum(24680); mutator.Mutate(c2); if (c1 == c2) { // Not a mutation (for example, appending the empty string). // Whether the checksum is removed is not defined. continue; } EXPECT_EQ(c2.ExpectedChecksum(), absl::nullopt); if (mutator.CanUndo()) { // Undoing an operation should not restore the checksum mutator.Undo(c2); EXPECT_EQ(c2, base_value); EXPECT_EQ(c2.ExpectedChecksum(), absl::nullopt); } } absl::Cord c3 = make_instance(); c3.SetExpectedChecksum(999); const absl::Cord& cc3 = c3; // Test that all cord reading operations function in the face of an // expected checksum. // Test data precondition ASSERT_TRUE(cc3.StartsWith("abcde")); EXPECT_EQ(cc3.size(), base_value_as_string.size()); EXPECT_FALSE(cc3.empty()); EXPECT_EQ(cc3.Compare(base_value), 0); EXPECT_EQ(cc3.Compare(base_value_as_string), 0); EXPECT_EQ(cc3.Compare("wxyz"), -1); EXPECT_EQ(cc3.Compare(absl::Cord("wxyz")), -1); EXPECT_EQ(cc3.Compare("aaaa"), 1); EXPECT_EQ(cc3.Compare(absl::Cord("aaaa")), 1); EXPECT_EQ(absl::Cord("wxyz").Compare(cc3), 1); EXPECT_EQ(absl::Cord("aaaa").Compare(cc3), -1); EXPECT_TRUE(cc3.StartsWith("abcd")); EXPECT_EQ(std::string(cc3), base_value_as_string); std::string dest; absl::CopyCordToString(cc3, &dest); EXPECT_EQ(dest, base_value_as_string); bool first_pass = true; for (absl::string_view chunk : cc3.Chunks()) { if (first_pass) { EXPECT_TRUE(absl::StartsWith(chunk, "abcde")); } first_pass = false; } first_pass = true; for (char ch : cc3.Chars()) { if (first_pass) { EXPECT_EQ(ch, 'a'); } first_pass = false; } EXPECT_TRUE(absl::StartsWith(*cc3.chunk_begin(), "abcde")); EXPECT_EQ(*cc3.char_begin(), 'a'); auto char_it = cc3.char_begin(); absl::Cord::Advance(&char_it, 2); EXPECT_EQ(absl::Cord::AdvanceAndRead(&char_it, 2), "cd"); EXPECT_EQ(*char_it, 'e'); char_it = cc3.char_begin(); absl::Cord::Advance(&char_it, 2); EXPECT_TRUE(absl::StartsWith(absl::Cord::ChunkRemaining(char_it), "cde")); EXPECT_EQ(cc3[0], 'a'); EXPECT_EQ(cc3[4], 'e'); EXPECT_EQ(absl::HashOf(cc3), absl::HashOf(base_value)); EXPECT_EQ(absl::HashOf(cc3), absl::HashOf(base_value_as_string)); } } } // Test the special cases encountered with an empty checksummed cord. TEST_P(CordTest, ChecksummedEmptyCord) { absl::Cord c1; EXPECT_FALSE(c1.ExpectedChecksum().has_value()); // Setting an expected checksum works. c1.SetExpectedChecksum(12345); EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); EXPECT_EQ(c1, ""); EXPECT_TRUE(c1.empty()); // Test that setting an expected checksum again doesn't crash or leak memory. c1.SetExpectedChecksum(12345); EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); EXPECT_EQ(c1, ""); EXPECT_TRUE(c1.empty()); // CRC persists through copies, assignments, and moves: absl::Cord c1_copy_construct = c1; EXPECT_EQ(c1_copy_construct.ExpectedChecksum().value_or(0), 12345); absl::Cord c1_copy_assign; c1_copy_assign = c1; EXPECT_EQ(c1_copy_assign.ExpectedChecksum().value_or(0), 12345); absl::Cord c1_move(std::move(c1_copy_assign)); EXPECT_EQ(c1_move.ExpectedChecksum().value_or(0), 12345); EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); // A CRC Cord compares equal to its non-CRC value. EXPECT_EQ(c1, absl::Cord()); for (const CordMutator& mutator : cord_mutators) { SCOPED_TRACE(mutator.Name()); // Exercise mutating an empty checksummed cord to catch crashes and exercise // memory sanitizers. absl::Cord c2; c2.SetExpectedChecksum(24680); mutator.Mutate(c2); if (c2.empty()) { // Not a mutation continue; } EXPECT_EQ(c2.ExpectedChecksum(), absl::nullopt); if (mutator.CanUndo()) { mutator.Undo(c2); } } absl::Cord c3; c3.SetExpectedChecksum(999); const absl::Cord& cc3 = c3; // Test that all cord reading operations function in the face of an // expected checksum. EXPECT_TRUE(cc3.StartsWith("")); EXPECT_TRUE(cc3.EndsWith("")); EXPECT_TRUE(cc3.empty()); EXPECT_EQ(cc3, ""); EXPECT_EQ(cc3, absl::Cord()); EXPECT_EQ(cc3.size(), 0); EXPECT_EQ(cc3.Compare(absl::Cord()), 0); EXPECT_EQ(cc3.Compare(c1), 0); EXPECT_EQ(cc3.Compare(cc3), 0); EXPECT_EQ(cc3.Compare(""), 0); EXPECT_EQ(cc3.Compare("wxyz"), -1); EXPECT_EQ(cc3.Compare(absl::Cord("wxyz")), -1); EXPECT_EQ(absl::Cord("wxyz").Compare(cc3), 1); EXPECT_EQ(std::string(cc3), ""); std::string dest; absl::CopyCordToString(cc3, &dest); EXPECT_EQ(dest, ""); for (absl::string_view chunk : cc3.Chunks()) { // NOLINT(unreachable loop) static_cast(chunk); GTEST_FAIL() << "no chunks expected"; } EXPECT_TRUE(cc3.chunk_begin() == cc3.chunk_end()); for (char ch : cc3.Chars()) { // NOLINT(unreachable loop) static_cast(ch); GTEST_FAIL() << "no chars expected"; } EXPECT_TRUE(cc3.char_begin() == cc3.char_end()); EXPECT_EQ(cc3.TryFlat(), ""); EXPECT_EQ(absl::HashOf(c3), absl::HashOf(absl::Cord())); EXPECT_EQ(absl::HashOf(c3), absl::HashOf(absl::string_view())); } TEST(CrcCordTest, ChecksummedEmptyCordEstimateMemoryUsage) { absl::Cord cord; cord.SetExpectedChecksum(0); EXPECT_NE(cord.EstimatedMemoryUsage(), 0); } TEST(CordThreeWayComparisonTest, CompareCords) { #ifndef __cpp_impl_three_way_comparison GTEST_SKIP() << "C++20 three-way <=> comparison not supported"; #else EXPECT_EQ(absl::Cord("a") <=> absl::Cord("a"), std::strong_ordering::equal); EXPECT_EQ(absl::Cord("aaaa") <=> absl::Cord("aaab"), std::strong_ordering::less); EXPECT_EQ(absl::Cord("baaa") <=> absl::Cord("a"), std::strong_ordering::greater); #endif } TEST(CordThreeWayComparisonTest, CompareCordsAndStringViews) { #ifndef __cpp_impl_three_way_comparison GTEST_SKIP() << "C++20 three-way <=> comparison not supported"; #else EXPECT_EQ(absl::string_view("a") <=> absl::Cord("a"), std::strong_ordering::equal); EXPECT_EQ(absl::Cord("a") <=> absl::string_view("b"), std::strong_ordering::less); EXPECT_EQ(absl::string_view("b") <=> absl::Cord("a"), std::strong_ordering::greater); #endif } #if defined(GTEST_HAS_DEATH_TEST) && defined(ABSL_INTERNAL_CORD_HAVE_SANITIZER) // Returns an expected poison / uninitialized death message expression. const char* MASanDeathExpr() { return "(use-after-poison|use-of-uninitialized-value)"; } TEST(CordSanitizerTest, SanitizesEmptyCord) { absl::Cord cord; const char* data = cord.Flatten().data(); EXPECT_DEATH(EXPECT_EQ(data[0], 0), MASanDeathExpr()); } TEST(CordSanitizerTest, SanitizesSmallCord) { absl::Cord cord("Hello"); const char* data = cord.Flatten().data(); EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr()); } TEST(CordSanitizerTest, SanitizesCordOnSetSSOValue) { absl::Cord cord("String that is too big to be an SSO value"); cord = "Hello"; const char* data = cord.Flatten().data(); EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr()); } TEST(CordSanitizerTest, SanitizesCordOnCopyCtor) { absl::Cord src("hello"); absl::Cord dst(src); const char* data = dst.Flatten().data(); EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr()); } TEST(CordSanitizerTest, SanitizesCordOnMoveCtor) { absl::Cord src("hello"); absl::Cord dst(std::move(src)); const char* data = dst.Flatten().data(); EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr()); } TEST(CordSanitizerTest, SanitizesCordOnAssign) { absl::Cord src("hello"); absl::Cord dst; dst = src; const char* data = dst.Flatten().data(); EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr()); } TEST(CordSanitizerTest, SanitizesCordOnMoveAssign) { absl::Cord src("hello"); absl::Cord dst; dst = std::move(src); const char* data = dst.Flatten().data(); EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr()); } TEST(CordSanitizerTest, SanitizesCordOnSsoAssign) { absl::Cord src("hello"); absl::Cord dst("String that is too big to be an SSO value"); dst = src; const char* data = dst.Flatten().data(); EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr()); } #endif // GTEST_HAS_DEATH_TEST && ABSL_INTERNAL_CORD_HAVE_SANITIZER