Chromium Code Reviews| Index: content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc |
| diff --git a/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc b/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..a73e010483e2c2093e5e66c9605207db1aeccded |
| --- /dev/null |
| +++ b/content/browser/indexed_db/indexed_db_dispatcher_host_unittest.cc |
| @@ -0,0 +1,445 @@ |
| +// 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 "content/browser/indexed_db/indexed_db_dispatcher_host.h" |
| + |
| +#include "base/barrier_closure.h" |
| +#include "base/callback.h" |
| +#include "base/files/scoped_temp_dir.h" |
| +#include "base/memory/ptr_util.h" |
| +#include "base/memory/ref_counted.h" |
| +#include "base/run_loop.h" |
| +#include "base/single_thread_task_runner.h" |
| +#include "base/strings/utf_offset_string_conversions.h" |
| +#include "base/test/test_simple_task_runner.h" |
| +#include "base/threading/thread.h" |
| +#include "content/browser/browser_thread_impl.h" |
| +#include "content/browser/indexed_db/indexed_db_callbacks.h" |
| +#include "content/browser/indexed_db/indexed_db_context_impl.h" |
| +#include "content/browser/indexed_db/indexed_db_database_callbacks.h" |
| +#include "content/browser/indexed_db/indexed_db_factory.h" |
| +#include "content/browser/indexed_db/indexed_db_pending_connection.h" |
| +#include "content/browser/indexed_db/mock_mojo_indexed_db_callbacks.h" |
| +#include "content/browser/indexed_db/mock_mojo_indexed_db_database_callbacks.h" |
| +#include "content/common/indexed_db/indexed_db.mojom.h" |
| +#include "content/public/test/test_browser_context.h" |
| +#include "content/public/test/test_browser_thread_bundle.h" |
| +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" |
| +#include "mojo/public/cpp/bindings/strong_associated_binding.h" |
| +#include "net/url_request/url_request_test_util.h" |
| +#include "storage/browser/test/mock_quota_manager.h" |
| +#include "storage/browser/test/mock_quota_manager_proxy.h" |
| +#include "storage/browser/test/mock_special_storage_policy.h" |
| +#include "testing/gmock/include/gmock/gmock.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| +#include "third_party/WebKit/public/platform/modules/indexeddb/WebIDBDatabaseException.h" |
| +#include "url/origin.h" |
| + |
| +using indexed_db::mojom::Callbacks; |
| +using indexed_db::mojom::CallbacksAssociatedPtrInfo; |
| +using indexed_db::mojom::DatabaseAssociatedPtr; |
| +using indexed_db::mojom::DatabaseAssociatedPtrInfo; |
| +using indexed_db::mojom::DatabaseAssociatedRequest; |
| +using indexed_db::mojom::DatabaseCallbacks; |
| +using indexed_db::mojom::DatabaseCallbacksAssociatedPtrInfo; |
| +using indexed_db::mojom::Factory; |
| +using indexed_db::mojom::FactoryAssociatedPtr; |
| +using indexed_db::mojom::FactoryAssociatedRequest; |
| +using indexed_db::mojom::KeyPath; |
| +using indexed_db::mojom::Value; |
| +using indexed_db::mojom::ValuePtr; |
| +using mojo::StrongAssociatedBindingPtr; |
| +using testing::_; |
| +using testing::StrictMock; |
| + |
| +namespace content { |
| +namespace { |
| + |
| +ACTION_TEMPLATE(MoveArg, |
| + HAS_1_TEMPLATE_PARAMS(int, k), |
| + AND_1_VALUE_PARAMS(out)) { |
| + *out = std::move(*::testing::get<k>(args)); |
| +}; |
| + |
| +ACTION_P(RunClosure, closure) { |
| + closure.Run(); |
| +} |
| + |
| +MATCHER_P(IsAssociatedInterfacePtrInfoValid, |
| + tf, |
| + std::string(negation ? "isn't" : "is") + " " + |
| + std::string(tf ? "valid" : "invalid")) { |
| + return tf == arg->is_valid(); |
| +} |
| + |
| +MATCHER_P(MatchesIDBKey, key, "") { |
| + return arg.Equals(key); |
| +} |
| + |
| +typedef void (base::Closure::*ClosureRunFcn)() const &; |
| + |
| +static const char kDatabaseName[] = "db"; |
| +static const char kOrigin[] = "https://www.example.com"; |
| +static const int kFakeProcessId = 2; |
| +static const int64_t kTemporaryQuota = 50 * 1024 * 1024; |
| +static const int64_t kPersistantQuota = 50 * 1024 * 1024; |
| + |
| +base::FilePath CreateAndReturnTempDir(base::ScopedTempDir* temp_dir) { |
| + CHECK(temp_dir->CreateUniqueTempDir()); |
| + return temp_dir->GetPath(); |
| +} |
| + |
| +// Stores data specific to a connection. |
| +struct TestDatabaseConnection { |
| + TestDatabaseConnection(url::Origin origin, |
| + base::string16 db_name, |
| + int64_t version, |
| + int64_t upgrade_txn_id) |
| + : origin(std::move(origin)), |
| + db_name(std::move(db_name)), |
| + version(version), |
| + upgrade_txn_id(upgrade_txn_id), |
| + open_callbacks(new StrictMock<MockMojoIndexedDBCallbacks>()), |
| + connection_callbacks( |
| + new StrictMock<MockMojoIndexedDBDatabaseCallbacks>()){}; |
| + ~TestDatabaseConnection() {} |
| + |
| + void Open(Factory* factory) { |
| + factory->Open(open_callbacks->CreateInterfacePtrAndBind(), |
| + connection_callbacks->CreateInterfacePtrAndBind(), origin, |
| + db_name, version, upgrade_txn_id); |
| + } |
| + |
| + url::Origin origin; |
| + base::string16 db_name; |
| + int64_t version; |
| + int64_t upgrade_txn_id; |
| + |
| + DatabaseAssociatedPtr database; |
| + |
| + std::unique_ptr<MockMojoIndexedDBCallbacks> open_callbacks; |
| + std::unique_ptr<MockMojoIndexedDBDatabaseCallbacks> connection_callbacks; |
| + |
| + private: |
| + DISALLOW_COPY_AND_ASSIGN(TestDatabaseConnection); |
| +}; |
| + |
| +} // namespace |
| + |
| +class IndexedDBDispatcherHostTest : public testing::Test { |
| + public: |
| + IndexedDBDispatcherHostTest() |
| + : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), |
| + idb_thread_(new base::Thread("IndexedDB")), |
| + io_task_runner_( |
| + BrowserThread::GetTaskRunnerForThread(BrowserThread::IO)), |
| + idb_task_runner_(idb_thread_->Start() ? idb_thread_->task_runner() |
| + : nullptr), |
| + request_context_getter_( |
| + new net::TestURLRequestContextGetter(io_task_runner_)), |
| + special_storage_policy_(new MockSpecialStoragePolicy()), |
| + quota_manager_(new MockQuotaManager(false, |
| + browser_context_.GetPath(), |
| + io_task_runner_, |
| + idb_task_runner_, |
| + special_storage_policy_)), |
| + quota_manager_proxy_(quota_manager_->proxy()), |
| + context_impl_( |
| + new IndexedDBContextImpl(CreateAndReturnTempDir(&temp_dir_), |
| + special_storage_policy_.get(), |
| + quota_manager_proxy_.get(), |
| + idb_task_runner_.get())), |
| + blob_storage_(ChromeBlobStorageContext::GetFor(&browser_context_)), |
| + host_(new IndexedDBDispatcherHost(kFakeProcessId, |
| + request_context_getter_.get(), |
| + context_impl_.get(), |
| + blob_storage_.get())) { |
| + quota_manager_->SetQuota(GURL(kOrigin), storage::kStorageTypePersistent, |
| + kTemporaryQuota); |
| + quota_manager_->SetQuota(GURL(kOrigin), storage::kStorageTypeTemporary, |
| + kPersistantQuota); |
| + } |
| + |
| + void TearDown() override { |
|
Ken Rockot(use gerrit already)
2017/05/16 23:12:47
Thanks, this seems better!
One more change here t
|
| + host_.reset(); |
| + // In order for idb_thread_.Stop() to not cause thread/taskrunner checking |
| + // errors, the handles must be deref'd before we join threads. This ensures |
| + // classes that require destruction on the idb thread can be destructed |
| + // correctly before scheduling on the the idb thread task runner turns into |
| + // a no-op after thread join. |
| + blob_storage_ = nullptr; |
| + context_impl_ = nullptr; |
| + quota_manager_proxy_ = nullptr; |
| + quota_manager_ = nullptr; |
| + special_storage_policy_ = nullptr; |
| + request_context_getter_ = nullptr; |
| + // This will run the idb task runner until idle, then join the threads. |
| + idb_thread_->Stop(); |
| + // File are leaked if this doesn't return true. |
| + ASSERT_TRUE(temp_dir_.Delete()); |
| + } |
| + |
| + void SetUp() override { |
| + ASSERT_TRUE(idb_task_runner_); |
| + FactoryAssociatedRequest request = |
| + ::mojo::MakeIsolatedRequest(&idb_mojo_factory_); |
| + host_->AddBinding(std::move(request)); |
| + } |
| + |
| + protected: |
| + TestBrowserThreadBundle thread_bundle_; |
| + TestBrowserContext browser_context_; |
| + |
| + base::ScopedTempDir temp_dir_; |
| + std::unique_ptr<base::Thread> idb_thread_; |
| + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; |
| + scoped_refptr<base::SingleThreadTaskRunner> idb_task_runner_; |
| + scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_; |
| + scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_; |
| + scoped_refptr<MockQuotaManager> quota_manager_; |
| + scoped_refptr<QuotaManagerProxy> quota_manager_proxy_; |
| + scoped_refptr<IndexedDBContextImpl> context_impl_; |
| + scoped_refptr<ChromeBlobStorageContext> blob_storage_; |
| + std::unique_ptr<IndexedDBDispatcherHost, BrowserThread::DeleteOnIOThread> |
| + host_; |
| + FactoryAssociatedPtr idb_mojo_factory_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(IndexedDBDispatcherHostTest); |
| +}; |
| + |
| +TEST_F(IndexedDBDispatcherHostTest, CloseConnectionBeforeUpgrade) { |
| + const int64_t kDBVersion = 1; |
| + const int64_t kTransactionId = 1; |
| + |
| + TestDatabaseConnection connection(url::Origin(GURL(kOrigin)), |
| + base::UTF8ToUTF16(kDatabaseName), |
| + kDBVersion, kTransactionId); |
| + IndexedDBDatabaseMetadata metadata; |
| + DatabaseAssociatedPtrInfo database_info; |
| + base::RunLoop loop; |
| + EXPECT_CALL( |
| + *connection.open_callbacks, |
| + MockedUpgradeNeeded(IsAssociatedInterfacePtrInfoValid(true), |
| + IndexedDBDatabaseMetadata::NO_VERSION, |
| + blink::kWebIDBDataLossNone, std::string(""), _)) |
| + .WillOnce(testing::DoAll(MoveArg<0>(&database_info), |
| + testing::SaveArg<4>(&metadata), |
| + RunClosure(loop.QuitClosure()))); |
| + |
| + connection.Open(idb_mojo_factory_.get()); |
| + loop.Run(); |
| + |
| + EXPECT_TRUE(database_info.is_valid()); |
| + EXPECT_EQ(connection.version, metadata.version); |
| + EXPECT_EQ(connection.db_name, metadata.name); |
| +} |
| + |
| +TEST_F(IndexedDBDispatcherHostTest, CloseAfterUpgrade) { |
| + const int64_t kDBVersion = 1; |
| + const int64_t kTransactionId = 1; |
| + const int64_t kObjectStoreId = 10; |
| + const char kObjectStoreName[] = "os"; |
| + |
| + // Open connection. |
| + TestDatabaseConnection connection(url::Origin(GURL(kOrigin)), |
| + base::UTF8ToUTF16(kDatabaseName), |
| + kDBVersion, kTransactionId); |
| + |
| + IndexedDBDatabaseMetadata metadata; |
| + DatabaseAssociatedPtrInfo database_info; |
| + { |
| + base::RunLoop loop; |
| + EXPECT_CALL( |
| + *connection.open_callbacks, |
| + MockedUpgradeNeeded(IsAssociatedInterfacePtrInfoValid(true), |
| + IndexedDBDatabaseMetadata::NO_VERSION, |
| + blink::kWebIDBDataLossNone, std::string(""), _)) |
| + .WillOnce(testing::DoAll(MoveArg<0>(&database_info), |
| + testing::SaveArg<4>(&metadata), |
| + RunClosure(loop.QuitClosure()))); |
| + |
| + // Queue open request message. |
| + connection.Open(idb_mojo_factory_.get()); |
| + loop.Run(); |
| + } |
| + |
| + ASSERT_TRUE(database_info.is_valid()); |
| + EXPECT_EQ(connection.version, metadata.version); |
| + EXPECT_EQ(connection.db_name, metadata.name); |
| + |
| + connection.database.Bind(std::move(database_info)); |
| + |
| + { |
| + ::testing::InSequence dummy; |
| + base::RunLoop loop; |
| + base::Closure quit_closure = base::BarrierClosure(2, loop.QuitClosure()); |
| + EXPECT_CALL(*connection.connection_callbacks, Complete(kTransactionId)) |
| + .Times(1) |
| + .WillOnce(RunClosure(quit_closure)); |
| + EXPECT_CALL( |
| + *connection.open_callbacks, |
| + MockedSuccessDatabase(IsAssociatedInterfacePtrInfoValid(false), _)) |
| + .Times(1) |
| + .WillOnce(RunClosure(quit_closure)); |
| + |
| + ASSERT_TRUE(connection.database.is_bound()); |
| + connection.database->CreateObjectStore(kTransactionId, kObjectStoreId, |
| + base::UTF8ToUTF16(kObjectStoreName), |
| + content::IndexedDBKeyPath(), false); |
| + connection.database->Commit(kTransactionId); |
| + loop.Run(); |
| + } |
| +} |
| + |
| +TEST_F(IndexedDBDispatcherHostTest, OpenNewConnectionWhileUpgrading) { |
| + const int64_t kDBVersion = 1; |
| + const int64_t kTransactionId = 1; |
| + const int64_t kObjectStoreId = 10; |
| + const char kObjectStoreName[] = "os"; |
| + |
| + // Open connection 1, and expect the upgrade needed. |
| + TestDatabaseConnection connection1(url::Origin(GURL(kOrigin)), |
| + base::UTF8ToUTF16(kDatabaseName), |
| + kDBVersion, kTransactionId); |
| + DatabaseAssociatedPtrInfo database_info1; |
| + { |
| + base::RunLoop loop; |
| + IndexedDBDatabaseMetadata metadata; |
| + EXPECT_CALL( |
| + *connection1.open_callbacks, |
| + MockedUpgradeNeeded(IsAssociatedInterfacePtrInfoValid(true), |
| + IndexedDBDatabaseMetadata::NO_VERSION, |
| + blink::kWebIDBDataLossNone, std::string(""), _)) |
| + .WillOnce(testing::DoAll(MoveArg<0>(&database_info1), |
| + testing::SaveArg<4>(&metadata), |
| + RunClosure(loop.QuitClosure()))); |
| + |
| + // Queue open request message. |
| + connection1.Open(idb_mojo_factory_.get()); |
| + loop.Run(); |
| + } |
| + connection1.database.Bind(std::move(database_info1)); |
| + |
| + // Open connection 2, but expect that we won't be called back. |
| + DatabaseAssociatedPtrInfo database_info2; |
| + IndexedDBDatabaseMetadata metadata2; |
| + TestDatabaseConnection connection2(url::Origin(GURL(kOrigin)), |
| + base::UTF8ToUTF16(kDatabaseName), |
| + kDBVersion, 0); |
| + connection2.Open(idb_mojo_factory_.get()); |
| + |
| + // Check that we're called in order and the second connection gets it's |
| + // database after the first connection completes. |
| + { |
| + ::testing::InSequence dummy; |
| + base::RunLoop loop; |
| + base::Closure quit_closure = base::BarrierClosure(3, loop.QuitClosure()); |
| + EXPECT_CALL(*connection1.connection_callbacks, Complete(kTransactionId)) |
| + .Times(1) |
| + .WillOnce(RunClosure(quit_closure)); |
| + EXPECT_CALL( |
| + *connection1.open_callbacks, |
| + MockedSuccessDatabase(IsAssociatedInterfacePtrInfoValid(false), _)) |
| + .Times(1) |
| + .WillOnce(RunClosure(quit_closure)); |
| + EXPECT_CALL( |
| + *connection2.open_callbacks, |
| + MockedSuccessDatabase(IsAssociatedInterfacePtrInfoValid(true), _)) |
| + .WillOnce(testing::DoAll(MoveArg<0>(&database_info2), |
| + testing::SaveArg<1>(&metadata2), |
| + RunClosure(quit_closure))); |
| + |
| + // Create object store. |
| + ASSERT_TRUE(connection1.database.is_bound()); |
| + connection1.database->CreateObjectStore(kTransactionId, kObjectStoreId, |
| + base::UTF8ToUTF16(kObjectStoreName), |
| + content::IndexedDBKeyPath(), false); |
| + connection1.database->Commit(kTransactionId); |
| + loop.Run(); |
| + } |
| + |
| + EXPECT_TRUE(database_info2.is_valid()); |
| + EXPECT_EQ(connection2.version, metadata2.version); |
| + EXPECT_EQ(connection2.db_name, metadata2.name); |
| +} |
| + |
| +TEST_F(IndexedDBDispatcherHostTest, PutWithInvalidBlob) { |
| + const int64_t kDBVersion = 1; |
| + const int64_t kTransactionId = 1; |
| + const int64_t kObjectStoreId = 10; |
| + const char kObjectStoreName[] = "os"; |
| + |
| + // Open connection. |
| + TestDatabaseConnection connection(url::Origin(GURL(kOrigin)), |
| + base::UTF8ToUTF16(kDatabaseName), |
| + kDBVersion, kTransactionId); |
| + |
| + IndexedDBDatabaseMetadata metadata; |
| + DatabaseAssociatedPtrInfo database_info; |
| + { |
| + base::RunLoop loop; |
| + EXPECT_CALL( |
| + *connection.open_callbacks, |
| + MockedUpgradeNeeded(IsAssociatedInterfacePtrInfoValid(true), |
| + IndexedDBDatabaseMetadata::NO_VERSION, |
| + blink::kWebIDBDataLossNone, std::string(""), _)) |
| + .WillOnce(testing::DoAll(MoveArg<0>(&database_info), |
| + testing::SaveArg<4>(&metadata), |
| + RunClosure(loop.QuitClosure()))); |
| + |
| + // Queue open request message. |
| + connection.Open(idb_mojo_factory_.get()); |
| + loop.Run(); |
| + } |
| + |
| + ASSERT_TRUE(database_info.is_valid()); |
| + EXPECT_EQ(connection.version, metadata.version); |
| + EXPECT_EQ(connection.db_name, metadata.name); |
| + |
| + connection.database.Bind(std::move(database_info)); |
| + |
| + { |
| + ::testing::InSequence dummy; |
| + base::RunLoop loop; |
| + base::Closure quit_closure = base::BarrierClosure(3, loop.QuitClosure()); |
| + |
| + auto put_callbacks = |
| + base::MakeUnique<StrictMock<MockMojoIndexedDBCallbacks>>(); |
| + |
| + EXPECT_CALL(*put_callbacks, |
| + Error(blink::kWebIDBDatabaseExceptionUnknownError, _)) |
| + .Times(1) |
| + .WillOnce(RunClosure(quit_closure)); |
| + |
| + EXPECT_CALL( |
| + *connection.connection_callbacks, |
| + Abort(kTransactionId, blink::kWebIDBDatabaseExceptionUnknownError, _)) |
| + .Times(1) |
| + .WillOnce(RunClosure(quit_closure)); |
| + |
| + EXPECT_CALL(*connection.open_callbacks, |
| + Error(blink::kWebIDBDatabaseExceptionAbortError, _)) |
| + .Times(1) |
| + .WillOnce(RunClosure(quit_closure)); |
| + |
| + ASSERT_TRUE(connection.database.is_bound()); |
| + connection.database->CreateObjectStore(kTransactionId, kObjectStoreId, |
| + base::UTF8ToUTF16(kObjectStoreName), |
| + content::IndexedDBKeyPath(), false); |
| + // Call Put with an invalid blob. |
| + std::vector<::indexed_db::mojom::BlobInfoPtr> blobs; |
| + blobs.push_back(::indexed_db::mojom::BlobInfo::New( |
| + "fakeUUID", base::string16(), 100, nullptr)); |
| + connection.database->Put(kTransactionId, kObjectStoreId, |
| + Value::New("hello", std::move(blobs)), |
| + content::IndexedDBKey(base::UTF8ToUTF16("hello")), |
| + blink::kWebIDBPutModeAddOnly, |
| + std::vector<content::IndexedDBIndexKeys>(), |
| + put_callbacks->CreateInterfacePtrAndBind()); |
| + connection.database->Commit(kTransactionId); |
| + loop.Run(); |
| + } |
| +} |
| + |
| +} // namespace content |