1// Copyright (c) 2012 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 "base/bind.h"
6#include "base/command_line.h"
7#include "base/files/file.h"
8#include "base/files/file_enumerator.h"
9#include "base/files/file_path.h"
10#include "base/files/file_util.h"
11#include "base/lazy_instance.h"
12#include "base/memory/ref_counted.h"
13#include "base/message_loop/message_loop.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/test/thread_test_helper.h"
16#include "content/browser/browser_main_loop.h"
17#include "content/browser/indexed_db/indexed_db_class_factory.h"
18#include "content/browser/indexed_db/indexed_db_context_impl.h"
19#include "content/browser/indexed_db/mock_browsertest_indexed_db_class_factory.h"
20#include "content/browser/web_contents/web_contents_impl.h"
21#include "content/public/browser/browser_context.h"
22#include "content/public/browser/browser_thread.h"
23#include "content/public/browser/render_process_host.h"
24#include "content/public/browser/storage_partition.h"
25#include "content/public/browser/web_contents.h"
26#include "content/public/common/content_switches.h"
27#include "content/public/common/url_constants.h"
28#include "content/public/test/browser_test_utils.h"
29#include "content/public/test/content_browser_test.h"
30#include "content/public/test/content_browser_test_utils.h"
31#include "content/shell/browser/shell.h"
32#include "net/base/escape.h"
33#include "net/base/net_errors.h"
34#include "net/test/embedded_test_server/embedded_test_server.h"
35#include "net/test/embedded_test_server/http_request.h"
36#include "net/test/embedded_test_server/http_response.h"
37#include "storage/browser/database/database_util.h"
38#include "storage/browser/quota/quota_manager.h"
39
40using base::ASCIIToUTF16;
41using storage::QuotaManager;
42using storage::DatabaseUtil;
43
44namespace content {
45
46// This browser test is aimed towards exercising the IndexedDB bindings and
47// the actual implementation that lives in the browser side.
48class IndexedDBBrowserTest : public ContentBrowserTest {
49 public:
50  IndexedDBBrowserTest() : disk_usage_(-1) {}
51
52  virtual void SetUp() OVERRIDE {
53    GetTestClassFactory()->Reset();
54    IndexedDBClassFactory::SetIndexedDBClassFactoryGetter(GetIDBClassFactory);
55    ContentBrowserTest::SetUp();
56  }
57
58  virtual void TearDown() OVERRIDE {
59    IndexedDBClassFactory::SetIndexedDBClassFactoryGetter(NULL);
60    ContentBrowserTest::TearDown();
61  }
62
63  void FailOperation(FailClass failure_class,
64                     FailMethod failure_method,
65                     int fail_on_instance_num,
66                     int fail_on_call_num) {
67    GetTestClassFactory()->FailOperation(
68        failure_class, failure_method, fail_on_instance_num, fail_on_call_num);
69  }
70
71  void SimpleTest(const GURL& test_url, bool incognito = false) {
72    // The test page will perform tests on IndexedDB, then navigate to either
73    // a #pass or #fail ref.
74    Shell* the_browser = incognito ? CreateOffTheRecordBrowser() : shell();
75
76    VLOG(0) << "Navigating to URL and blocking.";
77    NavigateToURLBlockUntilNavigationsComplete(the_browser, test_url, 2);
78    VLOG(0) << "Navigation done.";
79    std::string result =
80        the_browser->web_contents()->GetLastCommittedURL().ref();
81    if (result != "pass") {
82      std::string js_result;
83      ASSERT_TRUE(ExecuteScriptAndExtractString(
84          the_browser->web_contents(),
85          "window.domAutomationController.send(getLog())",
86          &js_result));
87      FAIL() << "Failed: " << js_result;
88    }
89  }
90
91  void NavigateAndWaitForTitle(Shell* shell,
92                               const char* filename,
93                               const char* hash,
94                               const char* expected_string) {
95    GURL url = GetTestUrl("indexeddb", filename);
96    if (hash)
97      url = GURL(url.spec() + hash);
98
99    base::string16 expected_title16(ASCIIToUTF16(expected_string));
100    TitleWatcher title_watcher(shell->web_contents(), expected_title16);
101    NavigateToURL(shell, url);
102    EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
103  }
104
105  IndexedDBContextImpl* GetContext() {
106    StoragePartition* partition =
107        BrowserContext::GetDefaultStoragePartition(
108            shell()->web_contents()->GetBrowserContext());
109    return static_cast<IndexedDBContextImpl*>(partition->GetIndexedDBContext());
110  }
111
112  void SetQuota(int quotaKilobytes) {
113    const int kTemporaryStorageQuotaSize = quotaKilobytes
114        * 1024 * QuotaManager::kPerHostTemporaryPortion;
115    SetTempQuota(kTemporaryStorageQuotaSize,
116        BrowserContext::GetDefaultStoragePartition(
117            shell()->web_contents()->GetBrowserContext())->GetQuotaManager());
118  }
119
120  static void SetTempQuota(int64 bytes, scoped_refptr<QuotaManager> qm) {
121    if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
122      BrowserThread::PostTask(
123          BrowserThread::IO, FROM_HERE,
124          base::Bind(&IndexedDBBrowserTest::SetTempQuota, bytes, qm));
125      return;
126    }
127    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
128    qm->SetTemporaryGlobalOverrideQuota(bytes, storage::QuotaCallback());
129    // Don't return until the quota has been set.
130    scoped_refptr<base::ThreadTestHelper> helper(new base::ThreadTestHelper(
131        BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB)));
132    ASSERT_TRUE(helper->Run());
133  }
134
135  virtual int64 RequestDiskUsage() {
136    PostTaskAndReplyWithResult(
137        GetContext()->TaskRunner(),
138        FROM_HERE,
139        base::Bind(&IndexedDBContext::GetOriginDiskUsage,
140                   GetContext(),
141                   GURL("file:///")),
142        base::Bind(&IndexedDBBrowserTest::DidGetDiskUsage, this));
143    scoped_refptr<base::ThreadTestHelper> helper(new base::ThreadTestHelper(
144        BrowserMainLoop::GetInstance()->indexed_db_thread()->
145            message_loop_proxy()));
146    EXPECT_TRUE(helper->Run());
147    // Wait for DidGetDiskUsage to be called.
148    base::MessageLoop::current()->RunUntilIdle();
149    return disk_usage_;
150  }
151
152 private:
153  static MockBrowserTestIndexedDBClassFactory* GetTestClassFactory() {
154    static ::base::LazyInstance<MockBrowserTestIndexedDBClassFactory>::Leaky
155        s_factory = LAZY_INSTANCE_INITIALIZER;
156    return s_factory.Pointer();
157  }
158
159  static IndexedDBClassFactory* GetIDBClassFactory() {
160    return GetTestClassFactory();
161  }
162
163  virtual void DidGetDiskUsage(int64 bytes) {
164    EXPECT_GT(bytes, 0);
165    disk_usage_ = bytes;
166  }
167
168  int64 disk_usage_;
169
170  DISALLOW_COPY_AND_ASSIGN(IndexedDBBrowserTest);
171};
172
173IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CursorTest) {
174  SimpleTest(GetTestUrl("indexeddb", "cursor_test.html"));
175}
176
177IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CursorTestIncognito) {
178  SimpleTest(GetTestUrl("indexeddb", "cursor_test.html"),
179             true /* incognito */);
180}
181
182IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CursorPrefetch) {
183  SimpleTest(GetTestUrl("indexeddb", "cursor_prefetch.html"));
184}
185
186IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, IndexTest) {
187  SimpleTest(GetTestUrl("indexeddb", "index_test.html"));
188}
189
190IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, KeyPathTest) {
191  SimpleTest(GetTestUrl("indexeddb", "key_path_test.html"));
192}
193
194IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, TransactionGetTest) {
195  SimpleTest(GetTestUrl("indexeddb", "transaction_get_test.html"));
196}
197
198IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, KeyTypesTest) {
199  SimpleTest(GetTestUrl("indexeddb", "key_types_test.html"));
200}
201
202IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, ObjectStoreTest) {
203  SimpleTest(GetTestUrl("indexeddb", "object_store_test.html"));
204}
205
206IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DatabaseTest) {
207  SimpleTest(GetTestUrl("indexeddb", "database_test.html"));
208}
209
210IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, TransactionTest) {
211  SimpleTest(GetTestUrl("indexeddb", "transaction_test.html"));
212}
213
214IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CallbackAccounting) {
215  SimpleTest(GetTestUrl("indexeddb", "callback_accounting.html"));
216}
217
218IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DoesntHangTest) {
219  SimpleTest(GetTestUrl("indexeddb", "transaction_run_forever.html"));
220  CrashTab(shell()->web_contents());
221  SimpleTest(GetTestUrl("indexeddb", "transaction_not_blocked.html"));
222}
223
224IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, Bug84933Test) {
225  const GURL url = GetTestUrl("indexeddb", "bug_84933.html");
226
227  // Just navigate to the URL. Test will crash if it fails.
228  NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1);
229}
230
231IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, Bug106883Test) {
232  const GURL url = GetTestUrl("indexeddb", "bug_106883.html");
233
234  // Just navigate to the URL. Test will crash if it fails.
235  NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1);
236}
237
238IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, Bug109187Test) {
239  const GURL url = GetTestUrl("indexeddb", "bug_109187.html");
240
241  // Just navigate to the URL. Test will crash if it fails.
242  NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1);
243}
244
245class IndexedDBBrowserTestWithLowQuota : public IndexedDBBrowserTest {
246 public:
247  IndexedDBBrowserTestWithLowQuota() {}
248
249  virtual void SetUpOnMainThread() OVERRIDE {
250    const int kInitialQuotaKilobytes = 5000;
251    SetQuota(kInitialQuotaKilobytes);
252  }
253
254 private:
255  DISALLOW_COPY_AND_ASSIGN(IndexedDBBrowserTestWithLowQuota);
256};
257
258IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithLowQuota, QuotaTest) {
259  SimpleTest(GetTestUrl("indexeddb", "quota_test.html"));
260}
261
262class IndexedDBBrowserTestWithGCExposed : public IndexedDBBrowserTest {
263 public:
264  IndexedDBBrowserTestWithGCExposed() {}
265
266  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
267    command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc");
268  }
269
270 private:
271  DISALLOW_COPY_AND_ASSIGN(IndexedDBBrowserTestWithGCExposed);
272};
273
274IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithGCExposed,
275                       DatabaseCallbacksTest) {
276  SimpleTest(GetTestUrl("indexeddb", "database_callbacks_first.html"));
277}
278
279static void CopyLevelDBToProfile(Shell* shell,
280                                 scoped_refptr<IndexedDBContextImpl> context,
281                                 const std::string& test_directory) {
282  DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread());
283  base::FilePath leveldb_dir(FILE_PATH_LITERAL("file__0.indexeddb.leveldb"));
284  base::FilePath test_data_dir =
285      GetTestFilePath("indexeddb", test_directory.c_str()).Append(leveldb_dir);
286  base::FilePath dest = context->data_path().Append(leveldb_dir);
287  // If we don't create the destination directory first, the contents of the
288  // leveldb directory are copied directly into profile/IndexedDB instead of
289  // profile/IndexedDB/file__0.xxx/
290  ASSERT_TRUE(base::CreateDirectory(dest));
291  const bool kRecursive = true;
292  ASSERT_TRUE(base::CopyDirectory(test_data_dir,
293                                  context->data_path(),
294                                  kRecursive));
295}
296
297class IndexedDBBrowserTestWithPreexistingLevelDB : public IndexedDBBrowserTest {
298 public:
299  IndexedDBBrowserTestWithPreexistingLevelDB() {}
300  virtual void SetUpOnMainThread() OVERRIDE {
301    scoped_refptr<IndexedDBContextImpl> context = GetContext();
302    context->TaskRunner()->PostTask(
303        FROM_HERE,
304        base::Bind(
305            &CopyLevelDBToProfile, shell(), context, EnclosingLevelDBDir()));
306    scoped_refptr<base::ThreadTestHelper> helper(new base::ThreadTestHelper(
307        BrowserMainLoop::GetInstance()->indexed_db_thread()->
308            message_loop_proxy()));
309    ASSERT_TRUE(helper->Run());
310  }
311
312  virtual std::string EnclosingLevelDBDir() = 0;
313
314 private:
315  DISALLOW_COPY_AND_ASSIGN(IndexedDBBrowserTestWithPreexistingLevelDB);
316};
317
318class IndexedDBBrowserTestWithVersion0Schema : public
319    IndexedDBBrowserTestWithPreexistingLevelDB {
320  virtual std::string EnclosingLevelDBDir() OVERRIDE {
321    return "migration_from_0";
322  }
323};
324
325IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithVersion0Schema, MigrationTest) {
326  SimpleTest(GetTestUrl("indexeddb", "migration_test.html"));
327}
328
329class IndexedDBBrowserTestWithVersion123456Schema : public
330    IndexedDBBrowserTestWithPreexistingLevelDB {
331  virtual std::string EnclosingLevelDBDir() OVERRIDE {
332    return "schema_version_123456";
333  }
334};
335
336IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithVersion123456Schema,
337                       DestroyTest) {
338  int64 original_size = RequestDiskUsage();
339  EXPECT_GT(original_size, 0);
340  SimpleTest(GetTestUrl("indexeddb", "open_bad_db.html"));
341  int64 new_size = RequestDiskUsage();
342  EXPECT_NE(original_size, new_size);
343}
344
345class IndexedDBBrowserTestWithVersion987654SSVData : public
346    IndexedDBBrowserTestWithPreexistingLevelDB {
347  virtual std::string EnclosingLevelDBDir() OVERRIDE {
348    return "ssv_version_987654";
349  }
350};
351
352IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithVersion987654SSVData,
353                       DestroyTest) {
354  int64 original_size = RequestDiskUsage();
355  EXPECT_GT(original_size, 0);
356  SimpleTest(GetTestUrl("indexeddb", "open_bad_db.html"));
357  int64 new_size = RequestDiskUsage();
358  EXPECT_NE(original_size, new_size);
359}
360
361class IndexedDBBrowserTestWithCorruptLevelDB : public
362    IndexedDBBrowserTestWithPreexistingLevelDB {
363  virtual std::string EnclosingLevelDBDir() OVERRIDE {
364    return "corrupt_leveldb";
365  }
366};
367
368IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithCorruptLevelDB,
369                       DestroyTest) {
370  int64 original_size = RequestDiskUsage();
371  EXPECT_GT(original_size, 0);
372  SimpleTest(GetTestUrl("indexeddb", "open_bad_db.html"));
373  int64 new_size = RequestDiskUsage();
374  EXPECT_NE(original_size, new_size);
375}
376
377class IndexedDBBrowserTestWithMissingSSTFile : public
378    IndexedDBBrowserTestWithPreexistingLevelDB {
379  virtual std::string EnclosingLevelDBDir() OVERRIDE {
380    return "missing_sst";
381  }
382};
383
384IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithMissingSSTFile,
385                       DestroyTest) {
386  int64 original_size = RequestDiskUsage();
387  EXPECT_GT(original_size, 0);
388  SimpleTest(GetTestUrl("indexeddb", "open_missing_table.html"));
389  int64 new_size = RequestDiskUsage();
390  EXPECT_NE(original_size, new_size);
391}
392
393IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, LevelDBLogFileTest) {
394  // Any page that opens an IndexedDB will work here.
395  SimpleTest(GetTestUrl("indexeddb", "database_test.html"));
396  base::FilePath leveldb_dir(FILE_PATH_LITERAL("file__0.indexeddb.leveldb"));
397  base::FilePath log_file(FILE_PATH_LITERAL("LOG"));
398  base::FilePath log_file_path =
399      GetContext()->data_path().Append(leveldb_dir).Append(log_file);
400  int64 size;
401  EXPECT_TRUE(base::GetFileSize(log_file_path, &size));
402  EXPECT_GT(size, 0);
403}
404
405IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CanDeleteWhenOverQuotaTest) {
406  SimpleTest(GetTestUrl("indexeddb", "fill_up_5k.html"));
407  int64 size = RequestDiskUsage();
408  const int kQuotaKilobytes = 2;
409  EXPECT_GT(size, kQuotaKilobytes * 1024);
410  SetQuota(kQuotaKilobytes);
411  SimpleTest(GetTestUrl("indexeddb", "delete_over_quota.html"));
412}
413
414namespace {
415
416static void CompactIndexedDBBackingStore(
417    scoped_refptr<IndexedDBContextImpl> context,
418    const GURL& origin_url) {
419  IndexedDBFactory* factory = context->GetIDBFactory();
420
421  std::pair<IndexedDBFactory::OriginDBMapIterator,
422            IndexedDBFactory::OriginDBMapIterator> range =
423      factory->GetOpenDatabasesForOrigin(origin_url);
424
425  if (range.first == range.second)  // If no open db's for this origin
426    return;
427
428  // Compact the first db's backing store since all the db's are in the same
429  // backing store.
430  IndexedDBDatabase* db = range.first->second;
431  IndexedDBBackingStore* backing_store = db->backing_store();
432  backing_store->Compact();
433}
434
435static void CorruptIndexedDBDatabase(
436    IndexedDBContextImpl* context,
437    const GURL& origin_url,
438    base::WaitableEvent* signal_when_finished) {
439
440  CompactIndexedDBBackingStore(context, origin_url);
441
442  int numFiles = 0;
443  int numErrors = 0;
444  base::FilePath idb_data_path = context->GetFilePath(origin_url);
445  const bool recursive = false;
446  base::FileEnumerator enumerator(
447      idb_data_path, recursive, base::FileEnumerator::FILES);
448  for (base::FilePath idb_file = enumerator.Next(); !idb_file.empty();
449       idb_file = enumerator.Next()) {
450    int64 size(0);
451    GetFileSize(idb_file, &size);
452
453    if (idb_file.Extension() == FILE_PATH_LITERAL(".ldb")) {
454      numFiles++;
455      base::File file(idb_file,
456                      base::File::FLAG_WRITE | base::File::FLAG_OPEN_TRUNCATED);
457      if (file.IsValid()) {
458        // Was opened truncated, expand back to the original
459        // file size and fill with zeros (corrupting the file).
460        file.SetLength(size);
461      } else {
462        numErrors++;
463      }
464    }
465  }
466
467  VLOG(0) << "There were " << numFiles << " in " << idb_data_path.value()
468          << " with " << numErrors << " errors";
469  signal_when_finished->Signal();
470}
471
472const std::string s_corrupt_db_test_prefix = "/corrupt/test/";
473
474static scoped_ptr<net::test_server::HttpResponse> CorruptDBRequestHandler(
475    IndexedDBContextImpl* context,
476    const GURL& origin_url,
477    const std::string& path,
478    IndexedDBBrowserTest* test,
479    const net::test_server::HttpRequest& request) {
480  std::string request_path;
481  if (path.find(s_corrupt_db_test_prefix) != std::string::npos)
482    request_path = request.relative_url.substr(s_corrupt_db_test_prefix.size());
483  else
484    return scoped_ptr<net::test_server::HttpResponse>();
485
486  // Remove the query string if present.
487  std::string request_query;
488  size_t query_pos = request_path.find('?');
489  if (query_pos != std::string::npos) {
490    request_query = request_path.substr(query_pos + 1);
491    request_path = request_path.substr(0, query_pos);
492  }
493
494  if (request_path == "corruptdb" && !request_query.empty()) {
495    VLOG(0) << "Requested to corrupt IndexedDB: " << request_query;
496    base::WaitableEvent signal_when_finished(false, false);
497    context->TaskRunner()->PostTask(FROM_HERE,
498                                    base::Bind(&CorruptIndexedDBDatabase,
499                                               base::ConstRef(context),
500                                               origin_url,
501                                               &signal_when_finished));
502    signal_when_finished.Wait();
503
504    scoped_ptr<net::test_server::BasicHttpResponse> http_response(
505        new net::test_server::BasicHttpResponse);
506    http_response->set_code(net::HTTP_OK);
507    return http_response.PassAs<net::test_server::HttpResponse>();
508  } else if (request_path == "fail" && !request_query.empty()) {
509    FailClass failure_class = FAIL_CLASS_NOTHING;
510    FailMethod failure_method = FAIL_METHOD_NOTHING;
511    int instance_num = 1;
512    int call_num = 1;
513    std::string fail_class;
514    std::string fail_method;
515
516    url::Component query(0, request_query.length()), key_pos, value_pos;
517    while (url::ExtractQueryKeyValue(
518        request_query.c_str(), &query, &key_pos, &value_pos)) {
519      std::string escaped_key(request_query.substr(key_pos.begin, key_pos.len));
520      std::string escaped_value(
521          request_query.substr(value_pos.begin, value_pos.len));
522
523      std::string key = net::UnescapeURLComponent(
524          escaped_key,
525          net::UnescapeRule::NORMAL | net::UnescapeRule::SPACES |
526              net::UnescapeRule::URL_SPECIAL_CHARS);
527
528      std::string value = net::UnescapeURLComponent(
529          escaped_value,
530          net::UnescapeRule::NORMAL | net::UnescapeRule::SPACES |
531              net::UnescapeRule::URL_SPECIAL_CHARS);
532
533      if (key == "method")
534        fail_method = value;
535      else if (key == "class")
536        fail_class = value;
537      else if (key == "instNum")
538        instance_num = atoi(value.c_str());
539      else if (key == "callNum")
540        call_num = atoi(value.c_str());
541      else
542        NOTREACHED() << "Unknown param: \"" << key << "\"";
543    }
544
545    if (fail_class == "LevelDBTransaction") {
546      failure_class = FAIL_CLASS_LEVELDB_TRANSACTION;
547      if (fail_method == "Get")
548        failure_method = FAIL_METHOD_GET;
549      else if (fail_method == "Commit")
550        failure_method = FAIL_METHOD_COMMIT;
551      else
552        NOTREACHED() << "Unknown method: \"" << fail_method << "\"";
553    } else if (fail_class == "LevelDBIterator") {
554      failure_class = FAIL_CLASS_LEVELDB_ITERATOR;
555      if (fail_method == "Seek")
556        failure_method = FAIL_METHOD_SEEK;
557      else
558        NOTREACHED() << "Unknown method: \"" << fail_method << "\"";
559    } else {
560      NOTREACHED() << "Unknown class: \"" << fail_class << "\"";
561    }
562
563    DCHECK_GE(instance_num, 1);
564    DCHECK_GE(call_num, 1);
565
566    test->FailOperation(failure_class, failure_method, instance_num, call_num);
567
568    scoped_ptr<net::test_server::BasicHttpResponse> http_response(
569        new net::test_server::BasicHttpResponse);
570    http_response->set_code(net::HTTP_OK);
571    return http_response.PassAs<net::test_server::HttpResponse>();
572  }
573
574  // A request for a test resource
575  base::FilePath resourcePath =
576      content::GetTestFilePath("indexeddb", request_path.c_str());
577  scoped_ptr<net::test_server::BasicHttpResponse> http_response(
578      new net::test_server::BasicHttpResponse);
579  http_response->set_code(net::HTTP_OK);
580  std::string file_contents;
581  if (!base::ReadFileToString(resourcePath, &file_contents))
582    return scoped_ptr<net::test_server::HttpResponse>();
583  http_response->set_content(file_contents);
584  return http_response.PassAs<net::test_server::HttpResponse>();
585}
586
587}  // namespace
588
589class IndexedDBBrowserCorruptionTest
590    : public IndexedDBBrowserTest,
591      public ::testing::WithParamInterface<const char*> {};
592
593IN_PROC_BROWSER_TEST_P(IndexedDBBrowserCorruptionTest,
594                       OperationOnCorruptedOpenDatabase) {
595  ASSERT_TRUE(embedded_test_server()->Started() ||
596              embedded_test_server()->InitializeAndWaitUntilReady());
597  const GURL& origin_url = embedded_test_server()->base_url();
598  embedded_test_server()->RegisterRequestHandler(
599      base::Bind(&CorruptDBRequestHandler,
600                 base::Unretained(GetContext()),
601                 origin_url,
602                 s_corrupt_db_test_prefix,
603                 this));
604
605  std::string test_file = s_corrupt_db_test_prefix +
606                          "corrupted_open_db_detection.html#" + GetParam();
607  SimpleTest(embedded_test_server()->GetURL(test_file));
608
609  test_file = s_corrupt_db_test_prefix + "corrupted_open_db_recovery.html";
610  SimpleTest(embedded_test_server()->GetURL(test_file));
611}
612
613INSTANTIATE_TEST_CASE_P(IndexedDBBrowserCorruptionTestInstantiation,
614                        IndexedDBBrowserCorruptionTest,
615                        ::testing::Values("failGetBlobJournal",
616                                          "get",
617                                          "failWebkitGetDatabaseNames",
618                                          "iterate",
619                                          "failTransactionCommit",
620                                          "clearObjectStore"));
621
622IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest,
623                       DeleteCompactsBackingStore) {
624  const GURL test_url = GetTestUrl("indexeddb", "delete_compact.html");
625  SimpleTest(GURL(test_url.spec() + "#fill"));
626  int64 after_filling = RequestDiskUsage();
627  EXPECT_GT(after_filling, 0);
628
629  SimpleTest(GURL(test_url.spec() + "#purge"));
630  int64 after_deleting = RequestDiskUsage();
631  EXPECT_LT(after_deleting, after_filling);
632
633  // The above tests verify basic assertions - that filling writes data and
634  // deleting reduces the amount stored.
635
636  // The below tests make assumptions about implementation specifics, such as
637  // data compression, compaction efficiency, and the maximum amount of
638  // metadata and log data remains after a deletion. It is possible that
639  // changes to the implementation may require these constants to be tweaked.
640
641  const int kTestFillBytes = 1024 * 1024 * 5;  // 5MB
642  EXPECT_GT(after_filling, kTestFillBytes);
643
644  const int kTestCompactBytes = 1024 * 10;  // 10kB
645  EXPECT_LT(after_deleting, kTestCompactBytes);
646}
647
648// Complex multi-step (converted from pyauto) tests begin here.
649
650// Verify null key path persists after restarting browser.
651IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, PRE_NullKeyPathPersistence) {
652  NavigateAndWaitForTitle(shell(), "bug_90635.html", "#part1",
653                          "pass - first run");
654}
655
656// Verify null key path persists after restarting browser.
657IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, NullKeyPathPersistence) {
658  NavigateAndWaitForTitle(shell(), "bug_90635.html", "#part2",
659                          "pass - second run");
660}
661
662// Verify that a VERSION_CHANGE transaction is rolled back after a
663// renderer/browser crash
664IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest,
665                       PRE_PRE_VersionChangeCrashResilience) {
666  NavigateAndWaitForTitle(shell(), "version_change_crash.html", "#part1",
667                          "pass - part1 - complete");
668}
669
670IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, PRE_VersionChangeCrashResilience) {
671  NavigateAndWaitForTitle(shell(), "version_change_crash.html", "#part2",
672                          "pass - part2 - crash me");
673  // If we actually crash here then googletest will not run the next step
674  // (VersionChangeCrashResilience) as an optimization. googletest's
675  // ASSERT_DEATH/EXIT fails to work properly (on Windows) due to how we
676  // implement the PRE_* test mechanism.
677  exit(0);
678}
679
680IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, VersionChangeCrashResilience) {
681  NavigateAndWaitForTitle(shell(), "version_change_crash.html", "#part3",
682                          "pass - part3 - rolled back");
683}
684
685// Verify that open DB connections are closed when a tab is destroyed.
686IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, ConnectionsClosedOnTabClose) {
687  NavigateAndWaitForTitle(shell(), "version_change_blocked.html", "#tab1",
688                          "setVersion(2) complete");
689
690  // Start on a different URL to force a new renderer process.
691  Shell* new_shell = CreateBrowser();
692  NavigateToURL(new_shell, GURL(url::kAboutBlankURL));
693  NavigateAndWaitForTitle(new_shell, "version_change_blocked.html", "#tab2",
694                          "setVersion(3) blocked");
695
696  base::string16 expected_title16(ASCIIToUTF16("setVersion(3) complete"));
697  TitleWatcher title_watcher(new_shell->web_contents(), expected_title16);
698
699  base::KillProcess(
700      shell()->web_contents()->GetRenderProcessHost()->GetHandle(), 0, true);
701  shell()->Close();
702
703  EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
704}
705
706// Verify that a "close" event is fired at database connections when
707// the backing store is deleted.
708IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, ForceCloseEventTest) {
709  NavigateAndWaitForTitle(shell(), "force_close_event.html", NULL,
710                          "connection ready");
711
712  GetContext()->TaskRunner()->PostTask(
713      FROM_HERE,
714      base::Bind(&IndexedDBContextImpl::DeleteForOrigin,
715                 GetContext(),
716                 GURL("file:///")));
717
718  base::string16 expected_title16(ASCIIToUTF16("connection closed"));
719  TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
720  title_watcher.AlsoWaitForTitle(ASCIIToUTF16("connection closed with error"));
721  EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
722}
723
724class IndexedDBBrowserTestSingleProcess : public IndexedDBBrowserTest {
725 public:
726  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
727    command_line->AppendSwitch(switches::kSingleProcess);
728  }
729};
730
731IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestSingleProcess,
732                       RenderThreadShutdownTest) {
733  SimpleTest(GetTestUrl("indexeddb", "shutdown_with_requests.html"));
734}
735
736}  // namespace content
737