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 <stdint.h>
8 #include <memory>
9
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/debug/stack_trace.h"
13 #include "base/logging.h"
14 #include "base/macros.h"
15 #include "base/run_loop.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/test/bind_test_util.h"
18 #include "base/test/task_environment.h"
19 #include "components/services/storage/indexed_db/scopes/disjoint_range_lock_manager.h"
20 #include "content/browser/indexed_db/fake_indexed_db_metadata_coding.h"
21 #include "content/browser/indexed_db/indexed_db_class_factory.h"
22 #include "content/browser/indexed_db/indexed_db_connection.h"
23 #include "content/browser/indexed_db/indexed_db_database_error.h"
24 #include "content/browser/indexed_db/indexed_db_factory_impl.h"
25 #include "content/browser/indexed_db/indexed_db_fake_backing_store.h"
26 #include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
27 #include "content/browser/indexed_db/indexed_db_metadata_coding.h"
28 #include "content/browser/indexed_db/indexed_db_observer.h"
29 #include "content/browser/indexed_db/mock_indexed_db_database_callbacks.h"
30 #include "content/browser/indexed_db/mock_indexed_db_factory.h"
31 #include "testing/gtest/include/gtest/gtest.h"
32 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
33
34 namespace content {
35 namespace indexed_db_transaction_unittest {
36 namespace {
37
SetToTrue(bool * value)38 void SetToTrue(bool* value) {
39 *value = true;
40 }
41
42 } // namespace
43
44 class AbortObserver {
45 public:
AbortObserver()46 AbortObserver() : abort_task_called_(false) {}
47
AbortTask()48 void AbortTask() { abort_task_called_ = true; }
49
abort_task_called() const50 bool abort_task_called() const { return abort_task_called_; }
51
52 private:
53 bool abort_task_called_;
54 DISALLOW_COPY_AND_ASSIGN(AbortObserver);
55 };
56
57 class IndexedDBTransactionTest : public testing::Test {
58 public:
IndexedDBTransactionTest()59 IndexedDBTransactionTest()
60 : task_environment_(std::make_unique<base::test::TaskEnvironment>()),
61 backing_store_(new IndexedDBFakeBackingStore()),
62 factory_(new MockIndexedDBFactory()),
63 lock_manager_(kIndexedDBLockLevelCount) {}
64
SetUp()65 void SetUp() override {
66 // DB is created here instead of the constructor to workaround a
67 // "peculiarity of C++". More info at
68 // 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
69 leveldb::Status s;
70 std::tie(db_, s) = IndexedDBClassFactory::Get()->CreateIndexedDBDatabase(
71 base::ASCIIToUTF16("db"), backing_store_.get(), factory_.get(),
72 CreateRunTasksCallback(),
73 std::make_unique<FakeIndexedDBMetadataCoding>(),
74 IndexedDBDatabase::Identifier(), &lock_manager_);
75 ASSERT_TRUE(s.ok());
76 }
77
CreateRunTasksCallback()78 TasksAvailableCallback CreateRunTasksCallback() {
79 return base::BindRepeating(&IndexedDBTransactionTest::RunTasksForDatabase,
80 base::Unretained(this), true);
81 }
82
RunTasksForDatabase(bool async)83 void RunTasksForDatabase(bool async) {
84 if (!db_)
85 return;
86 if (async) {
87 base::SequencedTaskRunnerHandle::Get()->PostTask(
88 FROM_HERE,
89 base::BindOnce(&IndexedDBTransactionTest::RunTasksForDatabase,
90 base::Unretained(this), false));
91 return;
92 }
93 IndexedDBDatabase::RunTasksResult result;
94 leveldb::Status status;
95 std::tie(result, status) = db_->RunTasks();
96 switch (result) {
97 case IndexedDBDatabase::RunTasksResult::kDone:
98 return;
99 case IndexedDBDatabase::RunTasksResult::kError:
100 error_called_ = true;
101 return;
102 case IndexedDBDatabase::RunTasksResult::kCanBeDestroyed:
103 db_.reset();
104 return;
105 }
106 }
107
RunPostedTasks()108 void RunPostedTasks() { base::RunLoop().RunUntilIdle(); }
109
DummyOperation(leveldb::Status result,IndexedDBTransaction * transaction)110 leveldb::Status DummyOperation(leveldb::Status result,
111 IndexedDBTransaction* transaction) {
112 return result;
113 }
AbortableOperation(AbortObserver * observer,IndexedDBTransaction * transaction)114 leveldb::Status AbortableOperation(AbortObserver* observer,
115 IndexedDBTransaction* transaction) {
116 transaction->ScheduleAbortTask(
117 base::BindOnce(&AbortObserver::AbortTask, base::Unretained(observer)));
118 return leveldb::Status::OK();
119 }
120
CreateConnection()121 std::unique_ptr<IndexedDBConnection> CreateConnection() {
122 auto connection = std::unique_ptr<
123 IndexedDBConnection>(std::make_unique<IndexedDBConnection>(
124 IndexedDBOriginStateHandle(), IndexedDBClassFactory::Get(),
125 db_->AsWeakPtr(), base::DoNothing(), base::DoNothing(),
126 new MockIndexedDBDatabaseCallbacks()));
127 db_->AddConnectionForTesting(connection.get());
128 return connection;
129 }
130
lock_manager()131 DisjointRangeLockManager* lock_manager() { return &lock_manager_; }
132
133 protected:
134 std::unique_ptr<base::test::TaskEnvironment> task_environment_;
135 std::unique_ptr<IndexedDBFakeBackingStore> backing_store_;
136 std::unique_ptr<IndexedDBDatabase> db_;
137 std::unique_ptr<MockIndexedDBFactory> factory_;
138
139 bool error_called_ = false;
140
141 private:
142 DisjointRangeLockManager lock_manager_;
143
144 DISALLOW_COPY_AND_ASSIGN(IndexedDBTransactionTest);
145 };
146
147 class IndexedDBTransactionTestMode
148 : public IndexedDBTransactionTest,
149 public testing::WithParamInterface<blink::mojom::IDBTransactionMode> {
150 public:
IndexedDBTransactionTestMode()151 IndexedDBTransactionTestMode() {}
152
153 private:
154 DISALLOW_COPY_AND_ASSIGN(IndexedDBTransactionTestMode);
155 };
156
TEST_F(IndexedDBTransactionTest,Timeout)157 TEST_F(IndexedDBTransactionTest, Timeout) {
158 const int64_t id = 0;
159 const std::set<int64_t> scope;
160 const leveldb::Status commit_success = leveldb::Status::OK();
161 std::unique_ptr<IndexedDBConnection> connection = CreateConnection();
162 IndexedDBTransaction* transaction = connection->CreateTransaction(
163 id, scope, blink::mojom::IDBTransactionMode::ReadWrite,
164 new IndexedDBFakeBackingStore::FakeTransaction(commit_success));
165 db_->RegisterAndScheduleTransaction(transaction);
166
167 // No conflicting transactions, so coordinator will start it immediately:
168 EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
169 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
170 EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
171 EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
172
173 // Schedule a task - timer won't be started until it's processed.
174 transaction->ScheduleTask(
175 base::BindOnce(&IndexedDBTransactionTest::DummyOperation,
176 base::Unretained(this), leveldb::Status::OK()));
177 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
178 EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
179 EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
180
181 RunPostedTasks();
182 EXPECT_TRUE(transaction->IsTimeoutTimerRunning());
183
184 transaction->Timeout();
185 EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
186 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
187 EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
188 EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
189
190 // This task will be ignored.
191 transaction->ScheduleTask(
192 base::BindOnce(&IndexedDBTransactionTest::DummyOperation,
193 base::Unretained(this), leveldb::Status::OK()));
194 EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
195 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
196 EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
197 EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
198 }
199
TEST_F(IndexedDBTransactionTest,TimeoutPreemptive)200 TEST_F(IndexedDBTransactionTest, TimeoutPreemptive) {
201 const int64_t id = 0;
202 const std::set<int64_t> scope;
203 const leveldb::Status commit_success = leveldb::Status::OK();
204 std::unique_ptr<IndexedDBConnection> connection = CreateConnection();
205 IndexedDBTransaction* transaction = connection->CreateTransaction(
206 id, scope, blink::mojom::IDBTransactionMode::ReadWrite,
207 new IndexedDBFakeBackingStore::FakeTransaction(commit_success));
208 db_->RegisterAndScheduleTransaction(transaction);
209
210 // No conflicting transactions, so coordinator will start it immediately:
211 EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
212 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
213 EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
214 EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
215
216 // Add a preemptive task.
217 transaction->ScheduleTask(
218 blink::mojom::IDBTaskType::Preemptive,
219 base::BindOnce(&IndexedDBTransactionTest::DummyOperation,
220 base::Unretained(this), leveldb::Status::OK()));
221 transaction->AddPreemptiveEvent();
222
223 EXPECT_TRUE(transaction->HasPendingTasks());
224 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
225 EXPECT_TRUE(transaction->task_queue_.empty());
226 EXPECT_FALSE(transaction->preemptive_task_queue_.empty());
227
228 // Pump the message loop so that the transaction completes all pending tasks,
229 // otherwise it will defer the commit.
230 RunPostedTasks();
231 EXPECT_TRUE(transaction->HasPendingTasks());
232 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
233 EXPECT_TRUE(transaction->task_queue_.empty());
234 EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
235
236 // Schedule a task - timer won't be started until preemptive tasks are done.
237 transaction->ScheduleTask(
238 base::BindOnce(&IndexedDBTransactionTest::DummyOperation,
239 base::Unretained(this), leveldb::Status::OK()));
240 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
241 EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
242 EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
243
244 // This shouldn't do anything - the preemptive task is still lurking.
245 RunPostedTasks();
246 EXPECT_TRUE(transaction->HasPendingTasks());
247 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
248 EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
249 EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
250
251 // Finish the preemptive task, which unblocks regular tasks.
252 transaction->DidCompletePreemptiveEvent();
253 // TODO(dmurph): Should this explicit call be necessary?
254 transaction->RunTasks();
255
256 // The task's completion should start the timer.
257 EXPECT_FALSE(transaction->HasPendingTasks());
258 EXPECT_TRUE(transaction->IsTimeoutTimerRunning());
259 EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
260 EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
261 }
262
TEST_F(IndexedDBTransactionTest,NoTimeoutReadOnly)263 TEST_F(IndexedDBTransactionTest, NoTimeoutReadOnly) {
264 const int64_t id = 0;
265 const std::set<int64_t> scope;
266 const leveldb::Status commit_success = leveldb::Status::OK();
267 std::unique_ptr<IndexedDBConnection> connection = CreateConnection();
268 IndexedDBTransaction* transaction = connection->CreateTransaction(
269 id, scope, blink::mojom::IDBTransactionMode::ReadOnly,
270 new IndexedDBFakeBackingStore::FakeTransaction(commit_success));
271 db_->RegisterAndScheduleTransaction(transaction);
272
273 // No conflicting transactions, so coordinator will start it immediately:
274 EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
275 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
276
277 // Schedule a task - timer won't be started until it's processed.
278 transaction->ScheduleTask(
279 base::BindOnce(&IndexedDBTransactionTest::DummyOperation,
280 base::Unretained(this), leveldb::Status::OK()));
281 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
282
283 // Transaction is read-only, so no need to time it out.
284 RunPostedTasks();
285 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
286
287 // Clean up to avoid leaks.
288 transaction->Abort(IndexedDBDatabaseError(
289 IndexedDBDatabaseError(blink::mojom::IDBException::kAbortError,
290 "Transaction aborted by user.")));
291 EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
292 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
293 }
294
TEST_P(IndexedDBTransactionTestMode,ScheduleNormalTask)295 TEST_P(IndexedDBTransactionTestMode, ScheduleNormalTask) {
296 const int64_t id = 0;
297 const std::set<int64_t> scope;
298 const leveldb::Status commit_success = leveldb::Status::OK();
299 std::unique_ptr<IndexedDBConnection> connection = CreateConnection();
300 IndexedDBTransaction* transaction = connection->CreateTransaction(
301 id, scope, GetParam(),
302 new IndexedDBFakeBackingStore::FakeTransaction(commit_success));
303
304 EXPECT_FALSE(transaction->HasPendingTasks());
305 EXPECT_TRUE(transaction->IsTaskQueueEmpty());
306 EXPECT_TRUE(transaction->task_queue_.empty());
307 EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
308 EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
309 EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
310
311 db_->RegisterAndScheduleTransaction(transaction);
312
313 EXPECT_FALSE(transaction->HasPendingTasks());
314 EXPECT_TRUE(transaction->IsTaskQueueEmpty());
315 EXPECT_TRUE(transaction->task_queue_.empty());
316 EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
317
318 transaction->ScheduleTask(
319 blink::mojom::IDBTaskType::Normal,
320 base::BindOnce(&IndexedDBTransactionTest::DummyOperation,
321 base::Unretained(this), leveldb::Status::OK()));
322
323 EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
324 EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
325
326 EXPECT_TRUE(transaction->HasPendingTasks());
327 EXPECT_FALSE(transaction->IsTaskQueueEmpty());
328 EXPECT_FALSE(transaction->task_queue_.empty());
329 EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
330
331 // Pump the message loop so that the transaction completes all pending tasks,
332 // otherwise it will defer the commit.
333 base::RunLoop().RunUntilIdle();
334 EXPECT_FALSE(transaction->HasPendingTasks());
335 EXPECT_TRUE(transaction->IsTaskQueueEmpty());
336 EXPECT_TRUE(transaction->task_queue_.empty());
337 EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
338 EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
339 EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
340 EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
341
342 transaction->SetCommitFlag();
343 RunPostedTasks();
344 EXPECT_EQ(0UL, connection->transactions().size());
345 }
346
TEST_P(IndexedDBTransactionTestMode,TaskFails)347 TEST_P(IndexedDBTransactionTestMode, TaskFails) {
348 const int64_t id = 0;
349 const std::set<int64_t> scope;
350 const leveldb::Status commit_success = leveldb::Status::OK();
351 std::unique_ptr<IndexedDBConnection> connection = CreateConnection();
352 IndexedDBTransaction* transaction = connection->CreateTransaction(
353 id, scope, GetParam(),
354 new IndexedDBFakeBackingStore::FakeTransaction(commit_success));
355
356 EXPECT_FALSE(transaction->HasPendingTasks());
357 EXPECT_TRUE(transaction->IsTaskQueueEmpty());
358 EXPECT_TRUE(transaction->task_queue_.empty());
359 EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
360 EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
361 EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
362
363 db_->RegisterAndScheduleTransaction(transaction);
364
365 EXPECT_FALSE(transaction->HasPendingTasks());
366 EXPECT_TRUE(transaction->IsTaskQueueEmpty());
367 EXPECT_TRUE(transaction->task_queue_.empty());
368 EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
369
370 transaction->ScheduleTask(
371 blink::mojom::IDBTaskType::Normal,
372 base::BindOnce(&IndexedDBTransactionTest::DummyOperation,
373 base::Unretained(this),
374 leveldb::Status::IOError("error")));
375
376 EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
377 EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
378
379 EXPECT_TRUE(transaction->HasPendingTasks());
380 EXPECT_FALSE(transaction->IsTaskQueueEmpty());
381 EXPECT_FALSE(transaction->task_queue_.empty());
382 EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
383
384 // Pump the message loop so that the transaction completes all pending tasks,
385 // otherwise it will defer the commit.
386 base::RunLoop().RunUntilIdle();
387 EXPECT_FALSE(transaction->HasPendingTasks());
388 EXPECT_TRUE(transaction->IsTaskQueueEmpty());
389 EXPECT_TRUE(transaction->task_queue_.empty());
390 EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
391 EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
392 EXPECT_EQ(1, transaction->diagnostics().tasks_scheduled);
393 EXPECT_EQ(1, transaction->diagnostics().tasks_completed);
394
395 EXPECT_TRUE(error_called_);
396
397 transaction->SetCommitFlag();
398 RunPostedTasks();
399 EXPECT_EQ(0UL, connection->transactions().size());
400 }
401
TEST_F(IndexedDBTransactionTest,SchedulePreemptiveTask)402 TEST_F(IndexedDBTransactionTest, SchedulePreemptiveTask) {
403 const int64_t id = 0;
404 const std::set<int64_t> scope;
405 const leveldb::Status commit_failure = leveldb::Status::Corruption("Ouch.");
406 std::unique_ptr<IndexedDBConnection> connection = CreateConnection();
407 IndexedDBTransaction* transaction = connection->CreateTransaction(
408 id, scope, blink::mojom::IDBTransactionMode::VersionChange,
409 new IndexedDBFakeBackingStore::FakeTransaction(commit_failure));
410
411 EXPECT_FALSE(transaction->HasPendingTasks());
412 EXPECT_TRUE(transaction->IsTaskQueueEmpty());
413 EXPECT_TRUE(transaction->task_queue_.empty());
414 EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
415 EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
416 EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
417
418 db_->RegisterAndScheduleTransaction(transaction);
419
420 EXPECT_FALSE(transaction->HasPendingTasks());
421 EXPECT_TRUE(transaction->IsTaskQueueEmpty());
422 EXPECT_TRUE(transaction->task_queue_.empty());
423 EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
424
425 transaction->ScheduleTask(
426 blink::mojom::IDBTaskType::Preemptive,
427 base::BindOnce(&IndexedDBTransactionTest::DummyOperation,
428 base::Unretained(this), leveldb::Status::OK()));
429 transaction->AddPreemptiveEvent();
430
431 EXPECT_TRUE(transaction->HasPendingTasks());
432 EXPECT_FALSE(transaction->IsTaskQueueEmpty());
433 EXPECT_TRUE(transaction->task_queue_.empty());
434 EXPECT_FALSE(transaction->preemptive_task_queue_.empty());
435
436 // Pump the message loop so that the transaction completes all pending tasks,
437 // otherwise it will defer the commit.
438 base::RunLoop().RunUntilIdle();
439 EXPECT_TRUE(transaction->HasPendingTasks());
440 EXPECT_TRUE(transaction->IsTaskQueueEmpty());
441 EXPECT_TRUE(transaction->task_queue_.empty());
442 EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
443 EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
444 EXPECT_EQ(0, transaction->diagnostics().tasks_scheduled);
445 EXPECT_EQ(0, transaction->diagnostics().tasks_completed);
446
447 transaction->DidCompletePreemptiveEvent();
448 transaction->SetCommitFlag();
449 RunPostedTasks();
450 EXPECT_TRUE(error_called_);
451 }
452
TEST_P(IndexedDBTransactionTestMode,AbortTasks)453 TEST_P(IndexedDBTransactionTestMode, AbortTasks) {
454 const int64_t id = 0;
455 const std::set<int64_t> scope;
456 const leveldb::Status commit_failure = leveldb::Status::Corruption("Ouch.");
457 std::unique_ptr<IndexedDBConnection> connection = CreateConnection();
458 IndexedDBTransaction* transaction = connection->CreateTransaction(
459 id, scope, GetParam(),
460 new IndexedDBFakeBackingStore::FakeTransaction(commit_failure));
461 db_->RegisterAndScheduleTransaction(transaction);
462
463 AbortObserver observer;
464 transaction->ScheduleTask(
465 base::BindOnce(&IndexedDBTransactionTest::AbortableOperation,
466 base::Unretained(this), base::Unretained(&observer)));
467
468 // Pump the message loop so that the transaction completes all pending tasks,
469 // otherwise it will defer the commit.
470 base::RunLoop().RunUntilIdle();
471
472 EXPECT_FALSE(observer.abort_task_called());
473 transaction->SetCommitFlag();
474 RunPostedTasks();
475 EXPECT_TRUE(observer.abort_task_called());
476 EXPECT_TRUE(error_called_);
477 }
478
TEST_P(IndexedDBTransactionTestMode,AbortPreemptive)479 TEST_P(IndexedDBTransactionTestMode, AbortPreemptive) {
480 const int64_t id = 0;
481 const std::set<int64_t> scope;
482 const leveldb::Status commit_success = leveldb::Status::OK();
483 std::unique_ptr<IndexedDBConnection> connection = CreateConnection();
484 IndexedDBTransaction* transaction = connection->CreateTransaction(
485 id, scope, GetParam(),
486 new IndexedDBFakeBackingStore::FakeTransaction(commit_success));
487 db_->RegisterAndScheduleTransaction(transaction);
488
489 // No conflicting transactions, so coordinator will start it immediately:
490 EXPECT_EQ(IndexedDBTransaction::STARTED, transaction->state());
491 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
492
493 transaction->ScheduleTask(
494 blink::mojom::IDBTaskType::Preemptive,
495 base::BindOnce(&IndexedDBTransactionTest::DummyOperation,
496 base::Unretained(this), leveldb::Status::OK()));
497 EXPECT_EQ(0, transaction->pending_preemptive_events_);
498 transaction->AddPreemptiveEvent();
499 EXPECT_EQ(1, transaction->pending_preemptive_events_);
500
501 RunPostedTasks();
502
503 transaction->Abort(IndexedDBDatabaseError(
504 blink::mojom::IDBException::kAbortError, "Transaction aborted by user."));
505 EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
506 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
507 EXPECT_EQ(0, transaction->pending_preemptive_events_);
508 EXPECT_TRUE(transaction->preemptive_task_queue_.empty());
509 EXPECT_TRUE(transaction->task_queue_.empty());
510 EXPECT_FALSE(transaction->HasPendingTasks());
511 EXPECT_EQ(transaction->diagnostics().tasks_completed,
512 transaction->diagnostics().tasks_scheduled);
513 EXPECT_TRUE(transaction->backing_store_transaction_begun_);
514 EXPECT_TRUE(transaction->used_);
515 EXPECT_FALSE(transaction->is_commit_pending_);
516
517 // This task will be ignored.
518 transaction->ScheduleTask(
519 base::BindOnce(&IndexedDBTransactionTest::DummyOperation,
520 base::Unretained(this), leveldb::Status::OK()));
521 EXPECT_EQ(IndexedDBTransaction::FINISHED, transaction->state());
522 EXPECT_FALSE(transaction->IsTimeoutTimerRunning());
523 EXPECT_FALSE(transaction->HasPendingTasks());
524 EXPECT_EQ(transaction->diagnostics().tasks_completed,
525 transaction->diagnostics().tasks_scheduled);
526 }
527
TEST_F(IndexedDBTransactionTest,IndexedDBObserver)528 TEST_F(IndexedDBTransactionTest, IndexedDBObserver) {
529 const int64_t id = 0;
530 const std::set<int64_t> scope;
531 const leveldb::Status commit_success = leveldb::Status::OK();
532 std::unique_ptr<IndexedDBConnection> connection = CreateConnection();
533 IndexedDBTransaction* transaction = connection->CreateTransaction(
534 id, scope, blink::mojom::IDBTransactionMode::ReadWrite,
535 new IndexedDBFakeBackingStore::FakeTransaction(commit_success));
536 ASSERT_TRUE(transaction);
537 db_->RegisterAndScheduleTransaction(transaction);
538
539 EXPECT_EQ(0UL, transaction->pending_observers_.size());
540 EXPECT_EQ(0UL, connection->active_observers().size());
541
542 // Add observers to pending observer list.
543 const int32_t observer_id1 = 1, observer_id2 = 2;
544 IndexedDBObserver::Options options(false, false, false, 0U);
545 transaction->AddPendingObserver(observer_id1, options);
546 transaction->AddPendingObserver(observer_id2, options);
547 EXPECT_EQ(2UL, transaction->pending_observers_.size());
548 EXPECT_EQ(0UL, connection->active_observers().size());
549
550 // Before commit, observer would be in pending list of transaction.
551 std::vector<int32_t> observer_to_remove1 = {observer_id1};
552 connection->RemoveObservers(observer_to_remove1);
553 EXPECT_EQ(1UL, transaction->pending_observers_.size());
554 EXPECT_EQ(0UL, connection->active_observers().size());
555
556 // After commit, observer moved to connection's active observer.
557 transaction->SetCommitFlag();
558 RunPostedTasks();
559 EXPECT_EQ(0UL, connection->transactions().size());
560 EXPECT_EQ(1UL, connection->active_observers().size());
561
562 // Observer does not exist, so no change to active_observers.
563 connection->RemoveObservers(observer_to_remove1);
564 EXPECT_EQ(1UL, connection->active_observers().size());
565
566 // Observer removed from connection's active observer.
567 std::vector<int32_t> observer_to_remove2 = {observer_id2};
568 connection->RemoveObservers(observer_to_remove2);
569 EXPECT_EQ(0UL, connection->active_observers().size());
570 }
571
572 static const blink::mojom::IDBTransactionMode kTestModes[] = {
573 blink::mojom::IDBTransactionMode::ReadOnly,
574 blink::mojom::IDBTransactionMode::ReadWrite,
575 blink::mojom::IDBTransactionMode::VersionChange};
576
577 INSTANTIATE_TEST_SUITE_P(IndexedDBTransactions,
578 IndexedDBTransactionTestMode,
579 ::testing::ValuesIn(kTestModes));
580
TEST_F(IndexedDBTransactionTest,AbortCancelsLockRequest)581 TEST_F(IndexedDBTransactionTest, AbortCancelsLockRequest) {
582 const int64_t id = 0;
583 const int64_t object_store_id = 1ll;
584 const std::set<int64_t> scope = {object_store_id};
585 std::unique_ptr<IndexedDBConnection> connection = CreateConnection();
586 IndexedDBTransaction* transaction = connection->CreateTransaction(
587 id, scope, blink::mojom::IDBTransactionMode::ReadWrite,
588 new IndexedDBFakeBackingStore::FakeTransaction(leveldb::Status::OK()));
589
590 // Acquire a lock to block the transaction's lock acquisition.
591 bool locks_recieved = false;
592 std::vector<ScopesLockManager::ScopeLockRequest> lock_requests;
593 lock_requests.emplace_back(kDatabaseRangeLockLevel, GetDatabaseLockRange(id),
594 ScopesLockManager::LockType::kShared);
595 lock_requests.emplace_back(kObjectStoreRangeLockLevel,
596 GetObjectStoreLockRange(id, object_store_id),
597 ScopesLockManager::LockType::kExclusive);
598 ScopesLocksHolder temp_lock_receiver;
599 lock_manager()->AcquireLocks(lock_requests,
600 temp_lock_receiver.weak_factory.GetWeakPtr(),
601 base::BindOnce(SetToTrue, &locks_recieved));
602 EXPECT_TRUE(locks_recieved);
603
604 // Register the transaction, which should request locks and wait for
605 // |temp_lock_receiver| to release the locks.
606 db_->RegisterAndScheduleTransaction(transaction);
607 EXPECT_EQ(transaction->state(), IndexedDBTransaction::CREATED);
608
609 // Abort the transaction, which should cancel the
610 // RegisterAndScheduleTransaction() pending lock request.
611 transaction->Abort(
612 IndexedDBDatabaseError(blink::mojom::IDBException::kUnknownError));
613 EXPECT_EQ(transaction->state(), IndexedDBTransaction::FINISHED);
614
615 // Clear |temp_lock_receiver| so we can test later that all locks have
616 // cleared.
617 temp_lock_receiver.locks.clear();
618
619 // Verify that the locks are available for acquisition again, as the
620 // transaction should have cancelled its lock request.
621 locks_recieved = false;
622 lock_manager()->AcquireLocks(lock_requests,
623 temp_lock_receiver.weak_factory.GetWeakPtr(),
624 base::BindOnce(SetToTrue, &locks_recieved));
625 EXPECT_TRUE(locks_recieved);
626 }
627
TEST_F(IndexedDBTransactionTest,PostedStartTaskRunAfterAbort)628 TEST_F(IndexedDBTransactionTest, PostedStartTaskRunAfterAbort) {
629 int64_t id = 0;
630 const int64_t object_store_id = 1ll;
631 const std::set<int64_t> scope = {object_store_id};
632 std::unique_ptr<IndexedDBConnection> connection = CreateConnection();
633
634 IndexedDBTransaction* transaction1 = connection->CreateTransaction(
635 id, scope, blink::mojom::IDBTransactionMode::ReadWrite,
636 new IndexedDBFakeBackingStore::FakeTransaction(leveldb::Status::OK()));
637
638 db_->RegisterAndScheduleTransaction(transaction1);
639 EXPECT_EQ(transaction1->state(), IndexedDBTransaction::STARTED);
640
641 // Register another transaction, which will block on the first transaction.
642 IndexedDBTransaction* transaction2 = connection->CreateTransaction(
643 ++id, scope, blink::mojom::IDBTransactionMode::ReadWrite,
644 new IndexedDBFakeBackingStore::FakeTransaction(leveldb::Status::OK()));
645
646 db_->RegisterAndScheduleTransaction(transaction2);
647 EXPECT_EQ(transaction2->state(), IndexedDBTransaction::CREATED);
648
649 // Flush posted tasks before making the Abort calls since there are
650 // posted RunTasksForDatabase() tasks which, if we waited to run them
651 // until after Abort is called, would destroy our transactions and mask
652 // a potential race condition.
653 RunPostedTasks();
654
655 // Abort all of the transactions, which should cause the second transaction's
656 // posted Start() task to run.
657 connection->AbortAllTransactions(
658 IndexedDBDatabaseError(blink::mojom::IDBException::kUnknownError));
659
660 EXPECT_EQ(transaction2->state(), IndexedDBTransaction::FINISHED);
661
662 // Run tasks to ensure Start() is called but does not DCHECK.
663 RunPostedTasks();
664
665 // It's not safe to check the state of the transaction at this point since it
666 // is freed when the IndexedDBDatabase::RunTasks call happens via the posted
667 // RunTasksForDatabase task.
668 }
669
670 } // namespace indexed_db_transaction_unittest
671 } // namespace content
672