1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "content/browser/indexed_db/indexed_db_tombstone_sweeper.h"
6 
7 #include <memory>
8 #include <string>
9 #include <utility>
10 
11 #include "base/files/scoped_temp_dir.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/test/metrics/histogram_tester.h"
15 #include "base/test/task_environment.h"
16 #include "base/time/tick_clock.h"
17 #include "components/services/storage/indexed_db/leveldb/leveldb_factory.h"
18 #include "components/services/storage/indexed_db/leveldb/mock_level_db.h"
19 #include "components/services/storage/indexed_db/scopes/leveldb_scopes.h"
20 #include "components/services/storage/indexed_db/scopes/varint_coding.h"
21 #include "components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_database.h"
22 #include "components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_factory.h"
23 #include "content/browser/indexed_db/indexed_db_class_factory.h"
24 #include "content/browser/indexed_db/indexed_db_leveldb_env.h"
25 #include "content/browser/indexed_db/indexed_db_leveldb_operations.h"
26 #include "testing/gmock/include/gmock/gmock.h"
27 #include "testing/gtest/include/gtest/gtest.h"
28 #include "third_party/blink/public/common/indexeddb/indexeddb_key.h"
29 #include "third_party/blink/public/common/indexeddb/indexeddb_key_path.h"
30 #include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h"
31 #include "third_party/leveldatabase/env_chromium.h"
32 #include "third_party/leveldatabase/src/include/leveldb/db.h"
33 #include "third_party/leveldatabase/src/include/leveldb/filter_policy.h"
34 #include "third_party/leveldatabase/src/include/leveldb/slice.h"
35 
36 using blink::IndexedDBDatabaseMetadata;
37 using blink::IndexedDBIndexMetadata;
38 using blink::IndexedDBKey;
39 using blink::IndexedDBKeyPath;
40 using blink::IndexedDBObjectStoreMetadata;
41 
42 namespace content {
43 class BrowserContext;
44 
45 namespace indexed_db_tombstone_sweeper_unittest {
46 using ::testing::_;
47 using ::testing::Eq;
48 using ::testing::Return;
49 using ::testing::StrictMock;
50 using Status = ::leveldb::Status;
51 using Slice = ::leveldb::Slice;
52 
53 constexpr int kRoundIterations = 11;
54 constexpr int kMaxIterations = 100;
55 const base::TimeTicks kTaskStartTime =
56     base::TimeTicks() + base::TimeDelta::FromSeconds(1);
57 const base::TimeTicks kTaskEndTime =
58     base::TimeTicks() + base::TimeDelta::FromSeconds(2);
59 
60 constexpr int64_t kDb1 = 1;
61 constexpr int64_t kDb2 = 1;
62 constexpr int64_t kOs1 = 3;
63 constexpr int64_t kOs2 = 5;
64 constexpr int64_t kOs3 = 8;
65 constexpr int64_t kOs4 = 9;
66 constexpr int64_t kIndex1 = 31;
67 constexpr int64_t kIndex2 = 32;
68 constexpr int64_t kIndex3 = 35;
69 constexpr int kTombstoneSize = 33;
70 
71 MATCHER_P(SliceEq,
72           str,
73           std::string(negation ? "isn't" : "is") + " equal to " +
74               base::HexEncode(str.data(), str.size())) {
75   *result_listener << "which is " << base::HexEncode(arg.data(), arg.size());
76   return std::string(arg.data(), arg.size()) == str;
77 }
78 
79 class MockTickClock : public base::TickClock {
80  public:
MockTickClock()81   MockTickClock() {}
~MockTickClock()82   ~MockTickClock() override {}
83 
84   MOCK_CONST_METHOD0(NowTicks, base::TimeTicks());
85 };
86 
87 class IndexedDBTombstoneSweeperTest : public testing::Test {
88  public:
IndexedDBTombstoneSweeperTest()89   IndexedDBTombstoneSweeperTest() {}
~IndexedDBTombstoneSweeperTest()90   ~IndexedDBTombstoneSweeperTest() {}
91 
PopulateMultiDBMetdata()92   void PopulateMultiDBMetdata() {
93     // db1
94     //   os1
95     //   os2
96     //     index1
97     //     index2
98     metadata_.emplace_back(base::ASCIIToUTF16("db1"), kDb1, 1, 29);
99     auto& db1 = metadata_.back();
100     db1.object_stores[kOs1] = IndexedDBObjectStoreMetadata(
101         base::ASCIIToUTF16("os1"), kOs1, IndexedDBKeyPath(), false, 1000);
102     db1.object_stores[kOs2] = IndexedDBObjectStoreMetadata(
103         base::ASCIIToUTF16("os2"), kOs2, IndexedDBKeyPath(), false, 1000);
104     auto& os2 = db1.object_stores[kOs2];
105     os2.indexes[kIndex1] = IndexedDBIndexMetadata(
106         base::ASCIIToUTF16("index1"), kIndex1, IndexedDBKeyPath(), true, false);
107     os2.indexes[kIndex2] = IndexedDBIndexMetadata(
108         base::ASCIIToUTF16("index2"), kIndex2, IndexedDBKeyPath(), true, false);
109     // db2
110     //   os3
111     //     index3
112     //   os4
113     metadata_.emplace_back(base::ASCIIToUTF16("db2"), kDb2, 1, 29);
114     auto& db2 = metadata_.back();
115     db2.object_stores[kOs3] = IndexedDBObjectStoreMetadata(
116         base::ASCIIToUTF16("os3"), kOs3, IndexedDBKeyPath(), false, 1000);
117     db2.object_stores[kOs4] = IndexedDBObjectStoreMetadata(
118         base::ASCIIToUTF16("os4"), kOs4, IndexedDBKeyPath(), false, 1000);
119     auto& os3 = db2.object_stores[kOs3];
120     os3.indexes[kIndex3] = IndexedDBIndexMetadata(
121         base::ASCIIToUTF16("index3"), kIndex3, IndexedDBKeyPath(), true, false);
122   }
123 
PopulateSingleIndexDBMetadata()124   void PopulateSingleIndexDBMetadata() {
125     // db1
126     //   os1
127     //     index1
128     metadata_.emplace_back(base::ASCIIToUTF16("db1"), kDb1, 1, 29);
129     auto& db1 = metadata_.back();
130     db1.object_stores[kOs1] = IndexedDBObjectStoreMetadata(
131         base::ASCIIToUTF16("os1"), kOs1, IndexedDBKeyPath(), false, 1000);
132     auto& os2 = db1.object_stores[kOs1];
133     os2.indexes[kIndex1] = IndexedDBIndexMetadata(
134         base::ASCIIToUTF16("index1"), kIndex1, IndexedDBKeyPath(), true, false);
135   }
136 
SetupMockDB()137   void SetupMockDB() {
138     sweeper_ = std::make_unique<IndexedDBTombstoneSweeper>(
139         kRoundIterations, kMaxIterations, &mock_db_);
140     sweeper_->SetStartSeedsForTesting(0, 0, 0);
141   }
142 
SetupRealDB()143   void SetupRealDB() {
144     scoped_refptr<LevelDBState> level_db_state;
145     leveldb::Status s;
146     std::tie(level_db_state, s, std::ignore) =
147         IndexedDBClassFactory::Get()->leveldb_factory().OpenLevelDBState(
148             base::FilePath(), indexed_db::GetDefaultLevelDBComparator(),
149             /* create_if_missing=*/true);
150     ASSERT_TRUE(s.ok());
151     in_memory_db_ =
152         IndexedDBClassFactory::Get()
153             ->transactional_leveldb_factory()
154             .CreateLevelDBDatabase(std::move(level_db_state), nullptr, nullptr,
155                                    TransactionalLevelDBDatabase::
156                                        kDefaultMaxOpenIteratorsPerDatabase);
157     sweeper_ = std::make_unique<IndexedDBTombstoneSweeper>(
158         kRoundIterations, kMaxIterations, in_memory_db_->db());
159     sweeper_->SetStartSeedsForTesting(0, 0, 0);
160   }
161 
SetClockExpectations()162   void SetClockExpectations() {
163     EXPECT_CALL(tick_clock_, NowTicks())
164         .WillOnce(testing::Return(kTaskStartTime))
165         .WillOnce(testing::Return(kTaskEndTime));
166     sweeper_->SetClockForTesting(&tick_clock_);
167   }
168 
SetFirstClockExpectation()169   void SetFirstClockExpectation() {
170     EXPECT_CALL(tick_clock_, NowTicks())
171         .WillOnce(testing::Return(kTaskStartTime));
172     sweeper_->SetClockForTesting(&tick_clock_);
173   }
174 
ExpectUmaTombstones(int num,int size,bool reached_max=false)175   void ExpectUmaTombstones(int num, int size, bool reached_max = false) {
176     std::string category = reached_max ? "MaxIterations" : "Complete";
177     histogram_tester_.ExpectUniqueSample(
178         "WebCore.IndexedDB.TombstoneSweeper.NumDeletedTombstones." + category,
179         num, 1);
180     histogram_tester_.ExpectUniqueSample(
181         "WebCore.IndexedDB.TombstoneSweeper.DeletedTombstonesSize." + category,
182         size, 1);
183   }
184 
ExpectTaskTimeRecorded()185   void ExpectTaskTimeRecorded() {
186     histogram_tester_.ExpectTimeBucketCount(
187         "WebCore.IndexedDB.TombstoneSweeper.DeletionTotalTime.Complete",
188         base::TimeDelta::FromSeconds(1), 1);
189   }
190 
ExpectIndexEntry(leveldb::MockIterator & iterator,int64_t db,int64_t os,int64_t index,const IndexedDBKey & index_key,const IndexedDBKey & primary_key,int index_version)191   void ExpectIndexEntry(leveldb::MockIterator& iterator,
192                         int64_t db,
193                         int64_t os,
194                         int64_t index,
195                         const IndexedDBKey& index_key,
196                         const IndexedDBKey& primary_key,
197                         int index_version) {
198     testing::InSequence sequence_enforcer;
199 
200     EXPECT_CALL(iterator, key())
201         .WillOnce(Return(
202             IndexDataKey::Encode(db, os, index, index_key, primary_key)));
203     std::string value_str;
204     EncodeVarInt(index_version, &value_str);
205     EncodeIDBKey(primary_key, &value_str);
206     EXPECT_CALL(iterator, value()).WillOnce(Return(value_str));
207   }
208 
ExpectIndexAndExistsEntries(leveldb::MockIterator & iterator,int64_t db,int64_t os,int64_t index,const IndexedDBKey & index_key,const IndexedDBKey & primary_key,int index_version,int exists_version)209   void ExpectIndexAndExistsEntries(leveldb::MockIterator& iterator,
210                                    int64_t db,
211                                    int64_t os,
212                                    int64_t index,
213                                    const IndexedDBKey& index_key,
214                                    const IndexedDBKey& primary_key,
215                                    int index_version,
216                                    int exists_version) {
217     ExpectIndexEntry(iterator, db, os, index, index_key, primary_key,
218                      index_version);
219 
220     testing::InSequence sequence_enforcer;
221 
222     std::string encoded_primary_key;
223     EncodeIDBKey(primary_key, &encoded_primary_key);
224 
225     std::string exists_value;
226     EncodeVarInt(exists_version, &exists_value);
227     EXPECT_CALL(
228         mock_db_,
229         Get(_, SliceEq(ExistsEntryKey::Encode(db, os, encoded_primary_key)), _))
230         .WillOnce(testing::DoAll(testing::SetArgPointee<2>(exists_value),
231                                  Return(Status::OK())));
232   }
233 
234  protected:
235   std::unique_ptr<TransactionalLevelDBDatabase> in_memory_db_;
236   leveldb::MockLevelDB mock_db_;
237 
238   std::unique_ptr<IndexedDBTombstoneSweeper> sweeper_;
239 
240   StrictMock<MockTickClock> tick_clock_;
241 
242   std::vector<IndexedDBDatabaseMetadata> metadata_;
243 
244   // Used to verify recorded data.
245   base::HistogramTester histogram_tester_;
246 
247  private:
248   base::test::TaskEnvironment task_environment_;
249 };
250 
TEST_F(IndexedDBTombstoneSweeperTest,EmptyDB)251 TEST_F(IndexedDBTombstoneSweeperTest, EmptyDB) {
252   SetupMockDB();
253   sweeper_->SetMetadata(&metadata_);
254   EXPECT_TRUE(sweeper_->RunRound());
255 
256   EXPECT_TRUE(
257       histogram_tester_.GetTotalCountsForPrefix("WebCore.IndexedDB.").empty());
258 }
259 
TEST_F(IndexedDBTombstoneSweeperTest,NoTombstonesComplexDB)260 TEST_F(IndexedDBTombstoneSweeperTest, NoTombstonesComplexDB) {
261   SetupMockDB();
262   PopulateMultiDBMetdata();
263   sweeper_->SetMetadata(&metadata_);
264   SetClockExpectations();
265 
266   // We'll have one index entry per index, and simulate reaching the end.
267   leveldb::MockIterator* mock_iterator = new leveldb::MockIterator();
268   EXPECT_CALL(mock_db_, NewIterator(testing::_))
269       .WillOnce(testing::Return(mock_iterator));
270 
271   // First index.
272   {
273     testing::InSequence sequence_enforcer;
274     EXPECT_CALL(*mock_iterator,
275                 Seek(SliceEq(IndexDataKey::EncodeMinKey(kDb1, kOs2, kIndex1))));
276     EXPECT_CALL(*mock_iterator, Valid()).Times(2).WillRepeatedly(Return(true));
277 
278     ExpectIndexAndExistsEntries(
279         *mock_iterator, kDb1, kOs2, kIndex1,
280         IndexedDBKey(10, blink::mojom::IDBKeyType::Number),
281         IndexedDBKey(20, blink::mojom::IDBKeyType::Number), 1, 1);
282     EXPECT_CALL(*mock_iterator, Next());
283 
284     EXPECT_CALL(*mock_iterator, Valid()).Times(2).WillRepeatedly(Return(true));
285     // Return the beginning of the second index, which should cause us to error
286     // & go restart our index seek.
287     ExpectIndexEntry(*mock_iterator, kDb1, kOs2, kIndex2,
288                      IndexedDBKey(30, blink::mojom::IDBKeyType::Number),
289                      IndexedDBKey(10, blink::mojom::IDBKeyType::Number), 1);
290   }
291 
292   // Second index.
293   {
294     testing::InSequence sequence_enforcer;
295     EXPECT_CALL(*mock_iterator,
296                 Seek(SliceEq(IndexDataKey::EncodeMinKey(kDb1, kOs2, kIndex2))));
297     EXPECT_CALL(*mock_iterator, Valid()).Times(2).WillRepeatedly(Return(true));
298     ExpectIndexAndExistsEntries(
299         *mock_iterator, kDb1, kOs2, kIndex2,
300         IndexedDBKey(30, blink::mojom::IDBKeyType::Number),
301         IndexedDBKey(10, blink::mojom::IDBKeyType::Number), 1, 1);
302     EXPECT_CALL(*mock_iterator, Next());
303 
304     // Return next key, which should make it error
305     EXPECT_CALL(*mock_iterator, Valid()).Times(2).WillRepeatedly(Return(true));
306     ExpectIndexEntry(*mock_iterator, kDb2, kOs3, kIndex3,
307                      IndexedDBKey(1501, blink::mojom::IDBKeyType::Number),
308                      IndexedDBKey(15123, blink::mojom::IDBKeyType::Number), 12);
309   }
310 
311   // Third index.
312   {
313     testing::InSequence sequence_enforcer;
314     EXPECT_CALL(*mock_iterator,
315                 Seek(SliceEq(IndexDataKey::EncodeMinKey(kDb2, kOs3, kIndex3))));
316     EXPECT_CALL(*mock_iterator, Valid()).Times(2).WillRepeatedly(Return(true));
317     ExpectIndexAndExistsEntries(
318         *mock_iterator, kDb2, kOs3, kIndex3,
319         IndexedDBKey(1501, blink::mojom::IDBKeyType::Number),
320         IndexedDBKey(15123, blink::mojom::IDBKeyType::Number), 12, 12);
321     EXPECT_CALL(*mock_iterator, Next());
322 
323     // Return next key, which should make it error
324     EXPECT_CALL(*mock_iterator, Valid()).WillOnce(Return(false));
325     EXPECT_CALL(*mock_iterator, status()).WillOnce(Return(Status::OK()));
326     EXPECT_CALL(*mock_iterator, Valid()).WillOnce(Return(false));
327   }
328 
329   ASSERT_TRUE(sweeper_->RunRound());
330   ExpectTaskTimeRecorded();
331   ExpectUmaTombstones(0, 0);
332   histogram_tester_.ExpectUniqueSample(
333       "WebCore.IndexedDB.TombstoneSweeper.IndexScanPercent", 20, 1);
334 }
335 
TEST_F(IndexedDBTombstoneSweeperTest,AllTombstonesComplexDB)336 TEST_F(IndexedDBTombstoneSweeperTest, AllTombstonesComplexDB) {
337   SetupMockDB();
338   PopulateMultiDBMetdata();
339   sweeper_->SetMetadata(&metadata_);
340   SetClockExpectations();
341 
342   // We'll have one index entry per index, and simulate reaching the end.
343   leveldb::MockIterator* mock_iterator = new leveldb::MockIterator();
344   EXPECT_CALL(mock_db_, NewIterator(testing::_))
345       .WillOnce(testing::Return(mock_iterator));
346 
347   // First index.
348   {
349     testing::InSequence sequence_enforcer;
350     EXPECT_CALL(*mock_iterator,
351                 Seek(SliceEq(IndexDataKey::EncodeMinKey(kDb1, kOs2, kIndex1))));
352     EXPECT_CALL(*mock_iterator, Valid()).Times(2).WillRepeatedly(Return(true));
353 
354     ExpectIndexAndExistsEntries(
355         *mock_iterator, kDb1, kOs2, kIndex1,
356         IndexedDBKey(10, blink::mojom::IDBKeyType::Number),
357         IndexedDBKey(20, blink::mojom::IDBKeyType::Number), 1, 2);
358     EXPECT_CALL(*mock_iterator, Next());
359 
360     EXPECT_CALL(*mock_iterator, Valid()).Times(2).WillRepeatedly(Return(true));
361     // Return the beginning of the second index, which should cause us to error
362     // & go restart our index seek.
363     ExpectIndexEntry(*mock_iterator, kDb1, kOs2, kIndex2,
364                      IndexedDBKey(30, blink::mojom::IDBKeyType::Number),
365                      IndexedDBKey(10, blink::mojom::IDBKeyType::Number), 1);
366   }
367 
368   // Second index.
369   {
370     testing::InSequence sequence_enforcer;
371     EXPECT_CALL(*mock_iterator,
372                 Seek(SliceEq(IndexDataKey::EncodeMinKey(kDb1, kOs2, kIndex2))));
373     EXPECT_CALL(*mock_iterator, Valid()).Times(2).WillRepeatedly(Return(true));
374     ExpectIndexAndExistsEntries(
375         *mock_iterator, kDb1, kOs2, kIndex2,
376         IndexedDBKey(30, blink::mojom::IDBKeyType::Number),
377         IndexedDBKey(10, blink::mojom::IDBKeyType::Number), 1, 2);
378     EXPECT_CALL(*mock_iterator, Next());
379 
380     // Return next key, which should make it error
381     EXPECT_CALL(*mock_iterator, Valid()).Times(2).WillRepeatedly(Return(true));
382     ExpectIndexEntry(*mock_iterator, kDb2, kOs3, kIndex3,
383                      IndexedDBKey(1501, blink::mojom::IDBKeyType::Number),
384                      IndexedDBKey(15123, blink::mojom::IDBKeyType::Number), 12);
385   }
386 
387   // Third index.
388   {
389     testing::InSequence sequence_enforcer;
390     EXPECT_CALL(*mock_iterator,
391                 Seek(SliceEq(IndexDataKey::EncodeMinKey(kDb2, kOs3, kIndex3))));
392     EXPECT_CALL(*mock_iterator, Valid()).Times(2).WillRepeatedly(Return(true));
393     ExpectIndexAndExistsEntries(
394         *mock_iterator, kDb2, kOs3, kIndex3,
395         IndexedDBKey(1501, blink::mojom::IDBKeyType::Number),
396         IndexedDBKey(15123, blink::mojom::IDBKeyType::Number), 12, 13);
397     EXPECT_CALL(*mock_iterator, Next());
398 
399     // Return next key, which should make it error
400     EXPECT_CALL(*mock_iterator, Valid()).WillOnce(Return(false));
401     EXPECT_CALL(*mock_iterator, status()).WillOnce(Return(Status::OK()));
402     EXPECT_CALL(*mock_iterator, Valid()).WillOnce(Return(false));
403   }
404 
405   EXPECT_CALL(mock_db_, Write(_, _));
406 
407   ASSERT_TRUE(sweeper_->RunRound());
408   ExpectTaskTimeRecorded();
409   ExpectUmaTombstones(3, kTombstoneSize * 3);
410   histogram_tester_.ExpectUniqueSample(
411       "WebCore.IndexedDB.TombstoneSweeper.IndexScanPercent", 20, 1);
412 }
413 
TEST_F(IndexedDBTombstoneSweeperTest,SimpleRealDBNoTombstones)414 TEST_F(IndexedDBTombstoneSweeperTest, SimpleRealDBNoTombstones) {
415   PopulateSingleIndexDBMetadata();
416   SetupRealDB();
417   sweeper_->SetMetadata(&metadata_);
418   SetClockExpectations();
419 
420   for (int i = 0; i < kRoundIterations; i++) {
421     auto index_key = IndexedDBKey(i, blink::mojom::IDBKeyType::Number);
422     auto primary_key = IndexedDBKey(i + 1, blink::mojom::IDBKeyType::Number);
423     std::string value_str;
424     EncodeVarInt(1, &value_str);
425     EncodeIDBKey(primary_key, &value_str);
426     in_memory_db_->Put(
427         IndexDataKey::Encode(kDb1, kOs1, kIndex1, index_key, primary_key),
428         &value_str);
429 
430     std::string exists_value;
431     std::string encoded_primary_key;
432     EncodeIDBKey(primary_key, &encoded_primary_key);
433     EncodeVarInt(1, &exists_value);
434     in_memory_db_->Put(ExistsEntryKey::Encode(kDb1, kOs1, encoded_primary_key),
435                        &exists_value);
436   }
437 
438   ASSERT_FALSE(sweeper_->RunRound());
439   EXPECT_TRUE(sweeper_->RunRound());
440 
441   ExpectTaskTimeRecorded();
442   ExpectUmaTombstones(0, 0);
443   histogram_tester_.ExpectUniqueSample(
444       "WebCore.IndexedDB.TombstoneSweeper.IndexScanPercent", 20, 1);
445 }
446 
TEST_F(IndexedDBTombstoneSweeperTest,SimpleRealDBWithTombstones)447 TEST_F(IndexedDBTombstoneSweeperTest, SimpleRealDBWithTombstones) {
448   PopulateSingleIndexDBMetadata();
449   SetupRealDB();
450   sweeper_->SetMetadata(&metadata_);
451   SetClockExpectations();
452 
453   int tombstones = 0;
454   for (int i = 0; i < kRoundIterations + 1; i++) {
455     auto index_key = IndexedDBKey(i, blink::mojom::IDBKeyType::Number);
456     auto primary_key = IndexedDBKey(i + 1, blink::mojom::IDBKeyType::Number);
457     std::string value_str;
458     EncodeVarInt(1, &value_str);
459     EncodeIDBKey(primary_key, &value_str);
460     in_memory_db_->Put(
461         IndexDataKey::Encode(kDb1, kOs1, kIndex1, index_key, primary_key),
462         &value_str);
463 
464     std::string exists_value;
465     std::string encoded_primary_key;
466     EncodeIDBKey(primary_key, &encoded_primary_key);
467     bool tombstone = i % 2 != 0;
468     tombstones += i % 2 ? 1 : 0;
469     EncodeVarInt(tombstone ? 2 : 1, &exists_value);
470     in_memory_db_->Put(ExistsEntryKey::Encode(kDb1, kOs1, encoded_primary_key),
471                        &exists_value);
472   }
473 
474   ASSERT_FALSE(sweeper_->RunRound());
475   EXPECT_TRUE(sweeper_->RunRound());
476 
477   ExpectTaskTimeRecorded();
478   ExpectUmaTombstones(tombstones, kTombstoneSize * tombstones);
479   histogram_tester_.ExpectUniqueSample(
480       "WebCore.IndexedDB.TombstoneSweeper.IndexScanPercent", 20, 1);
481 
482   for (int i = 0; i < kRoundIterations + 1; i++) {
483     if (i % 2 == 1) {
484       std::string out;
485       bool found = false;
486       auto index_key = IndexedDBKey(i, blink::mojom::IDBKeyType::Number);
487       auto primary_key = IndexedDBKey(i + 1, blink::mojom::IDBKeyType::Number);
488       EXPECT_TRUE(in_memory_db_
489                       ->Get(IndexDataKey::Encode(kDb1, kOs1, kIndex1, index_key,
490                                                  primary_key),
491                             &out, &found)
492                       .ok());
493       EXPECT_TRUE(!found);
494     }
495   }
496 }
497 
TEST_F(IndexedDBTombstoneSweeperTest,HitMaxIters)498 TEST_F(IndexedDBTombstoneSweeperTest, HitMaxIters) {
499   PopulateSingleIndexDBMetadata();
500   SetupRealDB();
501   sweeper_->SetMetadata(&metadata_);
502   SetFirstClockExpectation();
503 
504   for (int i = 0; i < kMaxIterations + 1; i++) {
505     auto index_key = IndexedDBKey(i, blink::mojom::IDBKeyType::Number);
506     auto primary_key = IndexedDBKey(i + 1, blink::mojom::IDBKeyType::Number);
507     std::string value_str;
508     EncodeVarInt(1, &value_str);
509     EncodeIDBKey(primary_key, &value_str);
510     in_memory_db_->Put(
511         IndexDataKey::Encode(kDb1, kOs1, kIndex1, index_key, primary_key),
512         &value_str);
513 
514     std::string exists_value;
515     std::string encoded_primary_key;
516     EncodeIDBKey(primary_key, &encoded_primary_key);
517     EncodeVarInt(i % 2 == 0 ? 2 : 1, &exists_value);
518     in_memory_db_->Put(ExistsEntryKey::Encode(kDb1, kOs1, encoded_primary_key),
519                        &exists_value);
520   }
521 
522   while (!sweeper_->RunRound())
523     ;
524 
525   ExpectUmaTombstones(41, kTombstoneSize * 41, true);
526   histogram_tester_.ExpectUniqueSample(
527       "WebCore.IndexedDB.TombstoneSweeper.IndexScanPercent", 0, 1);
528 }
529 
TEST_F(IndexedDBTombstoneSweeperTest,LevelDBError)530 TEST_F(IndexedDBTombstoneSweeperTest, LevelDBError) {
531   SetupMockDB();
532   PopulateMultiDBMetdata();
533   sweeper_->SetMetadata(&metadata_);
534   SetFirstClockExpectation();
535 
536   // We'll have one index entry per index, and simulate reaching the end.
537   leveldb::MockIterator* mock_iterator = new leveldb::MockIterator();
538   EXPECT_CALL(mock_db_, NewIterator(testing::_))
539       .WillOnce(testing::Return(mock_iterator));
540 
541   // First index.
542   {
543     testing::InSequence sequence_enforcer;
544     EXPECT_CALL(*mock_iterator,
545                 Seek(SliceEq(IndexDataKey::EncodeMinKey(kDb1, kOs2, kIndex1))));
546     EXPECT_CALL(*mock_iterator, Valid()).Times(2).WillRepeatedly(Return(true));
547 
548     ExpectIndexAndExistsEntries(
549         *mock_iterator, kDb1, kOs2, kIndex1,
550         IndexedDBKey(10, blink::mojom::IDBKeyType::Number),
551         IndexedDBKey(20, blink::mojom::IDBKeyType::Number), 1, 1);
552     EXPECT_CALL(*mock_iterator, Next());
553 
554     EXPECT_CALL(*mock_iterator, Valid()).Times(2).WillRepeatedly(Return(true));
555     // Return the beginning of the second index, which should cause us to error
556     // & go restart our index seek.
557     ExpectIndexEntry(*mock_iterator, kDb1, kOs2, kIndex2,
558                      IndexedDBKey(30, blink::mojom::IDBKeyType::Number),
559                      IndexedDBKey(10, blink::mojom::IDBKeyType::Number), 1);
560   }
561 
562   // Second index.
563   {
564     testing::InSequence sequence_enforcer;
565     EXPECT_CALL(*mock_iterator,
566                 Seek(SliceEq(IndexDataKey::EncodeMinKey(kDb1, kOs2, kIndex2))));
567     EXPECT_CALL(*mock_iterator, Valid()).Times(2).WillRepeatedly(Return(true));
568     ExpectIndexAndExistsEntries(
569         *mock_iterator, kDb1, kOs2, kIndex2,
570         IndexedDBKey(30, blink::mojom::IDBKeyType::Number),
571         IndexedDBKey(10, blink::mojom::IDBKeyType::Number), 1, 1);
572     EXPECT_CALL(*mock_iterator, Next());
573 
574     // Return read error.
575     EXPECT_CALL(*mock_iterator, Valid()).WillOnce(Return(false));
576     EXPECT_CALL(*mock_iterator, status())
577         .WillOnce(Return(Status::Corruption("Test error")));
578   }
579 
580   ASSERT_TRUE(sweeper_->RunRound());
581 
582   histogram_tester_.ExpectUniqueSample(
583       "WebCore.IndexedDB.TombstoneSweeper.SweepError",
584       leveldb_env::GetLevelDBStatusUMAValue(Status::Corruption("")), 1);
585   // Only finished scanning the first index.
586   histogram_tester_.ExpectUniqueSample(
587       "WebCore.IndexedDB.TombstoneSweeper.IndexScanPercent", 1 * 20 / 3, 1);
588 }
589 
590 }  // namespace indexed_db_tombstone_sweeper_unittest
591 }  // namespace content
592