blob: 7d75e106e7a45f2639a65cf74d6e697a4fe27858 [file] [log] [blame]
// 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 <cstdlib>
#include <ctime>
#include <memory>
#include <random>
#include <sstream>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
#include "absl/debugging/leak_check.h"
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_ring.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
extern thread_local bool cord_ring;
// TOOD(b/177688959): weird things happened with the original test
#define ASAN_BUG_177688959_FIXED false
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace {
using RandomEngine = std::mt19937_64;
using ::absl::cord_internal::CordRep;
using ::absl::cord_internal::CordRepConcat;
using ::absl::cord_internal::CordRepExternal;
using ::absl::cord_internal::CordRepFlat;
using ::absl::cord_internal::CordRepRing;
using ::absl::cord_internal::CordRepSubstring;
using ::absl::cord_internal::CONCAT;
using ::absl::cord_internal::EXTERNAL;
using ::absl::cord_internal::SUBSTRING;
using testing::ElementsAre;
using testing::ElementsAreArray;
using testing::Eq;
using testing::Ge;
using testing::Le;
using testing::Lt;
using testing::Ne;
using testing::SizeIs;
using index_type = CordRepRing::index_type;
enum InputShareMode { kPrivate, kShared, kSharedIndirect };
// TestParam class used by all test fixtures.
// Not all fixtures use all possible input combinations
struct TestParam {
TestParam() = default;
explicit TestParam(InputShareMode input_share_mode)
: input_share_mode(input_share_mode) {}
// Run the test with the 'rep under test' to be privately owned.
// Otherwise, the rep has a shared ref count of 2 or higher.
bool refcount_is_one = true;
// Run the test with the 'rep under test' being allocated with enough capacity
// to accommodate any modifications made to it. Otherwise, the rep has zero
// extra (reserve) capacity.
bool with_capacity = true;
// For test providing possibly shared input such as Append(.., CordpRep*),
// this field defines if that input is adopted with a refcount of one
// (privately owned / donated), or shared. For composite inputs such as
// 'substring of flat', we also have the 'shared indirect' value which means
// the top level node is not shared, but the contained child node is shared.
InputShareMode input_share_mode = kPrivate;
std::string ToString() const {
return absl::StrCat(refcount_is_one ? "Private" : "Shared",
with_capacity ? "" : "_NoCapacity",
(input_share_mode == kPrivate) ? ""
: (input_share_mode == kShared)
? "_SharedInput"
: "_IndirectSharedInput");
}
};
using TestParams = std::vector<TestParam>;
// Matcher validating when mutable copies are required / performed.
MATCHER_P2(EqIfPrivate, param, rep,
absl::StrCat("Equal 0x", absl::Hex(rep), " if private")) {
return param.refcount_is_one ? arg == rep : arg != rep;
}
// Matcher validating when mutable copies are required / performed.
MATCHER_P2(EqIfPrivateAndCapacity, param, rep,
absl::StrCat("Equal 0x", absl::Hex(rep),
" if private and capacity")) {
return (param.refcount_is_one && param.with_capacity) ? arg == rep
: arg != rep;
}
MATCHER_P2(EqIfInputPrivate, param, rep, "Equal if input is private") {
return param.input_share_mode == kPrivate ? arg == rep : arg != rep;
}
// Matcher validating the core in-variants of the CordRepRing instance.
MATCHER(IsValidRingBuffer, "RingBuffer is valid") {
std::stringstream ss;
if (!arg->IsValid(ss)) {
*result_listener << "\nERROR: " << ss.str() << "\nRING = " << *arg;
return false;
}
return true;
}
// Returns the flats contained in the provided CordRepRing
std::vector<string_view> ToFlats(const CordRepRing* r) {
std::vector<string_view> flats;
flats.reserve(r->entries());
index_type pos = r->head();
do {
flats.push_back(r->entry_data(pos));
} while ((pos = r->advance(pos)) != r->tail());
return flats;
}
class not_a_string_view {
public:
explicit not_a_string_view(absl::string_view s)
: data_(s.data()), size_(s.size()) {}
explicit not_a_string_view(const void* data, size_t size)
: data_(data), size_(size) {}
not_a_string_view remove_prefix(size_t n) const {
return not_a_string_view(static_cast<const char*>(data_) + n, size_ - n);
}
not_a_string_view remove_suffix(size_t n) const {
return not_a_string_view(data_, size_ - n);
}
const void* data() const { return data_; }
size_t size() const { return size_; }
private:
const void* data_;
size_t size_;
};
bool operator==(not_a_string_view lhs, not_a_string_view rhs) {
return lhs.data() == rhs.data() && lhs.size() == rhs.size();
}
std::ostream& operator<<(std::ostream& s, not_a_string_view rhs) {
return s << "{ data: " << rhs.data() << " size: " << rhs.size() << "}";
}
std::vector<not_a_string_view> ToRawFlats(const CordRepRing* r) {
std::vector<not_a_string_view> flats;
flats.reserve(r->entries());
index_type pos = r->head();
do {
flats.emplace_back(r->entry_data(pos));
} while ((pos = r->advance(pos)) != r->tail());
return flats;
}
// Returns the value contained in the provided CordRepRing
std::string ToString(const CordRepRing* r) {
std::string value;
value.reserve(r->length);
index_type pos = r->head();
do {
absl::string_view sv = r->entry_data(pos);
value.append(sv.data(), sv.size());
} while ((pos = r->advance(pos)) != r->tail());
return value;
}
// Creates a flat for testing
CordRep* MakeFlat(absl::string_view s, size_t extra = 0) {
CordRepFlat* flat = CordRepFlat::New(s.length() + extra);
memcpy(flat->Data(), s.data(), s.length());
flat->length = s.length();
return flat;
}
// Creates an external node for testing
CordRepExternal* MakeExternal(absl::string_view s) {
struct Rep : public CordRepExternal {
std::string s;
explicit Rep(absl::string_view s) : s(s) {
this->tag = EXTERNAL;
this->base = s.data();
this->length = s.length();
this->releaser_invoker = [](CordRepExternal* self) {
delete static_cast<Rep*>(self);
};
}
};
return new Rep(s);
}
CordRepExternal* MakeFakeExternal(size_t length) {
struct Rep : public CordRepExternal {
std::string s;
explicit Rep(size_t len) {
this->tag = EXTERNAL;
this->base = this->storage;
this->length = len;
this->releaser_invoker = [](CordRepExternal* self) {
delete static_cast<Rep*>(self);
};
}
};
return new Rep(length);
}
// Creates a flat or an external node for testing depending on the size.
CordRep* MakeLeaf(absl::string_view s, size_t extra = 0) {
if (s.size() <= absl::cord_internal::kMaxFlatLength) {
return MakeFlat(s, extra);
} else {
return MakeExternal(s);
}
}
// Creates a substring node
CordRepSubstring* MakeSubstring(size_t start, size_t len, CordRep* rep) {
auto* sub = new CordRepSubstring;
sub->tag = SUBSTRING;
sub->start = start;
sub->length = (len <= 0) ? rep->length - start + len : len;
sub->child = rep;
return sub;
}
// Creates a substring node removing the specified prefix
CordRepSubstring* RemovePrefix(size_t start, CordRep* rep) {
return MakeSubstring(start, rep->length - start, rep);
}
// Creates a substring node removing the specified suffix
CordRepSubstring* RemoveSuffix(size_t length, CordRep* rep) {
return MakeSubstring(0, rep->length - length, rep);
}
CordRepConcat* MakeConcat(CordRep* left, CordRep* right, int depth = 0) {
auto* concat = new CordRepConcat;
concat->tag = CONCAT;
concat->length = left->length + right->length;
concat->left = left;
concat->right = right;
concat->set_depth(depth);
return concat;
}
enum Composition { kMix, kAppend, kPrepend };
Composition RandomComposition() {
RandomEngine rng(testing::GTEST_FLAG(random_seed));
return (rng() & 1) ? kMix : ((rng() & 1) ? kAppend : kPrepend);
}
absl::string_view ToString(Composition composition) {
switch (composition) {
case kAppend:
return "Append";
case kPrepend:
return "Prepend";
case kMix:
return "Mix";
}
assert(false);
return "???";
}
constexpr const char* kFox = "The quick brown fox jumps over the lazy dog";
constexpr const char* kFoxFlats[] = {"The ", "quick ", "brown ",
"fox ", "jumps ", "over ",
"the ", "lazy ", "dog"};
constexpr const char* kAlphabet = "abcdefghijklmnopqrstuvwxyz";
CordRepRing* FromFlats(Span<const char* const> flats,
Composition composition = kAppend) {
if (flats.empty()) return nullptr;
CordRepRing* ring = nullptr;
switch (composition) {
case kAppend:
ring = CordRepRing::Create(MakeLeaf(flats.front()), flats.size() - 1);
for (int i = 1; i < flats.size(); ++i) {
ring = CordRepRing::Append(ring, MakeLeaf(flats[i]));
}
break;
case kPrepend:
ring = CordRepRing::Create(MakeLeaf(flats.back()), flats.size() - 1);
for (int i = static_cast<int>(flats.size() - 2); i >= 0; --i) {
ring = CordRepRing::Prepend(ring, MakeLeaf(flats[i]));
}
break;
case kMix:
size_t middle1 = flats.size() / 2, middle2 = middle1;
ring = CordRepRing::Create(MakeLeaf(flats[middle1]), flats.size() - 1);
if (!flats.empty()) {
if ((flats.size() & 1) == 0) {
ring = CordRepRing::Prepend(ring, MakeLeaf(flats[--middle1]));
}
for (int i = 1; i <= middle1; ++i) {
ring = CordRepRing::Prepend(ring, MakeLeaf(flats[middle1 - i]));
ring = CordRepRing::Append(ring, MakeLeaf(flats[middle2 + i]));
}
}
break;
}
EXPECT_THAT(ToFlats(ring), ElementsAreArray(flats));
return ring;
}
std::ostream& operator<<(std::ostream& s, const TestParam& param) {
return s << param.ToString();
}
std::string TestParamToString(const testing::TestParamInfo<TestParam>& info) {
return info.param.ToString();
}
class CordRingTest : public testing::Test {
public:
~CordRingTest() override {
#if ASAN_BUG_177688959_FIXED
for (CordRep* rep : unrefs_) {
CordRep::Unref(rep);
}
#endif
}
template <typename CordRepType>
CordRepType* NeedsUnref(CordRepType* rep) {
assert(rep);
#if ASAN_BUG_177688959_FIXED
unrefs_.push_back(rep);
#endif
return rep;
}
template <typename CordRepType>
CordRepType* Ref(CordRepType* rep) {
CordRep::Ref(rep);
return NeedsUnref(rep);
}
void Unref(CordRep* rep) {
#if !ASAN_BUG_177688959_FIXED
CordRep::Unref(rep);
#endif
}
private:
#if ASAN_BUG_177688959_FIXED
std::vector<CordRep*> unrefs_;
#endif
};
class CordRingTestWithParam : public testing::TestWithParam<TestParam> {
public:
~CordRingTestWithParam() override {
#if ASAN_BUG_177688959_FIXED
for (CordRep* rep : unrefs_) {
CordRep::Unref(rep);
}
#endif
}
CordRepRing* CreateWithCapacity(CordRep* child, size_t extra_capacity) {
if (!GetParam().with_capacity) extra_capacity = 0;
CordRepRing* ring = CordRepRing::Create(child, extra_capacity);
ring->SetCapacityForTesting(1 + extra_capacity);
return RefIfShared(ring);
}
bool Shared() const { return !GetParam().refcount_is_one; }
bool InputShared() const { return GetParam().input_share_mode == kShared; }
bool InputSharedIndirect() const {
return GetParam().input_share_mode == kSharedIndirect;
}
template <typename CordRepType>
CordRepType* NeedsUnref(CordRepType* rep) {
assert(rep);
#if ASAN_BUG_177688959_FIXED
unrefs_.push_back(rep);
#endif
return rep;
}
template <typename CordRepType>
CordRepType* Ref(CordRepType* rep) {
CordRep::Ref(rep);
return NeedsUnref(rep);
}
void Unref(CordRep* rep) {
#if !ASAN_BUG_177688959_FIXED
CordRep::Unref(rep);
#endif
}
template <typename CordRepType>
CordRepType* RefIfShared(CordRepType* rep) {
return Shared() ? Ref(rep) : rep;
}
void UnrefIfShared(CordRep* rep) {
if (Shared()) Unref(rep);
}
template <typename CordRepType>
CordRepType* RefIfInputShared(CordRepType* rep) {
return InputShared() ? Ref(rep) : rep;
}
void UnrefIfInputShared(CordRep* rep) {
if (InputShared()) Unref(rep);
}
template <typename CordRepType>
CordRepType* RefIfInputSharedIndirect(CordRepType* rep) {
return InputSharedIndirect() ? Ref(rep) : rep;
}
void UnrefIfInputSharedIndirect(CordRep* rep) {
if (InputSharedIndirect()) Unref(rep);
}
private:
#if ASAN_BUG_177688959_FIXED
std::vector<CordRep*> unrefs_;
#endif
};
class CordRingCreateTest : public CordRingTestWithParam {
public:
static TestParams CreateTestParams() {
TestParams params;
params.emplace_back(InputShareMode::kPrivate);
params.emplace_back(InputShareMode::kShared);
return params;
}
};
class CordRingSubTest : public CordRingTestWithParam {
public:
static TestParams CreateTestParams() {
TestParams params;
for (bool refcount_is_one : {true, false}) {
TestParam param;
param.refcount_is_one = refcount_is_one;
params.push_back(param);
}
return params;
}
};
class CordRingBuildTest : public CordRingTestWithParam {
public:
static TestParams CreateTestParams() {
TestParams params;
for (bool refcount_is_one : {true, false}) {
for (bool with_capacity : {true, false}) {
TestParam param;
param.refcount_is_one = refcount_is_one;
param.with_capacity = with_capacity;
params.push_back(param);
}
}
return params;
}
};
class CordRingCreateFromTreeTest : public CordRingTestWithParam {
public:
static TestParams CreateTestParams() {
TestParams params;
params.emplace_back(InputShareMode::kPrivate);
params.emplace_back(InputShareMode::kShared);
params.emplace_back(InputShareMode::kSharedIndirect);
return params;
}
};
class CordRingBuildInputTest : public CordRingTestWithParam {
public:
static TestParams CreateTestParams() {
TestParams params;
for (bool refcount_is_one : {true, false}) {
for (bool with_capacity : {true, false}) {
for (InputShareMode share_mode : {kPrivate, kShared, kSharedIndirect}) {
TestParam param;
param.refcount_is_one = refcount_is_one;
param.with_capacity = with_capacity;
param.input_share_mode = share_mode;
params.push_back(param);
}
}
}
return params;
}
};
INSTANTIATE_TEST_CASE_P(WithParam, CordRingSubTest,
testing::ValuesIn(CordRingSubTest::CreateTestParams()),
TestParamToString);
INSTANTIATE_TEST_CASE_P(
WithParam, CordRingCreateTest,
testing::ValuesIn(CordRingCreateTest::CreateTestParams()),
TestParamToString);
INSTANTIATE_TEST_CASE_P(
WithParam, CordRingCreateFromTreeTest,
testing::ValuesIn(CordRingCreateFromTreeTest::CreateTestParams()),
TestParamToString);
INSTANTIATE_TEST_CASE_P(
WithParam, CordRingBuildTest,
testing::ValuesIn(CordRingBuildTest::CreateTestParams()),
TestParamToString);
INSTANTIATE_TEST_CASE_P(
WithParam, CordRingBuildInputTest,
testing::ValuesIn(CordRingBuildInputTest::CreateTestParams()),
TestParamToString);
TEST_P(CordRingCreateTest, CreateFromFlat) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
CordRepRing* result = NeedsUnref(CordRepRing::Create(MakeFlat(str1)));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(str1.size()));
EXPECT_THAT(ToFlats(result), ElementsAre(str1));
Unref(result);
}
TEST_P(CordRingCreateTest, CreateFromRing) {
CordRepRing* ring = RefIfShared(FromFlats(kFoxFlats));
CordRepRing* result = NeedsUnref(CordRepRing::Create(ring));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats));
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringRing) {
CordRepRing* ring = RefIfInputSharedIndirect(FromFlats(kFoxFlats));
CordRep* sub = RefIfInputShared(MakeSubstring(2, 11, ring));
CordRepRing* result = NeedsUnref(CordRepRing::Create(sub));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfInputPrivate(GetParam(), ring));
EXPECT_THAT(ToString(result), string_view(kFox).substr(2, 11));
UnrefIfInputSharedIndirect(ring);
UnrefIfInputShared(sub);
Unref(result);
}
TEST_F(CordRingTest, CreateWithIllegalExtraCapacity) {
CordRep* flat = NeedsUnref(MakeFlat("Hello world"));
#if defined(ABSL_HAVE_EXCEPTIONS)
try {
CordRepRing::Create(flat, CordRepRing::kMaxCapacity);
GTEST_FAIL() << "expected std::length_error exception";
} catch (const std::length_error&) {
}
#elif defined(GTEST_HAS_DEATH_TEST)
EXPECT_DEATH(CordRepRing::Create(flat, CordRepRing::kMaxCapacity), ".*");
#endif
Unref(flat);
}
TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfFlat) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
auto* flat = RefIfInputShared(MakeFlat(str1));
auto* child = RefIfInputSharedIndirect(MakeSubstring(4, 20, flat));
CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(20));
EXPECT_THAT(ToFlats(result), ElementsAre(str1.substr(4, 20)));
Unref(result);
UnrefIfInputShared(flat);
UnrefIfInputSharedIndirect(child);
}
TEST_P(CordRingCreateTest, CreateFromExternal) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
auto* child = RefIfInputShared(MakeExternal(str1));
CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(str1.size()));
EXPECT_THAT(ToFlats(result), ElementsAre(str1));
Unref(result);
UnrefIfInputShared(child);
}
TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfExternal) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
auto* external = RefIfInputShared(MakeExternal(str1));
auto* child = RefIfInputSharedIndirect(MakeSubstring(1, 24, external));
CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(24));
EXPECT_THAT(ToFlats(result), ElementsAre(str1.substr(1, 24)));
Unref(result);
UnrefIfInputShared(external);
UnrefIfInputSharedIndirect(child);
}
TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfLargeExternal) {
auto* external = RefIfInputShared(MakeFakeExternal(1 << 20));
auto str = not_a_string_view(external->base, 1 << 20)
.remove_prefix(1 << 19)
.remove_suffix(6);
auto* child =
RefIfInputSharedIndirect(MakeSubstring(1 << 19, (1 << 19) - 6, external));
CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(str.size()));
EXPECT_THAT(ToRawFlats(result), ElementsAre(str));
Unref(result);
UnrefIfInputShared(external);
UnrefIfInputSharedIndirect(child);
}
TEST_P(CordRingBuildInputTest, CreateFromConcat) {
CordRep* flats[] = {MakeFlat("abcdefgh"), MakeFlat("ijklm"),
MakeFlat("nopqrstuv"), MakeFlat("wxyz")};
auto* left = MakeConcat(RefIfInputSharedIndirect(flats[0]), flats[1]);
auto* right = MakeConcat(flats[2], RefIfInputSharedIndirect(flats[3]));
auto* concat = RefIfInputShared(MakeConcat(left, right));
CordRepRing* result = NeedsUnref(CordRepRing::Create(concat));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(26));
EXPECT_THAT(ToString(result), Eq(kAlphabet));
UnrefIfInputSharedIndirect(flats[0]);
UnrefIfInputSharedIndirect(flats[3]);
UnrefIfInputShared(concat);
Unref(result);
}
TEST_P(CordRingBuildInputTest, CreateFromSubstringConcat) {
for (size_t off = 0; off < 26; ++off) {
for (size_t len = 1; len < 26 - off; ++len) {
CordRep* flats[] = {MakeFlat("abcdefgh"), MakeFlat("ijklm"),
MakeFlat("nopqrstuv"), MakeFlat("wxyz")};
auto* left = MakeConcat(RefIfInputSharedIndirect(flats[0]), flats[1]);
auto* right = MakeConcat(flats[2], RefIfInputSharedIndirect(flats[3]));
auto* concat = MakeConcat(left, right);
auto* child = RefIfInputShared(MakeSubstring(off, len, concat));
CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
ASSERT_THAT(result, IsValidRingBuffer());
ASSERT_THAT(result->length, Eq(len));
ASSERT_THAT(ToString(result), string_view(kAlphabet).substr(off, len));
UnrefIfInputSharedIndirect(flats[0]);
UnrefIfInputSharedIndirect(flats[3]);
UnrefIfInputShared(child);
Unref(result);
}
}
}
TEST_P(CordRingCreateTest, Properties) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
CordRepRing* result = NeedsUnref(CordRepRing::Create(MakeFlat(str1), 120));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->head(), Eq(0));
EXPECT_THAT(result->tail(), Eq(1));
EXPECT_THAT(result->capacity(), Ge(120 + 1));
EXPECT_THAT(result->capacity(), Le(2 * 120 + 1));
EXPECT_THAT(result->entries(), Eq(1));
EXPECT_THAT(result->begin_pos(), Eq(0));
Unref(result);
}
TEST_P(CordRingCreateTest, EntryForNewFlat) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
CordRep* child = MakeFlat(str1);
CordRepRing* result = NeedsUnref(CordRepRing::Create(child, 120));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->entry_child(0), Eq(child));
EXPECT_THAT(result->entry_end_pos(0), Eq(str1.length()));
EXPECT_THAT(result->entry_data_offset(0), Eq(0));
Unref(result);
}
TEST_P(CordRingCreateTest, EntryForNewFlatSubstring) {
absl::string_view str1 = "1234567890abcdefghijklmnopqrstuvwxyz";
CordRep* child = MakeFlat(str1);
CordRep* substring = MakeSubstring(10, 26, child);
CordRepRing* result = NeedsUnref(CordRepRing::Create(substring, 1));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->entry_child(0), Eq(child));
EXPECT_THAT(result->entry_end_pos(0), Eq(26));
EXPECT_THAT(result->entry_data_offset(0), Eq(10));
Unref(result);
}
TEST_P(CordRingBuildTest, AppendFlat) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, MakeFlat(str2)));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2));
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildTest, PrependFlat) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, MakeFlat(str2)));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1));
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildTest, AppendString) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2));
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildTest, AppendStringHavingExtra) {
absl::string_view str1 = "1234";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRepRing* ring = CreateWithCapacity(MakeFlat(str1, 26), 0);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildTest, AppendStringHavingPartialExtra) {
absl::string_view str1 = "1234";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// Create flat with at least one extra byte. We don't expect to have sized
// alloc and capacity rounding to grant us enough to not make it partial.
auto* flat = MakeFlat(str1, 1);
size_t avail = flat->flat()->Capacity() - flat->length;
ASSERT_THAT(avail, Lt(str2.size())) << " adjust test for larger flats!";
// Construct the flats we do expect using all of `avail`.
absl::string_view str1a = str2.substr(0, avail);
absl::string_view str2a = str2.substr(avail);
CordRepRing* ring = CreateWithCapacity(flat, 1);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
if (GetParam().refcount_is_one) {
EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str1, str1a), str2a));
} else {
EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2));
}
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildTest, AppendStringHavingExtraInSubstring) {
absl::string_view str1 = "123456789_1234";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRep* flat = RemovePrefix(10, MakeFlat(str1, 26));
CordRepRing* ring = CreateWithCapacity(flat, 0);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
EXPECT_THAT(result->length, Eq(4 + str2.size()));
if (GetParam().refcount_is_one) {
EXPECT_THAT(ToFlats(result), ElementsAre(StrCat("1234", str2)));
} else {
EXPECT_THAT(ToFlats(result), ElementsAre("1234", str2));
}
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildTest, AppendStringHavingSharedExtra) {
absl::string_view str1 = "123456789_1234";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (int shared_type = 0; shared_type < 2; ++shared_type) {
SCOPED_TRACE(absl::StrCat("Shared extra type ", shared_type));
// Create a flat that is shared in some way.
CordRep* flat = nullptr;
CordRep* flat1 = nullptr;
if (shared_type == 0) {
// Shared flat
flat = CordRep::Ref(MakeFlat(str1.substr(10), 100));
} else if (shared_type == 1) {
// Shared flat inside private substring
flat1 = CordRep::Ref(MakeFlat(str1));
flat = RemovePrefix(10, flat1);
} else {
// Private flat inside shared substring
flat = CordRep::Ref(RemovePrefix(10, MakeFlat(str1, 100)));
}
CordRepRing* ring = CreateWithCapacity(flat, 1);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(result->length, Eq(4 + str2.size()));
EXPECT_THAT(ToFlats(result), ElementsAre("1234", str2));
UnrefIfShared(ring);
Unref(result);
CordRep::Unref(shared_type == 1 ? flat1 : flat);
}
}
TEST_P(CordRingBuildTest, AppendStringWithExtra) {
absl::string_view str1 = "1234";
absl::string_view str2 = "1234567890";
absl::string_view str3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2, 26));
result = CordRepRing::Append(result, str3);
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(str1.size() + str2.size() + str3.size()));
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre(str1, StrCat(str2, str3)));
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildTest, PrependString) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// Use external rep to avoid appending to first flat
CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
if (GetParam().with_capacity && GetParam().refcount_is_one) {
EXPECT_THAT(result, Eq(ring));
} else {
EXPECT_THAT(result, Ne(ring));
}
EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1));
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildTest, PrependStringHavingExtra) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz1234";
absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRep* flat = RemovePrefix(26, MakeFlat(str1));
CordRepRing* ring = CreateWithCapacity(flat, 0);
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
EXPECT_THAT(result->length, Eq(4 + str2.size()));
if (GetParam().refcount_is_one) {
EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str2, "1234")));
} else {
EXPECT_THAT(ToFlats(result), ElementsAre(str2, "1234"));
}
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildTest, PrependStringHavingSharedExtra) {
absl::string_view str1 = "123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
absl::string_view str2 = "abcdefghij";
absl::string_view str1a = str1.substr(10);
for (int shared_type = 1; shared_type < 2; ++shared_type) {
SCOPED_TRACE(absl::StrCat("Shared extra type ", shared_type));
// Create a flat that is shared in some way.
CordRep* flat = nullptr;
CordRep* flat1 = nullptr;
if (shared_type == 1) {
// Shared flat inside private substring
flat = RemovePrefix(10, flat1 = CordRep::Ref(MakeFlat(str1)));
} else {
// Private flat inside shared substring
flat = CordRep::Ref(RemovePrefix(10, MakeFlat(str1, 100)));
}
CordRepRing* ring = CreateWithCapacity(flat, 1);
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result->length, Eq(str1a.size() + str2.size()));
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1a));
UnrefIfShared(ring);
Unref(result);
CordRep::Unref(shared_type == 1 ? flat1 : flat);
}
}
TEST_P(CordRingBuildTest, PrependStringWithExtra) {
absl::string_view str1 = "1234";
absl::string_view str2 = "1234567890";
absl::string_view str3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2, 26));
ASSERT_THAT(result, IsValidRingBuffer());
result = CordRepRing::Prepend(result, str3);
EXPECT_THAT(result->length, Eq(str1.size() + str2.size() + str3.size()));
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str3, str2), str1));
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildTest, AppendPrependStringMix) {
const auto& flats = kFoxFlats;
CordRepRing* ring = CreateWithCapacity(MakeFlat(flats[4]), 8);
CordRepRing* result = ring;
for (int i = 1; i <= 4; ++i) {
result = CordRepRing::Prepend(result, flats[4 - i]);
result = CordRepRing::Append(result, flats[4 + i]);
}
UnrefIfShared(ring);
NeedsUnref(result);
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToString(result), kFox);
Unref(result);
}
TEST_P(CordRingBuildTest, AppendPrependStringMixWithExtra) {
const auto& flats = kFoxFlats;
CordRepRing* ring = CreateWithCapacity(MakeFlat(flats[4], 100), 8);
CordRepRing* result = ring;
for (int i = 1; i <= 4; ++i) {
result = CordRepRing::Prepend(result, flats[4 - i], 100);
result = CordRepRing::Append(result, flats[4 + i], 100);
}
NeedsUnref(result);
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
if (GetParam().refcount_is_one) {
EXPECT_THAT(ToFlats(result),
ElementsAre("The quick brown fox ", "jumps over the lazy dog"));
} else {
EXPECT_THAT(ToFlats(result), ElementsAre("The quick brown fox ", "jumps ",
"over the lazy dog"));
}
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildTest, AppendPrependStringMixWithPrependedExtra) {
const auto& flats = kFoxFlats;
CordRep* flat = MakeFlat(StrCat(std::string(50, '.'), flats[4]), 50);
CordRepRing* ring = CreateWithCapacity(RemovePrefix(50, flat), 0);
CordRepRing* result = ring;
for (int i = 1; i <= 4; ++i) {
result = CordRepRing::Prepend(result, flats[4 - i], 100);
result = CordRepRing::Append(result, flats[4 + i], 100);
}
result = NeedsUnref(result);
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
if (GetParam().refcount_is_one) {
EXPECT_THAT(ToFlats(result), ElementsAre(kFox));
} else {
EXPECT_THAT(ToFlats(result), ElementsAre("The quick brown fox ", "jumps ",
"over the lazy dog"));
}
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingSubTest, SubRing) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
string_view all = kFox;
for (size_t offset = 0; offset < all.size() - 1; ++offset) {
CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
CordRepRing* result = CordRepRing::SubRing(ring, offset, 0);
EXPECT_THAT(result, nullptr);
UnrefIfShared(ring);
for (size_t len = 1; len < all.size() - offset; ++len) {
ring = RefIfShared(FromFlats(flats, composition));
result = NeedsUnref(CordRepRing::SubRing(ring, offset, len));
ASSERT_THAT(result, IsValidRingBuffer());
ASSERT_THAT(result, EqIfPrivate(GetParam(), ring));
ASSERT_THAT(ToString(result), Eq(all.substr(offset, len)));
UnrefIfShared(ring);
Unref(result);
}
}
}
TEST_P(CordRingSubTest, SubRingFromLargeExternal) {
auto composition = RandomComposition();
std::string large_string(1 << 20, '.');
const char* flats[] = {
"abcdefghijklmnopqrstuvwxyz",
large_string.c_str(),
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
};
std::string buffer = absl::StrCat(flats[0], flats[1], flats[2]);
absl::string_view all = buffer;
for (size_t offset = 0; offset < 30; ++offset) {
CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
CordRepRing* result = CordRepRing::SubRing(ring, offset, 0);
EXPECT_THAT(result, nullptr);
UnrefIfShared(ring);
for (size_t len = all.size() - 30; len < all.size() - offset; ++len) {
ring = RefIfShared(FromFlats(flats, composition));
result = NeedsUnref(CordRepRing::SubRing(ring, offset, len));
ASSERT_THAT(result, IsValidRingBuffer());
ASSERT_THAT(result, EqIfPrivate(GetParam(), ring));
auto str = ToString(result);
ASSERT_THAT(str, SizeIs(len));
ASSERT_THAT(str, Eq(all.substr(offset, len)));
UnrefIfShared(ring);
Unref(result);
}
}
}
TEST_P(CordRingSubTest, RemovePrefix) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
string_view all = kFox;
CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
CordRepRing* result = CordRepRing::RemovePrefix(ring, all.size());
EXPECT_THAT(result, nullptr);
UnrefIfShared(ring);
for (size_t len = 1; len < all.size(); ++len) {
ring = RefIfShared(FromFlats(flats, composition));
result = NeedsUnref(CordRepRing::RemovePrefix(ring, len));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
EXPECT_THAT(ToString(result), Eq(all.substr(len)));
UnrefIfShared(ring);
Unref(result);
}
}
TEST_P(CordRingSubTest, RemovePrefixFromLargeExternal) {
CordRepExternal* external1 = MakeFakeExternal(1 << 20);
CordRepExternal* external2 = MakeFakeExternal(1 << 20);
CordRepRing* ring = CordRepRing::Create(external1, 1);
ring = CordRepRing::Append(ring, external2);
CordRepRing* result = NeedsUnref(CordRepRing::RemovePrefix(ring, 1 << 16));
EXPECT_THAT(
ToRawFlats(result),
ElementsAre(
not_a_string_view(external1->base, 1 << 20).remove_prefix(1 << 16),
not_a_string_view(external2->base, 1 << 20)));
Unref(result);
}
TEST_P(CordRingSubTest, RemoveSuffix) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
string_view all = kFox;
CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
CordRepRing* result = CordRepRing::RemoveSuffix(ring, all.size());
EXPECT_THAT(result, nullptr);
UnrefIfShared(ring);
for (size_t len = 1; len < all.size(); ++len) {
ring = RefIfShared(FromFlats(flats, composition));
result = NeedsUnref(CordRepRing::RemoveSuffix(ring, len));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
EXPECT_THAT(ToString(result), Eq(all.substr(0, all.size() - len)));
UnrefIfShared(ring);
Unref(result);
}
}
TEST_P(CordRingSubTest, AppendRing) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats).subspan(1);
CordRepRing* ring = CreateWithCapacity(MakeFlat(kFoxFlats[0]), flats.size());
CordRepRing* child = FromFlats(flats, composition);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, child));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats));
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildInputTest, AppendRingWithFlatOffset) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
CordRep* stripped = RemovePrefix(10, child);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("Head", "brown ", "fox ", "jumps ",
"over ", "the ", "lazy ", "dog"));
UnrefIfInputSharedIndirect(child);
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildInputTest, AppendRingWithBrokenOffset) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
CordRep* stripped = RemovePrefix(21, child);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result),
ElementsAre("Head", "umps ", "over ", "the ", "lazy ", "dog"));
UnrefIfInputSharedIndirect(child);
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildInputTest, AppendRingWithFlatLength) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
CordRep* stripped = RemoveSuffix(8, child);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("Head", "The ", "quick ", "brown ",
"fox ", "jumps ", "over ", "the "));
UnrefIfInputSharedIndirect(child);
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildTest, AppendRingWithBrokenFlatLength) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
CordRep* stripped = RemoveSuffix(15, child);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("Head", "The ", "quick ", "brown ",
"fox ", "jumps ", "ov"));
UnrefIfInputSharedIndirect(child);
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildTest, AppendRingMiddlePiece) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
CordRep* stripped = MakeSubstring(7, child->length - 27, child);
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result),
ElementsAre("Head", "ck ", "brown ", "fox ", "jum"));
UnrefIfInputSharedIndirect(child);
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildTest, AppendRingSinglePiece) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
CordRep* stripped = RefIfInputShared(MakeSubstring(11, 3, child));
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("Head", "row"));
UnrefIfInputSharedIndirect(child);
UnrefIfInputShared(stripped);
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildInputTest, AppendRingSinglePieceWithPrefix) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
size_t extra_capacity = 1 + (GetParam().with_capacity ? flats.size() : 0);
CordRepRing* ring = CordRepRing::Create(MakeFlat("Head"), extra_capacity);
ring->SetCapacityForTesting(1 + extra_capacity);
ring = RefIfShared(CordRepRing::Prepend(ring, MakeFlat("Prepend")));
assert(ring->IsValid(std::cout));
CordRepRing* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
CordRep* stripped = RefIfInputShared(MakeSubstring(11, 3, child));
CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("Prepend", "Head", "row"));
UnrefIfInputSharedIndirect(child);
UnrefIfInputShared(stripped);
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildInputTest, PrependRing) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto fox = MakeSpan(kFoxFlats);
auto flats = MakeSpan(fox).subspan(0, fox.size() - 1);
CordRepRing* ring = CreateWithCapacity(MakeFlat(fox.back()), flats.size());
CordRepRing* child = RefIfInputShared(FromFlats(flats, composition));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, child));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats));
UnrefIfInputShared(child);
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildInputTest, PrependRingWithFlatOffset) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
CordRep* child = RefIfInputShared(FromFlats(flats, composition));
CordRep* stripped = RefIfInputSharedIndirect(RemovePrefix(10, child));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("brown ", "fox ", "jumps ", "over ",
"the ", "lazy ", "dog", "Tail"));
UnrefIfInputShared(child);
UnrefIfInputSharedIndirect(stripped);
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildInputTest, PrependRingWithBrokenOffset) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
CordRep* child = RefIfInputShared(FromFlats(flats, composition));
CordRep* stripped = RefIfInputSharedIndirect(RemovePrefix(21, child));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result),
ElementsAre("umps ", "over ", "the ", "lazy ", "dog", "Tail"));
UnrefIfInputShared(child);
UnrefIfInputSharedIndirect(stripped);
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildInputTest, PrependRingWithFlatLength) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
CordRep* child = RefIfInputShared(FromFlats(flats, composition));
CordRep* stripped = RefIfInputSharedIndirect(RemoveSuffix(8, child));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("The ", "quick ", "brown ", "fox ",
"jumps ", "over ", "the ", "Tail"));
UnrefIfShared(ring);
UnrefIfInputShared(child);
UnrefIfInputSharedIndirect(stripped);
Unref(result);
}
TEST_P(CordRingBuildInputTest, PrependRingWithBrokenFlatLength) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
CordRep* child = RefIfInputShared(FromFlats(flats, composition));
CordRep* stripped = RefIfInputSharedIndirect(RemoveSuffix(15, child));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("The ", "quick ", "brown ", "fox ",
"jumps ", "ov", "Tail"));
UnrefIfInputShared(child);
UnrefIfInputSharedIndirect(stripped);
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildInputTest, PrependRingMiddlePiece) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
CordRep* child = RefIfInputShared(FromFlats(flats, composition));
CordRep* stripped =
RefIfInputSharedIndirect(MakeSubstring(7, child->length - 27, child));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result),
ElementsAre("ck ", "brown ", "fox ", "jum", "Tail"));
UnrefIfInputShared(child);
UnrefIfInputSharedIndirect(stripped);
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildInputTest, PrependRingSinglePiece) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
CordRep* child = RefIfInputShared(FromFlats(flats, composition));
CordRep* stripped = RefIfInputSharedIndirect(MakeSubstring(11, 3, child));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("row", "Tail"));
UnrefIfInputShared(child);
UnrefIfInputSharedIndirect(stripped);
UnrefIfShared(ring);
Unref(result);
}
TEST_P(CordRingBuildInputTest, PrependRingSinglePieceWithPrefix) {
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
auto flats = MakeSpan(kFoxFlats);
size_t extra_capacity = 1 + (GetParam().with_capacity ? flats.size() : 0);
CordRepRing* ring = CordRepRing::Create(MakeFlat("Tail"), extra_capacity);
ring->SetCapacityForTesting(1 + extra_capacity);
ring = RefIfShared(CordRepRing::Prepend(ring, MakeFlat("Prepend")));
CordRep* child = RefIfInputShared(FromFlats(flats, composition));
CordRep* stripped = RefIfInputSharedIndirect(MakeSubstring(11, 3, child));
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
ASSERT_THAT(result, IsValidRingBuffer());
EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
EXPECT_THAT(ToFlats(result), ElementsAre("row", "Prepend", "Tail"));
UnrefIfInputShared(child);
UnrefIfInputSharedIndirect(stripped);
UnrefIfShared(ring);
Unref(result);
}
TEST_F(CordRingTest, Find) {
constexpr const char* flats[] = {
"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
"KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
"+-=", "[]\\{}|;':", ",/<>?", "."};
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
std::string value = ToString(ring);
for (int i = 0; i < value.length(); ++i) {
CordRepRing::Position found = ring->Find(i);
auto data = ring->entry_data(found.index);
ASSERT_THAT(found.offset, Lt(data.length()));
ASSERT_THAT(data[found.offset], Eq(value[i]));
}
Unref(ring);
}
TEST_F(CordRingTest, FindWithHint) {
constexpr const char* flats[] = {
"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
"KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
"+-=", "[]\\{}|;':", ",/<>?", "."};
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
std::string value = ToString(ring);
#if defined(GTEST_HAS_DEATH_TEST)
// Test hint beyond valid position
index_type head = ring->head();
EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head), 0), ".*");
EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head), 9), ".*");
EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head, 3), 24), ".*");
#endif
int flat_pos = 0;
size_t flat_offset = 0;
for (auto sflat : flats) {
string_view flat(sflat);
for (int offset = 0; offset < flat.length(); ++offset) {
for (int start = 0; start <= flat_pos; ++start) {
index_type hint = ring->advance(ring->head(), start);
CordRepRing::Position found = ring->Find(hint, flat_offset + offset);
ASSERT_THAT(found.index, Eq(ring->advance(ring->head(), flat_pos)));
ASSERT_THAT(found.offset, Eq(offset));
}
}
++flat_pos;
flat_offset += flat.length();
}
Unref(ring);
}
TEST_F(CordRingTest, FindInLargeRing) {
constexpr const char* flats[] = {
"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
"KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
"+-=", "[]\\{}|;':", ",/<>?", "."};
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
CordRepRing* ring = FromFlats(flats, composition);
for (int i = 0; i < 13; ++i) {
ring = CordRepRing::Append(ring, FromFlats(flats, composition));
}
NeedsUnref(ring);
std::string value = ToString(ring);
for (int i = 0; i < value.length(); ++i) {
CordRepRing::Position pos = ring->Find(i);
auto data = ring->entry_data(pos.index);
ASSERT_THAT(pos.offset, Lt(data.length()));
ASSERT_THAT(data[pos.offset], Eq(value[i]));
}
Unref(ring);
}
TEST_F(CordRingTest, FindTail) {
constexpr const char* flats[] = {
"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
"KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
"+-=", "[]\\{}|;':", ",/<>?", "."};
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
std::string value = ToString(ring);
for (int i = 0; i < value.length(); ++i) {
CordRepRing::Position pos = ring->FindTail(i + 1);
auto data = ring->entry_data(ring->retreat(pos.index));
ASSERT_THAT(pos.offset, Lt(data.length()));
ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i]));
}
Unref(ring);
}
TEST_F(CordRingTest, FindTailWithHint) {
constexpr const char* flats[] = {
"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
"KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
"+-=", "[]\\{}|;':", ",/<>?", "."};
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
std::string value = ToString(ring);
// Test hint beyond valid position
#if defined(GTEST_HAS_DEATH_TEST)
index_type head = ring->head();
EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head), 1), ".*");
EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head), 10), ".*");
EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head, 3), 26), ".*");
#endif
for (int i = 0; i < value.length(); ++i) {
CordRepRing::Position pos = ring->FindTail(i + 1);
auto data = ring->entry_data(ring->retreat(pos.index));
ASSERT_THAT(pos.offset, Lt(data.length()));
ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i]));
}
Unref(ring);
}
TEST_F(CordRingTest, FindTailInLargeRing) {
constexpr const char* flats[] = {
"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
"KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
"+-=", "[]\\{}|;':", ",/<>?", "."};
auto composition = RandomComposition();
SCOPED_TRACE(ToString(composition));
CordRepRing* ring = FromFlats(flats, composition);
for (int i = 0; i < 13; ++i) {
ring = CordRepRing::Append(ring, FromFlats(flats, composition));
}
NeedsUnref(ring);
std::string value = ToString(ring);
for (int i = 0; i < value.length(); ++i) {
CordRepRing::Position pos = ring->FindTail(i + 1);
auto data = ring->entry_data(ring->retreat(pos.index));
ASSERT_THAT(pos.offset, Lt(data.length()));
ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i]));
}
Unref(ring);
}
TEST_F(CordRingTest, GetCharacter) {
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = CordRepRing::Create(MakeFlat("Tail"), flats.size());
CordRep* child = FromFlats(flats, kAppend);
CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, child));
std::string value = ToString(result);
for (int i = 0; i < value.length(); ++i) {
ASSERT_THAT(result->GetCharacter(i), Eq(value[i]));
}
Unref(result);
}
TEST_F(CordRingTest, GetCharacterWithSubstring) {
absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
auto* child = MakeSubstring(4, 20, MakeFlat(str1));
CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
ASSERT_THAT(result, IsValidRingBuffer());
std::string value = ToString(result);
for (int i = 0; i < value.length(); ++i) {
ASSERT_THAT(result->GetCharacter(i), Eq(value[i]));
}
Unref(result);
}
TEST_F(CordRingTest, Dump) {
std::stringstream ss;
auto flats = MakeSpan(kFoxFlats);
CordRepRing* ring = NeedsUnref(FromFlats(flats, kPrepend));
ss << *ring;
Unref(ring);
}
} // namespace
ABSL_NAMESPACE_END
} // namespace absl