diff --git a/src/core/qlist.cc b/src/core/qlist.cc index c6d10023e..8f3c218de 100644 --- a/src/core/qlist.cc +++ b/src/core/qlist.cc @@ -65,7 +65,7 @@ static_assert(sizeof(QList) == 32); enum IterDir : uint8_t { FWD = 1, REV = 0 }; /* This is for test suite development purposes only, 0 means disabled. */ -static size_t packed_threshold = 0; +size_t packed_threshold = 0; /* Optimization levels for size-based filling. * Note that the largest possible limit is 64k, so even if each record takes @@ -123,7 +123,9 @@ bool NodeAllowMerge(const quicklistNode* a, const quicklistNode* b, const int fi /* approximate merged listpack size (- 7 to remove one listpack * header/trailer, see LP_HDR_SIZE and LP_EOF) */ unsigned int merge_sz = a->sz + b->sz - 7; - return quicklistNodeExceedsLimit(fill, merge_sz, a->count + b->count); + + // Allow merge if new node will not exceed the limit. + return !quicklistNodeExceedsLimit(fill, merge_sz, a->count + b->count); } quicklistNode* CreateNode() { @@ -140,6 +142,7 @@ quicklistNode* CreateNode() { } uint8_t* LP_Insert(uint8_t* lp, string_view elem, uint8_t* pos, int lp_where) { + DCHECK(pos); return lpInsertString(lp, uint_ptr(elem), elem.size(), pos, lp_where, NULL); } @@ -290,6 +293,10 @@ quicklistNode* SplitNode(quicklistNode* node, int offset, bool after) { } // namespace +void QList::SetPackedThreshold(unsigned threshold) { + packed_threshold = threshold; +} + QList::QList() : fill_(-2), compress_(0), bookmark_count_(0) { } @@ -529,28 +536,15 @@ void QList::InsertNode(quicklistNode* old_node, quicklistNode* new_node, InsertO } void QList::Insert(Iterator it, std::string_view elem, InsertOpt insert_opt) { + DCHECK(it.current_); + DCHECK(it.zi_); + int full = 0, at_tail = 0, at_head = 0, avail_next = 0, avail_prev = 0; quicklistNode* node = it.current_; quicklistNode* new_node = NULL; size_t sz = elem.size(); bool after = insert_opt == AFTER; - if (!node) { - /* we have no reference node, so let's create only node in the list */ - DCHECK_EQ(count_, 0u); - DCHECK_EQ(len_, 0u); - - if (ABSL_PREDICT_FALSE(IsLargeElement(sz, fill_))) { - InsertPlainNode(tail_, elem, insert_opt); - return; - } - - new_node = CreateNode(QUICKLIST_NODE_CONTAINER_PACKED, elem); - InsertNode(NULL, new_node, insert_opt); - count_++; - return; - } - /* Populate accounting flags for easier boolean checks later */ if (!NodeAllowInsert(node, fill_, sz)) { full = 1; @@ -831,10 +825,10 @@ quicklistNode* QList::ListpackMerge(quicklistNode* a, quicklistNode* b) { DelNode(nokeep); quicklistCompress(keep); return keep; - } else { - /* else, the merge returned NULL and nothing changed. */ - return NULL; } + + /* else, the merge returned NULL and nothing changed. */ + return NULL; } void QList::DelNode(quicklistNode* node) { @@ -909,13 +903,13 @@ auto QList::GetIterator(Where where) const -> Iterator { auto QList::GetIterator(long idx) const -> Iterator { quicklistNode* n; unsigned long long accum = 0; - unsigned long long index; int forward = idx < 0 ? 0 : 1; /* < 0 -> reverse, 0+ -> forward */ - - index = forward ? idx : (-idx) - 1; + uint64_t index = forward ? idx : (-idx) - 1; if (index >= count_) return {}; + DCHECK(head_); + /* Seek in the other direction if that way is shorter. */ int seek_forward = forward; unsigned long long seek_index = index; diff --git a/src/core/qlist.h b/src/core/qlist.h index bf95d35e6..e59ed9b56 100644 --- a/src/core/qlist.h +++ b/src/core/qlist.h @@ -46,6 +46,10 @@ class QList { bool operator==(std::string_view sv) const; + friend bool operator==(std::string_view sv, const Entry& entry) { + return entry == sv; + } + std::string to_string() const { if (std::holds_alternative(value_)) { return std::to_string(std::get(value_)); @@ -100,6 +104,8 @@ class QList { // Returns true if pivot found and elem inserted, false otherwise. bool Insert(std::string_view pivot, std::string_view elem, InsertOpt opt); + void Insert(Iterator it, std::string_view elem, InsertOpt opt); + // Returns true if item was replaced, false if index is out of range. bool Replace(long index, std::string_view elem); @@ -141,6 +147,12 @@ class QList { return tail_; } + void set_fill(int fill) { + fill_ = fill; + } + + static void SetPackedThreshold(unsigned threshold); + private: bool AllowCompression() const { return compress_ != 0; @@ -153,7 +165,6 @@ class QList { bool PushTail(std::string_view value); void InsertPlainNode(quicklistNode* old_node, std::string_view, InsertOpt insert_opt); void InsertNode(quicklistNode* old_node, quicklistNode* new_node, InsertOpt insert_opt); - void Insert(Iterator it, std::string_view elem, InsertOpt opt); void Replace(Iterator it, std::string_view elem); void Compress(quicklistNode* node); diff --git a/src/core/qlist_test.cc b/src/core/qlist_test.cc index 31c6b2210..3623bd759 100644 --- a/src/core/qlist_test.cc +++ b/src/core/qlist_test.cc @@ -4,6 +4,7 @@ #include "core/qlist.h" +#include #include #include #include @@ -76,6 +77,7 @@ static int ql_verify(const QList& ql, uint32_t nc, uint32_t count, uint32_t head while (node) { node_size += node->count; node = node->next; + CHECK(node != ql.Head()); } if (node_size != ql.Size()) { @@ -101,13 +103,13 @@ static int ql_verify(const QList& ql, uint32_t nc, uint32_t count, uint32_t head return 0; } - if (ql.Head() && head_count != ql.Head()->count && head_count != lpLength(ql.Head()->entry)) { + if (head_count != ql.Head()->count && head_count != lpLength(ql.Head()->entry)) { LOG(ERROR) << absl::StrFormat("head count wrong: expected %u got cached %u vs. actual %lu", head_count, ql.Head()->count, lpLength(ql.Head()->entry)); errors++; } - if (ql.Tail() && tail_count != ql.Tail()->count && tail_count != lpLength(ql.Tail()->entry)) { + if (tail_count != ql.Tail()->count && tail_count != lpLength(ql.Tail()->entry)) { LOG(ERROR) << "tail count wrong: expected " << tail_count << "got cached " << ql.Tail()->count << " vs. actual " << lpLength(ql.Tail()->entry); errors++; @@ -247,6 +249,37 @@ TEST_F(QListTest, PushPlain) { EXPECT_THAT(items, ElementsAre(val)); } +TEST_F(QListTest, GetNum) { + ql_.Push("1251977", QList::HEAD); + QList::Iterator it = ql_.GetIterator(QList::HEAD); + ASSERT_TRUE(it.Next()); + EXPECT_EQ(1251977, it.Get().ival()); +} + +TEST_F(QListTest, CompressionPlain) { + char buf[256]; + QList::SetPackedThreshold(1); + ql_ = QList(-2, 1); + + for (int i = 0; i < 500; i++) { + /* Set to 256 to allow the node to be triggered to compress, + * if it is less than 48(nocompress), the test will be successful. */ + snprintf(buf, sizeof(buf), "hello%d", i); + ql_.Push(string_view{buf, sizeof(buf)}, QList::HEAD); + } + QList::SetPackedThreshold(0); + + QList::Iterator it = ql_.GetIterator(QList::TAIL); + int i = 0; + while (it.Next()) { + string_view sv = it.Get().view(); + ASSERT_EQ(sizeof(buf), sv.size()); + ASSERT_TRUE(absl::StartsWith(sv, StrCat("hello", i))); + i++; + } + EXPECT_EQ(500, i); +} + using FillCompress = tuple; class PrintToFillCompress { @@ -289,6 +322,25 @@ TEST_P(OptionsTest, Numbers) { EXPECT_EQ("xxxxxxxxxxxxxxxxxxxx", it.Get().view()); } +TEST_P(OptionsTest, NumbersIndex) { + auto [fill, compress] = GetParam(); + ql_ = QList(fill, compress); + + long long nums[5000]; + for (int i = 0; i < 760; i++) { + nums[i] = -5157318210846258176 + i; + ql_.Push(absl::StrCat(nums[i]), QList::TAIL); + } + + unsigned i = 437; + QList::Iterator it = ql_.GetIterator(i); + while (it.Next()) { + ASSERT_EQ(nums[i], it.Get().ival()); + i++; + } + ASSERT_EQ(760, i); +} + TEST_P(OptionsTest, DelRangeA) { auto [fill, compress] = GetParam(); ql_ = QList(fill, compress); @@ -297,15 +349,17 @@ TEST_P(OptionsTest, DelRangeA) { nums[i] = -5157318210846258176 + i; ql_.Push(absl::StrCat(nums[i]), QList::TAIL); } - if (fill == 32) - ql_verify(ql_, 2, 33, 32, 1); + + if (fill == 32) { + ASSERT_EQ(0, ql_verify(ql_, 2, 33, 32, 1)); + } /* ltrim 3 3 (keep [3,3] inclusive = 1 remaining) */ ql_.Erase(0, 3); ql_.Erase(-29, 4000); /* make sure not loop forever */ - if (fill == 32) - ql_verify(ql_, 1, 1, 1, 1); - + if (fill == 32) { + ASSERT_EQ(0, ql_verify(ql_, 1, 1, 1, 1)); + } auto it = ql_.GetIterator(0); ASSERT_TRUE(it.Next()); EXPECT_EQ(-5157318210846258173, it.Get().ival()); @@ -320,14 +374,15 @@ TEST_P(OptionsTest, DelRangeB) { nums[i] = i; ql_.Push(absl::StrCat(nums[i]), QList::TAIL); } - if (fill == 32) - ql_verify(ql_, 2, 33, 32, 1); - + if (fill == 32) { + ASSERT_EQ(0, ql_verify(ql_, 2, 33, 32, 1)); + } /* ltrim 5 16 (keep [5,16] inclusive = 12 remaining) */ ql_.Erase(0, 5); ql_.Erase(-16, 16); - if (fill == 32) - ql_verify(ql_, 1, 12, 12, 12); + if (fill == 32) { + ASSERT_EQ(0, ql_verify(ql_, 1, 12, 12, 12)); + } auto it = ql_.GetIterator(0); ASSERT_TRUE(it.Next()); @@ -357,14 +412,16 @@ TEST_P(OptionsTest, DelRangeC) { nums[i] = -5157318210846258176 + i; ql_.Push(absl::StrCat(nums[i]), QList::TAIL); } - if (fill == 32) - ql_verify(ql_, 2, 33, 32, 1); + if (fill == 32) { + ASSERT_EQ(0, ql_verify(ql_, 2, 33, 32, 1)); + } /* ltrim 3 3 (keep [3,3] inclusive = 1 remaining) */ ql_.Erase(0, 3); ql_.Erase(-29, 4000); /* make sure not loop forever */ - if (fill == 32) - ql_verify(ql_, 1, 1, 1, 1); + if (fill == 32) { + ASSERT_EQ(0, ql_verify(ql_, 1, 1, 1, 1)); + } auto it = ql_.GetIterator(0); ASSERT_TRUE(it.Next()); ASSERT_EQ(-5157318210846258173, it.Get().ival()); @@ -378,8 +435,9 @@ TEST_P(OptionsTest, DelRangeD) { nums[i] = -5157318210846258176 + i; ql_.Push(absl::StrCat(nums[i]), QList::TAIL); } - if (fill == 32) - ql_verify(ql_, 2, 33, 32, 1); + if (fill == 32) { + ASSERT_EQ(0, ql_verify(ql_, 2, 33, 32, 1)); + } ql_.Erase(-12, 3); ASSERT_EQ(30, ql_.Size()); @@ -392,9 +450,9 @@ TEST_P(OptionsTest, DelRangeNode) { for (int i = 0; i < 32; i++) ql_.Push(StrCat("hello", i), QList::HEAD); - ql_verify(ql_, 1, 32, 32, 32); + ASSERT_EQ(0, ql_verify(ql_, 1, 32, 32, 32)); ql_.Erase(0, 32); - ql_verify(ql_, 0, 0, 0, 0); + ASSERT_EQ(0, ql_verify(ql_, 0, 0, 0, 0)); } TEST_P(OptionsTest, DelRangeNodeOverflow) { @@ -403,9 +461,9 @@ TEST_P(OptionsTest, DelRangeNodeOverflow) { for (int i = 0; i < 32; i++) ql_.Push(StrCat("hello", i), QList::HEAD); - ql_verify(ql_, 1, 32, 32, 32); + ASSERT_EQ(0, ql_verify(ql_, 1, 32, 32, 32)); ql_.Erase(0, 128); - ql_verify(ql_, 0, 0, 0, 0); + ASSERT_EQ(0, ql_verify(ql_, 0, 0, 0, 0)); } TEST_P(OptionsTest, DelRangeMiddle100of500) { @@ -415,9 +473,9 @@ TEST_P(OptionsTest, DelRangeMiddle100of500) { for (int i = 0; i < 500; i++) ql_.Push(StrCat("hello", i + 1), QList::TAIL); - ql_verify(ql_, 16, 500, 32, 20); + ASSERT_EQ(0, ql_verify(ql_, 16, 500, 32, 20)); ql_.Erase(200, 100); - ql_verify(ql_, 14, 400, 32, 20); + ASSERT_EQ(0, ql_verify(ql_, 14, 400, 32, 20)); } TEST_P(OptionsTest, DelLessFillAcrossNodes) { @@ -426,9 +484,9 @@ TEST_P(OptionsTest, DelLessFillAcrossNodes) { for (int i = 0; i < 500; i++) ql_.Push(StrCat("hello", i + 1), QList::TAIL); - ql_verify(ql_, 16, 500, 32, 20); + ASSERT_EQ(0, ql_verify(ql_, 16, 500, 32, 20)); ql_.Erase(60, 10); - ql_verify(ql_, 16, 490, 32, 20); + ASSERT_EQ(0, ql_verify(ql_, 16, 490, 32, 20)); } TEST_P(OptionsTest, DelNegOne) { @@ -436,9 +494,9 @@ TEST_P(OptionsTest, DelNegOne) { ql_ = QList(32, compress); for (int i = 0; i < 500; i++) ql_.Push(StrCat("hello", i + 1), QList::TAIL); - ql_verify(ql_, 16, 500, 32, 20); + ASSERT_EQ(0, ql_verify(ql_, 16, 500, 32, 20)); ql_.Erase(-1, 1); - ql_verify(ql_, 16, 499, 32, 19); + ASSERT_EQ(0, ql_verify(ql_, 16, 499, 32, 19)); } TEST_P(OptionsTest, DelNegOneOverflow) { @@ -447,10 +505,10 @@ TEST_P(OptionsTest, DelNegOneOverflow) { for (int i = 0; i < 500; i++) ql_.Push(StrCat("hello", i + 1), QList::TAIL); - ql_verify(ql_, 16, 500, 32, 20); + ASSERT_EQ(0, ql_verify(ql_, 16, 500, 32, 20)); ql_.Erase(-1, 128); - ql_verify(ql_, 16, 499, 32, 19); + ASSERT_EQ(0, ql_verify(ql_, 16, 499, 32, 19)); } TEST_P(OptionsTest, DelNeg100From500) { @@ -459,7 +517,7 @@ TEST_P(OptionsTest, DelNeg100From500) { for (int i = 0; i < 500; i++) ql_.Push(StrCat("hello", i + 1), QList::TAIL); ql_.Erase(-100, 100); - ql_verify(ql_, 13, 400, 32, 16); + ASSERT_EQ(0, ql_verify(ql_, 13, 400, 32, 16)); } TEST_P(OptionsTest, DelMin10_5_from50) { @@ -468,9 +526,9 @@ TEST_P(OptionsTest, DelMin10_5_from50) { for (int i = 0; i < 50; i++) ql_.Push(StrCat("hello", i + 1), QList::TAIL); - ql_verify(ql_, 2, 50, 32, 18); + ASSERT_EQ(0, ql_verify(ql_, 2, 50, 32, 18)); ql_.Erase(-10, 5); - ql_verify(ql_, 2, 45, 32, 13); + ASSERT_EQ(0, ql_verify(ql_, 2, 45, 32, 13)); } TEST_P(OptionsTest, DelElems) { @@ -513,4 +571,254 @@ TEST_P(OptionsTest, DelElems) { EXPECT_THAT(ToItems(), ElementsAreArray(resultB)); } +TEST_P(OptionsTest, IterateReverse) { + auto [_, compress] = GetParam(); + ql_ = QList(32, compress); + + for (int i = 0; i < 500; i++) + ql_.Push(StrCat("hello", i), QList::HEAD); + QList::Iterator it = ql_.GetIterator(QList::TAIL); + int i = 0; + while (it.Next()) { + ASSERT_EQ(StrCat("hello", i), it.Get()); + i++; + } + ASSERT_EQ(500, i); + ASSERT_EQ(0, ql_verify(ql_, 16, 500, 20, 32)); +} + +TEST_P(OptionsTest, Iterate500) { + auto [_, compress] = GetParam(); + ql_ = QList(32, compress); + for (int i = 0; i < 500; i++) + ql_.Push(StrCat("hello", i), QList::HEAD); + + QList::Iterator it = ql_.GetIterator(QList::HEAD); + int i = 499, count = 0; + while (it.Next()) { + QList::Entry entry = it.Get(); + ASSERT_EQ(StrCat("hello", i), entry); + i--; + count++; + } + EXPECT_EQ(500, count); + ASSERT_EQ(0, ql_verify(ql_, 16, 500, 20, 32)); + + it = ql_.GetIterator(QList::TAIL); + i = 0; + while (it.Next()) { + ASSERT_EQ(StrCat("hello", i), it.Get()); + i++; + } + EXPECT_EQ(500, i); +} + +TEST_P(OptionsTest, IterateAfterOne) { + auto [_, compress] = GetParam(); + ql_ = QList(-2, compress); + ql_.Push("hello", QList::HEAD); + + QList::Iterator it = ql_.GetIterator(0); + ASSERT_TRUE(it.Next()); + ql_.Insert(it, "abc", QList::AFTER); + + ASSERT_EQ(0, ql_verify(ql_, 1, 2, 2, 2)); + + /* verify results */ + it = ql_.GetIterator(0); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("hello", it.Get()); + + it = ql_.GetIterator(1); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("abc", it.Get()); +} + +TEST_P(OptionsTest, IterateDelete) { + auto [fill, compress] = GetParam(); + ql_ = QList(fill, compress); + + ql_.Push("abc", QList::TAIL); + ql_.Push("def", QList::TAIL); + ql_.Push("hij", QList::TAIL); + ql_.Push("jkl", QList::TAIL); + ql_.Push("oop", QList::TAIL); + + QList::Iterator it = ql_.GetIterator(QList::HEAD); + int i = 0; + while (it.Next()) { + if (it.Get() == "hij") { + it = ql_.Erase(it); + } + i++; + } + + ASSERT_EQ(5, i); + + ASSERT_THAT(ToItems(), ElementsAre("abc", "def", "jkl", "oop")); +} + +TEST_P(OptionsTest, InsertBeforeOne) { + auto [_, compress] = GetParam(); + ql_ = QList(-2, compress); + + ql_.Push("hello", QList::HEAD); + QList::Iterator it = ql_.GetIterator(0); + ASSERT_TRUE(it.Next()); + ql_.Insert(it, "abc", QList::BEFORE); + ql_verify(ql_, 1, 2, 2, 2); + + /* verify results */ + it = ql_.GetIterator(0); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("abc", it.Get()); + + it = ql_.GetIterator(1); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("hello", it.Get()); +} + +TEST_P(OptionsTest, InsertWithHeadFull) { + auto [_, compress] = GetParam(); + ql_ = QList(4, compress); + + for (int i = 0; i < 10; i++) + ql_.Push(StrCat("hello", i), QList::TAIL); + + ql_.set_fill(-1); + QList::Iterator it = ql_.GetIterator(-10); + ASSERT_TRUE(it.Next()); + + char buf[4096] = {0}; + ql_.Insert(it, string_view{buf, sizeof(buf)}, QList::BEFORE); + ql_verify(ql_, 4, 11, 1, 2); +} + +TEST_P(OptionsTest, InsertWithTailFull) { + auto [_, compress] = GetParam(); + ql_ = QList(4, compress); + for (int i = 0; i < 10; i++) + ql_.Push(StrCat("hello", i), QList::HEAD); + + ql_.set_fill(-1); + QList::Iterator it = ql_.GetIterator(-1); + ASSERT_TRUE(it.Next()); + + char buf[4096] = {0}; + ql_.Insert(it, string_view{buf, sizeof(buf)}, QList::AFTER); + ql_verify(ql_, 4, 11, 2, 1); +} + +TEST_P(OptionsTest, InsertOnceWhileIterating) { + auto [fill, compress] = GetParam(); + ql_ = QList(fill, compress); + + ql_.Push("abc", QList::TAIL); + ql_.set_fill(1); + + ql_.Push("def", QList::TAIL); + ql_.set_fill(fill); + ql_.Push("bob", QList::TAIL); + ql_.Push("foo", QList::TAIL); + ql_.Push("zoo", QList::TAIL); + + /* insert "bar" before "bob" while iterating over list. */ + QList::Iterator it = ql_.GetIterator(QList::HEAD); + while (it.Next()) { + if (it.Get() == "bob") { + ql_.Insert(it, "bar", QList::BEFORE); + break; /* didn't we fix insert-while-iterating? */ + } + } + EXPECT_THAT(ToItems(), ElementsAre("abc", "def", "bar", "bob", "foo", "zoo")); +} + +TEST_P(OptionsTest, InsertBefore250NewInMiddleOf500Elements) { + auto [fill, compress] = GetParam(); + ql_ = QList(fill, compress); + for (int i = 0; i < 500; i++) { + string val = StrCat("hello", i); + val.resize(32); + ql_.Push(val, QList::TAIL); + } + + for (int i = 0; i < 250; i++) { + QList::Iterator it = ql_.GetIterator(250); + ASSERT_TRUE(it.Next()); + ql_.Insert(it, StrCat("abc", i), QList::BEFORE); + } + + if (fill == 32) { + ASSERT_EQ(0, ql_verify(ql_, 25, 750, 32, 20)); + } +} + +TEST_P(OptionsTest, InsertAfter250NewInMiddleOf500Elements) { + auto [fill, compress] = GetParam(); + ql_ = QList(fill, compress); + for (int i = 0; i < 500; i++) + ql_.Push(StrCat("hello", i), QList::HEAD); + + for (int i = 0; i < 250; i++) { + QList::Iterator it = ql_.GetIterator(250); + ASSERT_TRUE(it.Next()); + ql_.Insert(it, StrCat("abc", i), QList::AFTER); + } + + ASSERT_EQ(750, ql_.Size()); + + if (fill == 32) { + ASSERT_EQ(0, ql_verify(ql_, 26, 750, 20, 32)); + } +} + +TEST_P(OptionsTest, NextPlain) { + auto [_, compress] = GetParam(); + ql_ = QList(-2, compress); + + QList::SetPackedThreshold(3); + + const char* strings[] = {"hello1", "hello2", "h3", "h4", "hello5"}; + + for (int i = 0; i < 5; ++i) + ql_.Push(strings[i], QList::HEAD); + + QList::Iterator it = ql_.GetIterator(QList::TAIL); + int j = 0; + + while (it.Next()) { + ASSERT_EQ(strings[j], it.Get()); + j++; + } +} + +TEST_P(OptionsTest, IndexFrom500) { + auto [fill, compress] = GetParam(); + ql_ = QList(fill, compress); + for (int i = 0; i < 500; i++) + ql_.Push(StrCat("hello", i + 1), QList::TAIL); + + QList::Iterator it = ql_.GetIterator(1); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("hello2", it.Get()); + it = ql_.GetIterator(200); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("hello201", it.Get()); + + it = ql_.GetIterator(-1); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("hello500", it.Get()); + + it = ql_.GetIterator(-2); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("hello499", it.Get()); + + it = ql_.GetIterator(-100); + ASSERT_TRUE(it.Next()); + ASSERT_EQ("hello401", it.Get()); + + it = ql_.GetIterator(500); + ASSERT_FALSE(it.Next()); +} + } // namespace dfly