1// Copyright 2013 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_transaction.h"
6
7#include "base/bind.h"
8#include "base/logging.h"
9#include "base/message_loop/message_loop.h"
10#include "base/strings/utf_string_conversions.h"
11#include "content/browser/indexed_db/indexed_db_fake_backing_store.h"
12#include "content/browser/indexed_db/mock_indexed_db_database_callbacks.h"
13#include "content/browser/indexed_db/mock_indexed_db_factory.h"
14#include "testing/gtest/include/gtest/gtest.h"
15
16namespace content {
17
18class AbortObserver {
19 public:
20  AbortObserver() : abort_task_called_(false) {}
21
22  void AbortTask(IndexedDBTransaction* transaction) {
23    abort_task_called_ = true;
24  }
25
26  bool abort_task_called() const { return abort_task_called_; }
27
28 private:
29  bool abort_task_called_;
30  DISALLOW_COPY_AND_ASSIGN(AbortObserver);
31};
32
33class IndexedDBTransactionTest : public testing::Test {
34 public:
35  IndexedDBTransactionTest() : factory_(new MockIndexedDBFactory()) {
36    backing_store_ = new IndexedDBFakeBackingStore();
37    CreateDB();
38  }
39
40  void CreateDB() {
41    // DB is created here instead of the constructor to workaround a
42    // "peculiarity of C++". More info at
43    // https://code.google.com/p/googletest/wiki/FAQ#My_compiler_complains_that_a_constructor_(or_destructor)_cannot
44    leveldb::Status s;
45    db_ = IndexedDBDatabase::Create(base::ASCIIToUTF16("db"),
46                                    backing_store_.get(),
47                                    factory_.get(),
48                                    IndexedDBDatabase::Identifier(),
49                                    &s);
50    ASSERT_TRUE(s.ok());
51  }
52
53  void RunPostedTasks() { message_loop_.RunUntilIdle(); }
54  void DummyOperation(IndexedDBTransaction* transaction) {}
55  void AbortableOperation(AbortObserver* observer,
56                          IndexedDBTransaction* transaction) {
57    transaction->ScheduleAbortTask(
58        base::Bind(&AbortObserver::AbortTask, base::Unretained(observer)));
59  }
60
61 protected:
62  scoped_refptr<IndexedDBFakeBackingStore> backing_store_;
63  scoped_refptr<IndexedDBDatabase> db_;
64
65 private:
66  base::MessageLoop message_loop_;
67  scoped_refptr<MockIndexedDBFactory> factory_;
68
69  DISALLOW_COPY_AND_ASSIGN(IndexedDBTransactionTest);
70};
71
72class IndexedDBTransactionTestMode
73    : public IndexedDBTransactionTest,
74      public testing::WithParamInterface<blink::WebIDBTransactionMode> {
75 public:
76  IndexedDBTransactionTestMode() {}
77 private:
78  DISALLOW_COPY_AND_ASSIGN(IndexedDBTransactionTestMode);
79};
80
81TEST_F(IndexedDBTransactionTest, Timeout) {
82  const int64 id = 0;
83  const std::set<int64> scope;
84  const leveldb::Status commit_success = leveldb::Status::OK();
85  scoped_refptr<IndexedDBTransaction> transaction = new IndexedDBTransaction(
86      id,
87      new MockIndexedDBDatabaseCallbacks(),
88      scope,
89      blink::WebIDBTransactionModeReadWrite,
90      db_.get(),
91      new IndexedDBFakeBackingStore::FakeTransaction(commit_success));
92  db_->TransactionCreated(transaction.get());
93
94  // No conflicting transactions, so coordinator will start it immediately:
95  EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
96  EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
97  EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
98  EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
99
100  // Schedule a task - timer won't be started until it's processed.
101  transaction->ScheduleTask(base::Bind(
102      &IndexedDBTransactionTest::DummyOperation, base::Unretained(this)));
103  EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
104  EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
105  EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
106
107  RunPostedTasks();
108  EXPECT_TRUE(transaction->IsTimeoutTimerRunning());
109
110  transaction->Timeout();
111  EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
112  EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
113  EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
114  EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
115
116  // This task will be ignored.
117  transaction->ScheduleTask(base::Bind(
118      &IndexedDBTransactionTest::DummyOperation, base::Unretained(this)));
119  EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
120  EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
121  EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
122  EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
123}
124
125TEST_F(IndexedDBTransactionTest, NoTimeoutReadOnly) {
126  const int64 id = 0;
127  const std::set<int64> scope;
128  const leveldb::Status commit_success = leveldb::Status::OK();
129  scoped_refptr<IndexedDBTransaction> transaction = new IndexedDBTransaction(
130      id,
131      new MockIndexedDBDatabaseCallbacks(),
132      scope,
133      blink::WebIDBTransactionModeReadOnly,
134      db_.get(),
135      new IndexedDBFakeBackingStore::FakeTransaction(commit_success));
136  db_->TransactionCreated(transaction.get());
137
138  // No conflicting transactions, so coordinator will start it immediately:
139  EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
140  EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
141
142  // Schedule a task - timer won't be started until it's processed.
143  transaction->ScheduleTask(base::Bind(
144      &IndexedDBTransactionTest::DummyOperation, base::Unretained(this)));
145  EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
146
147  // Transaction is read-only, so no need to time it out.
148  RunPostedTasks();
149  EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
150
151  // Clean up to avoid leaks.
152  transaction->Abort();
153  EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
154  EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
155}
156
157TEST_P(IndexedDBTransactionTestMode, ScheduleNormalTask) {
158  const int64 id = 0;
159  const std::set<int64> scope;
160  const leveldb::Status commit_success = leveldb::Status::OK();
161  scoped_refptr<IndexedDBTransaction> transaction = new IndexedDBTransaction(
162      id,
163      new MockIndexedDBDatabaseCallbacks(),
164      scope,
165      GetParam(),
166      db_.get(),
167      new IndexedDBFakeBackingStore::FakeTransaction(commit_success));
168
169  EXPECT_FALSE(transaction->HasPendingTasks());
170  EXPECT_TRUE(transaction->IsTaskQueueEmpty());
171  EXPECT_TRUE(transaction->task_queue_.empty());
172  EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
173  EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
174  EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
175
176  db_->TransactionCreated(transaction.get());
177
178  EXPECT_FALSE(transaction->HasPendingTasks());
179  EXPECT_TRUE(transaction->IsTaskQueueEmpty());
180  EXPECT_TRUE(transaction->task_queue_.empty());
181  EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
182
183  transaction->ScheduleTask(
184      blink::WebIDBTaskTypeNormal,
185      base::Bind(&IndexedDBTransactionTest::DummyOperation,
186                 base::Unretained(this)));
187
188  EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
189  EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
190
191  EXPECT_TRUE(transaction->HasPendingTasks());
192  EXPECT_FALSE(transaction->IsTaskQueueEmpty());
193  EXPECT_FALSE(transaction->task_queue_.empty());
194  EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
195
196  // Pump the message loop so that the transaction completes all pending tasks,
197  // otherwise it will defer the commit.
198  base::MessageLoop::current()->RunUntilIdle();
199  EXPECT_FALSE(transaction->HasPendingTasks());
200  EXPECT_TRUE(transaction->IsTaskQueueEmpty());
201  EXPECT_TRUE(transaction->task_queue_.empty());
202  EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
203  EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
204  EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
205  EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
206
207  transaction->Commit();
208
209  EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
210  EXPECT_FALSE(transaction->HasPendingTasks());
211  EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
212  EXPECT_TRUE(transaction->IsTaskQueueEmpty());
213  EXPECT_TRUE(transaction->task_queue_.empty());
214  EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
215  EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
216  EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
217}
218
219TEST_F(IndexedDBTransactionTest, SchedulePreemptiveTask) {
220  const int64 id = 0;
221  const std::set<int64> scope;
222  const leveldb::Status commit_failure = leveldb::Status::Corruption("Ouch.");
223  scoped_refptr<IndexedDBTransaction> transaction = new IndexedDBTransaction(
224      id,
225      new MockIndexedDBDatabaseCallbacks(),
226      scope,
227      blink::WebIDBTransactionModeVersionChange,
228      db_.get(),
229      new IndexedDBFakeBackingStore::FakeTransaction(commit_failure));
230
231  EXPECT_FALSE(transaction->HasPendingTasks());
232  EXPECT_TRUE(transaction->IsTaskQueueEmpty());
233  EXPECT_TRUE(transaction->task_queue_.empty());
234  EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
235  EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
236  EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
237
238  db_->TransactionCreated(transaction.get());
239
240  EXPECT_FALSE(transaction->HasPendingTasks());
241  EXPECT_TRUE(transaction->IsTaskQueueEmpty());
242  EXPECT_TRUE(transaction->task_queue_.empty());
243  EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
244
245  transaction->ScheduleTask(
246      blink::WebIDBTaskTypePreemptive,
247      base::Bind(&IndexedDBTransactionTest::DummyOperation,
248                 base::Unretained(this)));
249  transaction->AddPreemptiveEvent();
250
251  EXPECT_TRUE(transaction->HasPendingTasks());
252  EXPECT_FALSE(transaction->IsTaskQueueEmpty());
253  EXPECT_TRUE(transaction->task_queue_.empty());
254  EXPECT_FALSE(transaction->preemptive_task_queue_.empty());
255
256  // Pump the message loop so that the transaction completes all pending tasks,
257  // otherwise it will defer the commit.
258  base::MessageLoop::current()->RunUntilIdle();
259  EXPECT_TRUE(transaction->HasPendingTasks());
260  EXPECT_TRUE(transaction->IsTaskQueueEmpty());
261  EXPECT_TRUE(transaction->task_queue_.empty());
262  EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
263  EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
264  EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
265  EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
266
267  transaction->DidCompletePreemptiveEvent();
268  transaction->Commit();
269
270  EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
271  EXPECT_FALSE(transaction->HasPendingTasks());
272  EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
273  EXPECT_TRUE(transaction->IsTaskQueueEmpty());
274  EXPECT_TRUE(transaction->task_queue_.empty());
275  EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
276  EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
277  EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
278}
279
280TEST_P(IndexedDBTransactionTestMode, AbortTasks) {
281  const int64 id = 0;
282  const std::set<int64> scope;
283  const leveldb::Status commit_failure = leveldb::Status::Corruption("Ouch.");
284  scoped_refptr<IndexedDBTransaction> transaction = new IndexedDBTransaction(
285      id,
286      new MockIndexedDBDatabaseCallbacks(),
287      scope,
288      GetParam(),
289      db_.get(),
290      new IndexedDBFakeBackingStore::FakeTransaction(commit_failure));
291  db_->TransactionCreated(transaction.get());
292
293  AbortObserver observer;
294  transaction->ScheduleTask(
295      base::Bind(&IndexedDBTransactionTest::AbortableOperation,
296                 base::Unretained(this),
297                 base::Unretained(&observer)));
298
299  // Pump the message loop so that the transaction completes all pending tasks,
300  // otherwise it will defer the commit.
301  base::MessageLoop::current()->RunUntilIdle();
302
303  EXPECT_FALSE(observer.abort_task_called());
304  transaction->Commit();
305  EXPECT_TRUE(observer.abort_task_called());
306  EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
307  EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
308}
309
310TEST_P(IndexedDBTransactionTestMode, AbortPreemptive) {
311  const int64 id = 0;
312  const std::set<int64> scope;
313  const leveldb::Status commit_success = leveldb::Status::OK();
314  scoped_refptr<IndexedDBTransaction> transaction = new IndexedDBTransaction(
315      id,
316      new MockIndexedDBDatabaseCallbacks(),
317      scope,
318      GetParam(),
319      db_.get(),
320      new IndexedDBFakeBackingStore::FakeTransaction(commit_success));
321  db_->TransactionCreated(transaction.get());
322
323  // No conflicting transactions, so coordinator will start it immediately:
324  EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
325  EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
326
327  transaction->ScheduleTask(
328      blink::WebIDBTaskTypePreemptive,
329      base::Bind(&IndexedDBTransactionTest::DummyOperation,
330                 base::Unretained(this)));
331  EXPECT_EQ(0, transaction->pending_preemptive_events_);
332  transaction->AddPreemptiveEvent();
333  EXPECT_EQ(1, transaction->pending_preemptive_events_);
334
335  RunPostedTasks();
336
337  transaction->Abort();
338  EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
339  EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
340  EXPECT_EQ(0, transaction->pending_preemptive_events_);
341  EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
342  EXPECT_TRUE(transaction->task_queue_.empty());
343  EXPECT_FALSE(transaction->HasPendingTasks());
344  EXPECT_EQ(transaction->diagnostics().tasks_completed,
345            transaction->diagnostics().tasks_scheduled);
346  EXPECT_FALSE(transaction->should_process_queue_);
347  EXPECT_TRUE(transaction->backing_store_transaction_begun_);
348  EXPECT_TRUE(transaction->used_);
349  EXPECT_FALSE(transaction->commit_pending_);
350
351  // This task will be ignored.
352  transaction->ScheduleTask(base::Bind(
353      &IndexedDBTransactionTest::DummyOperation, base::Unretained(this)));
354  EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
355  EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
356  EXPECT_FALSE(transaction->HasPendingTasks());
357  EXPECT_EQ(transaction->diagnostics().tasks_completed,
358            transaction->diagnostics().tasks_scheduled);
359}
360
361static const blink::WebIDBTransactionMode kTestModes[] = {
362    blink::WebIDBTransactionModeReadOnly, blink::WebIDBTransactionModeReadWrite,
363    blink::WebIDBTransactionModeVersionChange};
364
365INSTANTIATE_TEST_CASE_P(IndexedDBTransactions,
366                        IndexedDBTransactionTestMode,
367                        ::testing::ValuesIn(kTestModes));
368
369}  // namespace content
370