| // 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 <algorithm> | |
| #include <array> | |
| #include <cassert> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstdio> | |
| #include <cstring> | |
| #include <iostream> | |
| #include <iterator> | |
| #include <limits> | |
| #include <optional> | |
| #include <random> | |
| #include <set> | |
| #include <sstream> | |
| #include <string> | |
| #include <type_traits> | |
| #include <utility> | |
| #include <vector> | |
| #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/span.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<int> 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<size_t> 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<int> chars('a', 'z'); | |
| std::generate(result.begin(), result.end(), | |
| [&]() { return static_cast<char>(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<std::string*>(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<void(absl::string_view)> 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<bool /*useCrc*/> { | |
| 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<bool> 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(), std::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<std::pair<absl::string_view, absl::string_view>> | |
| test_string_pairs = {{"hi there", "foo"}, | |
| {"loooooong coooooord", "short cord"}, | |
| {"short cord", "loooooong coooooord"}, | |
| {"loooooong coooooord1", "loooooong coooooord2"}}; | |
| for (std::pair<absl::string_view, absl::string_view> 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, FindLinearScaling) { | |
| // Verifies that finding a mismatching string in an adversarial haystack | |
| // scales sub-linearly/linearly via Boyer-Moore-Horspool without quadratic | |
| // DoS. This uses a 1MB haystack and a slightly smaller needle that should | |
| // timeout for quadratic behavior. | |
| constexpr size_t kSize = 256 * 1024; | |
| std::string haystack_str(kSize, 'a'); | |
| absl::Cord haystack = absl::MakeFragmentedCord( | |
| {haystack_str.substr(0, kSize / 2), haystack_str.substr(kSize / 2)}); | |
| std::string needle_str(kSize / 2 - 1, 'a'); | |
| needle_str.push_back('b'); | |
| absl::Cord needle_cord = | |
| absl::MakeFragmentedCord({needle_str.substr(0, needle_str.size() / 2), | |
| needle_str.substr(needle_str.size() / 2)}); | |
| EXPECT_EQ(haystack.Find(needle_str), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(needle_str)); | |
| EXPECT_EQ(haystack.Find(needle_cord), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(needle_cord)); | |
| } | |
| TEST_P(CordTest, FindBMHCrossChunkMatch) { | |
| // Test finding a large needle (M >= 256) in a fragmented Cord haystack where | |
| // the match spans across chunk boundaries. | |
| const size_t kChunkSize = 512; | |
| const size_t kNeedleSize = 300; | |
| std::string needle_str(kNeedleSize, 'x'); | |
| for (size_t i = 0; i < kNeedleSize; ++i) { | |
| needle_str[i] = static_cast<char>('A' + (i % 26)); | |
| } | |
| std::vector<std::string> chunks; | |
| chunks.push_back(std::string(kChunkSize, '.')); | |
| chunks.push_back(std::string(kChunkSize, '.')); | |
| chunks.push_back(std::string(kChunkSize, '.')); | |
| absl::Cord haystack = absl::MakeFragmentedCord(chunks); | |
| std::string flat_haystack(haystack); | |
| flat_haystack.replace(400, kNeedleSize, needle_str); | |
| std::vector<std::string> modified_chunks = { | |
| flat_haystack.substr(0, kChunkSize), | |
| flat_haystack.substr(kChunkSize, kChunkSize), | |
| flat_haystack.substr(2 * kChunkSize, kChunkSize)}; | |
| haystack = absl::MakeFragmentedCord(modified_chunks); | |
| // Test string_view needle | |
| auto it = haystack.Find(needle_str); | |
| EXPECT_NE(it, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it), 400); | |
| EXPECT_TRUE(haystack.Contains(needle_str)); | |
| // Test Cord needle | |
| absl::Cord needle_cord = absl::MakeFragmentedCord( | |
| {needle_str.substr(0, 150), needle_str.substr(150)}); | |
| auto it_cord = haystack.Find(needle_cord); | |
| EXPECT_NE(it_cord, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 400); | |
| EXPECT_TRUE(haystack.Contains(needle_cord)); | |
| } | |
| TEST_P(CordTest, FindBMHChunkBoundaryPrefixMismatch) { | |
| // Test BMH search when the first character of needle matches near the end of | |
| // a chunk (chunk_rem < M), but the rest of the chunk prefix mismatches. | |
| // Exercises the `chunk.substr(idx) != needle.substr(0, chunk_rem)` branch in | |
| // FindImplBMH and FindCordBMH. | |
| const size_t kNeedleSize = 300; | |
| std::string needle_str(kNeedleSize, 'A'); | |
| needle_str[0] = 'Z'; | |
| needle_str[1] = 'B'; | |
| std::string chunk0(512, '.'); | |
| chunk0[500] = 'Z'; | |
| std::string chunk1(512, '.'); | |
| absl::Cord haystack = absl::MakeFragmentedCord({chunk0, chunk1}); | |
| EXPECT_EQ(haystack.Find(needle_str), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(needle_str)); | |
| absl::Cord needle_cord(needle_str); | |
| EXPECT_EQ(haystack.Find(needle_cord), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(needle_cord)); | |
| } | |
| TEST_P(CordTest, FindBMHCrossChunkLastByteMismatch) { | |
| // Test BMH search when the first character and chunk prefix match near the | |
| // end of a chunk, but the last byte across the chunk boundary mismatches. | |
| // Exercises `last_hay != last_needle` after CharacterAt(it, m - 1) in | |
| // FindImplBMH and FindCordBMH. | |
| const size_t kNeedleSize = 300; | |
| std::string needle_str(kNeedleSize, 'A'); | |
| needle_str[0] = 'Z'; | |
| needle_str.back() = 'Y'; | |
| std::string chunk0(512, '.'); | |
| chunk0.replace(500, 12, 12, 'A'); | |
| chunk0[500] = 'Z'; | |
| std::string chunk1(512, '.'); | |
| chunk1[287] = 'W'; // Mismatches needle_str.back() ('Y') | |
| absl::Cord haystack = absl::MakeFragmentedCord({chunk0, chunk1}); | |
| EXPECT_EQ(haystack.Find(needle_str), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(needle_str)); | |
| absl::Cord needle_cord(needle_str); | |
| EXPECT_EQ(haystack.Find(needle_cord), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(needle_cord)); | |
| } | |
| TEST_P(CordTest, FindBMHCrossChunkInteriorMismatch) { | |
| // Test BMH search when first and last characters match across chunk | |
| // boundaries, but an interior character inside the second chunk mismatches. | |
| // Exercises IsSubstringInCordAt / IsSubcordInCordAt returning false across | |
| // chunks. | |
| const size_t kNeedleSize = 300; | |
| std::string needle_str(kNeedleSize, 'A'); | |
| needle_str[0] = 'Z'; | |
| needle_str.back() = 'Y'; | |
| std::string chunk0(512, '.'); | |
| chunk0.replace(500, 12, 12, 'A'); | |
| chunk0[500] = 'Z'; | |
| std::string chunk1(512, 'A'); | |
| chunk1[287] = 'Y'; // Last char matches | |
| chunk1[100] = 'W'; // Interior byte mismatches | |
| absl::Cord haystack = absl::MakeFragmentedCord({chunk0, chunk1}); | |
| EXPECT_EQ(haystack.Find(needle_str), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(needle_str)); | |
| // Verify valid match when interior byte is restored | |
| chunk1[100] = 'A'; | |
| haystack = absl::MakeFragmentedCord({chunk0, chunk1}); | |
| auto it = haystack.Find(needle_str); | |
| EXPECT_NE(it, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it), 500); | |
| EXPECT_TRUE(haystack.Contains(needle_str)); | |
| } | |
| TEST_P(CordTest, FindBMHNearEndRemainingBytesBreak) { | |
| // Test BMH search when the first character of needle matches near the end of | |
| // haystack, but remaining bytes are less than needle size (bytes_remaining < | |
| // M). Exercises the `idx + m > bytes_remaining` break condition in | |
| // FindImplBMH and FindCordBMH. | |
| const size_t kNeedleSize = 300; | |
| std::string needle_str(kNeedleSize, 'A'); | |
| needle_str[0] = 'Z'; | |
| std::string chunk0(500, '.'); | |
| std::string chunk1(500, '.'); | |
| chunk1[300] = 'Z'; // Offset 800 in haystack, remaining bytes = 200 (< 300) | |
| absl::Cord haystack = absl::MakeFragmentedCord({chunk0, chunk1}); | |
| EXPECT_EQ(haystack.Find(needle_str), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(needle_str)); | |
| absl::Cord needle_cord = absl::MakeFragmentedCord( | |
| {needle_str.substr(0, 150), needle_str.substr(150)}); | |
| EXPECT_EQ(haystack.Find(needle_cord), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(needle_cord)); | |
| } | |
| TEST_P(CordTest, FindCordBMHCrossChunkInteriorMismatch) { | |
| // Test FindCordBMH when needle is a fragmented Cord (M >= 256), last byte | |
| // matches, but IsSubcordInCordAt returns false due to an interior mismatch | |
| // across chunks. Exercises lazy shift table initialization after | |
| // IsSubcordInCordAt failure in FindCordBMH. | |
| const size_t kNeedleSize = 300; | |
| std::string needle_str(kNeedleSize, 'A'); | |
| needle_str[0] = 'Z'; | |
| needle_str.back() = 'Y'; | |
| std::string chunk0(512, '.'); | |
| chunk0.replace(500, 12, 12, 'A'); | |
| chunk0[500] = 'Z'; | |
| std::string chunk1(512, 'A'); | |
| chunk1[287] = 'Y'; // Last char matches | |
| chunk1[150] = 'W'; // Interior mismatch in chunk 1 | |
| absl::Cord haystack = absl::MakeFragmentedCord({chunk0, chunk1}); | |
| absl::Cord needle_cord = absl::MakeFragmentedCord( | |
| {needle_str.substr(0, 150), needle_str.substr(150)}); | |
| EXPECT_EQ(haystack.Find(needle_cord), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(needle_cord)); | |
| // Restore interior byte and verify match | |
| chunk1[150] = 'A'; | |
| haystack = absl::MakeFragmentedCord({chunk0, chunk1}); | |
| auto it = haystack.Find(needle_cord); | |
| EXPECT_NE(it, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it), 500); | |
| EXPECT_TRUE(haystack.Contains(needle_cord)); | |
| } | |
| TEST_P(CordTest, FindBMHNonAsciiBytes) { | |
| // Test BMH search with high-bit non-ASCII bytes (values > 127) in Cord | |
| // haystack and needle across chunk boundaries. | |
| const size_t kNeedleSize = 300; | |
| std::string needle_str(kNeedleSize, '\0'); | |
| for (size_t i = 0; i < kNeedleSize; ++i) { | |
| needle_str[i] = static_cast<char>(128 + (i % 128)); | |
| } | |
| std::string chunk0(512, '\x7F'); | |
| std::string chunk1(512, '\x7F'); | |
| // Place needle straddling chunk 0 and chunk 1 at offset 400 | |
| std::string flat_haystack = chunk0 + chunk1; | |
| flat_haystack.replace(400, kNeedleSize, needle_str); | |
| absl::Cord haystack = absl::MakeFragmentedCord( | |
| {flat_haystack.substr(0, 512), flat_haystack.substr(512)}); | |
| auto it = haystack.Find(needle_str); | |
| EXPECT_NE(it, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it), 400); | |
| EXPECT_TRUE(haystack.Contains(needle_str)); | |
| absl::Cord needle_cord = absl::MakeFragmentedCord( | |
| {needle_str.substr(0, 150), needle_str.substr(150)}); | |
| auto it_cord = haystack.Find(needle_cord); | |
| EXPECT_NE(it_cord, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 400); | |
| EXPECT_TRUE(haystack.Contains(needle_cord)); | |
| } | |
| TEST_P(CordTest, FindSingleCharNeedle) { | |
| // Test single-character string_view and Cord needles (M = 1) in fragmented | |
| // Cord, exercising the FindChar fast path. | |
| absl::Cord haystack = absl::MakeFragmentedCord({"hello", " ", "world"}); | |
| // string_view needle | |
| auto it_sv = haystack.Find("w"); | |
| EXPECT_NE(it_sv, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it_sv), 6); | |
| EXPECT_TRUE(haystack.Contains("w")); | |
| EXPECT_EQ(haystack.Find("z"), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains("z")); | |
| // Cord needle | |
| auto it_cord = haystack.Find(absl::Cord("w")); | |
| EXPECT_NE(it_cord, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 6); | |
| EXPECT_TRUE(haystack.Contains(absl::Cord("w"))); | |
| EXPECT_EQ(haystack.Find(absl::Cord("z")), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(absl::Cord("z"))); | |
| } | |
| TEST_P(CordTest, FindStringViewNeedleSizeLargerThanHaystack) { | |
| absl::Cord haystack("hello"); | |
| EXPECT_EQ(haystack.Find("hello world"), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains("hello world")); | |
| } | |
| TEST_P(CordTest, FindStringViewNeedleSizeEqualToHaystack) { | |
| absl::Cord haystack("hello world"); | |
| EXPECT_EQ(haystack.Find("hello world"), haystack.char_begin()); | |
| EXPECT_TRUE(haystack.Contains("hello world")); | |
| EXPECT_EQ(haystack.Find("hello WOrld"), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains("hello WOrld")); | |
| } | |
| TEST_P(CordTest, FindCordNeedleSizeEqualToHaystack) { | |
| absl::Cord haystack("hello world"); | |
| absl::Cord needle_match("hello world"); | |
| absl::Cord needle_mismatch("hello WOrld"); | |
| EXPECT_EQ(haystack.Find(needle_match), haystack.char_begin()); | |
| EXPECT_TRUE(haystack.Contains(needle_match)); | |
| EXPECT_EQ(haystack.Find(needle_mismatch), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(needle_mismatch)); | |
| } | |
| TEST_P(CordTest, FindImplNearEndRemainingBytesBreak) { | |
| std::string haystack_str(50, '.'); | |
| haystack_str[47] = 'a'; | |
| absl::Cord haystack = absl::MakeFragmentedCord( | |
| {haystack_str.substr(0, 25), haystack_str.substr(25)}); | |
| EXPECT_EQ(haystack.Find("abcde"), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains("abcde")); | |
| } | |
| TEST_P(CordTest, FindCordNearEndRemainingBytesBreak) { | |
| std::string haystack_str(50, '.'); | |
| haystack_str[47] = 'a'; | |
| absl::Cord haystack = absl::MakeFragmentedCord( | |
| {haystack_str.substr(0, 25), haystack_str.substr(25)}); | |
| absl::Cord needle_cord("abcde"); | |
| EXPECT_EQ(haystack.Find(needle_cord), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(needle_cord)); | |
| } | |
| TEST_P(CordTest, FindImplBMHSingleChunkInteriorMismatch) { | |
| const size_t kNeedleSize = 300; | |
| std::string needle_str(kNeedleSize, 'A'); | |
| needle_str[0] = 'Z'; | |
| needle_str.back() = 'Y'; | |
| std::string haystack_str(1000, 'A'); | |
| haystack_str.replace(100, kNeedleSize, needle_str); | |
| haystack_str[100 + 10] = 'W'; | |
| absl::Cord haystack(haystack_str); | |
| EXPECT_EQ(haystack.Find(needle_str), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(needle_str)); | |
| } | |
| TEST_P(CordTest, FindCordBMHSingleChunkInteriorMismatch) { | |
| const size_t kNeedleSize = 300; | |
| std::string needle_str(kNeedleSize, 'A'); | |
| needle_str[0] = 'Z'; | |
| needle_str.back() = 'Y'; | |
| std::string haystack_str(1000, 'A'); | |
| haystack_str.replace(100, kNeedleSize, needle_str); | |
| haystack_str[100 + 10] = 'W'; | |
| absl::Cord haystack(haystack_str); | |
| absl::Cord needle_cord(needle_str); | |
| EXPECT_EQ(haystack.Find(needle_cord), haystack.char_end()); | |
| EXPECT_FALSE(haystack.Contains(needle_cord)); | |
| } | |
| TEST_P(CordTest, FindBMHDuplicateCharsInNeedle) { | |
| // Test BMH shift table calculation when needle contains duplicate characters | |
| // at different distances from the end. Verifies that the smallest shift | |
| // distance (rightmost occurrence) is preserved and prevents over-shifting. | |
| const size_t kNeedleSize = 300; | |
| std::string needle_str(kNeedleSize, 'A'); | |
| needle_str[0] = 'Z'; | |
| needle_str[kNeedleSize - 50] = 'Q'; | |
| needle_str[kNeedleSize - 3] = 'Q'; | |
| needle_str.back() = 'Y'; | |
| std::string haystack_str(1000, '.'); | |
| haystack_str.replace(202, kNeedleSize, needle_str); | |
| haystack_str[200] = 'Z'; | |
| haystack_str[499] = 'Q'; | |
| absl::Cord haystack = absl::MakeFragmentedCord( | |
| {haystack_str.substr(0, 500), haystack_str.substr(500)}); | |
| auto it = haystack.Find(needle_str); | |
| EXPECT_NE(it, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it), 202); | |
| EXPECT_TRUE(haystack.Contains(needle_str)); | |
| absl::Cord needle_cord = absl::MakeFragmentedCord( | |
| {needle_str.substr(0, 150), needle_str.substr(150)}); | |
| auto it_cord = haystack.Find(needle_cord); | |
| EXPECT_NE(it_cord, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 202); | |
| EXPECT_TRUE(haystack.Contains(needle_cord)); | |
| } | |
| TEST_P(CordTest, FindBMHBoundaryThreshold256) { | |
| // Test BMH search at the exact boundary size of 256 bytes for string_view | |
| // and Cord needles. Verifies correct shift table boundary bounds. | |
| const size_t kNeedleSize = 256; | |
| std::string needle_str(kNeedleSize, 'B'); | |
| needle_str[0] = 'X'; | |
| needle_str[kNeedleSize - 2] = 'Y'; | |
| needle_str.back() = 'Z'; | |
| std::string haystack_str(1000, '.'); | |
| haystack_str.replace(500, kNeedleSize, needle_str); | |
| absl::Cord haystack = absl::MakeFragmentedCord( | |
| {haystack_str.substr(0, 400), haystack_str.substr(400)}); | |
| auto it = haystack.Find(needle_str); | |
| EXPECT_NE(it, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it), 500); | |
| EXPECT_TRUE(haystack.Contains(needle_str)); | |
| absl::Cord needle_cord = absl::MakeFragmentedCord( | |
| {needle_str.substr(0, 100), needle_str.substr(100)}); | |
| auto it_cord = haystack.Find(needle_cord); | |
| EXPECT_NE(it_cord, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 500); | |
| EXPECT_TRUE(haystack.Contains(needle_cord)); | |
| } | |
| TEST_P(CordTest, FindBMHMatchAtVeryEndOfCord) { | |
| // Test BMH search when the needle is located at the very end of haystack | |
| // (idx + m == bytes_remaining_). Verifies that boundary check uses > instead | |
| // of >=. | |
| const size_t kNeedleSize = 300; | |
| std::string needle_str(kNeedleSize, 'M'); | |
| needle_str[0] = 'S'; | |
| needle_str.back() = 'E'; | |
| std::string haystack_str = std::string(500, '.') + needle_str; | |
| absl::Cord haystack = absl::MakeFragmentedCord( | |
| {haystack_str.substr(0, 400), haystack_str.substr(400)}); | |
| auto it = haystack.Find(needle_str); | |
| EXPECT_NE(it, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it), 500); | |
| EXPECT_TRUE(haystack.Contains(needle_str)); | |
| absl::Cord needle_cord = absl::MakeFragmentedCord( | |
| {needle_str.substr(0, 150), needle_str.substr(150)}); | |
| auto it_cord = haystack.Find(needle_cord); | |
| EXPECT_NE(it_cord, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 500); | |
| EXPECT_TRUE(haystack.Contains(needle_cord)); | |
| } | |
| TEST_P(CordTest, FindBMHMatchInLaterChunkAfterEmptyChunk) { | |
| // Test BMH search when the first chunk of a fragmented Cord haystack does NOT | |
| // contain needle[0]. Verifies that idx == npos advances past the entire first | |
| // chunk and continues searching subsequent chunks. | |
| const size_t kNeedleSize = 300; | |
| std::string needle_str(kNeedleSize, 'A'); | |
| needle_str[0] = 'Z'; | |
| std::string chunk0(512, '.'); | |
| std::string chunk1 = needle_str + std::string(212, '.'); | |
| absl::Cord haystack = absl::MakeFragmentedCord({chunk0, chunk1}); | |
| auto it = haystack.Find(needle_str); | |
| EXPECT_NE(it, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it), 512); | |
| EXPECT_TRUE(haystack.Contains(needle_str)); | |
| absl::Cord needle_cord = absl::MakeFragmentedCord( | |
| {needle_str.substr(0, 150), needle_str.substr(150)}); | |
| auto it_cord = haystack.Find(needle_cord); | |
| EXPECT_NE(it_cord, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 512); | |
| EXPECT_TRUE(haystack.Contains(needle_cord)); | |
| } | |
| TEST_P(CordTest, FindShortNeedleConsecutiveCandidates) { | |
| // Test short needle (2 <= M < 256) when consecutive positions in haystack | |
| // start with needle.front(), where position 10 mismatches but position 11 | |
| // matches. Verifies that failing IsSubstringInCordAt advances by exactly 1. | |
| std::string needle = "ABZ"; | |
| std::string haystack_str(100, '.'); | |
| haystack_str.replace(10, 3, "ABW"); | |
| haystack_str.replace(11, 3, "ABZ"); | |
| absl::Cord haystack = absl::MakeFragmentedCord( | |
| {haystack_str.substr(0, 50), haystack_str.substr(50)}); | |
| auto it = haystack.Find(needle); | |
| EXPECT_NE(it, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it), 11); | |
| EXPECT_TRUE(haystack.Contains(needle)); | |
| absl::Cord needle_cord(needle); | |
| auto it_cord = haystack.Find(needle_cord); | |
| EXPECT_NE(it_cord, haystack.char_end()); | |
| EXPECT_EQ(std::distance(haystack.char_begin(), it_cord), 11); | |
| EXPECT_TRUE(haystack.Contains(needle_cord)); | |
| } | |
| 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<size_t> 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(), std::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(), std::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(), std::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."}))); | |
| } | |
| static void VerifyCopyToSpan(const absl::Cord& cord) { | |
| // Test with span exactly the same size as the cord. | |
| { | |
| std::string dst(cord.size(), '\0'); | |
| size_t copied = absl::CopyCordToSpan(cord, absl::MakeSpan(dst)); | |
| EXPECT_EQ(copied, cord.size()); | |
| EXPECT_EQ(dst, cord); | |
| } | |
| // Test with span larger than the cord. | |
| { | |
| std::string dst(cord.size() + 10, 'x'); | |
| size_t copied = absl::CopyCordToSpan(cord, absl::MakeSpan(dst)); | |
| EXPECT_EQ(copied, cord.size()); | |
| EXPECT_EQ(absl::string_view(dst).substr(0, copied), cord); | |
| if (cord.size() < dst.size()) { | |
| absl::string_view tail = absl::string_view(dst).substr(copied); | |
| EXPECT_EQ(tail, std::string(tail.size(), 'x')); | |
| } | |
| } | |
| // Test with span smaller than the cord. | |
| { | |
| size_t target_size = cord.size() / 2; | |
| std::string dst(target_size, '\0'); | |
| size_t copied = absl::CopyCordToSpan(cord, absl::MakeSpan(dst)); | |
| EXPECT_EQ(copied, target_size); | |
| EXPECT_EQ(dst, std::string(cord).substr(0, target_size)); | |
| } | |
| // Test with empty span. | |
| { | |
| char c = 'x'; | |
| size_t copied = absl::CopyCordToSpan(cord, absl::MakeSpan(&c, 0)); | |
| EXPECT_EQ(copied, 0); | |
| EXPECT_EQ(c, 'x'); | |
| } | |
| } | |
| TEST_P(CordTest, CopyToSpan) { | |
| VerifyCopyToSpan(absl::Cord()); // Empty cords cannot be hardened. | |
| VerifyCopyToSpan(MaybeHardened(absl::Cord("small cord"))); | |
| VerifyCopyToSpan(MaybeHardened( | |
| absl::MakeFragmentedCord({"fragmented ", "cord ", "to ", "test ", | |
| "copying ", "to ", "a ", "span."}))); | |
| } | |
| 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<bool> { | |
| public: | |
| size_t is_default() const { return GetParam(); } | |
| // Returns human readable string representation of the test parameter. | |
| static std::string ToString(testing::TestParamInfo<bool> 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<size_t>::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(), std::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<std::string> 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 | |
| EXPECT_THAT(other_ref, testing::Eq(c)); | |
| 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<size_t>(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 <typename LHS, typename RHS> | |
| 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<CordCompareTestCase> 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<char>::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<uint32_t> uniform_uint8(0, 255); | |
| char x = static_cast<char>(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, std::size(a))]); | |
| d.Append(a[GetUniformRandomUpTo(&rng, std::size(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 <typename T1, typename T2> | |
| 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_v<T1> || a == T1("a")); | |
| EXPECT_TRUE(std::is_pointer_v<T2> || 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<absl::Cord, absl::Cord>(); | |
| } | |
| TEST_P(CordTest, ComparisonOperators_Cord_StringPiece) { | |
| CompareOperators<absl::Cord, absl::string_view>(); | |
| } | |
| TEST_P(CordTest, ComparisonOperators_StringPiece_Cord) { | |
| CompareOperators<absl::string_view, absl::Cord>(); | |
| } | |
| TEST_P(CordTest, ComparisonOperators_Cord_string) { | |
| CompareOperators<absl::Cord, std::string>(); | |
| } | |
| TEST_P(CordTest, ComparisonOperators_string_Cord) { | |
| CompareOperators<std::string, absl::Cord>(); | |
| } | |
| TEST_P(CordTest, ComparisonOperators_stdstring_Cord) { | |
| CompareOperators<std::string, absl::Cord>(); | |
| } | |
| TEST_P(CordTest, ComparisonOperators_Cord_stdstring) { | |
| CompareOperators<absl::Cord, std::string>(); | |
| } | |
| TEST_P(CordTest, ComparisonOperators_charstar_Cord) { | |
| CompareOperators<const char*, absl::Cord>(); | |
| } | |
| TEST_P(CordTest, ComparisonOperators_Cord_charstar) { | |
| CompareOperators<absl::Cord, const char*>(); | |
| } | |
| 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_THAT(c, testing::Eq("")); | |
| 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_THAT(c, testing::Eq(large_dummy)); | |
| 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<char, kLength> 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<void (*)(absl::string_view)>([](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, ConstructFromExternalNonConstReleaser) { | |
| struct Releaser { | |
| explicit Releaser(bool* invoked) : invoked(invoked) {} | |
| // Non const method. | |
| void operator()(absl::string_view) { *invoked = true; } | |
| bool* invoked; | |
| }; | |
| bool invoked = false; | |
| (void)MaybeHardened(absl::MakeCordFromExternal("dummy", Releaser(&invoked))); | |
| EXPECT_TRUE(invoked); | |
| } | |
| TEST_P(CordTest, ConstructFromExternalNonConstNoArgReleaser) { | |
| struct Releaser { | |
| explicit Releaser(bool* invoked) : invoked(invoked) {} | |
| // Non const method. | |
| void operator()() { *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(<one char>) 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); | |
| EXPECT_THAT(cord, testing::Eq(shared)); | |
| 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<size_t>(std::numeric_limits<uint32_t>::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<absl::string_view> 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_v<absl::Cord::ChunkIterator>); | |
| static_assert(std::is_copy_assignable_v<absl::Cord::ChunkIterator>); | |
| // Move semantics to satisfy swappable via std::swap | |
| static_assert(std::is_move_constructible_v<absl::Cord::ChunkIterator>); | |
| static_assert(std::is_move_assignable_v<absl::Cord::ChunkIterator>); | |
| static_assert( | |
| std::is_same_v< | |
| std::iterator_traits<absl::Cord::ChunkIterator>::iterator_category, | |
| std::input_iterator_tag>); | |
| static_assert(std::is_same_v< | |
| std::iterator_traits<absl::Cord::ChunkIterator>::value_type, | |
| absl::string_view>); | |
| static_assert( | |
| std::is_same_v< | |
| std::iterator_traits<absl::Cord::ChunkIterator>::difference_type, | |
| ptrdiff_t>); | |
| static_assert( | |
| std::is_same_v<std::iterator_traits<absl::Cord::ChunkIterator>::pointer, | |
| const absl::string_view*>); | |
| static_assert( | |
| std::is_same_v<std::iterator_traits<absl::Cord::ChunkIterator>::reference, | |
| absl::string_view>); | |
| } | |
| 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<int>(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_v<absl::Cord::CharIterator>); | |
| static_assert(std::is_copy_assignable_v<absl::Cord::CharIterator>); | |
| // Move semantics to satisfy swappable via std::swap | |
| static_assert(std::is_move_constructible_v<absl::Cord::CharIterator>); | |
| static_assert(std::is_move_assignable_v<absl::Cord::CharIterator>); | |
| static_assert( | |
| std::is_same_v< | |
| std::iterator_traits<absl::Cord::CharIterator>::iterator_category, | |
| std::input_iterator_tag>); | |
| static_assert( | |
| std::is_same_v<std::iterator_traits<absl::Cord::CharIterator>::value_type, | |
| char>); | |
| static_assert(std::is_same_v< | |
| std::iterator_traits<absl::Cord::CharIterator>::difference_type, | |
| ptrdiff_t>); | |
| static_assert( | |
| std::is_same_v<std::iterator_traits<absl::Cord::CharIterator>::pointer, | |
| const char*>); | |
| static_assert( | |
| std::is_same_v<std::iterator_traits<absl::Cord::CharIterator>::reference, | |
| const char&>); | |
| } | |
| 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()); | |
| EXPECT_EQ(absl::Cord::Distance(range.begin(), range.end()), | |
| static_cast<ptrdiff_t>(cord.size())); | |
| EXPECT_EQ(absl::Cord::Distance(range.end(), range.begin()), | |
| -static_cast<ptrdiff_t>(cord.size())); | |
| 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); | |
| 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<int>(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); | |
| EXPECT_EQ(absl::Cord::Distance(range.begin(), advance_iter), | |
| static_cast<ptrdiff_t>(i)); | |
| advance_iter = range.begin(); | |
| EXPECT_EQ(absl::Cord::AdvanceAndRead(&advance_iter, i), cord.Subcord(0, i)); | |
| EXPECT_EQ(pre_iter, advance_iter); | |
| EXPECT_EQ(absl::Cord::Distance(range.begin(), advance_iter), | |
| static_cast<ptrdiff_t>(i)); | |
| advance_iter = pre_iter; | |
| absl::Cord::Advance(&advance_iter, cord.size() - i); | |
| EXPECT_EQ(range.end(), advance_iter); | |
| EXPECT_EQ(absl::Cord::Distance(range.begin(), advance_iter), | |
| static_cast<ptrdiff_t>(cord.size())); | |
| EXPECT_EQ(absl::Cord::Distance(advance_iter, range.end()), 0); | |
| 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); | |
| EXPECT_EQ(absl::Cord::Distance(range.begin(), advance_iter), | |
| static_cast<ptrdiff_t>(cord.size())); | |
| EXPECT_EQ(absl::Cord::Distance(advance_iter, range.end()), 0); | |
| ++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 it_remaining = cord.size(); | |
| size_t it_advanced = 0; | |
| size_t offset = 0; | |
| while (offset < data.length()) { | |
| EXPECT_EQ(absl::Cord::Distance(it, cord.char_end()), it_remaining); | |
| EXPECT_EQ(absl::Cord::Distance(cord.char_begin(), it), it_advanced); | |
| const size_t n = std::min<size_t>(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; | |
| it_remaining -= n; | |
| it_advanced += n; | |
| EXPECT_EQ(absl::Cord::Distance(it, cord.char_end()), it_remaining); | |
| EXPECT_EQ(absl::Cord::Distance(cord.char_begin(), it), it_advanced); | |
| } | |
| } | |
| } | |
| 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<std::string> 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<std::string> 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<void>(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 <typename Str> | |
| void TestAfterExit(Str) { | |
| const auto expected = Str::value; | |
| // Defined before `cord` to be destroyed after it. | |
| static AfterExitCordTester exit_tester; // NOLINT | |
| static absl::NoDestructor<absl::Cord> 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<std::string> 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 large string", | |
| [](absl::Cord& c) { c.Append(std::string(1000, 'x')); }, | |
| [](absl::Cord& c) { c.RemoveSuffix(1000); } | |
| }, | |
| { | |
| "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 large string", | |
| [](absl::Cord& c) { c.Prepend(std::string(1000, 'x')); }, | |
| [](absl::Cord& c) { c.RemovePrefix(1000); } | |
| }, | |
| { | |
| "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(), std::nullopt); | |
| if (mutator.CanUndo()) { | |
| // Undoing an operation should not restore the checksum | |
| mutator.Undo(c2); | |
| EXPECT_EQ(c2, base_value); | |
| EXPECT_EQ(c2.ExpectedChecksum(), std::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(), std::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<void>(chunk); | |
| GTEST_FAIL() << "no chunks expected"; | |
| } | |
| EXPECT_TRUE(cc3.chunk_begin() == cc3.chunk_end()); | |
| for (char ch : cc3.Chars()) { // NOLINT(unreachable loop) | |
| static_cast<void>(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())); | |
| } | |
| // This must not be static to avoid aggressive optimizations. | |
| ABSL_ATTRIBUTE_WEAK | |
| size_t FalseReport(const absl::Cord& a, bool f); | |
| ABSL_ATTRIBUTE_NOINLINE | |
| size_t FalseReport(const absl::Cord& a, bool f) { | |
| absl::Cord b; | |
| const absl::Cord& ref = f ? b : a; | |
| // Test that sanitizers report nothing here. Without | |
| // InlineData::Rep::annotated_this() compiler can unconditionally load | |
| // poisoned parts, assuming that local variable is fully accessible. | |
| return ref.size(); | |
| } | |
| TEST(CordSanitizerTest, SanitizesCordFalseReport) { | |
| absl::Cord c; | |
| for (int i = 0; i < 1000; ++i) c.Append("a"); | |
| FalseReport(c, false); | |
| } | |
| 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 | |
| } |