46626ce73f1bc31a3113748e1ffa59f96b2f0dd9
[ep-engine.git] / tests / module_tests / ephemeral_vb_test.cc
1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2017 Couchbase, Inc
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17
18 /**
19  * Tests specific to Ephemeral VBuckets.
20  */
21
22 #include "config.h"
23
24 #include "../mock/mock_ephemeral_vb.h"
25 #include "failover-table.h"
26 #include "test_helpers.h"
27 #include "thread_gate.h"
28 #include "vbucket_test.h"
29
30 #include <thread>
31
32 class EphemeralVBucketTest : public VBucketTest {
33 protected:
34     void SetUp() {
35         /* to test ephemeral vbucket specific stuff */
36         mockEpheVB = new MockEphemeralVBucket(0,
37                                               vbucket_state_active,
38                                               global_stats,
39                                               checkpoint_config,
40                                               /*kvshard*/ nullptr,
41                                               /*lastSeqno*/ 0,
42                                               /*lastSnapStart*/ 0,
43                                               /*lastSnapEnd*/ 0,
44                                               /*table*/ nullptr,
45                                               /*newSeqnoCb*/ nullptr,
46                                               config,
47                                               VALUE_ONLY);
48         /* vbucket manages the life time of mockEpheVB and is a base test class
49            ptr of owning type */
50         vbucket.reset(mockEpheVB);
51     }
52
53     void TearDown() {
54         vbucket.reset();
55     }
56
57     /* We want a ptr to MockEphemeralVBucket as we test ephemeral vbucket
58        specific stuff in this class */
59     MockEphemeralVBucket* mockEpheVB;
60
61     EPStats global_stats;
62     CheckpointConfig checkpoint_config;
63     Configuration config;
64 };
65
66 // Verify that attempting to pageOut an item twice has no effect the second
67 // time.
68 TEST_F(EphemeralVBucketTest, DoublePageOut) {
69     auto key = makeStoredDocKey("key");
70     ASSERT_EQ(AddStatus::Success, addOne(key));
71     ASSERT_EQ(1, vbucket->getNumItems());
72
73     auto lock_sv = lockAndFind(key);
74     auto* storedVal = lock_sv.second;
75     ASSERT_FALSE(storedVal->isDeleted());
76
77     // Page out the item (once).
78     EXPECT_TRUE(vbucket->pageOut(lock_sv.first, storedVal));
79     EXPECT_EQ(0, vbucket->getNumItems());
80     EXPECT_TRUE(storedVal->isDeleted());
81
82     // Attempt to page out again - should not be possible.
83     EXPECT_FALSE(vbucket->pageOut(lock_sv.first, storedVal));
84     EXPECT_EQ(0, vbucket->getNumItems());
85     EXPECT_TRUE(storedVal->isDeleted());
86 }
87
88
89 // Verify that we can pageOut deleted items which have a value associated with
90 // them - and afterwards the value is null.
91 TEST_F(EphemeralVBucketTest, PageOutAfterDeleteWithValue) {
92     // Add an item which is marked as deleted, but has a body (e.g. system
93     // XATTR).
94     auto key = makeStoredDocKey("key");
95     std::string value = "deleted value";
96     Item item(key, 0, /*expiry*/0, value.data(), value.size());
97     item.setDeleted();
98     ASSERT_EQ(AddStatus::Success, public_processAdd(item));
99     ASSERT_EQ(0, vbucket->getNumItems());
100
101     // Check preconditions
102     auto lock_sv = lockAndFind(key);
103     auto* storedVal = lock_sv.second;
104     ASSERT_TRUE(storedVal->isDeleted());
105     ASSERT_EQ(value, storedVal->getValue()->to_s());
106
107     // Page it out.
108     EXPECT_TRUE(vbucket->pageOut(lock_sv.first, storedVal));
109     EXPECT_EQ(0, vbucket->getNumItems());
110     EXPECT_TRUE(storedVal->isDeleted());
111     EXPECT_FALSE(storedVal->getValue());
112 }
113
114 // NRU: check the seqlist has correct statistics for a create, pageout,
115 // and (re)create of the same key.
116 TEST_F(EphemeralVBucketTest, CreatePageoutCreate) {
117     auto key = makeStoredDocKey("key");
118
119     // Add a key, then page out.
120     ASSERT_EQ(AddStatus::Success, addOne(key));
121     {
122         auto lock_sv = lockAndFind(key);
123         EXPECT_TRUE(vbucket->pageOut(lock_sv.first, lock_sv.second));
124     }
125     // Sanity check - should have just the one deleted item.
126     ASSERT_EQ(0, vbucket->getNumItems());
127     ASSERT_EQ(1, mockEpheVB->getLL()->getNumDeletedItems());
128
129     // Test: Set the key again.
130     ASSERT_EQ(MutationStatus::WasDirty, setOne(key));
131
132     EXPECT_EQ(1, vbucket->getNumItems());
133     EXPECT_EQ(0, mockEpheVB->getLL()->getNumDeletedItems());
134
135     // Finally for good measure, delete again and check the numbers are correct.
136     {
137         auto lock_sv = lockAndFind(key);
138         EXPECT_TRUE(vbucket->pageOut(lock_sv.first, lock_sv.second));
139     }
140     EXPECT_EQ(0, vbucket->getNumItems());
141     EXPECT_EQ(1, mockEpheVB->getLL()->getNumDeletedItems());
142 }
143
144 TEST_F(EphemeralVBucketTest, SetItems) {
145     const int numItems = 3;
146
147     auto keys = generateKeys(numItems);
148     setMany(keys, MutationStatus::WasClean);
149
150     EXPECT_EQ(numItems, vbucket->getNumItems());
151     EXPECT_EQ(numItems, vbucket->getHighSeqno());
152 }
153
154 TEST_F(EphemeralVBucketTest, UpdateItems) {
155     /* Add 3 items and then update all of them */
156     const int numItems = 3;
157
158     auto keys = generateKeys(numItems);
159     setMany(keys, MutationStatus::WasClean);
160
161     /* Update the items */
162     setMany(keys, MutationStatus::WasDirty);
163
164     EXPECT_EQ(numItems * 2, vbucket->getHighSeqno());
165     EXPECT_EQ(numItems, vbucket->getNumItems());
166 }
167
168 TEST_F(EphemeralVBucketTest, SoftDelete) {
169     /* Add 3 items and then delete all of them */
170     const int numItems = 3;
171
172     auto keys = generateKeys(numItems);
173     setMany(keys, MutationStatus::WasClean);
174
175     /* soft delete all */
176     softDeleteMany(keys, MutationStatus::WasDirty);
177
178     EXPECT_EQ(numItems * 2, vbucket->getHighSeqno());
179     EXPECT_EQ(0, vbucket->getNumItems());
180 }
181
182 TEST_F(EphemeralVBucketTest, Backfill) {
183     /* Add 3 items and get them by backfill */
184     const int numItems = 3;
185
186     auto keys = generateKeys(numItems);
187     setMany(keys, MutationStatus::WasClean);
188
189     auto res = mockEpheVB->inMemoryBackfill(1, numItems);
190     EXPECT_EQ(ENGINE_SUCCESS, std::get<0>(res));
191     EXPECT_EQ(numItems, std::get<1>(res).size());
192 }
193
194 TEST_F(EphemeralVBucketTest, UpdateDuringBackfill) {
195     /* Add 5 items and then update all of them */
196     const int numItems = 5;
197
198     auto keys = generateKeys(numItems);
199     setMany(keys, MutationStatus::WasClean);
200
201     /* Set up a mock backfill by setting the range of the backfill */
202     mockEpheVB->registerFakeReadRange(2, numItems - 1);
203
204     /* Update the first, middle and last item in the range read and 2 items
205        that are outside (before and after) range read */
206     ASSERT_EQ(MutationStatus::WasDirty, setOne(keys[0]));
207     for (int i = 1; i < numItems - 1; ++i) {
208         ASSERT_EQ(MutationStatus::WasClean, setOne(keys[i]));
209     }
210     ASSERT_EQ(MutationStatus::WasDirty, setOne(keys[numItems - 1]));
211
212     /* Hash table must have only recent (updated) items */
213     EXPECT_EQ(numItems, vbucket->getNumItems());
214
215     /* High Seqno must be 2 * numItems */
216     EXPECT_EQ(numItems * 2, vbucket->getHighSeqno());
217
218     /* LinkedList must have 3 stale items */
219     EXPECT_EQ(3, mockEpheVB->public_getNumStaleItems());
220
221     EXPECT_EQ(numItems * 2 - /* since 2 items are deduped*/ 2,
222               mockEpheVB->public_getNumListItems());
223 }
224
225 TEST_F(EphemeralVBucketTest, GetAndUpdateTtl) {
226     const int numItems = 2;
227
228     /* Add 2 keys */
229     auto keys = generateKeys(numItems);
230     setMany(keys, MutationStatus::WasClean);
231
232     ASSERT_EQ(numItems, vbucket->getNumItems());
233     EXPECT_EQ(numItems, vbucket->getHighSeqno());
234     EXPECT_EQ(0, mockEpheVB->public_getNumStaleItems());
235
236     /* --- basic test --- */
237     /* set the ttl of one item */
238     GetValue gv1 = public_getAndUpdateTtl(keys[0], 100);
239
240     /* New seqno should have been used */
241     EXPECT_EQ(numItems + 1, vbucket->getHighSeqno());
242
243     /* No.of items in the bucket should NOT change */
244     EXPECT_EQ(numItems, vbucket->getNumItems());
245
246     /* No.of items in the list should NOT change */
247     EXPECT_EQ(numItems, mockEpheVB->public_getNumListItems());
248
249     /* There should be NO stale items */
250     EXPECT_EQ(0, mockEpheVB->public_getNumStaleItems());
251
252     /* --- Repeat the above test with a similated ReadRange --- */
253     mockEpheVB->registerFakeReadRange(1, numItems);
254     GetValue gv2 = public_getAndUpdateTtl(keys[1], 101);
255
256     /* New seqno should have been used */
257     EXPECT_EQ(numItems + 2, vbucket->getHighSeqno());
258
259     /* No.of items in the bucket should remain the same */
260     EXPECT_EQ(numItems, vbucket->getNumItems());
261
262     /* No.of items in the sequence list should inc by 1 */
263     EXPECT_EQ(numItems + 1, mockEpheVB->public_getNumListItems());
264
265     /* There should be 1 stale item */
266     EXPECT_EQ(1, mockEpheVB->public_getNumStaleItems());
267
268     auto seqNoVec = mockEpheVB->getLL()->getAllSeqnoForVerification();
269     seqno_t prevSeqNo = 0;
270
271     for (const auto& seqNo : seqNoVec) {
272         EXPECT_GT(seqNo, prevSeqNo);
273         prevSeqNo = seqNo;
274     }
275
276     // explicit delete to keep valgrind happy
277     delete (Item*)gv1.getValue();
278     delete (Item*)gv2.getValue();
279 }
280
281 TEST_F(EphemeralVBucketTest, SoftDeleteDuringBackfill) {
282     /* Add 5 items and then soft delete all of them */
283     const int numItems = 5;
284
285     auto keys = generateKeys(numItems);
286     setMany(keys, MutationStatus::WasClean);
287
288     /* Set up a mock backfill by setting the range of the backfill */
289     mockEpheVB->registerFakeReadRange(2, numItems - 1);
290
291     /* Update the first, middle and last item in the range read and 2 items
292        that are outside (before and after) range read */
293     softDeleteMany(keys, MutationStatus::WasDirty);
294
295     /* Hash table must have only recent (updated) items */
296     EXPECT_EQ(0, vbucket->getNumItems());
297
298     /* High Seqno must be 2 * numItems */
299     EXPECT_EQ(numItems * 2, vbucket->getHighSeqno());
300
301     /* LinkedList must have 3 stale items */
302     EXPECT_EQ(3, mockEpheVB->public_getNumStaleItems());
303
304     EXPECT_EQ(numItems * 2 - /* since 2 items are deduped*/ 2,
305               mockEpheVB->public_getNumListItems());
306 }
307
308 // EphemeralVB Tombstone Purging //////////////////////////////////////////////
309
310 class EphTombstoneTest : public EphemeralVBucketTest {
311 protected:
312     void SetUp() override {
313         EphemeralVBucketTest::SetUp();
314
315         // Store three items to work with.
316         keys = generateKeys(3);
317         setMany(keys, MutationStatus::WasClean);
318         ASSERT_EQ(3, vbucket->getNumItems());
319     }
320     std::vector<StoredDocKey> keys;
321 };
322
323 // Check an empty seqList is handled correctly.
324 TEST_F(EphTombstoneTest, ZeroElementPurge) {
325     // Create a new empty VB (using parent class SetUp).
326     EphemeralVBucketTest::SetUp();
327     ASSERT_EQ(0, mockEpheVB->public_getNumListItems());
328
329     EXPECT_EQ(0, mockEpheVB->purgeTombstones(0));
330 }
331
332 // Check a seqList with one element is handled correctly.
333 TEST_F(EphTombstoneTest, OneElementPurge) {
334     // Create a new empty VB (using parent class SetUp).
335     EphemeralVBucketTest::SetUp();
336     ASSERT_EQ(MutationStatus::WasClean, setOne(makeStoredDocKey("one")));
337     ASSERT_EQ(1, mockEpheVB->public_getNumListItems());
338
339     EXPECT_EQ(0, mockEpheVB->purgeTombstones(0));
340 }
341
342 // Check that nothing is purged if no items are stale.
343 TEST_F(EphTombstoneTest, NoPurgeIfNoneStale) {
344     // Run purger - nothing should be removed.
345     EXPECT_EQ(0, mockEpheVB->purgeTombstones(0));
346     EXPECT_EQ(keys.size(), vbucket->getNumItems());
347 }
348
349 // Check that deletes are not purged if they are not old enough.
350 TEST_F(EphTombstoneTest, NoPurgeIfNoneOldEnough) {
351     // Delete the first item "now"
352     softDeleteOne(keys.at(0), MutationStatus::WasDirty);
353     ASSERT_EQ(2, vbucket->getNumItems());
354     ASSERT_EQ(1, vbucket->getNumInMemoryDeletes());
355
356     // Advance time by 5 seconds and run the EphTombstonePurger specifying a
357     // purge_age of 10s - nothing
358     // should be purged.
359     TimeTraveller theTerminator(5);
360     EXPECT_EQ(0, mockEpheVB->purgeTombstones(10));
361
362     EXPECT_EQ(2, vbucket->getNumItems());
363     EXPECT_EQ(1, vbucket->getNumInMemoryDeletes());
364 }
365
366 // Check that items should be purged when they are old enough.
367 TEST_F(EphTombstoneTest, OnePurgeIfDeletedItemOld) {
368     // Delete the first item "now"
369     softDeleteOne(keys.at(0), MutationStatus::WasDirty);
370     ASSERT_EQ(2, vbucket->getNumItems());
371     ASSERT_EQ(1, vbucket->getNumInMemoryDeletes());
372
373     // Delete the second item at time 30.
374     TimeTraveller looper(30);
375     softDeleteOne(keys.at(1), MutationStatus::WasDirty);
376     ASSERT_EQ(1, vbucket->getNumItems());
377     ASSERT_EQ(2, vbucket->getNumInMemoryDeletes());
378
379     // and the third at time 60.
380     TimeTraveller looper2(30);
381     softDeleteOne(keys.at(2), MutationStatus::WasDirty);
382     ASSERT_EQ(0, vbucket->getNumItems());
383     ASSERT_EQ(3, vbucket->getNumInMemoryDeletes());
384
385     // Run the EphTombstonePurger specifying a purge_age of 60s - only key0
386     // should be purged.
387     mockEpheVB->purgeTombstones(60);
388
389     EXPECT_EQ(0, vbucket->getNumItems());
390     EXPECT_EQ(2, vbucket->getNumInMemoryDeletes());
391     EXPECT_EQ(4, vbucket->getPurgeSeqno())
392             << "Should have purged up to 4th update (1st delete, after 3 sets)";
393     EXPECT_EQ(nullptr, findValue(keys.at(0)));
394     EXPECT_NE(nullptr, findValue(keys.at(1)));
395     EXPECT_NE(nullptr, findValue(keys.at(2)));
396 }
397
398 // Check that deleted items can be purged immediately.
399 TEST_F(EphTombstoneTest, ImmediateDeletedPurge) {
400     // Advance to non-zero time.
401     TimeTraveller jamesCole(10);
402
403     // Delete the first item at 10s
404     softDeleteOne(keys.at(0), MutationStatus::WasDirty);
405     ASSERT_EQ(2, vbucket->getNumItems());
406     ASSERT_EQ(1, vbucket->getNumInMemoryDeletes());
407
408     // Run the EphTombstonePurger specifying a purge_age of 0s - key0 should
409     // be immediately purged.
410     mockEpheVB->purgeTombstones(0);
411     EXPECT_EQ(2, vbucket->getNumItems());
412     EXPECT_EQ(0, vbucket->getNumInMemoryDeletes());
413     EXPECT_EQ(4, vbucket->getPurgeSeqno())
414             << "Should have purged up to 4th update (1st delete, after 3 sets)";
415     EXPECT_EQ(nullptr, findValue(keys.at(0)));
416     EXPECT_NE(nullptr, findValue(keys.at(1)));
417     EXPECT_NE(nullptr, findValue(keys.at(2)));
418 }
419
420 // Check that alive, stale items have no constraint on age.
421 TEST_F(EphTombstoneTest, ImmediatePurgeOfAliveStale) {
422     // Perform a mutation on the second element, with a (fake) Range Read in
423     // place; causing the initial OSV to be marked as stale and a new OSV to
424     // be added for that key.
425     auto& seqList = mockEpheVB->getLL()->getSeqList();
426     {
427         std::lock_guard<std::mutex> rrGuard(
428                 mockEpheVB->getLL()->getRangeReadLock());
429         mockEpheVB->registerFakeReadRange(1, 2);
430         ASSERT_EQ(MutationStatus::WasClean, setOne(keys.at(1)));
431
432         // Sanity check - our state is as expected:
433         ASSERT_EQ(3, vbucket->getNumItems());
434         ASSERT_EQ(4, seqList.size());
435         auto staleIt = std::next(seqList.begin());
436         auto newIt = seqList.rbegin();
437         ASSERT_EQ(staleIt->getKey(), newIt->getKey());
438         {
439             std::lock_guard<std::mutex> writeGuard(
440                     mockEpheVB->getLL()->getListWriteLock());
441             ASSERT_TRUE(staleIt->isStale(writeGuard));
442             ASSERT_FALSE(newIt->isStale(writeGuard));
443         }
444
445         // Attempt a purge - should not remove anything as read range is in
446         // place.
447         EXPECT_EQ(0, mockEpheVB->purgeTombstones(0));
448         EXPECT_EQ(3, vbucket->getNumItems());
449         EXPECT_EQ(4, seqList.size());
450         EXPECT_EQ(0, vbucket->getPurgeSeqno());
451
452         // Clear the ReadRange (so we can actually purge items) and retry the
453         // purge which should now succeed.
454         mockEpheVB->getLL()->resetReadRange();
455     } // END rrGuard.
456
457     EXPECT_EQ(1, mockEpheVB->purgeTombstones(0));
458     EXPECT_EQ(3, vbucket->getNumItems());
459     EXPECT_EQ(3, seqList.size());
460 }
461
462 // Test that deleted items purged out of order are handled correctly (and
463 // highestDeletedPurged is updated).
464 TEST_F(EphTombstoneTest, PurgeOutOfOrder) {
465     // Delete the 3rd item.
466     softDeleteOne(keys.at(2), MutationStatus::WasDirty);
467
468     // Run the tombstone purger.
469     mockEpheVB->getLL()->resetReadRange();
470     ASSERT_EQ(1, mockEpheVB->purgeTombstones(0));
471     ASSERT_EQ(2, vbucket->getNumItems());
472     EXPECT_EQ(4, vbucket->getPurgeSeqno());
473
474     // Delete the 1st item
475     softDeleteOne(keys.at(0), MutationStatus::WasDirty);
476
477     // Run the tombstone purger. This should succeed, but with
478     // highestDeletedPurged unchanged.
479     ASSERT_EQ(1, mockEpheVB->purgeTombstones(0));
480     ASSERT_EQ(1, vbucket->getNumItems());
481     EXPECT_EQ(5, vbucket->getPurgeSeqno());
482 }
483
484 // Thread-safety test (intended to run via Valgrind / ASan / TSan) -
485 // perform sets and deletes on 2 additional threads while the purger
486 // runs constantly in the main thread.
487 TEST_F(EphTombstoneTest, ConcurrentPurge) {
488     ThreadGate started(2);
489     std::atomic<size_t> completed(0);
490
491     auto writer = [this](
492             ThreadGate& started, std::atomic<size_t>& completed, size_t id) {
493         started.threadUp();
494         for (size_t ii = 0; ii < 1000; ++ii) {
495             auto key = makeStoredDocKey(std::to_string(id) + ":key_" +
496                                         std::to_string(ii));
497             Item item(key, /*flags*/ 0, /*expiry*/ 0, key.data(), key.size());
498             public_processSet(item, item.getCas());
499             softDeleteOne(key, MutationStatus::WasDirty);
500         }
501         ++completed;
502     };
503     std::thread fe1{writer, std::ref(started), std::ref(completed), 1};
504     std::thread fe2{writer, std::ref(started), std::ref(completed), 2};
505
506     size_t purged = 0;
507     do {
508         purged += mockEpheVB->purgeTombstones(0);
509         std::this_thread::yield();
510     } while (completed != 2);
511
512     fe1.join();
513     fe2.join();
514 }
515
516 // Test that on a double-delete (delete with a different value) the deleted time
517 // is updated correctly.
518 TEST_F(EphTombstoneTest, DoubleDeleteTimeCorrect) {
519     // Delete the first item at +0s
520     auto key = keys.at(0);
521     softDeleteOne(key, MutationStatus::WasDirty);
522     auto* delOSV = findValue(key)->toOrderedStoredValue();
523     auto initialDelTime = delOSV->getDeletedTime();
524     ASSERT_EQ(2, delOSV->getRevSeqno()) << "Should be initial set + 1";
525
526     // Advance to non-zero time.
527     const int timeJump = 10;
528     TimeTraveller nonZero(timeJump);
529     ASSERT_GE(ep_current_time(), initialDelTime + timeJump)
530             << "Failed to advance at least " + std::to_string(timeJump) +
531                        " seconds from when initial delete "
532                        "occcured";
533
534     // Delete the same key again (delete-with-Value), checking the deleted
535     // time has changed.
536     Item item(key, 0, 0, "deleted", strlen("deleted"));
537     item.setDeleted();
538     ASSERT_EQ(MutationStatus::WasDirty, public_processSet(item, item.getCas()));
539     ASSERT_EQ(3, delOSV->getRevSeqno()) << "Should be initial set + 2";
540
541     auto secondDelTime = delOSV->getDeletedTime();
542     EXPECT_GE(secondDelTime, initialDelTime + timeJump);
543 }
544
545 TEST_F(EphemeralVBucketTest, UpdateUpdatesHighestDedupedSeqno) {
546     /* Add 3 items and then update all of them */
547     const int numItems = 3;
548
549     auto keys = generateKeys(numItems);
550     setMany(keys, MutationStatus::WasClean);
551
552     ASSERT_EQ(0, mockEpheVB->getLL()->getHighestDedupedSeqno());
553
554     /* Update the items */
555     setMany(keys, MutationStatus::WasDirty);
556
557     EXPECT_EQ(6, mockEpheVB->getLL()->getHighestDedupedSeqno());
558 }
559
560 TEST_F(EphemeralVBucketTest, AppendUpdatesHighestDedupedSeqno) {
561     /* Add 3 items and then update all of them */
562     const int numItems = 3;
563
564     auto keys = generateKeys(numItems);
565     setMany(keys, MutationStatus::WasClean);
566
567     ASSERT_EQ(0, mockEpheVB->getLL()->getHighestDedupedSeqno());
568
569     {
570         auto itr = mockEpheVB->getLL()->makeRangeIterator();
571
572         /* Update the items */
573         setMany(keys, MutationStatus::WasClean);
574     }
575
576     ASSERT_EQ(6, mockEpheVB->getLL()->getHighestDedupedSeqno());
577 }
578
579 TEST_F(EphemeralVBucketTest, SnapshotHasNoDuplicates) {
580     /* Add 2 items and then update all of them */
581     const int numItems = 2;
582
583     auto keys = generateKeys(numItems);
584     setMany(keys, MutationStatus::WasClean);
585
586     {
587         auto itr = mockEpheVB->getLL()->makeRangeIterator();
588
589         /* Update the items  */
590         setMany(keys, MutationStatus::WasClean);
591     }
592
593     // backfill to infinity would include both the stale and updated versions,
594     // ensure we receive the right number of items
595     auto res = mockEpheVB->inMemoryBackfill(
596             1, std::numeric_limits<seqno_t>::max());
597     EXPECT_EQ(ENGINE_SUCCESS, std::get<0>(res));
598     EXPECT_EQ(numItems, std::get<1>(res).size());
599     EXPECT_EQ(numItems * 2, std::get<2>(res));
600 }
601
602 TEST_F(EphemeralVBucketTest, SnapshotIncludesNonDuplicateStaleItems) {
603     /* Add 2 items and then update all of them */
604     const int numItems = 2;
605
606     auto keys = generateKeys(numItems);
607     setMany(keys, MutationStatus::WasClean);
608
609     {
610         auto itr = mockEpheVB->getLL()->makeRangeIterator();
611
612         /* Update the items  */
613         setMany(keys, MutationStatus::WasClean);
614     }
615
616     // backfill to numItems would /not/ include both the stale and updated
617     // versions, ensure we receive only one copy
618     auto res = mockEpheVB->inMemoryBackfill(1, numItems);
619     EXPECT_EQ(ENGINE_SUCCESS, std::get<0>(res));
620     EXPECT_EQ(numItems, std::get<1>(res).size());
621     EXPECT_EQ(numItems * 2, std::get<2>(res));
622 }
623
624 TEST_F(EphemeralVBucketTest, SnapshotHasNoDuplicatesWithInterveningItems) {
625     // Add 3 items, begin a rangeRead, add 1 more item, then update the first 2
626     const int numItems = 2;
627
628     auto keysToUpdate = generateKeys(numItems);
629     auto firstFillerKey = makeStoredDocKey(std::to_string(numItems + 1));
630     auto secondFillerKey = makeStoredDocKey(std::to_string(numItems + 2));
631
632     setMany(keysToUpdate, MutationStatus::WasClean);
633     EXPECT_EQ(MutationStatus::WasClean, setOne(firstFillerKey));
634
635     {
636         auto itr = mockEpheVB->getLL()->makeRangeIterator();
637
638         EXPECT_EQ(MutationStatus::WasClean, setOne(secondFillerKey));
639
640         /* Update the items  */
641         setMany(keysToUpdate, MutationStatus::WasClean);
642     }
643
644     // backfill to infinity would include both the stale and updated versions,
645     // ensure we receive the right number of items
646     auto res = mockEpheVB->inMemoryBackfill(
647             1, std::numeric_limits<seqno_t>::max());
648     EXPECT_EQ(ENGINE_SUCCESS, std::get<0>(res));
649     EXPECT_EQ(numItems * 2, std::get<1>(res).size()); // how many items returned
650     EXPECT_EQ(numItems * 3, std::get<2>(res)); // extended end of readRange
651 }
652
653 TEST_F(EphemeralVBucketTest, SnapshotHasNoDuplicatesWithMultipleStale) {
654     /* repeatedly update two items, ensure the backfill ignores all stale
655      * versions */
656     const int numItems = 2;
657     const int updateIterations = 10;
658
659     auto keys = generateKeys(numItems);
660
661     setMany(keys, MutationStatus::WasClean);
662     for (int i = 0; i < updateIterations; ++i) {
663         /* Set up a mock backfill, cover all items */
664         {
665             auto itr = mockEpheVB->getLL()->makeRangeIterator();
666             /* Update the items  */
667             setMany(keys, MutationStatus::WasClean);
668         }
669     }
670
671     // backfill to infinity would include both the stale and updated versions,
672     // ensure we receive the right number of items
673     auto res = mockEpheVB->inMemoryBackfill(
674             1, std::numeric_limits<seqno_t>::max());
675     EXPECT_EQ(ENGINE_SUCCESS, std::get<0>(res));
676     EXPECT_EQ(numItems, std::get<1>(res).size()); // how many items returned
677     EXPECT_EQ(numItems * (updateIterations + 1),
678               std::get<2>(res)); // extended end of readRange
679 }