// 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 "base/bind.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "base/strings/utf_string_conversions.h" #include "content/browser/indexed_db/indexed_db_fake_backing_store.h" #include "content/browser/indexed_db/mock_indexed_db_database_callbacks.h" #include "testing/gtest/include/gtest/gtest.h" namespace content { class IndexedDBTransactionTest : public testing::Test { public: IndexedDBTransactionTest() { 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://code.google.com/p/googletest/wiki/FAQ#My_compiler_complains_that_a_constructor_(or_destructor)_cannot IndexedDBFactory* factory = NULL; leveldb::Status s; db_ = IndexedDBDatabase::Create(base::ASCIIToUTF16("db"), backing_store_, factory, IndexedDBDatabase::Identifier(), &s); ASSERT_TRUE(s.ok()); } void RunPostedTasks() { message_loop_.RunUntilIdle(); } void DummyOperation(IndexedDBTransaction* transaction) {} protected: scoped_refptr backing_store_; scoped_refptr db_; private: base::MessageLoop message_loop_; DISALLOW_COPY_AND_ASSIGN(IndexedDBTransactionTest); }; class IndexedDBTransactionTestMode : public IndexedDBTransactionTest, public testing::WithParamInterface { public: IndexedDBTransactionTestMode() {} private: DISALLOW_COPY_AND_ASSIGN(IndexedDBTransactionTestMode); }; TEST_F(IndexedDBTransactionTest, Timeout) { const int64 id = 0; const std::set scope; const bool commit_success = true; scoped_refptr transaction = new IndexedDBTransaction( id, new MockIndexedDBDatabaseCallbacks(), scope, indexed_db::TRANSACTION_READ_WRITE, db_, new IndexedDBFakeBackingStore::FakeTransaction(commit_success)); db_->TransactionCreated(transaction); // 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))); 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))); 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 id = 0; const std::set scope; const bool commit_success = true; scoped_refptr transaction = new IndexedDBTransaction( id, new MockIndexedDBDatabaseCallbacks(), scope, indexed_db::TRANSACTION_READ_ONLY, db_, new IndexedDBFakeBackingStore::FakeTransaction(commit_success)); db_->TransactionCreated(transaction); // 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))); 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()); } class AbortObserver { public: AbortObserver() : abort_task_called_(false) {} void AbortTask(IndexedDBTransaction* transaction) { abort_task_called_ = true; } bool abort_task_called() const { return abort_task_called_; } private: bool abort_task_called_; DISALLOW_COPY_AND_ASSIGN(AbortObserver); }; TEST_P(IndexedDBTransactionTestMode, ScheduleNormalTask) { const int64 id = 0; const std::set scope; const bool commit_failure = false; scoped_refptr transaction = new IndexedDBTransaction( id, new MockIndexedDBDatabaseCallbacks(), scope, GetParam(), db_, 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); EXPECT_FALSE(transaction->HasPendingTasks()); EXPECT_TRUE(transaction->IsTaskQueueEmpty()); EXPECT_TRUE(transaction->task_queue_.empty()); EXPECT_TRUE(transaction->preemptive_task_queue_.empty()); transaction->ScheduleTask( IndexedDBDatabase::NORMAL_TASK, base::Bind(&IndexedDBTransactionTest::DummyOperation, base::Unretained(this))); 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::MessageLoop::current()->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 id = 0; const std::set scope; const bool commit_failure = false; scoped_refptr transaction = new IndexedDBTransaction( id, new MockIndexedDBDatabaseCallbacks(), scope, indexed_db::TRANSACTION_VERSION_CHANGE, db_, 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); EXPECT_FALSE(transaction->HasPendingTasks()); EXPECT_TRUE(transaction->IsTaskQueueEmpty()); EXPECT_TRUE(transaction->task_queue_.empty()); EXPECT_TRUE(transaction->preemptive_task_queue_.empty()); transaction->ScheduleTask( IndexedDBDatabase::PREEMPTIVE_TASK, base::Bind(&IndexedDBTransactionTest::DummyOperation, base::Unretained(this))); 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::MessageLoop::current()->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 id = 0; const std::set scope; const bool commit_failure = false; scoped_refptr transaction = new IndexedDBTransaction( id, new MockIndexedDBDatabaseCallbacks(), scope, GetParam(), db_, new IndexedDBFakeBackingStore::FakeTransaction(commit_failure)); db_->TransactionCreated(transaction); AbortObserver observer; transaction->ScheduleTask( base::Bind(&IndexedDBTransactionTest::DummyOperation, base::Unretained(this)), base::Bind(&AbortObserver::AbortTask, base::Unretained(&observer))); // Pump the message loop so that the transaction completes all pending tasks, // otherwise it will defer the commit. base::MessageLoop::current()->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 id = 0; const std::set scope; const bool commit_success = true; scoped_refptr transaction = new IndexedDBTransaction( id, new MockIndexedDBDatabaseCallbacks(), scope, GetParam(), db_, new IndexedDBFakeBackingStore::FakeTransaction(commit_success)); db_->TransactionCreated(transaction); // No conflicting transactions, so coordinator will start it immediately: EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state()); EXPECT_FALSE(transaction->IsTimeoutTimerRunning()); transaction->ScheduleTask( IndexedDBDatabase::PREEMPTIVE_TASK, base::Bind(&IndexedDBTransactionTest::DummyOperation, base::Unretained(this))); 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))); EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state()); EXPECT_FALSE(transaction->IsTimeoutTimerRunning()); EXPECT_FALSE(transaction->HasPendingTasks()); EXPECT_EQ(transaction->diagnostics().tasks_completed, transaction->diagnostics().tasks_scheduled); } static const indexed_db::TransactionMode kTestModes[] = { indexed_db::TRANSACTION_READ_ONLY, indexed_db::TRANSACTION_READ_WRITE, indexed_db::TRANSACTION_VERSION_CHANGE }; INSTANTIATE_TEST_CASE_P(IndexedDBTransactions, IndexedDBTransactionTestMode, ::testing::ValuesIn(kTestModes)); } // namespace content