summaryrefslogtreecommitdiff
path: root/absl/strings/internal
diff options
context:
space:
mode:
Diffstat (limited to 'absl/strings/internal')
-rw-r--r--absl/strings/internal/cord_rep_btree_reader.cc68
-rw-r--r--absl/strings/internal/cord_rep_btree_reader.h219
-rw-r--r--absl/strings/internal/cord_rep_btree_reader_test.cc285
3 files changed, 572 insertions, 0 deletions
diff --git a/absl/strings/internal/cord_rep_btree_reader.cc b/absl/strings/internal/cord_rep_btree_reader.cc
new file mode 100644
index 00000000..3ba43144
--- /dev/null
+++ b/absl/strings/internal/cord_rep_btree_reader.cc
@@ -0,0 +1,68 @@
+// Copyright 2021 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/internal/cord_rep_btree_reader.h"
+
+#include <cassert>
+
+#include "absl/base/config.h"
+#include "absl/strings/internal/cord_internal.h"
+#include "absl/strings/internal/cord_rep_btree.h"
+#include "absl/strings/internal/cord_rep_btree_navigator.h"
+#include "absl/strings/internal/cord_rep_flat.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace cord_internal {
+
+absl::string_view CordRepBtreeReader::Read(size_t n, size_t chunk_size,
+ CordRep*& tree) {
+ assert(chunk_size <= navigator_.Current()->length);
+
+ // If chunk_size is non-zero, we need to start inside last returned edge.
+ // Else we start reading at the next data edge of the tree.
+ CordRep* edge = chunk_size ? navigator_.Current() : navigator_.Next();
+ const size_t offset = chunk_size ? edge->length - chunk_size : 0;
+
+ // Read the sub tree and verify we got what we wanted.
+ ReadResult result = navigator_.Read(offset, n);
+ tree = result.tree;
+
+ // If the data returned in `tree` was covered entirely by `chunk_size`, i.e.,
+ // read from the 'previous' edge, we did not consume any additional data, and
+ // can directly return the substring into the current data edge as the next
+ // chunk. We can easily establish from the above code that `navigator_.Next()`
+ // has not been called as that requires `chunk_size` to be zero.
+ if (n < chunk_size) return CordRepBtree::EdgeData(edge).substr(result.n);
+
+ // The amount of data taken from the last edge is `chunk_size` and `result.n`
+ // contains the offset into the current edge trailing the read data (which can
+ // be 0). As the call to `navigator_.Read()` could have consumed all remaining
+ // data, calling `navigator_.Current()` is not safe before checking if we
+ // already consumed all remaining data.
+ const size_t consumed_by_read = n - chunk_size - result.n;
+ if (consumed_ + consumed_by_read >= length()) {
+ consumed_ = length();
+ return {};
+ }
+
+ // We did not read all data, return remaining data from current edge.
+ edge = navigator_.Current();
+ consumed_ += consumed_by_read + edge->length;
+ return CordRepBtree::EdgeData(edge).substr(result.n);
+}
+
+} // namespace cord_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/strings/internal/cord_rep_btree_reader.h b/absl/strings/internal/cord_rep_btree_reader.h
new file mode 100644
index 00000000..c19fa43d
--- /dev/null
+++ b/absl/strings/internal/cord_rep_btree_reader.h
@@ -0,0 +1,219 @@
+// Copyright 2021 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.
+
+#ifndef ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_READER_H_
+#define ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_READER_H_
+
+#include <cassert>
+
+#include "absl/base/config.h"
+#include "absl/strings/internal/cord_internal.h"
+#include "absl/strings/internal/cord_rep_btree.h"
+#include "absl/strings/internal/cord_rep_btree_navigator.h"
+#include "absl/strings/internal/cord_rep_flat.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace cord_internal {
+
+// CordRepBtreeReader implements logic to iterate over cord btrees.
+// References to the underlying data are returned as absl::string_view values.
+// The most typical use case is a forward only iteration over tree data.
+// The class also provides `Skip()`, `Seek()` and `Read()` methods similar to
+// CordRepBtreeNavigator that allow more advanced navigation. The class provides
+// a `consumed` property which contains the end offset of the chunk last
+// returned to the user which is useful in cord iteration logic.
+//
+// Example: iterate over all data inside a cord btree:
+//
+// CordRepBtreeReader reader;
+// for (string_view sv = reader.Init(tree); !sv.Empty(); sv = sv.Next()) {
+// DoSomethingWithDataIn(sv);
+// }
+//
+// All navigation methods always return the next 'chunk' of data. The class
+// assumes that all data is directly 'consumed' by the caller. For example:
+// invoking `Skip()` will skip the desired number of bytes, and directly
+// read and return the next chunk of data directly after the skipped bytes.
+//
+// Example: iterate over all data inside a btree skipping the first 100 bytes:
+//
+// CordRepBtreeReader reader;
+// absl::string_view sv = reader.Init(tree);
+// if (sv.length() > 100) {
+// sv.RemovePrefix(100);
+// } else {
+// sv = reader.Skip(100 - sv.length());
+// }
+// while (!sv.empty()) {
+// DoSomethingWithDataIn(sv);
+// absl::string_view sv = reader.Next();
+// }
+//
+// It is important to notice that `consumed` represents the end position of the
+// last data edge returned to the caller, not the cumulative data returned to
+// the caller which can be less in cases of skipping or seeking over data.
+//
+// For example, consider a cord btree with five data edges: "abc", "def", "ghi",
+// "jkl" and "mno":
+//
+// absl::string_view sv;
+// CordRepBtreeReader reader;
+//
+// sv = reader.Init(tree); // sv = "abc", reader.consumed() = 3
+// sv = reader.Skip(4); // sv = "hi", reader.consumed() = 9
+// sv = reader.Skip(2); // sv = "l", reader.consumed() = 12
+// sv = reader.Next(); // sv = "mno", reader.consumed() = 15
+//
+// In the above example, `reader.consumed()` reflects the data edges iterated
+// over or skipped by the reader, not the amount of data 'consumed' by the
+// caller.
+class CordRepBtreeReader {
+ public:
+ using ReadResult = CordRepBtreeNavigator::ReadResult;
+ using Position = CordRepBtreeNavigator::Position;
+
+ // Returns true if this instance is not empty.
+ explicit operator bool() const { return navigator_.btree() != nullptr; }
+
+ // Returns the tree referenced by this instance or nullptr if empty.
+ CordRepBtree* btree() const { return navigator_.btree(); }
+
+ // Returns the current data edge inside the referenced btree.
+ // Requires that the current instance is not empty.
+ CordRep* node() const { return navigator_.Current(); }
+
+ // Returns the length of the referenced tree.
+ // Requires that the current instance is not empty.
+ size_t length() const;
+
+ // Returns the end offset of the last navigated to chunk, which represents the
+ // total bytes 'consumed' relative to the start of the tree. The returned
+ // value is never zero. For example, initializing a reader with a tree with a
+ // first data edge of 19 bytes will return `consumed() = 19`. See also the
+ // class comments on the meaning of `consumed`.
+ // Requires that the current instance is not empty.
+ size_t consumed() const;
+
+ // Resets this instance to an empty value.
+ void Reset() { navigator_.Reset(); }
+
+ // Initializes this instance with `tree`. `tree` must not be null.
+ // Returns a reference to the first data edge of the provided tree.
+ absl::string_view Init(CordRepBtree* tree);
+
+ // Navigates to and returns the next data edge of the referenced tree.
+ // Returns an empty string_view if an attempt is made to read beyond the end
+ // of the tree, i.e.: if `remaining()` is zero indicating an EOF condition.
+ // Requires that the current instance is not empty.
+ absl::string_view Next();
+
+ // Skips the provided amount of bytes and returns a reference to the data
+ // directly following the skipped bytes.
+ absl::string_view Skip(size_t skip);
+
+ // Reads `n` bytes into `tree`.
+ // If `chunk_size` is zero, starts reading at the next data edge. If
+ // `chunk_size` is non zero, the read starts at the last `chunk_size` bytes of
+ // the last returned data edge. Effectively, this means that the read starts
+ // at offset `consumed() - chunk_size`.
+ // Requires that `chunk_size` is less than or equal to the length of the
+ // last returned data edge. The purpose of `chunk_size` is to simplify code
+ // partially consuming a returned chunk and wanting to include the remaining
+ // bytes in the Read call. For example, the below code will read 1000 bytes of
+ // data into a cord tree if the first chunk starts with "big:":
+ //
+ // CordRepBtreeReader reader;
+ // absl::string_view sv = reader.Init(tree);
+ // if (absl::StartsWith(sv, "big:")) {
+ // CordRepBtree tree;
+ // sv = reader.Read(1000, sv.size() - 4 /* "big:" */, &tree);
+ // }
+ //
+ // This method will return an empty string view if all remaining data was
+ // read. If `n` exceeded the amount of remaining data this function will
+ // return an empty string view and `tree` will be set to nullptr.
+ // In both cases, `consumed` will be set to `length`.
+ absl::string_view Read(size_t n, size_t chunk_size, CordRep*& tree);
+
+ // Navigates to the chunk at offset `offset`.
+ // Returns a reference into the navigated to chunk, adjusted for the relative
+ // position of `offset` into that chunk. For example, calling `Seek(13)` on a
+ // cord tree containing 2 chunks of 10 and 20 bytes respectively will return
+ // a string view into the second chunk starting at offset 3 with a size of 17.
+ // Returns an empty string view if `offset` is equal to or greater than the
+ // length of the referenced tree.
+ absl::string_view Seek(size_t offset);
+
+ private:
+ size_t consumed_;
+ CordRepBtreeNavigator navigator_;
+};
+
+inline size_t CordRepBtreeReader::length() const {
+ assert(btree() != nullptr);
+ return btree()->length;
+}
+
+inline size_t CordRepBtreeReader::consumed() const {
+ assert(btree() != nullptr);
+ return consumed_;
+}
+
+inline absl::string_view CordRepBtreeReader::Init(CordRepBtree* tree) {
+ assert(tree != nullptr);
+ const CordRep* edge = navigator_.InitFirst(tree);
+ consumed_ = edge->length;
+ return CordRepBtree::EdgeData(edge);
+}
+
+inline absl::string_view CordRepBtreeReader::Next() {
+ assert(consumed() < length());
+ const CordRep* edge = navigator_.Next();
+ assert(edge != nullptr);
+ consumed_ += edge->length;
+ return CordRepBtree::EdgeData(edge);
+}
+
+inline absl::string_view CordRepBtreeReader::Skip(size_t skip) {
+ // As we are always positioned on the last 'consumed' edge, we
+ // need to skip the current edge as well as `skip`.
+ const size_t edge_length = navigator_.Current()->length;
+ CordRepBtreeNavigator::Position pos = navigator_.Skip(skip + edge_length);
+ if (ABSL_PREDICT_FALSE(pos.edge == nullptr)) {
+ consumed_ = length();
+ return {};
+ }
+ // The combined length of all edges skipped before `pos.edge` is `skip -
+ // pos.offset`, all of which are 'consumed', as well as the current edge.
+ consumed_ += skip - pos.offset + pos.edge->length;
+ return CordRepBtree::EdgeData(pos.edge).substr(pos.offset);
+}
+
+inline absl::string_view CordRepBtreeReader::Seek(size_t offset) {
+ const CordRepBtreeNavigator::Position pos = navigator_.Seek(offset);
+ if (ABSL_PREDICT_FALSE(pos.edge == nullptr)) {
+ consumed_ = length();
+ return {};
+ }
+ absl::string_view chunk = CordRepBtree::EdgeData(pos.edge).substr(pos.offset);
+ consumed_ = offset + chunk.length();
+ return chunk;
+}
+
+} // namespace cord_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_READER_H_
diff --git a/absl/strings/internal/cord_rep_btree_reader_test.cc b/absl/strings/internal/cord_rep_btree_reader_test.cc
new file mode 100644
index 00000000..44d3365f
--- /dev/null
+++ b/absl/strings/internal/cord_rep_btree_reader_test.cc
@@ -0,0 +1,285 @@
+// Copyright 2021 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/internal/cord_rep_btree_reader.h"
+
+#include <iostream>
+#include <random>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/strings/cord.h"
+#include "absl/strings/internal/cord_internal.h"
+#include "absl/strings/internal/cord_rep_btree.h"
+#include "absl/strings/internal/cord_rep_test_util.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace cord_internal {
+namespace {
+
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::testing::Ne;
+using ::testing::Not;
+
+using ::absl::cordrep_testing::CordRepBtreeFromFlats;
+using ::absl::cordrep_testing::MakeFlat;
+using ::absl::cordrep_testing::CordToString;
+using ::absl::cordrep_testing::CreateFlatsFromString;
+using ::absl::cordrep_testing::CreateRandomString;
+
+using ReadResult = CordRepBtreeReader::ReadResult;
+
+TEST(CordRepBtreeReaderTest, Next) {
+ constexpr size_t kChars = 3;
+ const size_t cap = CordRepBtree::kMaxCapacity;
+ int counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17};
+
+ for (int count : counts) {
+ std::string data = CreateRandomString(count * kChars);
+ std::vector<CordRep*> flats = CreateFlatsFromString(data, kChars);
+ CordRepBtree* node = CordRepBtreeFromFlats(flats);
+
+ CordRepBtreeReader reader;
+ absl::string_view chunk = reader.Init(node);
+ EXPECT_THAT(chunk, Eq(data.substr(0, chunk.length())));
+
+ size_t consumed = chunk.length();
+ EXPECT_THAT(reader.consumed(), Eq(consumed));
+
+ while (consumed < data.length()) {
+ chunk = reader.Next();
+ EXPECT_THAT(chunk, Eq(data.substr(consumed, chunk.length())));
+
+ consumed += chunk.length();
+ EXPECT_THAT(reader.consumed(), Eq(consumed));
+ }
+
+ EXPECT_THAT(consumed, Eq(data.length()));
+ EXPECT_THAT(reader.consumed(), Eq(data.length()));
+
+ CordRep::Unref(node);
+ }
+}
+
+TEST(CordRepBtreeReaderTest, Skip) {
+ constexpr size_t kChars = 3;
+ const size_t cap = CordRepBtree::kMaxCapacity;
+ int counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17};
+
+ for (int count : counts) {
+ std::string data = CreateRandomString(count * kChars);
+ std::vector<CordRep*> flats = CreateFlatsFromString(data, kChars);
+ CordRepBtree* node = CordRepBtreeFromFlats(flats);
+
+ for (size_t skip1 = 0; skip1 < data.length() - kChars; ++skip1) {
+ for (size_t skip2 = 0; skip2 < data.length() - kChars; ++skip2) {
+ CordRepBtreeReader reader;
+ absl::string_view chunk = reader.Init(node);
+ size_t consumed = chunk.length();
+
+ chunk = reader.Skip(skip1);
+ ASSERT_THAT(chunk, Eq(data.substr(consumed + skip1, chunk.length())));
+ consumed += chunk.length() + skip1;
+ ASSERT_THAT(reader.consumed(), Eq(consumed));
+
+ if (consumed >= data.length()) continue;
+
+ size_t skip = std::min(data.length() - consumed - 1, skip2);
+ chunk = reader.Skip(skip);
+ ASSERT_THAT(chunk, Eq(data.substr(consumed + skip, chunk.length())));
+ }
+ }
+
+ CordRep::Unref(node);
+ }
+}
+
+TEST(CordRepBtreeReaderTest, SkipBeyondLength) {
+ CordRepBtree* tree = CordRepBtree::Create(MakeFlat("abc"));
+ tree = CordRepBtree::Append(tree, MakeFlat("def"));
+ CordRepBtreeReader reader;
+ reader.Init(tree);
+ EXPECT_THAT(reader.Skip(100), IsEmpty());
+ EXPECT_THAT(reader.consumed(), Eq(6));
+ CordRep::Unref(tree);
+}
+
+TEST(CordRepBtreeReaderTest, Seek) {
+ constexpr size_t kChars = 3;
+ const size_t cap = CordRepBtree::kMaxCapacity;
+ int counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17};
+
+ for (int count : counts) {
+ std::string data = CreateRandomString(count * kChars);
+ std::vector<CordRep*> flats = CreateFlatsFromString(data, kChars);
+ CordRepBtree* node = CordRepBtreeFromFlats(flats);
+
+ for (size_t seek = 0; seek < data.length() - 1; ++seek) {
+ CordRepBtreeReader reader;
+ reader.Init(node);
+ absl::string_view chunk = reader.Seek(seek);
+ ASSERT_THAT(chunk, Not(IsEmpty()));
+ ASSERT_THAT(chunk, Eq(data.substr(seek, chunk.length())));
+ ASSERT_THAT(reader.consumed(), Eq(seek + chunk.length()));
+ }
+
+ CordRep::Unref(node);
+ }
+}
+
+TEST(CordRepBtreeReaderTest, SeekBeyondLength) {
+ CordRepBtree* tree = CordRepBtree::Create(MakeFlat("abc"));
+ tree = CordRepBtree::Append(tree, MakeFlat("def"));
+ CordRepBtreeReader reader;
+ reader.Init(tree);
+ EXPECT_THAT(reader.Seek(6), IsEmpty());
+ EXPECT_THAT(reader.consumed(), Eq(6));
+ EXPECT_THAT(reader.Seek(100), IsEmpty());
+ EXPECT_THAT(reader.consumed(), Eq(6));
+ CordRep::Unref(tree);
+}
+
+TEST(CordRepBtreeReaderTest, Read) {
+ std::string data = "abcdefghijklmno";
+ std::vector<CordRep*> flats = CreateFlatsFromString(data, 5);
+ CordRepBtree* node = CordRepBtreeFromFlats(flats);
+
+ CordRep* tree;
+ CordRepBtreeReader reader;
+ absl::string_view chunk;
+
+ // Read zero bytes
+ chunk = reader.Init(node);
+ chunk = reader.Read(0, chunk.length(), tree);
+ EXPECT_THAT(tree, Eq(nullptr));
+ EXPECT_THAT(chunk, Eq("abcde"));
+ EXPECT_THAT(reader.consumed(), Eq(5));
+ EXPECT_THAT(reader.Next(), Eq("fghij"));
+
+ // Read in full
+ chunk = reader.Init(node);
+ chunk = reader.Read(15, chunk.length(), tree);
+ EXPECT_THAT(tree, Ne(nullptr));
+ EXPECT_THAT(CordToString(tree), Eq("abcdefghijklmno"));
+ EXPECT_THAT(chunk, Eq(""));
+ EXPECT_THAT(reader.consumed(), Eq(15));
+ CordRep::Unref(tree);
+
+ // Read < chunk bytes
+ chunk = reader.Init(node);
+ chunk = reader.Read(3, chunk.length(), tree);
+ ASSERT_THAT(tree, Ne(nullptr));
+ EXPECT_THAT(CordToString(tree), Eq("abc"));
+ EXPECT_THAT(chunk, Eq("de"));
+ EXPECT_THAT(reader.consumed(), Eq(5));
+ EXPECT_THAT(reader.Next(), Eq("fghij"));
+ CordRep::Unref(tree);
+
+ // Read < chunk bytes at offset
+ chunk = reader.Init(node);
+ chunk = reader.Read(2, chunk.length() - 2, tree);
+ ASSERT_THAT(tree, Ne(nullptr));
+ EXPECT_THAT(CordToString(tree), Eq("cd"));
+ EXPECT_THAT(chunk, Eq("e"));
+ EXPECT_THAT(reader.consumed(), Eq(5));
+ EXPECT_THAT(reader.Next(), Eq("fghij"));
+ CordRep::Unref(tree);
+
+ // Read from consumed chunk
+ chunk = reader.Init(node);
+ chunk = reader.Read(3, 0, tree);
+ ASSERT_THAT(tree, Ne(nullptr));
+ EXPECT_THAT(CordToString(tree), Eq("fgh"));
+ EXPECT_THAT(chunk, Eq("ij"));
+ EXPECT_THAT(reader.consumed(), Eq(10));
+ EXPECT_THAT(reader.Next(), Eq("klmno"));
+ CordRep::Unref(tree);
+
+ // Read across chunks
+ chunk = reader.Init(node);
+ chunk = reader.Read(12, chunk.length() - 2, tree);
+ ASSERT_THAT(tree, Ne(nullptr));
+ EXPECT_THAT(CordToString(tree), Eq("cdefghijklmn"));
+ EXPECT_THAT(chunk, Eq("o"));
+ EXPECT_THAT(reader.consumed(), Eq(15));
+ CordRep::Unref(tree);
+
+ // Read across chunks landing on exact edge boundary
+ chunk = reader.Init(node);
+ chunk = reader.Read(10 - 2, chunk.length() - 2, tree);
+ ASSERT_THAT(tree, Ne(nullptr));
+ EXPECT_THAT(CordToString(tree), Eq("cdefghij"));
+ EXPECT_THAT(chunk, Eq("klmno"));
+ EXPECT_THAT(reader.consumed(), Eq(15));
+ CordRep::Unref(tree);
+
+ CordRep::Unref(node);
+}
+
+TEST(CordRepBtreeReaderTest, ReadExhaustive) {
+ constexpr size_t kChars = 3;
+ const size_t cap = CordRepBtree::kMaxCapacity;
+ int counts[] = {1, 2, cap, cap * cap + 1, cap * cap * cap * 2 + 17};
+
+ for (int count : counts) {
+ std::string data = CreateRandomString(count * kChars);
+ std::vector<CordRep*> flats = CreateFlatsFromString(data, kChars);
+ CordRepBtree* node = CordRepBtreeFromFlats(flats);
+
+ for (size_t read_size : {kChars - 1, kChars, kChars + 7, cap * cap}) {
+ CordRepBtreeReader reader;
+ absl::string_view chunk = reader.Init(node);
+
+ // `consumed` tracks the end of last consumed chunk which is the start of
+ // the next chunk: we always read with `chunk_size = chunk.length()`.
+ size_t consumed = 0;
+ size_t remaining = data.length();
+ while (remaining > 0) {
+ CordRep* tree;
+ size_t n = (std::min)(remaining, read_size);
+ chunk = reader.Read(n, chunk.length(), tree);
+ EXPECT_THAT(tree, Ne(nullptr));
+ if (tree) {
+ EXPECT_THAT(CordToString(tree), Eq(data.substr(consumed, n)));
+ CordRep::Unref(tree);
+ }
+
+ consumed += n;
+ remaining -= n;
+ EXPECT_THAT(reader.consumed(), Eq(consumed + chunk.length()));
+
+ if (remaining > 0) {
+ ASSERT_FALSE(chunk.empty());
+ ASSERT_THAT(chunk, Eq(data.substr(consumed, chunk.length())));
+ } else {
+ ASSERT_TRUE(chunk.empty()) << chunk;
+ }
+ }
+ }
+
+ CordRep::Unref(node);
+ }
+}
+
+} // namespace
+} // namespace cord_internal
+ABSL_NAMESPACE_END
+} // namespace absl