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/files/file_util.h"
6#include "base/files/scoped_temp_dir.h"
7#include "base/test/test_simple_task_runner.h"
8#include "base/threading/thread.h"
9#include "content/browser/browser_thread_impl.h"
10#include "content/browser/indexed_db/indexed_db_connection.h"
11#include "content/browser/indexed_db/indexed_db_context_impl.h"
12#include "content/browser/indexed_db/indexed_db_factory_impl.h"
13#include "content/browser/indexed_db/mock_indexed_db_callbacks.h"
14#include "content/browser/indexed_db/mock_indexed_db_database_callbacks.h"
15#include "content/public/browser/storage_partition.h"
16#include "content/public/common/url_constants.h"
17#include "content/public/test/mock_special_storage_policy.h"
18#include "content/public/test/test_browser_context.h"
19#include "storage/browser/quota/quota_manager.h"
20#include "storage/browser/quota/special_storage_policy.h"
21#include "storage/common/database/database_identifier.h"
22#include "testing/gtest/include/gtest/gtest.h"
23
24namespace content {
25
26class IndexedDBTest : public testing::Test {
27 public:
28  const GURL kNormalOrigin;
29  const GURL kSessionOnlyOrigin;
30
31  IndexedDBTest()
32      : kNormalOrigin("http://normal/"),
33        kSessionOnlyOrigin("http://session-only/"),
34        task_runner_(new base::TestSimpleTaskRunner),
35        special_storage_policy_(new MockSpecialStoragePolicy),
36        file_thread_(BrowserThread::FILE_USER_BLOCKING, &message_loop_),
37        io_thread_(BrowserThread::IO, &message_loop_) {
38    special_storage_policy_->AddSessionOnly(kSessionOnlyOrigin);
39  }
40
41 protected:
42  void FlushIndexedDBTaskRunner() { task_runner_->RunUntilIdle(); }
43
44  base::MessageLoopForIO message_loop_;
45  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
46  scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_;
47
48 private:
49  BrowserThreadImpl file_thread_;
50  BrowserThreadImpl io_thread_;
51
52  DISALLOW_COPY_AND_ASSIGN(IndexedDBTest);
53};
54
55TEST_F(IndexedDBTest, ClearSessionOnlyDatabases) {
56  base::ScopedTempDir temp_dir;
57  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
58
59  base::FilePath normal_path;
60  base::FilePath session_only_path;
61
62  // Create the scope which will ensure we run the destructor of the context
63  // which should trigger the clean up.
64  {
65    scoped_refptr<IndexedDBContextImpl> idb_context =
66        new IndexedDBContextImpl(temp_dir.path(),
67                                 special_storage_policy_.get(),
68                                 NULL,
69                                 task_runner_.get());
70
71    normal_path = idb_context->GetFilePathForTesting(
72        storage::GetIdentifierFromOrigin(kNormalOrigin));
73    session_only_path = idb_context->GetFilePathForTesting(
74        storage::GetIdentifierFromOrigin(kSessionOnlyOrigin));
75    ASSERT_TRUE(base::CreateDirectory(normal_path));
76    ASSERT_TRUE(base::CreateDirectory(session_only_path));
77    FlushIndexedDBTaskRunner();
78    message_loop_.RunUntilIdle();
79  }
80
81  FlushIndexedDBTaskRunner();
82  message_loop_.RunUntilIdle();
83
84  EXPECT_TRUE(base::DirectoryExists(normal_path));
85  EXPECT_FALSE(base::DirectoryExists(session_only_path));
86}
87
88TEST_F(IndexedDBTest, SetForceKeepSessionState) {
89  base::ScopedTempDir temp_dir;
90  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
91
92  base::FilePath normal_path;
93  base::FilePath session_only_path;
94
95  // Create the scope which will ensure we run the destructor of the context.
96  {
97    // Create some indexedDB paths.
98    // With the levelDB backend, these are directories.
99    scoped_refptr<IndexedDBContextImpl> idb_context =
100        new IndexedDBContextImpl(temp_dir.path(),
101                                 special_storage_policy_.get(),
102                                 NULL,
103                                 task_runner_.get());
104
105    // Save session state. This should bypass the destruction-time deletion.
106    idb_context->SetForceKeepSessionState();
107
108    normal_path = idb_context->GetFilePathForTesting(
109        storage::GetIdentifierFromOrigin(kNormalOrigin));
110    session_only_path = idb_context->GetFilePathForTesting(
111        storage::GetIdentifierFromOrigin(kSessionOnlyOrigin));
112    ASSERT_TRUE(base::CreateDirectory(normal_path));
113    ASSERT_TRUE(base::CreateDirectory(session_only_path));
114    message_loop_.RunUntilIdle();
115  }
116
117  // Make sure we wait until the destructor has run.
118  message_loop_.RunUntilIdle();
119
120  // No data was cleared because of SetForceKeepSessionState.
121  EXPECT_TRUE(base::DirectoryExists(normal_path));
122  EXPECT_TRUE(base::DirectoryExists(session_only_path));
123}
124
125class ForceCloseDBCallbacks : public IndexedDBCallbacks {
126 public:
127  ForceCloseDBCallbacks(scoped_refptr<IndexedDBContextImpl> idb_context,
128                        const GURL& origin_url)
129      : IndexedDBCallbacks(NULL, 0, 0),
130        idb_context_(idb_context),
131        origin_url_(origin_url) {}
132
133  virtual void OnSuccess() OVERRIDE {}
134  virtual void OnSuccess(const std::vector<base::string16>&) OVERRIDE {}
135  virtual void OnSuccess(scoped_ptr<IndexedDBConnection> connection,
136                         const IndexedDBDatabaseMetadata& metadata) OVERRIDE {
137    connection_ = connection.Pass();
138    idb_context_->ConnectionOpened(origin_url_, connection_.get());
139  }
140
141  IndexedDBConnection* connection() { return connection_.get(); }
142
143 protected:
144  virtual ~ForceCloseDBCallbacks() {}
145
146 private:
147  scoped_refptr<IndexedDBContextImpl> idb_context_;
148  GURL origin_url_;
149  scoped_ptr<IndexedDBConnection> connection_;
150  DISALLOW_COPY_AND_ASSIGN(ForceCloseDBCallbacks);
151};
152
153TEST_F(IndexedDBTest, ForceCloseOpenDatabasesOnDelete) {
154  base::ScopedTempDir temp_dir;
155  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
156
157  scoped_refptr<MockIndexedDBDatabaseCallbacks> open_db_callbacks(
158      new MockIndexedDBDatabaseCallbacks());
159  scoped_refptr<MockIndexedDBDatabaseCallbacks> closed_db_callbacks(
160      new MockIndexedDBDatabaseCallbacks());
161
162  base::FilePath test_path;
163
164  // Create the scope which will ensure we run the destructor of the context.
165  {
166    TestBrowserContext browser_context;
167
168    const GURL kTestOrigin("http://test/");
169
170    scoped_refptr<IndexedDBContextImpl> idb_context =
171        new IndexedDBContextImpl(temp_dir.path(),
172                                 special_storage_policy_.get(),
173                                 NULL,
174                                 task_runner_.get());
175
176    scoped_refptr<ForceCloseDBCallbacks> open_callbacks =
177        new ForceCloseDBCallbacks(idb_context, kTestOrigin);
178
179    scoped_refptr<ForceCloseDBCallbacks> closed_callbacks =
180        new ForceCloseDBCallbacks(idb_context, kTestOrigin);
181
182    IndexedDBFactory* factory = idb_context->GetIDBFactory();
183
184    test_path = idb_context->GetFilePathForTesting(
185        storage::GetIdentifierFromOrigin(kTestOrigin));
186
187    IndexedDBPendingConnection open_connection(open_callbacks,
188                                               open_db_callbacks,
189                                               0 /* child_process_id */,
190                                               0 /* host_transaction_id */,
191                                               0 /* version */);
192    factory->Open(base::ASCIIToUTF16("opendb"),
193                  open_connection,
194                  NULL /* request_context */,
195                  kTestOrigin,
196                  idb_context->data_path());
197    IndexedDBPendingConnection closed_connection(closed_callbacks,
198                                                 closed_db_callbacks,
199                                                 0 /* child_process_id */,
200                                                 0 /* host_transaction_id */,
201                                                 0 /* version */);
202    factory->Open(base::ASCIIToUTF16("closeddb"),
203                  closed_connection,
204                  NULL /* request_context */,
205                  kTestOrigin,
206                  idb_context->data_path());
207
208    closed_callbacks->connection()->Close();
209
210    idb_context->TaskRunner()->PostTask(
211        FROM_HERE,
212        base::Bind(
213            &IndexedDBContextImpl::DeleteForOrigin, idb_context, kTestOrigin));
214    FlushIndexedDBTaskRunner();
215    message_loop_.RunUntilIdle();
216  }
217
218  // Make sure we wait until the destructor has run.
219  message_loop_.RunUntilIdle();
220
221  EXPECT_TRUE(open_db_callbacks->forced_close_called());
222  EXPECT_FALSE(closed_db_callbacks->forced_close_called());
223  EXPECT_FALSE(base::DirectoryExists(test_path));
224}
225
226TEST_F(IndexedDBTest, DeleteFailsIfDirectoryLocked) {
227  base::ScopedTempDir temp_dir;
228  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
229  const GURL kTestOrigin("http://test/");
230
231  scoped_refptr<IndexedDBContextImpl> idb_context = new IndexedDBContextImpl(
232      temp_dir.path(), special_storage_policy_.get(), NULL, task_runner_.get());
233
234  base::FilePath test_path = idb_context->GetFilePathForTesting(
235      storage::GetIdentifierFromOrigin(kTestOrigin));
236  ASSERT_TRUE(base::CreateDirectory(test_path));
237
238  scoped_ptr<LevelDBLock> lock =
239      LevelDBDatabase::LockForTesting(test_path);
240  ASSERT_TRUE(lock);
241
242  idb_context->TaskRunner()->PostTask(
243      FROM_HERE,
244      base::Bind(
245          &IndexedDBContextImpl::DeleteForOrigin, idb_context, kTestOrigin));
246  FlushIndexedDBTaskRunner();
247
248  EXPECT_TRUE(base::DirectoryExists(test_path));
249}
250
251TEST_F(IndexedDBTest, ForceCloseOpenDatabasesOnCommitFailure) {
252  const GURL kTestOrigin("http://test/");
253
254  base::ScopedTempDir temp_dir;
255  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
256
257  scoped_refptr<IndexedDBContextImpl> context = new IndexedDBContextImpl(
258      temp_dir.path(), special_storage_policy_.get(), NULL, task_runner_.get());
259
260  scoped_refptr<IndexedDBFactoryImpl> factory =
261      static_cast<IndexedDBFactoryImpl*>(context->GetIDBFactory());
262
263  scoped_refptr<MockIndexedDBCallbacks> callbacks(new MockIndexedDBCallbacks());
264  scoped_refptr<MockIndexedDBDatabaseCallbacks> db_callbacks(
265      new MockIndexedDBDatabaseCallbacks());
266  const int64 transaction_id = 1;
267  IndexedDBPendingConnection connection(
268      callbacks,
269      db_callbacks,
270      0 /* child_process_id */,
271      transaction_id,
272      IndexedDBDatabaseMetadata::DEFAULT_INT_VERSION);
273  factory->Open(base::ASCIIToUTF16("db"),
274                connection,
275                NULL /* request_context */,
276                kTestOrigin,
277                temp_dir.path());
278
279  EXPECT_TRUE(callbacks->connection());
280
281  // ConnectionOpened() is usually called by the dispatcher.
282  context->ConnectionOpened(kTestOrigin, callbacks->connection());
283
284  EXPECT_TRUE(factory->IsBackingStoreOpen(kTestOrigin));
285
286  // Simulate the write failure.
287  leveldb::Status status = leveldb::Status::IOError("Simulated failure");
288  callbacks->connection()->database()->TransactionCommitFailed(status);
289
290  EXPECT_TRUE(db_callbacks->forced_close_called());
291  EXPECT_FALSE(factory->IsBackingStoreOpen(kTestOrigin));
292}
293
294}  // namespace content
295