Commit 48419595 by Derek Mauro Committed by Copybara-Service

Release absl::CordBuffer

absl::CordBuffer holds data for eventual inclusion within an existing
absl::Cord. CordBuffers are useful for building large Cords that may
require custom allocation of its associated memory, a pattern that is
common in zero-copy APIs.
PiperOrigin-RevId: 453212229
Change-Id: I6a8adc3a8d206691cb1b0001a9161e5080dd1c5f
parent 9cdb98e7
...@@ -194,9 +194,11 @@ set(ABSL_INTERNAL_DLL_FILES ...@@ -194,9 +194,11 @@ set(ABSL_INTERNAL_DLL_FILES
"strings/charconv.cc" "strings/charconv.cc"
"strings/charconv.h" "strings/charconv.h"
"strings/cord.cc" "strings/cord.cc"
"strings/cord.h"
"strings/cord_analysis.cc" "strings/cord_analysis.cc"
"strings/cord_analysis.h" "strings/cord_analysis.h"
"strings/cord.h" "strings/cord_buffer.cc"
"strings/cord_buffer.h"
"strings/escaping.cc" "strings/escaping.cc"
"strings/escaping.h" "strings/escaping.h"
"strings/internal/charconv_bigint.cc" "strings/internal/charconv_bigint.cc"
......
...@@ -415,9 +415,11 @@ cc_library( ...@@ -415,9 +415,11 @@ cc_library(
"cord.cc", "cord.cc",
"cord_analysis.cc", "cord_analysis.cc",
"cord_analysis.h", "cord_analysis.h",
"cord_buffer.cc",
], ],
hdrs = [ hdrs = [
"cord.h", "cord.h",
"cord_buffer.h",
], ],
copts = ABSL_DEFAULT_COPTS, copts = ABSL_DEFAULT_COPTS,
deps = [ deps = [
...@@ -702,6 +704,22 @@ cc_library( ...@@ -702,6 +704,22 @@ cc_library(
) )
cc_test( cc_test(
name = "cord_buffer_test",
size = "small",
srcs = ["cord_buffer_test.cc"],
copts = ABSL_TEST_COPTS,
visibility = ["//visibility:private"],
deps = [
":cord",
":cord_internal",
":cord_rep_test_util",
"//absl/base:config",
"//absl/types:span",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "cord_test", name = "cord_test",
size = "medium", size = "medium",
srcs = ["cord_test.cc"], srcs = ["cord_test.cc"],
......
...@@ -845,10 +845,12 @@ absl_cc_library( ...@@ -845,10 +845,12 @@ absl_cc_library(
cord cord
HDRS HDRS
"cord.h" "cord.h"
"cord_buffer.h"
SRCS SRCS
"cord.cc" "cord.cc"
"cord_analysis.cc" "cord_analysis.cc"
"cord_analysis.h" "cord_analysis.h"
"cord_buffer.cc"
COPTS COPTS
${ABSL_DEFAULT_COPTS} ${ABSL_DEFAULT_COPTS}
DEPS DEPS
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
#include "absl/base/port.h" #include "absl/base/port.h"
#include "absl/container/fixed_array.h" #include "absl/container/fixed_array.h"
#include "absl/container/inlined_vector.h" #include "absl/container/inlined_vector.h"
#include "absl/strings/cord_buffer.h"
#include "absl/strings/escaping.h" #include "absl/strings/escaping.h"
#include "absl/strings/internal/cord_data_edge.h" #include "absl/strings/internal/cord_data_edge.h"
#include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_internal.h"
...@@ -520,6 +521,46 @@ inline void Cord::AppendImpl(C&& src) { ...@@ -520,6 +521,46 @@ inline void Cord::AppendImpl(C&& src) {
contents_.AppendTree(rep, CordzUpdateTracker::kAppendCord); contents_.AppendTree(rep, CordzUpdateTracker::kAppendCord);
} }
static CordRep::ExtractResult ExtractAppendBuffer(CordRep* rep,
size_t min_capacity) {
switch (rep->tag) {
case cord_internal::BTREE:
return CordRepBtree::ExtractAppendBuffer(rep->btree(), min_capacity);
default:
if (rep->IsFlat() && rep->refcount.IsOne() &&
rep->flat()->Capacity() - rep->length >= min_capacity) {
return {nullptr, rep};
}
return {rep, nullptr};
}
}
static CordBuffer CreateAppendBuffer(InlineData& data, size_t capacity) {
// Watch out for overflow, people can ask for size_t::max().
const size_t size = data.inline_size();
capacity = (std::min)(std::numeric_limits<size_t>::max() - size, capacity);
CordBuffer buffer = CordBuffer::CreateWithDefaultLimit(size + capacity);
cord_internal::SmallMemmove(buffer.data(), data.as_chars(), size);
buffer.SetLength(size);
data = {};
return buffer;
}
CordBuffer Cord::GetAppendBufferSlowPath(size_t capacity, size_t min_capacity) {
auto constexpr method = CordzUpdateTracker::kGetAppendBuffer;
CordRep* tree = contents_.tree();
if (tree != nullptr) {
CordzUpdateScope scope(contents_.cordz_info(), method);
CordRep::ExtractResult result = ExtractAppendBuffer(tree, min_capacity);
if (result.extracted != nullptr) {
contents_.SetTreeOrEmpty(result.tree, scope);
return CordBuffer(result.extracted->flat());
}
return CordBuffer::CreateWithDefaultLimit(capacity);
}
return CreateAppendBuffer(contents_.data_, capacity);
}
void Cord::Append(const Cord& src) { void Cord::Append(const Cord& src) {
AppendImpl(src); AppendImpl(src);
} }
...@@ -572,6 +613,33 @@ void Cord::PrependArray(absl::string_view src, MethodIdentifier method) { ...@@ -572,6 +613,33 @@ void Cord::PrependArray(absl::string_view src, MethodIdentifier method) {
contents_.PrependTree(rep, method); contents_.PrependTree(rep, method);
} }
void Cord::AppendPrecise(absl::string_view src, MethodIdentifier method) {
assert(!src.empty());
assert(src.size() <= cord_internal::kMaxFlatLength);
if (contents_.remaining_inline_capacity() >= src.size()) {
const size_t inline_length = contents_.inline_size();
memcpy(contents_.data_.as_chars() + inline_length, src.data(), src.size());
contents_.set_inline_size(inline_length + src.size());
} else {
contents_.AppendTree(CordRepFlat::Create(src), method);
}
}
void Cord::PrependPrecise(absl::string_view src, MethodIdentifier method) {
assert(!src.empty());
assert(src.size() <= cord_internal::kMaxFlatLength);
if (contents_.remaining_inline_capacity() >= src.size()) {
const size_t inline_length = contents_.inline_size();
char data[InlineRep::kMaxInline + 1] = {0};
memcpy(data, src.data(), src.size());
memcpy(data + src.size(), contents_.data(), inline_length);
memcpy(contents_.data_.as_chars(), data, InlineRep::kMaxInline + 1);
contents_.set_inline_size(inline_length + src.size());
} else {
contents_.PrependTree(CordRepFlat::Create(src), method);
}
}
template <typename T, Cord::EnableIfString<T>> template <typename T, Cord::EnableIfString<T>>
inline void Cord::Prepend(T&& src) { inline void Cord::Prepend(T&& src) {
if (src.size() <= kMaxBytesToCopy) { if (src.size() <= kMaxBytesToCopy) {
......
...@@ -80,6 +80,7 @@ ...@@ -80,6 +80,7 @@
#include "absl/functional/function_ref.h" #include "absl/functional/function_ref.h"
#include "absl/meta/type_traits.h" #include "absl/meta/type_traits.h"
#include "absl/strings/cord_analysis.h" #include "absl/strings/cord_analysis.h"
#include "absl/strings/cord_buffer.h"
#include "absl/strings/internal/cord_data_edge.h" #include "absl/strings/internal/cord_data_edge.h"
#include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_btree.h" #include "absl/strings/internal/cord_rep_btree.h"
...@@ -244,6 +245,45 @@ class Cord { ...@@ -244,6 +245,45 @@ class Cord {
template <typename T, EnableIfString<T> = 0> template <typename T, EnableIfString<T> = 0>
void Append(T&& src); void Append(T&& src);
// Appends `buffer` to this cord, unless `buffer` has a zero length in which
// case this method has no effect on this cord instance.
// This method is guaranteed to consume `buffer`.
void Append(CordBuffer buffer);
// Returns a CordBuffer, re-using potential existing capacity in this cord.
//
// Cord instances may have additional unused capacity in the last (or first)
// nodes of the underlying tree to facilitate amortized growth. This method
// allows applications to explicitly use this spare capacity if available,
// or create a new CordBuffer instance otherwise.
// If this cord has a final non-shared node with at least `min_capacity`
// available, then this method will return that buffer including its data
// contents. I.e.; the returned buffer will have a non-zero length, and
// a capacity of at least `buffer.length + min_capacity`. Otherwise, this
// method will return `CordBuffer::CreateWithDefaultLimit(capacity)`.
//
// Below an example of using GetAppendBuffer. Notice that in this example we
// use `GetAppendBuffer()` only on the first iteration. As we know nothing
// about any initial extra capacity in `cord`, we may be able to use the extra
// capacity. But as we add new buffers with fully utilized contents after that
// we avoid calling `GetAppendBuffer()` on subsequent iterations: while this
// works fine, it results in an unnecessary inspection of cord contents:
//
// void AppendRandomDataToCord(absl::Cord &cord, size_t n) {
// bool first = true;
// while (n > 0) {
// CordBuffer buffer = first ? cord.GetAppendBuffer(n)
// : CordBuffer::CreateWithDefaultLimit(n);
// absl::Span<char> data = buffer.available_up_to(n);
// FillRandomValues(data.data(), data.size());
// buffer.IncreaseLengthBy(data.size());
// cord.Append(std::move(buffer));
// n -= data.size();
// first = false;
// }
// }
CordBuffer GetAppendBuffer(size_t capacity, size_t min_capacity = 16);
// Cord::Prepend() // Cord::Prepend()
// //
// Prepends data to the Cord, which may come from another Cord or other string // Prepends data to the Cord, which may come from another Cord or other string
...@@ -253,6 +293,11 @@ class Cord { ...@@ -253,6 +293,11 @@ class Cord {
template <typename T, EnableIfString<T> = 0> template <typename T, EnableIfString<T> = 0>
void Prepend(T&& src); void Prepend(T&& src);
// Prepends `buffer` to this cord, unless `buffer` has a zero length in which
// case this method has no effect on this cord instance.
// This method is guaranteed to consume `buffer`.
void Prepend(CordBuffer buffer);
// Cord::RemovePrefix() // Cord::RemovePrefix()
// //
// Removes the first `n` bytes of a Cord. // Removes the first `n` bytes of a Cord.
...@@ -928,6 +973,15 @@ class Cord { ...@@ -928,6 +973,15 @@ class Cord {
template <typename C> template <typename C>
void AppendImpl(C&& src); void AppendImpl(C&& src);
// Appends / Prepends `src` to this instance, using precise sizing.
// This method does explicitly not attempt to use any spare capacity
// in any pending last added private owned flat.
// Requires `src` to be <= kMaxFlatLength.
void AppendPrecise(absl::string_view src, MethodIdentifier method);
void PrependPrecise(absl::string_view src, MethodIdentifier method);
CordBuffer GetAppendBufferSlowPath(size_t capacity, size_t min_capacity);
// Prepends the provided data to this instance. `method` contains the public // Prepends the provided data to this instance. `method` contains the public
// API method for this action which is tracked for Cordz sampling purposes. // API method for this action which is tracked for Cordz sampling purposes.
void PrependArray(absl::string_view src, MethodIdentifier method); void PrependArray(absl::string_view src, MethodIdentifier method);
...@@ -1284,6 +1338,31 @@ inline void Cord::Prepend(absl::string_view src) { ...@@ -1284,6 +1338,31 @@ inline void Cord::Prepend(absl::string_view src) {
PrependArray(src, CordzUpdateTracker::kPrependString); PrependArray(src, CordzUpdateTracker::kPrependString);
} }
inline void Cord::Append(CordBuffer buffer) {
if (ABSL_PREDICT_FALSE(buffer.length() == 0)) return;
absl::string_view short_value;
if (CordRep* rep = buffer.ConsumeValue(short_value)) {
contents_.AppendTree(rep, CordzUpdateTracker::kAppendCordBuffer);
} else {
AppendPrecise(short_value, CordzUpdateTracker::kAppendCordBuffer);
}
}
inline void Cord::Prepend(CordBuffer buffer) {
if (ABSL_PREDICT_FALSE(buffer.length() == 0)) return;
absl::string_view short_value;
if (CordRep* rep = buffer.ConsumeValue(short_value)) {
contents_.PrependTree(rep, CordzUpdateTracker::kPrependCordBuffer);
} else {
PrependPrecise(short_value, CordzUpdateTracker::kPrependCordBuffer);
}
}
inline CordBuffer Cord::GetAppendBuffer(size_t capacity, size_t min_capacity) {
if (empty()) return CordBuffer::CreateWithDefaultLimit(capacity);
return GetAppendBufferSlowPath(capacity, min_capacity);
}
extern template void Cord::Append(std::string&& src); extern template void Cord::Append(std::string&& src);
extern template void Cord::Prepend(std::string&& src); extern template void Cord::Prepend(std::string&& src);
......
// Copyright 2022 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_buffer.h"
#include <cstddef>
#include "absl/base/config.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
constexpr size_t CordBuffer::kDefaultLimit;
constexpr size_t CordBuffer::kCustomLimit;
ABSL_NAMESPACE_END
} // namespace absl
...@@ -597,6 +597,284 @@ TEST_P(CordTest, CopyToString) { ...@@ -597,6 +597,284 @@ TEST_P(CordTest, CopyToString) {
"copying ", "to ", "a ", "string."}))); "copying ", "to ", "a ", "string."})));
} }
TEST_P(CordTest, AppendEmptyBuffer) {
absl::Cord cord;
cord.Append(absl::CordBuffer());
cord.Append(absl::CordBuffer::CreateWithDefaultLimit(2000));
}
TEST_P(CordTest, AppendEmptyBufferToFlat) {
absl::Cord cord(std::string(2000, 'x'));
cord.Append(absl::CordBuffer());
cord.Append(absl::CordBuffer::CreateWithDefaultLimit(2000));
}
TEST_P(CordTest, AppendEmptyBufferToTree) {
absl::Cord cord(std::string(2000, 'x'));
cord.Append(std::string(2000, 'y'));
cord.Append(absl::CordBuffer());
cord.Append(absl::CordBuffer::CreateWithDefaultLimit(2000));
}
TEST_P(CordTest, AppendSmallBuffer) {
absl::Cord cord;
absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(3);
ASSERT_THAT(buffer.capacity(), ::testing::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(), ::testing::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(), ::testing::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(), ::testing::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(), ::testing::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(), ::testing::ElementsAre(s2, s1));
}
TEST_P(CordTest, GetAppendBufferOnEmptyCord) {
absl::Cord cord;
absl::CordBuffer buffer = cord.GetAppendBuffer(1000);
EXPECT_GE(buffer.capacity(), 1000);
EXPECT_EQ(buffer.length(), 0);
}
TEST_P(CordTest, 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 = cord.GetAppendBuffer(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(CordTest, GetAppendBufferOnInlinedCordWithCapacityCloseToMax) {
// 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 = cord.GetAppendBuffer(size, 1);
EXPECT_EQ(buffer.capacity(), absl::CordBuffer::kDefaultLimit);
EXPECT_EQ(buffer.length(), 3);
EXPECT_EQ(absl::string_view(buffer.data(), buffer.length()), "Abc");
EXPECT_TRUE(cord.empty());
}
}
TEST_P(CordTest, GetAppendBufferOnFlat) {
// Create a 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));
buffer = cord.GetAppendBuffer(6);
EXPECT_GE(buffer.capacity(), 500);
EXPECT_EQ(buffer.length(), 3);
EXPECT_EQ(absl::string_view(buffer.data(), buffer.length()), "Abc");
EXPECT_TRUE(cord.empty());
}
TEST_P(CordTest, 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 = cord.GetAppendBuffer(1000, 900);
EXPECT_GE(buffer.capacity(), 1000);
EXPECT_EQ(buffer.length(), 0);
EXPECT_EQ(cord, std::string(30, 'x'));
}
TEST_P(CordTest, 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 = cord.GetAppendBuffer(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(CordTest, 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 = cord.GetAppendBuffer(1000, 900);
EXPECT_GE(buffer.capacity(), 1000);
EXPECT_EQ(buffer.length(), 0);
EXPECT_EQ(cord, "Abcdef");
}
TEST_P(CordTest, 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 = cord.GetAppendBuffer(6);
EXPECT_EQ(buffer.length(), 0);
EXPECT_EQ(cord, std::string(449, 'x'));
}
TEST_P(CordTest, 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 = cord.GetAppendBuffer(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 = cord.GetAppendBuffer(6);
EXPECT_EQ(buffer.length(), 0);
EXPECT_EQ(cord, "Abcdef");
}
TEST_P(CordTest, TryFlatEmpty) { TEST_P(CordTest, TryFlatEmpty) {
absl::Cord c; absl::Cord c;
EXPECT_EQ(c.TryFlat(), ""); EXPECT_EQ(c.TryFlat(), "");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment