// Copyright (c) 2012 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 "base/bind.h" #include "base/command_line.h" #include "base/files/file.h" #include "base/files/file_enumerator.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/lazy_instance.h" #include "base/location.h" #include "base/memory/ref_counted.h" #include "base/single_thread_task_runner.h" #include "base/strings/utf_string_conversions.h" #include "base/test/thread_test_helper.h" #include "content/browser/browser_main_loop.h" #include "content/browser/indexed_db/indexed_db_class_factory.h" #include "content/browser/indexed_db/indexed_db_context_impl.h" #include "content/browser/indexed_db/mock_browsertest_indexed_db_class_factory.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" #include "content/public/common/content_switches.h" #include "content/public/common/url_constants.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test_utils.h" #include "content/shell/browser/shell.h" #include "net/base/escape.h" #include "net/base/net_errors.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/http_request.h" #include "net/test/embedded_test_server/http_response.h" #include "storage/browser/blob/blob_storage_context.h" #include "storage/browser/database/database_util.h" #include "storage/browser/quota/quota_manager.h" using base::ASCIIToUTF16; using storage::QuotaManager; using storage::DatabaseUtil; namespace content { // This browser test is aimed towards exercising the IndexedDB bindings and // the actual implementation that lives in the browser side. class IndexedDBBrowserTest : public ContentBrowserTest { public: IndexedDBBrowserTest() : disk_usage_(-1) {} void SetUp() override { GetTestClassFactory()->Reset(); IndexedDBClassFactory::SetIndexedDBClassFactoryGetter(GetIDBClassFactory); ContentBrowserTest::SetUp(); } void TearDown() override { IndexedDBClassFactory::SetIndexedDBClassFactoryGetter(NULL); ContentBrowserTest::TearDown(); } void FailOperation(FailClass failure_class, FailMethod failure_method, int fail_on_instance_num, int fail_on_call_num) { GetTestClassFactory()->FailOperation( failure_class, failure_method, fail_on_instance_num, fail_on_call_num); } void SimpleTest(const GURL& test_url, bool incognito = false) { // The test page will perform tests on IndexedDB, then navigate to either // a #pass or #fail ref. Shell* the_browser = incognito ? CreateOffTheRecordBrowser() : shell(); VLOG(0) << "Navigating to URL and blocking."; NavigateToURLBlockUntilNavigationsComplete(the_browser, test_url, 2); VLOG(0) << "Navigation done."; std::string result = the_browser->web_contents()->GetLastCommittedURL().ref(); if (result != "pass") { std::string js_result; ASSERT_TRUE(ExecuteScriptAndExtractString( the_browser->web_contents(), "window.domAutomationController.send(getLog())", &js_result)); FAIL() << "Failed: " << js_result; } } void NavigateAndWaitForTitle(Shell* shell, const char* filename, const char* hash, const char* expected_string) { GURL url = GetTestUrl("indexeddb", filename); if (hash) url = GURL(url.spec() + hash); base::string16 expected_title16(ASCIIToUTF16(expected_string)); TitleWatcher title_watcher(shell->web_contents(), expected_title16); NavigateToURL(shell, url); EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle()); } IndexedDBContextImpl* GetContext() { StoragePartition* partition = BrowserContext::GetDefaultStoragePartition( shell()->web_contents()->GetBrowserContext()); return static_cast(partition->GetIndexedDBContext()); } void SetQuota(int quota_kilobytes) { const int kTemporaryStorageQuotaSize = quota_kilobytes * 1024 * QuotaManager::kPerHostTemporaryPortion; SetTempQuota(kTemporaryStorageQuotaSize, BrowserContext::GetDefaultStoragePartition( shell()->web_contents()->GetBrowserContext())->GetQuotaManager()); } static void SetTempQuota(int64 bytes, scoped_refptr qm) { if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&IndexedDBBrowserTest::SetTempQuota, bytes, qm)); return; } DCHECK_CURRENTLY_ON(BrowserThread::IO); qm->SetTemporaryGlobalOverrideQuota(bytes, storage::QuotaCallback()); // Don't return until the quota has been set. scoped_refptr helper(new base::ThreadTestHelper( BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); ASSERT_TRUE(helper->Run()); } virtual int64 RequestDiskUsage() { PostTaskAndReplyWithResult( GetContext()->TaskRunner(), FROM_HERE, base::Bind(&IndexedDBContext::GetOriginDiskUsage, GetContext(), GURL("file:///")), base::Bind(&IndexedDBBrowserTest::DidGetDiskUsage, this)); scoped_refptr helper(new base::ThreadTestHelper( BrowserMainLoop::GetInstance()->indexed_db_thread()->task_runner())); EXPECT_TRUE(helper->Run()); // Wait for DidGetDiskUsage to be called. base::MessageLoop::current()->RunUntilIdle(); return disk_usage_; } virtual int RequestBlobFileCount() { PostTaskAndReplyWithResult( GetContext()->TaskRunner(), FROM_HERE, base::Bind(&IndexedDBContextImpl::GetOriginBlobFileCount, GetContext(), GURL("file:///")), base::Bind(&IndexedDBBrowserTest::DidGetBlobFileCount, this)); scoped_refptr helper(new base::ThreadTestHelper( BrowserMainLoop::GetInstance()->indexed_db_thread()->task_runner())); EXPECT_TRUE(helper->Run()); // Wait for DidGetBlobFileCount to be called. base::MessageLoop::current()->RunUntilIdle(); return blob_file_count_; } private: static MockBrowserTestIndexedDBClassFactory* GetTestClassFactory() { static ::base::LazyInstance::Leaky s_factory = LAZY_INSTANCE_INITIALIZER; return s_factory.Pointer(); } static IndexedDBClassFactory* GetIDBClassFactory() { return GetTestClassFactory(); } virtual void DidGetDiskUsage(int64 bytes) { disk_usage_ = bytes; } virtual void DidGetBlobFileCount(int count) { blob_file_count_ = count; } int64 disk_usage_; int blob_file_count_ = 0; DISALLOW_COPY_AND_ASSIGN(IndexedDBBrowserTest); }; IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CursorTest) { SimpleTest(GetTestUrl("indexeddb", "cursor_test.html")); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CursorTestIncognito) { SimpleTest(GetTestUrl("indexeddb", "cursor_test.html"), true /* incognito */); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CursorPrefetch) { SimpleTest(GetTestUrl("indexeddb", "cursor_prefetch.html")); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, IndexTest) { SimpleTest(GetTestUrl("indexeddb", "index_test.html")); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, KeyPathTest) { SimpleTest(GetTestUrl("indexeddb", "key_path_test.html")); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, TransactionGetTest) { SimpleTest(GetTestUrl("indexeddb", "transaction_get_test.html")); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, KeyTypesTest) { SimpleTest(GetTestUrl("indexeddb", "key_types_test.html")); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, ObjectStoreTest) { SimpleTest(GetTestUrl("indexeddb", "object_store_test.html")); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DatabaseTest) { SimpleTest(GetTestUrl("indexeddb", "database_test.html")); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, TransactionTest) { SimpleTest(GetTestUrl("indexeddb", "transaction_test.html")); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CallbackAccounting) { SimpleTest(GetTestUrl("indexeddb", "callback_accounting.html")); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DoesntHangTest) { SimpleTest(GetTestUrl("indexeddb", "transaction_run_forever.html")); CrashTab(shell()->web_contents()); SimpleTest(GetTestUrl("indexeddb", "transaction_not_blocked.html")); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, Bug84933Test) { const GURL url = GetTestUrl("indexeddb", "bug_84933.html"); // Just navigate to the URL. Test will crash if it fails. NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, Bug106883Test) { const GURL url = GetTestUrl("indexeddb", "bug_106883.html"); // Just navigate to the URL. Test will crash if it fails. NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, Bug109187Test) { const GURL url = GetTestUrl("indexeddb", "bug_109187.html"); // Just navigate to the URL. Test will crash if it fails. NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1); } class IndexedDBBrowserTestWithLowQuota : public IndexedDBBrowserTest { public: IndexedDBBrowserTestWithLowQuota() {} void SetUpOnMainThread() override { const int kInitialQuotaKilobytes = 5000; SetQuota(kInitialQuotaKilobytes); } private: DISALLOW_COPY_AND_ASSIGN(IndexedDBBrowserTestWithLowQuota); }; IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithLowQuota, QuotaTest) { SimpleTest(GetTestUrl("indexeddb", "quota_test.html")); } class IndexedDBBrowserTestWithGCExposed : public IndexedDBBrowserTest { public: IndexedDBBrowserTestWithGCExposed() {} void SetUpCommandLine(base::CommandLine* command_line) override { command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc"); } private: DISALLOW_COPY_AND_ASSIGN(IndexedDBBrowserTestWithGCExposed); }; IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithGCExposed, DatabaseCallbacksTest) { SimpleTest(GetTestUrl("indexeddb", "database_callbacks_first.html")); } static void CopyLevelDBToProfile(Shell* shell, scoped_refptr context, const std::string& test_directory) { DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread()); base::FilePath leveldb_dir(FILE_PATH_LITERAL("file__0.indexeddb.leveldb")); base::FilePath test_data_dir = GetTestFilePath("indexeddb", test_directory.c_str()).Append(leveldb_dir); base::FilePath dest = context->data_path().Append(leveldb_dir); // If we don't create the destination directory first, the contents of the // leveldb directory are copied directly into profile/IndexedDB instead of // profile/IndexedDB/file__0.xxx/ ASSERT_TRUE(base::CreateDirectory(dest)); const bool kRecursive = true; ASSERT_TRUE(base::CopyDirectory(test_data_dir, context->data_path(), kRecursive)); } class IndexedDBBrowserTestWithPreexistingLevelDB : public IndexedDBBrowserTest { public: IndexedDBBrowserTestWithPreexistingLevelDB() {} void SetUpOnMainThread() override { scoped_refptr context = GetContext(); context->TaskRunner()->PostTask( FROM_HERE, base::Bind( &CopyLevelDBToProfile, shell(), context, EnclosingLevelDBDir())); scoped_refptr helper(new base::ThreadTestHelper( BrowserMainLoop::GetInstance()->indexed_db_thread()->task_runner())); ASSERT_TRUE(helper->Run()); } virtual std::string EnclosingLevelDBDir() = 0; private: DISALLOW_COPY_AND_ASSIGN(IndexedDBBrowserTestWithPreexistingLevelDB); }; class IndexedDBBrowserTestWithVersion0Schema : public IndexedDBBrowserTestWithPreexistingLevelDB { std::string EnclosingLevelDBDir() override { return "migration_from_0"; } }; IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithVersion0Schema, MigrationTest) { SimpleTest(GetTestUrl("indexeddb", "migration_test.html")); } class IndexedDBBrowserTestWithVersion123456Schema : public IndexedDBBrowserTestWithPreexistingLevelDB { std::string EnclosingLevelDBDir() override { return "schema_version_123456"; } }; IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithVersion123456Schema, DestroyTest) { int64 original_size = RequestDiskUsage(); EXPECT_GT(original_size, 0); SimpleTest(GetTestUrl("indexeddb", "open_bad_db.html")); int64 new_size = RequestDiskUsage(); EXPECT_GT(new_size, 0); EXPECT_NE(original_size, new_size); } class IndexedDBBrowserTestWithVersion987654SSVData : public IndexedDBBrowserTestWithPreexistingLevelDB { std::string EnclosingLevelDBDir() override { return "ssv_version_987654"; } }; IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithVersion987654SSVData, DestroyTest) { int64 original_size = RequestDiskUsage(); EXPECT_GT(original_size, 0); SimpleTest(GetTestUrl("indexeddb", "open_bad_db.html")); int64 new_size = RequestDiskUsage(); EXPECT_GT(new_size, 0); EXPECT_NE(original_size, new_size); } class IndexedDBBrowserTestWithCorruptLevelDB : public IndexedDBBrowserTestWithPreexistingLevelDB { std::string EnclosingLevelDBDir() override { return "corrupt_leveldb"; } }; IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithCorruptLevelDB, DestroyTest) { int64 original_size = RequestDiskUsage(); EXPECT_GT(original_size, 0); SimpleTest(GetTestUrl("indexeddb", "open_bad_db.html")); int64 new_size = RequestDiskUsage(); EXPECT_GT(new_size, 0); EXPECT_NE(original_size, new_size); } class IndexedDBBrowserTestWithMissingSSTFile : public IndexedDBBrowserTestWithPreexistingLevelDB { std::string EnclosingLevelDBDir() override { return "missing_sst"; } }; IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithMissingSSTFile, DestroyTest) { int64 original_size = RequestDiskUsage(); EXPECT_GT(original_size, 0); SimpleTest(GetTestUrl("indexeddb", "open_missing_table.html")); int64 new_size = RequestDiskUsage(); EXPECT_GT(new_size, 0); EXPECT_NE(original_size, new_size); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, LevelDBLogFileTest) { // Any page that opens an IndexedDB will work here. SimpleTest(GetTestUrl("indexeddb", "database_test.html")); base::FilePath leveldb_dir(FILE_PATH_LITERAL("file__0.indexeddb.leveldb")); base::FilePath log_file(FILE_PATH_LITERAL("LOG")); base::FilePath log_file_path = GetContext()->data_path().Append(leveldb_dir).Append(log_file); int64 size; EXPECT_TRUE(base::GetFileSize(log_file_path, &size)); EXPECT_GT(size, 0); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CanDeleteWhenOverQuotaTest) { SimpleTest(GetTestUrl("indexeddb", "fill_up_5k.html")); int64 size = RequestDiskUsage(); const int kQuotaKilobytes = 2; EXPECT_GT(size, kQuotaKilobytes * 1024); SetQuota(kQuotaKilobytes); SimpleTest(GetTestUrl("indexeddb", "delete_over_quota.html")); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, EmptyBlob) { // First delete all IDB's for the test origin GetContext()->TaskRunner()->PostTask( FROM_HERE, base::Bind(&IndexedDBContextImpl::DeleteForOrigin, GetContext(), GURL("file:///"))); EXPECT_EQ(0, RequestBlobFileCount()); // Start with no blob files. const GURL test_url = GetTestUrl("indexeddb", "empty_blob.html"); // For some reason Android's futimes fails (EPERM) in this test. Do not assert // file times on Android, but do so on other platforms. crbug.com/467247 // TODO(cmumford): Figure out why this is the case and fix if possible. #if defined(OS_ANDROID) SimpleTest(GURL(test_url.spec() + "#ignoreTimes")); #else SimpleTest(GURL(test_url.spec())); #endif // Test stores one blob and one file to disk, so expect two files. EXPECT_EQ(2, RequestBlobFileCount()); } // Very flaky on many bots. See crbug.com/459835 IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithGCExposed, DISABLED_BlobDidAck) { SimpleTest(GetTestUrl("indexeddb", "blob_did_ack.html")); // Wait for idle so that the blob ack has time to be received/processed by // the browser process. base::MessageLoop::current()->RunUntilIdle(); content::ChromeBlobStorageContext* blob_context = ChromeBlobStorageContext::GetFor( shell()->web_contents()->GetBrowserContext()); EXPECT_EQ(0UL, blob_context->context()->blob_count()); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithGCExposed, BlobDidAckPrefetch) { SimpleTest(GetTestUrl("indexeddb", "blob_did_ack_prefetch.html")); // Wait for idle so that the blob ack has time to be received/processed by // the browser process. base::MessageLoop::current()->RunUntilIdle(); content::ChromeBlobStorageContext* blob_context = ChromeBlobStorageContext::GetFor( shell()->web_contents()->GetBrowserContext()); EXPECT_EQ(0UL, blob_context->context()->blob_count()); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, BlobsCountAgainstQuota) { SimpleTest(GetTestUrl("indexeddb", "blobs_use_quota.html")); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DeleteForOriginDeletesBlobs) { SimpleTest(GetTestUrl("indexeddb", "write_20mb_blob.html")); int64 size = RequestDiskUsage(); // This assertion assumes that we do not compress blobs. EXPECT_GT(size, 20 << 20 /* 20 MB */); GetContext()->TaskRunner()->PostTask( FROM_HERE, base::Bind(&IndexedDBContextImpl::DeleteForOrigin, GetContext(), GURL("file:///"))); scoped_refptr helper(new base::ThreadTestHelper( BrowserMainLoop::GetInstance()->indexed_db_thread()->task_runner())); ASSERT_TRUE(helper->Run()); EXPECT_EQ(0, RequestDiskUsage()); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DiskFullOnCommit) { // Ignore several preceding transactions: // * The test calls deleteDatabase() which opens the backing store: // #1: IndexedDBBackingStore::OpenBackingStore // => IndexedDBBackingStore::SetUpMetadata // #2: IndexedDBBackingStore::OpenBackingStore // => IndexedDBBackingStore::CleanUpBlobJournal (no-op) // * The test calls open(), to create a new database: // #3: IndexedDBFactoryImpl::Open // => IndexedDBDatabase::Create // => IndexedDBBackingStore::CreateIDBDatabaseMetaData // #4: IndexedDBTransaction::Commit - initial "versionchange" transaction // * Once the connection is opened, the test runs: // #5: IndexedDBTransaction::Commit - the test's "readwrite" transaction) const int instance_num = 5; const int call_num = 1; FailOperation(FAIL_CLASS_LEVELDB_TRANSACTION, FAIL_METHOD_COMMIT_DISK_FULL, instance_num, call_num); SimpleTest(GetTestUrl("indexeddb", "disk_full_on_commit.html")); } namespace { static void CompactIndexedDBBackingStore( scoped_refptr context, const GURL& origin_url) { IndexedDBFactory* factory = context->GetIDBFactory(); std::pair range = factory->GetOpenDatabasesForOrigin(origin_url); if (range.first == range.second) // If no open db's for this origin return; // Compact the first db's backing store since all the db's are in the same // backing store. IndexedDBDatabase* db = range.first->second; IndexedDBBackingStore* backing_store = db->backing_store(); backing_store->Compact(); } static void CorruptIndexedDBDatabase( IndexedDBContextImpl* context, const GURL& origin_url, base::WaitableEvent* signal_when_finished) { CompactIndexedDBBackingStore(context, origin_url); int num_files = 0; int num_errors = 0; const bool recursive = false; for (const base::FilePath& idb_data_path : context->GetStoragePaths(origin_url)) { base::FileEnumerator enumerator( idb_data_path, recursive, base::FileEnumerator::FILES); for (base::FilePath idb_file = enumerator.Next(); !idb_file.empty(); idb_file = enumerator.Next()) { int64 size(0); GetFileSize(idb_file, &size); if (idb_file.Extension() == FILE_PATH_LITERAL(".ldb")) { num_files++; base::File file( idb_file, base::File::FLAG_WRITE | base::File::FLAG_OPEN_TRUNCATED); if (file.IsValid()) { // Was opened truncated, expand back to the original // file size and fill with zeros (corrupting the file). file.SetLength(size); } else { num_errors++; } } } VLOG(0) << "There were " << num_files << " in " << idb_data_path.value() << " with " << num_errors << " errors"; } signal_when_finished->Signal(); } const std::string s_corrupt_db_test_prefix = "/corrupt/test/"; static scoped_ptr CorruptDBRequestHandler( IndexedDBContextImpl* context, const GURL& origin_url, const std::string& path, IndexedDBBrowserTest* test, const net::test_server::HttpRequest& request) { std::string request_path; if (path.find(s_corrupt_db_test_prefix) != std::string::npos) request_path = request.relative_url.substr(s_corrupt_db_test_prefix.size()); else return scoped_ptr(); // Remove the query string if present. std::string request_query; size_t query_pos = request_path.find('?'); if (query_pos != std::string::npos) { request_query = request_path.substr(query_pos + 1); request_path = request_path.substr(0, query_pos); } if (request_path == "corruptdb" && !request_query.empty()) { VLOG(0) << "Requested to corrupt IndexedDB: " << request_query; base::WaitableEvent signal_when_finished(false, false); context->TaskRunner()->PostTask(FROM_HERE, base::Bind(&CorruptIndexedDBDatabase, base::ConstRef(context), origin_url, &signal_when_finished)); signal_when_finished.Wait(); scoped_ptr http_response( new net::test_server::BasicHttpResponse); http_response->set_code(net::HTTP_OK); return http_response.Pass(); } else if (request_path == "fail" && !request_query.empty()) { FailClass failure_class = FAIL_CLASS_NOTHING; FailMethod failure_method = FAIL_METHOD_NOTHING; int instance_num = 1; int call_num = 1; std::string fail_class; std::string fail_method; url::Component query(0, request_query.length()), key_pos, value_pos; while (url::ExtractQueryKeyValue( request_query.c_str(), &query, &key_pos, &value_pos)) { std::string escaped_key(request_query.substr(key_pos.begin, key_pos.len)); std::string escaped_value( request_query.substr(value_pos.begin, value_pos.len)); std::string key = net::UnescapeURLComponent( escaped_key, net::UnescapeRule::NORMAL | net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS); std::string value = net::UnescapeURLComponent( escaped_value, net::UnescapeRule::NORMAL | net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS); if (key == "method") fail_method = value; else if (key == "class") fail_class = value; else if (key == "instNum") instance_num = atoi(value.c_str()); else if (key == "callNum") call_num = atoi(value.c_str()); else NOTREACHED() << "Unknown param: \"" << key << "\""; } if (fail_class == "LevelDBTransaction") { failure_class = FAIL_CLASS_LEVELDB_TRANSACTION; if (fail_method == "Get") failure_method = FAIL_METHOD_GET; else if (fail_method == "Commit") failure_method = FAIL_METHOD_COMMIT; else NOTREACHED() << "Unknown method: \"" << fail_method << "\""; } else if (fail_class == "LevelDBIterator") { failure_class = FAIL_CLASS_LEVELDB_ITERATOR; if (fail_method == "Seek") failure_method = FAIL_METHOD_SEEK; else NOTREACHED() << "Unknown method: \"" << fail_method << "\""; } else { NOTREACHED() << "Unknown class: \"" << fail_class << "\""; } DCHECK_GE(instance_num, 1); DCHECK_GE(call_num, 1); test->FailOperation(failure_class, failure_method, instance_num, call_num); scoped_ptr http_response( new net::test_server::BasicHttpResponse); http_response->set_code(net::HTTP_OK); return http_response.Pass(); } // A request for a test resource base::FilePath resource_path = content::GetTestFilePath("indexeddb", request_path.c_str()); scoped_ptr http_response( new net::test_server::BasicHttpResponse); http_response->set_code(net::HTTP_OK); std::string file_contents; if (!base::ReadFileToString(resource_path, &file_contents)) return scoped_ptr(); http_response->set_content(file_contents); return http_response.Pass(); } } // namespace class IndexedDBBrowserCorruptionTest : public IndexedDBBrowserTest, public ::testing::WithParamInterface { public: void SetUpCommandLine(base::CommandLine* command_line) override { // Experimental for IDBObjectStore.getAll() command_line->AppendSwitch( switches::kEnableExperimentalWebPlatformFeatures); } }; IN_PROC_BROWSER_TEST_P(IndexedDBBrowserCorruptionTest, OperationOnCorruptedOpenDatabase) { ASSERT_TRUE(embedded_test_server()->Started() || embedded_test_server()->InitializeAndWaitUntilReady()); const GURL& origin_url = embedded_test_server()->base_url(); embedded_test_server()->RegisterRequestHandler( base::Bind(&CorruptDBRequestHandler, base::Unretained(GetContext()), origin_url, s_corrupt_db_test_prefix, this)); std::string test_file = s_corrupt_db_test_prefix + "corrupted_open_db_detection.html#" + GetParam(); SimpleTest(embedded_test_server()->GetURL(test_file)); test_file = s_corrupt_db_test_prefix + "corrupted_open_db_recovery.html"; SimpleTest(embedded_test_server()->GetURL(test_file)); } INSTANTIATE_TEST_CASE_P(IndexedDBBrowserCorruptionTestInstantiation, IndexedDBBrowserCorruptionTest, ::testing::Values("failGetBlobJournal", "get", "getAll", "failWebkitGetDatabaseNames", "iterate", "failTransactionCommit", "clearObjectStore")); IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DeleteCompactsBackingStore) { const GURL test_url = GetTestUrl("indexeddb", "delete_compact.html"); SimpleTest(GURL(test_url.spec() + "#fill")); int64 after_filling = RequestDiskUsage(); EXPECT_GT(after_filling, 0); SimpleTest(GURL(test_url.spec() + "#purge")); int64 after_deleting = RequestDiskUsage(); EXPECT_LT(after_deleting, after_filling); // The above tests verify basic assertions - that filling writes data and // deleting reduces the amount stored. // The below tests make assumptions about implementation specifics, such as // data compression, compaction efficiency, and the maximum amount of // metadata and log data remains after a deletion. It is possible that // changes to the implementation may require these constants to be tweaked. const int kTestFillBytes = 1024 * 1024 * 5; // 5MB EXPECT_GT(after_filling, kTestFillBytes); const int kTestCompactBytes = 1024 * 10; // 10kB EXPECT_LT(after_deleting, kTestCompactBytes); } // Complex multi-step (converted from pyauto) tests begin here. // Verify null key path persists after restarting browser. IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, PRE_NullKeyPathPersistence) { NavigateAndWaitForTitle(shell(), "bug_90635.html", "#part1", "pass - first run"); } // Verify null key path persists after restarting browser. IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, NullKeyPathPersistence) { NavigateAndWaitForTitle(shell(), "bug_90635.html", "#part2", "pass - second run"); } // Verify that a VERSION_CHANGE transaction is rolled back after a // renderer/browser crash IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, PRE_PRE_VersionChangeCrashResilience) { NavigateAndWaitForTitle(shell(), "version_change_crash.html", "#part1", "pass - part1 - complete"); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, PRE_VersionChangeCrashResilience) { NavigateAndWaitForTitle(shell(), "version_change_crash.html", "#part2", "pass - part2 - crash me"); // If we actually crash here then googletest will not run the next step // (VersionChangeCrashResilience) as an optimization. googletest's // ASSERT_DEATH/EXIT fails to work properly (on Windows) due to how we // implement the PRE_* test mechanism. exit(0); } IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, VersionChangeCrashResilience) { NavigateAndWaitForTitle(shell(), "version_change_crash.html", "#part3", "pass - part3 - rolled back"); } // crbug.com/427529 // Disable this test for ASAN on Android because it takes too long to run. #if defined(ANDROID) && defined(ADDRESS_SANITIZER) #define MAYBE_ConnectionsClosedOnTabClose DISABLED_ConnectionsClosedOnTabClose #else #define MAYBE_ConnectionsClosedOnTabClose ConnectionsClosedOnTabClose #endif // Verify that open DB connections are closed when a tab is destroyed. IN_PROC_BROWSER_TEST_F( IndexedDBBrowserTest, MAYBE_ConnectionsClosedOnTabClose) { NavigateAndWaitForTitle(shell(), "version_change_blocked.html", "#tab1", "setVersion(2) complete"); // Start on a different URL to force a new renderer process. Shell* new_shell = CreateBrowser(); NavigateToURL(new_shell, GURL(url::kAboutBlankURL)); NavigateAndWaitForTitle(new_shell, "version_change_blocked.html", "#tab2", "setVersion(3) blocked"); base::string16 expected_title16(ASCIIToUTF16("setVersion(3) complete")); TitleWatcher title_watcher(new_shell->web_contents(), expected_title16); shell()->web_contents()->GetRenderProcessHost()->Shutdown(0, true); shell()->Close(); EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle()); } // Verify that a "close" event is fired at database connections when // the backing store is deleted. IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, ForceCloseEventTest) { NavigateAndWaitForTitle(shell(), "force_close_event.html", NULL, "connection ready"); GetContext()->TaskRunner()->PostTask( FROM_HERE, base::Bind(&IndexedDBContextImpl::DeleteForOrigin, GetContext(), GURL("file:///"))); base::string16 expected_title16(ASCIIToUTF16("connection closed")); TitleWatcher title_watcher(shell()->web_contents(), expected_title16); title_watcher.AlsoWaitForTitle(ASCIIToUTF16("connection closed with error")); EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle()); } class IndexedDBBrowserTestSingleProcess : public IndexedDBBrowserTest { public: void SetUpCommandLine(base::CommandLine* command_line) override { command_line->AppendSwitch(switches::kSingleProcess); } }; IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestSingleProcess, RenderThreadShutdownTest) { SimpleTest(GetTestUrl("indexeddb", "shutdown_with_requests.html")); } } // namespace content