blob: 373fe388ce64c960627362f5d19b77893fe5adfd [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <algorithm>
#include <cstring>
#include <string>
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/string_piece.h"
#include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
#include "content/browser/indexed_db/leveldb/leveldb_database.h"
#include "content/browser/indexed_db/leveldb/leveldb_env.h"
#include "content/browser/indexed_db/leveldb/leveldb_iterator.h"
#include "content/browser/indexed_db/leveldb/leveldb_transaction.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/leveldatabase/env_chromium.h"
namespace content {
namespace {
static const size_t kTestingMaxOpenCursors = 3;
class SimpleComparator : public LevelDBComparator {
public:
int Compare(const base::StringPiece& a,
const base::StringPiece& b) const override {
size_t len = std::min(a.size(), b.size());
return memcmp(a.begin(), b.begin(), len);
}
const char* Name() const override { return "temp_comparator"; }
};
} // namespace
class LevelDBTransactionTest : public testing::Test {
public:
LevelDBTransactionTest() {}
void SetUp() override {
ASSERT_TRUE(temp_directory_.CreateUniqueTempDir());
leveldb::Status s =
LevelDBDatabase::Open(temp_directory_.GetPath(), &comparator_,
kTestingMaxOpenCursors, &leveldb_);
ASSERT_TRUE(s.ok());
ASSERT_TRUE(leveldb_);
}
void TearDown() override {}
protected:
// Convenience methods to access the database outside any
// transaction to cut down on boilerplate around calls.
void Put(const base::StringPiece& key, const std::string& value) {
std::string put_value = value;
leveldb::Status s = leveldb_->Put(key, &put_value);
ASSERT_TRUE(s.ok());
}
void Remove(const base::StringPiece& key) {
leveldb::Status s = leveldb_->Remove(key);
ASSERT_TRUE(s.ok());
}
void Get(const base::StringPiece& key, std::string* value, bool* found) {
leveldb::Status s = leveldb_->Get(key, value, found);
ASSERT_TRUE(s.ok());
}
bool Has(const base::StringPiece& key) {
bool found;
std::string value;
leveldb::Status s = leveldb_->Get(key, &value, &found);
EXPECT_TRUE(s.ok());
return found;
}
// Convenience wrappers for LevelDBTransaction operations to
// avoid boilerplate in tests.
bool TransactionHas(LevelDBTransaction* transaction,
const base::StringPiece& key) {
std::string value;
bool found;
leveldb::Status s = transaction->Get(key, &value, &found);
EXPECT_TRUE(s.ok());
return found;
}
void TransactionPut(LevelDBTransaction* transaction,
const base::StringPiece& key,
const std::string& value) {
std::string put_value = value;
transaction->Put(key, &put_value);
}
int Compare(const base::StringPiece& a, const base::StringPiece& b) const {
return comparator_.Compare(a, b);
}
LevelDBDatabase* db() { return leveldb_.get(); }
scoped_refptr<LevelDBTransaction> CreateTransaction() {
return new LevelDBTransaction(db());
}
static constexpr size_t SizeOfRecord() {
return sizeof(LevelDBTransaction::Record);
}
private:
base::ScopedTempDir temp_directory_;
SimpleComparator comparator_;
std::unique_ptr<LevelDBDatabase> leveldb_;
DISALLOW_COPY_AND_ASSIGN(LevelDBTransactionTest);
};
TEST_F(LevelDBTransactionTest, GetPutDelete) {
leveldb::Status status;
const std::string key("key");
std::string got_value;
const std::string old_value("value");
Put(key, old_value);
scoped_refptr<LevelDBTransaction> transaction = new LevelDBTransaction(db());
const std::string new_value("new value");
Put(key, new_value);
bool found = false;
status = transaction->Get(key, &got_value, &found);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(found);
EXPECT_EQ(Compare(got_value, old_value), 0);
Get(key, &got_value, &found);
EXPECT_TRUE(found);
EXPECT_EQ(Compare(got_value, new_value), 0);
const std::string added_key("added key");
const std::string added_value("added value");
Put(added_key, added_value);
Get(added_key, &got_value, &found);
EXPECT_TRUE(found);
EXPECT_EQ(Compare(got_value, added_value), 0);
EXPECT_FALSE(TransactionHas(transaction.get(), added_key));
const std::string another_key("another key");
const std::string another_value("another value");
EXPECT_EQ(0ull, transaction->GetTransactionSize());
TransactionPut(transaction.get(), another_key, another_value);
EXPECT_EQ(SizeOfRecord() + another_key.size() * 2 + another_value.size(),
transaction->GetTransactionSize());
status = transaction->Get(another_key, &got_value, &found);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(found);
EXPECT_EQ(Compare(got_value, another_value), 0);
transaction->Remove(another_key);
EXPECT_EQ(SizeOfRecord() + another_key.size() * 2,
transaction->GetTransactionSize());
}
TEST_F(LevelDBTransactionTest, Iterator) {
const std::string key1("key1");
const std::string value1("value1");
const std::string key2("key2");
const std::string value2("value2");
Put(key1, value1);
Put(key2, value2);
scoped_refptr<LevelDBTransaction> transaction = new LevelDBTransaction(db());
Remove(key2);
std::unique_ptr<LevelDBIterator> it = transaction->CreateIterator();
it->Seek(std::string());
EXPECT_TRUE(it->IsValid());
EXPECT_EQ(Compare(it->Key(), key1), 0);
EXPECT_EQ(Compare(it->Value(), value1), 0);
it->Next();
EXPECT_TRUE(it->IsValid());
EXPECT_EQ(Compare(it->Key(), key2), 0);
EXPECT_EQ(Compare(it->Value(), value2), 0);
it->Next();
EXPECT_FALSE(it->IsValid());
}
TEST_F(LevelDBTransactionTest, Commit) {
const std::string key1("key1");
const std::string key2("key2");
const std::string value1("value1");
const std::string value2("value2");
const std::string value3("value3");
std::string got_value;
bool found;
scoped_refptr<LevelDBTransaction> transaction = new LevelDBTransaction(db());
TransactionPut(transaction.get(), key1, value1);
TransactionPut(transaction.get(), key2, value2);
TransactionPut(transaction.get(), key2, value3);
leveldb::Status status = transaction->Commit();
EXPECT_TRUE(status.ok());
Get(key1, &got_value, &found);
EXPECT_TRUE(found);
EXPECT_EQ(value1, got_value);
Get(key2, &got_value, &found);
EXPECT_TRUE(found);
EXPECT_EQ(value3, got_value);
}
TEST_F(LevelDBTransactionTest, IterationWithEvictedCursors) {
leveldb::Status status;
Put("key1", "value1");
Put("key2", "value2");
Put("key3", "value3");
scoped_refptr<LevelDBTransaction> transaction = CreateTransaction();
std::unique_ptr<LevelDBIterator> evicted_normal_location =
transaction->CreateIterator();
std::unique_ptr<LevelDBIterator> evicted_before_start =
transaction->CreateIterator();
std::unique_ptr<LevelDBIterator> evicted_after_end =
transaction->CreateIterator();
std::unique_ptr<LevelDBIterator> it1 = transaction->CreateIterator();
std::unique_ptr<LevelDBIterator> it2 = transaction->CreateIterator();
std::unique_ptr<LevelDBIterator> it3 = transaction->CreateIterator();
evicted_normal_location->Seek("key1");
evicted_before_start->Seek("key1");
evicted_before_start->Prev();
evicted_after_end->SeekToLast();
evicted_after_end->Next();
// Nothing is purged, as we just have 3 iterators used.
EXPECT_FALSE(evicted_normal_location->IsDetached());
EXPECT_FALSE(evicted_before_start->IsDetached());
EXPECT_FALSE(evicted_after_end->IsDetached());
// Should purge all of our earlier iterators.
it1->Seek("key1");
it2->Seek("key2");
it3->Seek("key3");
EXPECT_TRUE(evicted_normal_location->IsDetached());
EXPECT_TRUE(evicted_before_start->IsDetached());
EXPECT_TRUE(evicted_after_end->IsDetached());
EXPECT_FALSE(evicted_before_start->IsValid());
EXPECT_TRUE(evicted_normal_location->IsValid());
EXPECT_FALSE(evicted_after_end->IsValid());
// Check we don't need to reload for just the key.
EXPECT_EQ("key1", evicted_normal_location->Key());
EXPECT_TRUE(evicted_normal_location->IsDetached());
// Make sure iterators are reloaded correctly.
EXPECT_TRUE(evicted_normal_location->IsValid());
EXPECT_EQ("value1", evicted_normal_location->Value());
EXPECT_FALSE(evicted_normal_location->IsDetached());
EXPECT_FALSE(evicted_before_start->IsValid());
EXPECT_FALSE(evicted_after_end->IsValid());
// And our |Value()| call purged the earlier iterator.
EXPECT_TRUE(it1->IsDetached());
}
namespace {
enum RangePrepareMode {
DataInMemory,
DataInDatabase,
DataMixed,
};
} // namespace
class LevelDBTransactionRangeTest
: public LevelDBTransactionTest,
public testing::WithParamInterface<RangePrepareMode> {
public:
LevelDBTransactionRangeTest() {}
void SetUp() override {
LevelDBTransactionTest::SetUp();
switch (GetParam()) {
case DataInMemory:
transaction_ = new LevelDBTransaction(db());
TransactionPut(transaction_.get(), key_before_range_, value_);
TransactionPut(transaction_.get(), range_start_, value_);
TransactionPut(transaction_.get(), key_in_range1_, value_);
TransactionPut(transaction_.get(), key_in_range2_, value_);
TransactionPut(transaction_.get(), range_end_, value_);
TransactionPut(transaction_.get(), key_after_range_, value_);
break;
case DataInDatabase:
Put(key_before_range_, value_);
Put(range_start_, value_);
Put(key_in_range1_, value_);
Put(key_in_range2_, value_);
Put(range_end_, value_);
Put(key_after_range_, value_);
transaction_ = new LevelDBTransaction(db());
break;
case DataMixed:
Put(key_before_range_, value_);
Put(key_in_range1_, value_);
Put(range_end_, value_);
transaction_ = new LevelDBTransaction(db());
TransactionPut(transaction_.get(), range_start_, value_);
TransactionPut(transaction_.get(), key_in_range2_, value_);
TransactionPut(transaction_.get(), key_after_range_, value_);
break;
}
}
protected:
const std::string key_before_range_ = "a";
const std::string range_start_ = "b";
const std::string key_in_range1_ = "c";
const std::string key_in_range2_ = "d";
const std::string range_end_ = "e";
const std::string key_after_range_ = "f";
const std::string value_ = "value";
scoped_refptr<LevelDBTransaction> transaction_;
private:
DISALLOW_COPY_AND_ASSIGN(LevelDBTransactionRangeTest);
};
TEST_P(LevelDBTransactionRangeTest, RemoveRangeUpperClosed) {
leveldb::Status status;
const bool upper_open = false;
status = transaction_->RemoveRange(range_start_, range_end_, upper_open);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(TransactionHas(transaction_.get(), key_before_range_));
EXPECT_FALSE(TransactionHas(transaction_.get(), range_start_));
EXPECT_FALSE(TransactionHas(transaction_.get(), key_in_range1_));
EXPECT_FALSE(TransactionHas(transaction_.get(), key_in_range2_));
EXPECT_FALSE(TransactionHas(transaction_.get(), range_end_));
EXPECT_TRUE(TransactionHas(transaction_.get(), key_after_range_));
status = transaction_->Commit();
EXPECT_TRUE(status.ok());
EXPECT_TRUE(Has(key_before_range_));
EXPECT_FALSE(Has(range_start_));
EXPECT_FALSE(Has(key_in_range1_));
EXPECT_FALSE(Has(key_in_range2_));
EXPECT_FALSE(Has(range_end_));
EXPECT_TRUE(Has(key_after_range_));
}
TEST_P(LevelDBTransactionRangeTest, RemoveRangeUpperOpen) {
leveldb::Status status;
const bool upper_open = true;
status = transaction_->RemoveRange(range_start_, range_end_, upper_open);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(TransactionHas(transaction_.get(), key_before_range_));
EXPECT_FALSE(TransactionHas(transaction_.get(), range_start_));
EXPECT_FALSE(TransactionHas(transaction_.get(), key_in_range1_));
EXPECT_FALSE(TransactionHas(transaction_.get(), key_in_range2_));
EXPECT_TRUE(TransactionHas(transaction_.get(), range_end_));
EXPECT_TRUE(TransactionHas(transaction_.get(), key_after_range_));
status = transaction_->Commit();
EXPECT_TRUE(status.ok());
EXPECT_TRUE(Has(key_before_range_));
EXPECT_FALSE(Has(range_start_));
EXPECT_FALSE(Has(key_in_range1_));
EXPECT_FALSE(Has(key_in_range2_));
EXPECT_TRUE(Has(range_end_));
EXPECT_TRUE(Has(key_after_range_));
}
TEST_P(LevelDBTransactionRangeTest, RemoveRangeIteratorRetainsKey) {
leveldb::Status status;
std::unique_ptr<LevelDBIterator> it = transaction_->CreateIterator();
status = it->Seek(key_in_range1_);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(it->IsValid());
EXPECT_EQ(Compare(key_in_range1_, it->Key()), 0);
status = it->Next();
EXPECT_TRUE(status.ok());
EXPECT_TRUE(it->IsValid());
EXPECT_EQ(Compare(key_in_range2_, it->Key()), 0);
const bool upper_open = false;
status = transaction_->RemoveRange(range_start_, range_end_, upper_open);
EXPECT_TRUE(status.ok());
EXPECT_TRUE(it->IsValid());
EXPECT_EQ(Compare(key_in_range2_, it->Key()), 0);
status = it->Next();
EXPECT_TRUE(status.ok());
EXPECT_TRUE(it->IsValid());
EXPECT_EQ(Compare(key_after_range_, it->Key()), 0);
}
INSTANTIATE_TEST_CASE_P(LevelDBTransactionRangeTests,
LevelDBTransactionRangeTest,
::testing::Values(DataInMemory,
DataInDatabase,
DataMixed));
} // namespace content