blob: 381c03cf41c8eb5c98825e17b25814975038628a [file] [log] [blame]
// Copyright 2013 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 "content/browser/indexed_db/indexed_db_transaction.h"
#include <stdint.h>
#include <memory>
#include "base/bind.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/indexed_db/indexed_db_connection.h"
#include "content/browser/indexed_db/indexed_db_fake_backing_store.h"
#include "content/browser/indexed_db/indexed_db_observer.h"
#include "content/browser/indexed_db/mock_indexed_db_database_callbacks.h"
#include "content/browser/indexed_db/mock_indexed_db_factory.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
const int kFakeProcessId = 10;
class AbortObserver {
public:
AbortObserver() : abort_task_called_(false) {}
void AbortTask() { abort_task_called_ = true; }
bool abort_task_called() const { return abort_task_called_; }
private:
bool abort_task_called_;
DISALLOW_COPY_AND_ASSIGN(AbortObserver);
};
class IndexedDBTransactionTest : public testing::Test {
public:
IndexedDBTransactionTest() : factory_(new MockIndexedDBFactory()) {
backing_store_ = new IndexedDBFakeBackingStore();
CreateDB();
}
void CreateDB() {
// DB is created here instead of the constructor to workaround a
// "peculiarity of C++". More info at
// https://github.com/google/googletest/blob/master/googletest/docs/FAQ.md#my-compiler-complains-that-a-constructor-or-destructor-cannot-return-a-value-whats-going-on
leveldb::Status s;
std::tie(db_, s) = IndexedDBDatabase::Create(
base::ASCIIToUTF16("db"), backing_store_.get(), factory_.get(),
IndexedDBDatabase::Identifier());
ASSERT_TRUE(s.ok());
}
void RunPostedTasks() { base::RunLoop().RunUntilIdle(); }
leveldb::Status DummyOperation(leveldb::Status result,
IndexedDBTransaction* transaction) {
return result;
}
leveldb::Status AbortableOperation(AbortObserver* observer,
IndexedDBTransaction* transaction) {
transaction->ScheduleAbortTask(
base::Bind(&AbortObserver::AbortTask, base::Unretained(observer)));
return leveldb::Status::OK();
}
protected:
scoped_refptr<IndexedDBFakeBackingStore> backing_store_;
scoped_refptr<IndexedDBDatabase> db_;
scoped_refptr<MockIndexedDBFactory> factory_;
private:
TestBrowserThreadBundle thread_bundle_;
DISALLOW_COPY_AND_ASSIGN(IndexedDBTransactionTest);
};
class IndexedDBTransactionTestMode
: public IndexedDBTransactionTest,
public testing::WithParamInterface<blink::WebIDBTransactionMode> {
public:
IndexedDBTransactionTestMode() {}
private:
DISALLOW_COPY_AND_ASSIGN(IndexedDBTransactionTestMode);
};
TEST_F(IndexedDBTransactionTest, Timeout) {
const int64_t id = 0;
const std::set<int64_t> scope;
const leveldb::Status commit_success = leveldb::Status::OK();
std::unique_ptr<IndexedDBConnection> connection(
base::MakeUnique<IndexedDBConnection>(
kFakeProcessId, db_, new MockIndexedDBDatabaseCallbacks()));
std::unique_ptr<IndexedDBTransaction> transaction =
std::unique_ptr<IndexedDBTransaction>(new IndexedDBTransaction(
id, connection.get(), scope, blink::WebIDBTransactionModeReadWrite,
new IndexedDBFakeBackingStore::FakeTransaction(commit_success)));
db_->TransactionCreated(transaction.get());
// No conflicting transactions, so coordinator will start it immediately:
EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
// Schedule a task - timer won't be started until it's processed.
transaction->ScheduleTask(
base::Bind(&IndexedDBTransactionTest::DummyOperation,
base::Unretained(this), leveldb::Status::OK()));
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
RunPostedTasks();
EXPECT_TRUE(transaction->IsTimeoutTimerRunning());
transaction->Timeout();
EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
// This task will be ignored.
transaction->ScheduleTask(
base::Bind(&IndexedDBTransactionTest::DummyOperation,
base::Unretained(this), leveldb::Status::OK()));
EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
}
TEST_F(IndexedDBTransactionTest, NoTimeoutReadOnly) {
const int64_t id = 0;
const std::set<int64_t> scope;
const leveldb::Status commit_success = leveldb::Status::OK();
std::unique_ptr<IndexedDBConnection> connection(
base::MakeUnique<IndexedDBConnection>(
kFakeProcessId, db_, new MockIndexedDBDatabaseCallbacks()));
std::unique_ptr<IndexedDBTransaction> transaction =
std::unique_ptr<IndexedDBTransaction>(new IndexedDBTransaction(
id, connection.get(), scope, blink::WebIDBTransactionModeReadOnly,
new IndexedDBFakeBackingStore::FakeTransaction(commit_success)));
db_->TransactionCreated(transaction.get());
// No conflicting transactions, so coordinator will start it immediately:
EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
// Schedule a task - timer won't be started until it's processed.
transaction->ScheduleTask(
base::Bind(&IndexedDBTransactionTest::DummyOperation,
base::Unretained(this), leveldb::Status::OK()));
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
// Transaction is read-only, so no need to time it out.
RunPostedTasks();
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
// Clean up to avoid leaks.
transaction->Abort();
EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
}
TEST_P(IndexedDBTransactionTestMode, ScheduleNormalTask) {
const int64_t id = 0;
const std::set<int64_t> scope;
const leveldb::Status commit_success = leveldb::Status::OK();
std::unique_ptr<IndexedDBConnection> connection(
base::MakeUnique<IndexedDBConnection>(
kFakeProcessId, db_, new MockIndexedDBDatabaseCallbacks()));
std::unique_ptr<IndexedDBTransaction> transaction =
std::unique_ptr<IndexedDBTransaction>(new IndexedDBTransaction(
id, connection.get(), scope, GetParam(),
new IndexedDBFakeBackingStore::FakeTransaction(commit_success)));
EXPECT_FALSE(transaction->HasPendingTasks());
EXPECT_TRUE(transaction->IsTaskQueueEmpty());
EXPECT_TRUE(transaction->task_queue_.empty());
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
db_->TransactionCreated(transaction.get());
EXPECT_FALSE(transaction->HasPendingTasks());
EXPECT_TRUE(transaction->IsTaskQueueEmpty());
EXPECT_TRUE(transaction->task_queue_.empty());
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
transaction->ScheduleTask(
blink::WebIDBTaskTypeNormal,
base::Bind(&IndexedDBTransactionTest::DummyOperation,
base::Unretained(this), leveldb::Status::OK()));
EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
EXPECT_TRUE(transaction->HasPendingTasks());
EXPECT_FALSE(transaction->IsTaskQueueEmpty());
EXPECT_FALSE(transaction->task_queue_.empty());
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
// Pump the message loop so that the transaction completes all pending tasks,
// otherwise it will defer the commit.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(transaction->HasPendingTasks());
EXPECT_TRUE(transaction->IsTaskQueueEmpty());
EXPECT_TRUE(transaction->task_queue_.empty());
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
transaction->Commit();
EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
EXPECT_FALSE(transaction->HasPendingTasks());
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
EXPECT_TRUE(transaction->IsTaskQueueEmpty());
EXPECT_TRUE(transaction->task_queue_.empty());
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
}
TEST_P(IndexedDBTransactionTestMode, TaskFails) {
const int64_t id = 0;
const std::set<int64_t> scope;
const leveldb::Status commit_success = leveldb::Status::OK();
std::unique_ptr<IndexedDBConnection> connection(
base::MakeUnique<IndexedDBConnection>(
kFakeProcessId, db_, new MockIndexedDBDatabaseCallbacks()));
std::unique_ptr<IndexedDBTransaction> transaction =
std::unique_ptr<IndexedDBTransaction>(new IndexedDBTransaction(
id, connection.get(), scope, GetParam(),
new IndexedDBFakeBackingStore::FakeTransaction(commit_success)));
EXPECT_FALSE(transaction->HasPendingTasks());
EXPECT_TRUE(transaction->IsTaskQueueEmpty());
EXPECT_TRUE(transaction->task_queue_.empty());
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
db_->TransactionCreated(transaction.get());
EXPECT_FALSE(transaction->HasPendingTasks());
EXPECT_TRUE(transaction->IsTaskQueueEmpty());
EXPECT_TRUE(transaction->task_queue_.empty());
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
EXPECT_CALL(*factory_, HandleBackingStoreFailure(testing::_)).Times(1);
transaction->ScheduleTask(
blink::WebIDBTaskTypeNormal,
base::Bind(&IndexedDBTransactionTest::DummyOperation,
base::Unretained(this), leveldb::Status::IOError("error")));
EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
EXPECT_TRUE(transaction->HasPendingTasks());
EXPECT_FALSE(transaction->IsTaskQueueEmpty());
EXPECT_FALSE(transaction->task_queue_.empty());
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
// Pump the message loop so that the transaction completes all pending tasks,
// otherwise it will defer the commit.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(transaction->HasPendingTasks());
EXPECT_TRUE(transaction->IsTaskQueueEmpty());
EXPECT_TRUE(transaction->task_queue_.empty());
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
transaction->Commit();
EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
EXPECT_FALSE(transaction->HasPendingTasks());
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
EXPECT_TRUE(transaction->IsTaskQueueEmpty());
EXPECT_TRUE(transaction->task_queue_.empty());
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
}
TEST_F(IndexedDBTransactionTest, SchedulePreemptiveTask) {
const int64_t id = 0;
const std::set<int64_t> scope;
const leveldb::Status commit_failure = leveldb::Status::Corruption("Ouch.");
std::unique_ptr<IndexedDBConnection> connection(
base::MakeUnique<IndexedDBConnection>(
kFakeProcessId, db_, new MockIndexedDBDatabaseCallbacks()));
std::unique_ptr<IndexedDBTransaction> transaction =
std::unique_ptr<IndexedDBTransaction>(new IndexedDBTransaction(
id, connection.get(), scope,
blink::WebIDBTransactionModeVersionChange,
new IndexedDBFakeBackingStore::FakeTransaction(commit_failure)));
EXPECT_FALSE(transaction->HasPendingTasks());
EXPECT_TRUE(transaction->IsTaskQueueEmpty());
EXPECT_TRUE(transaction->task_queue_.empty());
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
db_->TransactionCreated(transaction.get());
EXPECT_FALSE(transaction->HasPendingTasks());
EXPECT_TRUE(transaction->IsTaskQueueEmpty());
EXPECT_TRUE(transaction->task_queue_.empty());
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
transaction->ScheduleTask(
blink::WebIDBTaskTypePreemptive,
base::Bind(&IndexedDBTransactionTest::DummyOperation,
base::Unretained(this), leveldb::Status::OK()));
transaction->AddPreemptiveEvent();
EXPECT_TRUE(transaction->HasPendingTasks());
EXPECT_FALSE(transaction->IsTaskQueueEmpty());
EXPECT_TRUE(transaction->task_queue_.empty());
EXPECT_FALSE(transaction->preemptive_task_queue_.empty());
// Pump the message loop so that the transaction completes all pending tasks,
// otherwise it will defer the commit.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(transaction->HasPendingTasks());
EXPECT_TRUE(transaction->IsTaskQueueEmpty());
EXPECT_TRUE(transaction->task_queue_.empty());
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
transaction->DidCompletePreemptiveEvent();
transaction->Commit();
EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
EXPECT_FALSE(transaction->HasPendingTasks());
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
EXPECT_TRUE(transaction->IsTaskQueueEmpty());
EXPECT_TRUE(transaction->task_queue_.empty());
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
}
TEST_P(IndexedDBTransactionTestMode, AbortTasks) {
const int64_t id = 0;
const std::set<int64_t> scope;
const leveldb::Status commit_failure = leveldb::Status::Corruption("Ouch.");
std::unique_ptr<IndexedDBConnection> connection(
base::MakeUnique<IndexedDBConnection>(
kFakeProcessId, db_, new MockIndexedDBDatabaseCallbacks()));
std::unique_ptr<IndexedDBTransaction> transaction =
std::unique_ptr<IndexedDBTransaction>(new IndexedDBTransaction(
id, connection.get(), scope, GetParam(),
new IndexedDBFakeBackingStore::FakeTransaction(commit_failure)));
db_->TransactionCreated(transaction.get());
AbortObserver observer;
transaction->ScheduleTask(
base::Bind(&IndexedDBTransactionTest::AbortableOperation,
base::Unretained(this),
base::Unretained(&observer)));
// Pump the message loop so that the transaction completes all pending tasks,
// otherwise it will defer the commit.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(observer.abort_task_called());
transaction->Commit();
EXPECT_TRUE(observer.abort_task_called());
EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
}
TEST_P(IndexedDBTransactionTestMode, AbortPreemptive) {
const int64_t id = 0;
const std::set<int64_t> scope;
const leveldb::Status commit_success = leveldb::Status::OK();
std::unique_ptr<IndexedDBConnection> connection(
base::MakeUnique<IndexedDBConnection>(
kFakeProcessId, db_, new MockIndexedDBDatabaseCallbacks()));
std::unique_ptr<IndexedDBTransaction> transaction =
std::unique_ptr<IndexedDBTransaction>(new IndexedDBTransaction(
id, connection.get(), scope, GetParam(),
new IndexedDBFakeBackingStore::FakeTransaction(commit_success)));
db_->TransactionCreated(transaction.get());
// No conflicting transactions, so coordinator will start it immediately:
EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
transaction->ScheduleTask(
blink::WebIDBTaskTypePreemptive,
base::Bind(&IndexedDBTransactionTest::DummyOperation,
base::Unretained(this), leveldb::Status::OK()));
EXPECT_EQ(0, transaction->pending_preemptive_events_);
transaction->AddPreemptiveEvent();
EXPECT_EQ(1, transaction->pending_preemptive_events_);
RunPostedTasks();
transaction->Abort();
EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
EXPECT_EQ(0, transaction->pending_preemptive_events_);
EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
EXPECT_TRUE(transaction->task_queue_.empty());
EXPECT_FALSE(transaction->HasPendingTasks());
EXPECT_EQ(transaction->diagnostics().tasks_completed,
transaction->diagnostics().tasks_scheduled);
EXPECT_FALSE(transaction->should_process_queue_);
EXPECT_TRUE(transaction->backing_store_transaction_begun_);
EXPECT_TRUE(transaction->used_);
EXPECT_FALSE(transaction->commit_pending_);
// This task will be ignored.
transaction->ScheduleTask(
base::Bind(&IndexedDBTransactionTest::DummyOperation,
base::Unretained(this), leveldb::Status::OK()));
EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
EXPECT_FALSE(transaction->HasPendingTasks());
EXPECT_EQ(transaction->diagnostics().tasks_completed,
transaction->diagnostics().tasks_scheduled);
}
TEST_F(IndexedDBTransactionTest, IndexedDBObserver) {
const int64_t id = 0;
const std::set<int64_t> scope;
const leveldb::Status commit_success = leveldb::Status::OK();
std::unique_ptr<IndexedDBConnection> connection(
base::MakeUnique<IndexedDBConnection>(
kFakeProcessId, db_, new MockIndexedDBDatabaseCallbacks()));
base::WeakPtr<IndexedDBTransaction> transaction =
connection->AddTransactionForTesting(
std::unique_ptr<IndexedDBTransaction>(new IndexedDBTransaction(
id, connection.get(), scope,
blink::WebIDBTransactionModeReadWrite,
new IndexedDBFakeBackingStore::FakeTransaction(commit_success))));
ASSERT_TRUE(transaction);
db_->TransactionCreated(transaction.get());
EXPECT_EQ(0UL, transaction->pending_observers_.size());
EXPECT_EQ(0UL, connection->active_observers().size());
// Add observers to pending observer list.
const int32_t observer_id1 = 1, observer_id2 = 2;
IndexedDBObserver::Options options(false, false, false, 0U);
transaction->AddPendingObserver(observer_id1, options);
transaction->AddPendingObserver(observer_id2, options);
EXPECT_EQ(2UL, transaction->pending_observers_.size());
EXPECT_EQ(0UL, connection->active_observers().size());
// Before commit, observer would be in pending list of transaction.
std::vector<int32_t> observer_to_remove1 = {observer_id1};
connection->RemoveObservers(observer_to_remove1);
EXPECT_EQ(1UL, transaction->pending_observers_.size());
EXPECT_EQ(0UL, connection->active_observers().size());
// After commit, observer moved to connection's active observer.
transaction->Commit();
EXPECT_FALSE(transaction);
EXPECT_EQ(1UL, connection->active_observers().size());
// Observer does not exist, so no change to active_observers.
connection->RemoveObservers(observer_to_remove1);
EXPECT_EQ(1UL, connection->active_observers().size());
// Observer removed from connection's active observer.
std::vector<int32_t> observer_to_remove2 = {observer_id2};
connection->RemoveObservers(observer_to_remove2);
EXPECT_EQ(0UL, connection->active_observers().size());
}
static const blink::WebIDBTransactionMode kTestModes[] = {
blink::WebIDBTransactionModeReadOnly, blink::WebIDBTransactionModeReadWrite,
blink::WebIDBTransactionModeVersionChange};
INSTANTIATE_TEST_CASE_P(IndexedDBTransactions,
IndexedDBTransactionTestMode,
::testing::ValuesIn(kTestModes));
} // namespace content