| // 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_navigator.h" |
| |
| #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/internal/cord_internal.h" |
| #include "absl/strings/internal/cord_rep_btree.h" |
| #include "absl/strings/internal/cord_rep_test_util.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| |
| namespace absl { |
| ABSL_NAMESPACE_BEGIN |
| namespace cord_internal { |
| namespace { |
| |
| using ::testing::Eq; |
| using ::testing::Ne; |
| |
| using ::absl::cordrep_testing::CordRepBtreeFromFlats; |
| using ::absl::cordrep_testing::CordToString; |
| using ::absl::cordrep_testing::CreateFlatsFromString; |
| using ::absl::cordrep_testing::CreateRandomString; |
| using ::absl::cordrep_testing::MakeFlat; |
| using ::absl::cordrep_testing::MakeSubstring; |
| |
| using ReadResult = CordRepBtreeNavigator::ReadResult; |
| using Position = CordRepBtreeNavigator::Position; |
| |
| // CordRepBtreeNavigatorTest is a test fixture which automatically creates a |
| // tree to test navigation logic on. The parameter `count' defines the number of |
| // data edges in the test tree. |
| class CordRepBtreeNavigatorTest : public testing::TestWithParam<size_t> { |
| public: |
| using Flats = std::vector<CordRep*>; |
| static constexpr size_t kCharsPerFlat = 3; |
| |
| CordRepBtreeNavigatorTest() { |
| data_ = CreateRandomString(count() * kCharsPerFlat); |
| flats_ = CreateFlatsFromString(data_, kCharsPerFlat); |
| |
| // Turn flat 0 or 1 into a substring to cover partial reads on substrings. |
| if (count() > 1) { |
| CordRep::Unref(flats_[1]); |
| flats_[1] = MakeSubstring(kCharsPerFlat, kCharsPerFlat, MakeFlat(data_)); |
| } else { |
| CordRep::Unref(flats_[0]); |
| flats_[0] = MakeSubstring(0, kCharsPerFlat, MakeFlat(data_)); |
| } |
| |
| tree_ = CordRepBtreeFromFlats(flats_); |
| } |
| |
| ~CordRepBtreeNavigatorTest() override { CordRep::Unref(tree_); } |
| |
| size_t count() const { return GetParam(); } |
| CordRepBtree* tree() { return tree_; } |
| const std::string& data() const { return data_; } |
| const std::vector<CordRep*>& flats() const { return flats_; } |
| |
| static std::string ToString(testing::TestParamInfo<size_t> param) { |
| return absl::StrCat(param.param, "_Flats"); |
| } |
| |
| private: |
| std::string data_; |
| Flats flats_; |
| CordRepBtree* tree_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| WithParam, CordRepBtreeNavigatorTest, |
| testing::Values(1, CordRepBtree::kMaxCapacity - 1, |
| CordRepBtree::kMaxCapacity, |
| CordRepBtree::kMaxCapacity* CordRepBtree::kMaxCapacity - 1, |
| CordRepBtree::kMaxCapacity* CordRepBtree::kMaxCapacity, |
| CordRepBtree::kMaxCapacity* CordRepBtree::kMaxCapacity + 1, |
| CordRepBtree::kMaxCapacity* CordRepBtree::kMaxCapacity * 2 + |
| 17), |
| CordRepBtreeNavigatorTest::ToString); |
| |
| TEST(CordRepBtreeNavigatorTest, Uninitialized) { |
| CordRepBtreeNavigator nav; |
| EXPECT_FALSE(nav); |
| EXPECT_THAT(nav.btree(), Eq(nullptr)); |
| #if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG) |
| EXPECT_DEATH(nav.Current(), ".*"); |
| #endif |
| } |
| |
| TEST_P(CordRepBtreeNavigatorTest, InitFirst) { |
| CordRepBtreeNavigator nav; |
| CordRep* edge = nav.InitFirst(tree()); |
| EXPECT_TRUE(nav); |
| EXPECT_THAT(nav.btree(), Eq(tree())); |
| EXPECT_THAT(nav.Current(), Eq(flats().front())); |
| EXPECT_THAT(edge, Eq(flats().front())); |
| } |
| |
| TEST_P(CordRepBtreeNavigatorTest, InitLast) { |
| CordRepBtreeNavigator nav; |
| CordRep* edge = nav.InitLast(tree()); |
| EXPECT_TRUE(nav); |
| EXPECT_THAT(nav.btree(), Eq(tree())); |
| EXPECT_THAT(nav.Current(), Eq(flats().back())); |
| EXPECT_THAT(edge, Eq(flats().back())); |
| } |
| |
| TEST_P(CordRepBtreeNavigatorTest, NextPrev) { |
| CordRepBtreeNavigator nav; |
| nav.InitFirst(tree()); |
| const Flats& flats = this->flats(); |
| |
| EXPECT_THAT(nav.Previous(), Eq(nullptr)); |
| EXPECT_THAT(nav.Current(), Eq(flats.front())); |
| for (size_t i = 1; i < flats.size(); ++i) { |
| ASSERT_THAT(nav.Next(), Eq(flats[i])); |
| EXPECT_THAT(nav.Current(), Eq(flats[i])); |
| } |
| EXPECT_THAT(nav.Next(), Eq(nullptr)); |
| EXPECT_THAT(nav.Current(), Eq(flats.back())); |
| for (size_t i = flats.size() - 1; i > 0; --i) { |
| ASSERT_THAT(nav.Previous(), Eq(flats[i - 1])); |
| EXPECT_THAT(nav.Current(), Eq(flats[i - 1])); |
| } |
| EXPECT_THAT(nav.Previous(), Eq(nullptr)); |
| EXPECT_THAT(nav.Current(), Eq(flats.front())); |
| } |
| |
| TEST_P(CordRepBtreeNavigatorTest, PrevNext) { |
| CordRepBtreeNavigator nav; |
| nav.InitLast(tree()); |
| const Flats& flats = this->flats(); |
| |
| EXPECT_THAT(nav.Next(), Eq(nullptr)); |
| EXPECT_THAT(nav.Current(), Eq(flats.back())); |
| for (size_t i = flats.size() - 1; i > 0; --i) { |
| ASSERT_THAT(nav.Previous(), Eq(flats[i - 1])); |
| EXPECT_THAT(nav.Current(), Eq(flats[i - 1])); |
| } |
| EXPECT_THAT(nav.Previous(), Eq(nullptr)); |
| EXPECT_THAT(nav.Current(), Eq(flats.front())); |
| for (size_t i = 1; i < flats.size(); ++i) { |
| ASSERT_THAT(nav.Next(), Eq(flats[i])); |
| EXPECT_THAT(nav.Current(), Eq(flats[i])); |
| } |
| EXPECT_THAT(nav.Next(), Eq(nullptr)); |
| EXPECT_THAT(nav.Current(), Eq(flats.back())); |
| } |
| |
| TEST(CordRepBtreeNavigatorTest, Reset) { |
| CordRepBtree* tree = CordRepBtree::Create(MakeFlat("abc")); |
| CordRepBtreeNavigator nav; |
| nav.InitFirst(tree); |
| nav.Reset(); |
| EXPECT_FALSE(nav); |
| EXPECT_THAT(nav.btree(), Eq(nullptr)); |
| #if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG) |
| EXPECT_DEATH(nav.Current(), ".*"); |
| #endif |
| CordRep::Unref(tree); |
| } |
| |
| TEST_P(CordRepBtreeNavigatorTest, Skip) { |
| size_t count = this->count(); |
| const Flats& flats = this->flats(); |
| CordRepBtreeNavigator nav; |
| nav.InitFirst(tree()); |
| |
| for (size_t char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) { |
| Position pos = nav.Skip(char_offset); |
| EXPECT_THAT(pos.edge, Eq(nav.Current())); |
| EXPECT_THAT(pos.edge, Eq(flats[0])); |
| EXPECT_THAT(pos.offset, Eq(char_offset)); |
| } |
| |
| for (size_t index1 = 0; index1 < count; ++index1) { |
| for (size_t index2 = index1; index2 < count; ++index2) { |
| for (size_t char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) { |
| CordRepBtreeNavigator nav; |
| nav.InitFirst(tree()); |
| |
| size_t length1 = index1 * kCharsPerFlat; |
| Position pos1 = nav.Skip(length1 + char_offset); |
| ASSERT_THAT(pos1.edge, Eq(flats[index1])); |
| ASSERT_THAT(pos1.edge, Eq(nav.Current())); |
| ASSERT_THAT(pos1.offset, Eq(char_offset)); |
| |
| size_t length2 = index2 * kCharsPerFlat; |
| Position pos2 = nav.Skip(length2 - length1 + char_offset); |
| ASSERT_THAT(pos2.edge, Eq(flats[index2])); |
| ASSERT_THAT(pos2.edge, Eq(nav.Current())); |
| ASSERT_THAT(pos2.offset, Eq(char_offset)); |
| } |
| } |
| } |
| } |
| |
| TEST_P(CordRepBtreeNavigatorTest, Seek) { |
| size_t count = this->count(); |
| const Flats& flats = this->flats(); |
| CordRepBtreeNavigator nav; |
| nav.InitFirst(tree()); |
| |
| for (size_t char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) { |
| Position pos = nav.Seek(char_offset); |
| EXPECT_THAT(pos.edge, Eq(nav.Current())); |
| EXPECT_THAT(pos.edge, Eq(flats[0])); |
| EXPECT_THAT(pos.offset, Eq(char_offset)); |
| } |
| |
| for (size_t index = 0; index < count; ++index) { |
| for (size_t char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) { |
| size_t offset = index * kCharsPerFlat + char_offset; |
| Position pos1 = nav.Seek(offset); |
| ASSERT_THAT(pos1.edge, Eq(flats[index])); |
| ASSERT_THAT(pos1.edge, Eq(nav.Current())); |
| ASSERT_THAT(pos1.offset, Eq(char_offset)); |
| } |
| } |
| } |
| |
| TEST(CordRepBtreeNavigatorTest, InitOffset) { |
| // Whitebox: InitOffset() is implemented in terms of Seek() which is |
| // exhaustively tested. Only test it initializes / forwards properly.. |
| CordRepBtree* tree = CordRepBtree::Create(MakeFlat("abc")); |
| tree = CordRepBtree::Append(tree, MakeFlat("def")); |
| CordRepBtreeNavigator nav; |
| Position pos = nav.InitOffset(tree, 5); |
| EXPECT_TRUE(nav); |
| EXPECT_THAT(nav.btree(), Eq(tree)); |
| EXPECT_THAT(pos.edge, Eq(tree->Edges()[1])); |
| EXPECT_THAT(pos.edge, Eq(nav.Current())); |
| EXPECT_THAT(pos.offset, Eq(2u)); |
| CordRep::Unref(tree); |
| } |
| |
| TEST(CordRepBtreeNavigatorTest, InitOffsetAndSeekBeyondLength) { |
| CordRepBtree* tree1 = CordRepBtree::Create(MakeFlat("abc")); |
| CordRepBtree* tree2 = CordRepBtree::Create(MakeFlat("def")); |
| |
| CordRepBtreeNavigator nav; |
| nav.InitFirst(tree1); |
| EXPECT_THAT(nav.Seek(3).edge, Eq(nullptr)); |
| EXPECT_THAT(nav.Seek(100).edge, Eq(nullptr)); |
| EXPECT_THAT(nav.btree(), Eq(tree1)); |
| EXPECT_THAT(nav.Current(), Eq(tree1->Edges().front())); |
| |
| EXPECT_THAT(nav.InitOffset(tree2, 3).edge, Eq(nullptr)); |
| EXPECT_THAT(nav.InitOffset(tree2, 100).edge, Eq(nullptr)); |
| EXPECT_THAT(nav.btree(), Eq(tree1)); |
| EXPECT_THAT(nav.Current(), Eq(tree1->Edges().front())); |
| |
| CordRep::Unref(tree1); |
| CordRep::Unref(tree2); |
| } |
| |
| TEST_P(CordRepBtreeNavigatorTest, Read) { |
| const Flats& flats = this->flats(); |
| const std::string& data = this->data(); |
| |
| for (size_t offset = 0; offset < data.size(); ++offset) { |
| for (size_t length = 1; length <= data.size() - offset; ++length) { |
| CordRepBtreeNavigator nav; |
| nav.InitFirst(tree()); |
| |
| // Skip towards edge holding offset |
| size_t edge_offset = nav.Skip(offset).offset; |
| |
| // Read node |
| ReadResult result = nav.Read(edge_offset, length); |
| ASSERT_THAT(result.tree, Ne(nullptr)); |
| EXPECT_THAT(result.tree->length, Eq(length)); |
| if (result.tree->tag == BTREE) { |
| ASSERT_TRUE(CordRepBtree::IsValid(result.tree->btree())); |
| } |
| |
| // Verify contents |
| std::string value = CordToString(result.tree); |
| EXPECT_THAT(value, Eq(data.substr(offset, length))); |
| |
| // Verify 'partial last edge' reads. |
| size_t partial = (offset + length) % kCharsPerFlat; |
| ASSERT_THAT(result.n, Eq(partial)); |
| |
| // Verify ending position if not EOF |
| if (offset + length < data.size()) { |
| size_t index = (offset + length) / kCharsPerFlat; |
| EXPECT_THAT(nav.Current(), Eq(flats[index])); |
| } |
| |
| CordRep::Unref(result.tree); |
| } |
| } |
| } |
| |
| TEST_P(CordRepBtreeNavigatorTest, ReadBeyondLengthOfTree) { |
| CordRepBtreeNavigator nav; |
| nav.InitFirst(tree()); |
| ReadResult result = nav.Read(2, tree()->length); |
| ASSERT_THAT(result.tree, Eq(nullptr)); |
| } |
| |
| TEST(CordRepBtreeNavigatorTest, NavigateMaximumTreeDepth) { |
| CordRepFlat* flat1 = MakeFlat("Hello world"); |
| CordRepFlat* flat2 = MakeFlat("World Hello"); |
| |
| CordRepBtree* node = CordRepBtree::Create(flat1); |
| node = CordRepBtree::Append(node, flat2); |
| while (node->height() < CordRepBtree::kMaxHeight) { |
| node = CordRepBtree::New(node); |
| } |
| |
| CordRepBtreeNavigator nav; |
| CordRep* edge = nav.InitFirst(node); |
| EXPECT_THAT(edge, Eq(flat1)); |
| EXPECT_THAT(nav.Next(), Eq(flat2)); |
| EXPECT_THAT(nav.Next(), Eq(nullptr)); |
| EXPECT_THAT(nav.Previous(), Eq(flat1)); |
| EXPECT_THAT(nav.Previous(), Eq(nullptr)); |
| |
| CordRep::Unref(node); |
| } |
| |
| } // namespace |
| } // namespace cord_internal |
| ABSL_NAMESPACE_END |
| } // namespace absl |