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.
6 #include "base/file_util.h"
7 #include "base/files/scoped_temp_dir.h"
8 #include "base/message_loop.h"
9 #include "base/message_loop_proxy.h"
10 #include "base/threading/sequenced_worker_pool.h"
11 #include "base/time.h"
12 #include "base/utf_string_conversions.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14 #include "webkit/dom_storage/dom_storage_area.h"
15 #include "webkit/dom_storage/dom_storage_database.h"
16 #include "webkit/dom_storage/dom_storage_database_adapter.h"
17 #include "webkit/dom_storage/dom_storage_task_runner.h"
18 #include "webkit/dom_storage/dom_storage_types.h"
19 #include "webkit/dom_storage/local_storage_database_adapter.h"
21 namespace dom_storage
{
24 class DomStorageAreaTest
: public testing::Test
{
27 : kOrigin(GURL("http://dom_storage/")),
28 kKey(ASCIIToUTF16("key")),
29 kValue(ASCIIToUTF16("value")),
30 kKey2(ASCIIToUTF16("key2")),
31 kValue2(ASCIIToUTF16("value2")) {
35 const base::string16 kKey
;
36 const base::string16 kValue
;
37 const base::string16 kKey2
;
38 const base::string16 kValue2
;
40 // Method used in the CommitTasks test case.
41 void InjectedCommitSequencingTask(DomStorageArea
* area
) {
42 // At this point the OnCommitTimer has run.
43 // Verify that it put a commit in flight.
44 EXPECT_EQ(1, area
->commit_batches_in_flight_
);
45 EXPECT_FALSE(area
->commit_batch_
.get());
46 EXPECT_TRUE(area
->HasUncommittedChanges());
47 // Make additional change and verify that a new commit batch
48 // is created for that change.
49 NullableString16 old_value
;
50 EXPECT_TRUE(area
->SetItem(kKey2
, kValue2
, &old_value
));
51 EXPECT_TRUE(area
->commit_batch_
.get());
52 EXPECT_EQ(1, area
->commit_batches_in_flight_
);
53 EXPECT_TRUE(area
->HasUncommittedChanges());
56 // Class used in the CommitChangesAtShutdown test case.
57 class VerifyChangesCommittedDatabase
: public DomStorageDatabase
{
59 VerifyChangesCommittedDatabase() {}
60 virtual ~VerifyChangesCommittedDatabase() {
61 const base::string16
kKey(ASCIIToUTF16("key"));
62 const base::string16
kValue(ASCIIToUTF16("value"));
64 ReadAllValues(&values
);
65 EXPECT_EQ(1u, values
.size());
66 EXPECT_EQ(kValue
, values
[kKey
].string());
71 MessageLoop message_loop_
;
74 TEST_F(DomStorageAreaTest
, DomStorageAreaBasics
) {
75 scoped_refptr
<DomStorageArea
> area(
76 new DomStorageArea(1, std::string(), kOrigin
, NULL
, NULL
));
77 base::string16 old_value
;
78 NullableString16 old_nullable_value
;
79 scoped_refptr
<DomStorageArea
> copy
;
81 // We don't focus on the underlying DomStorageMap functionality
82 // since that's covered by seperate unit tests.
83 EXPECT_EQ(kOrigin
, area
->origin());
84 EXPECT_EQ(1, area
->namespace_id());
85 EXPECT_EQ(0u, area
->Length());
86 EXPECT_TRUE(area
->SetItem(kKey
, kValue
, &old_nullable_value
));
87 EXPECT_TRUE(area
->SetItem(kKey2
, kValue2
, &old_nullable_value
));
88 EXPECT_FALSE(area
->HasUncommittedChanges());
90 // Verify that a copy shares the same map.
91 copy
= area
->ShallowCopy(2, std::string());
92 EXPECT_EQ(kOrigin
, copy
->origin());
93 EXPECT_EQ(2, copy
->namespace_id());
94 EXPECT_EQ(area
->Length(), copy
->Length());
95 EXPECT_EQ(area
->GetItem(kKey
).string(), copy
->GetItem(kKey
).string());
96 EXPECT_EQ(area
->Key(0).string(), copy
->Key(0).string());
97 EXPECT_EQ(copy
->map_
.get(), area
->map_
.get());
99 // But will deep copy-on-write as needed.
100 EXPECT_TRUE(area
->RemoveItem(kKey
, &old_value
));
101 EXPECT_NE(copy
->map_
.get(), area
->map_
.get());
102 copy
= area
->ShallowCopy(2, std::string());
103 EXPECT_EQ(copy
->map_
.get(), area
->map_
.get());
104 EXPECT_TRUE(area
->SetItem(kKey
, kValue
, &old_nullable_value
));
105 EXPECT_NE(copy
->map_
.get(), area
->map_
.get());
106 copy
= area
->ShallowCopy(2, std::string());
107 EXPECT_EQ(copy
->map_
.get(), area
->map_
.get());
108 EXPECT_NE(0u, area
->Length());
109 EXPECT_TRUE(area
->Clear());
110 EXPECT_EQ(0u, area
->Length());
111 EXPECT_NE(copy
->map_
.get(), area
->map_
.get());
113 // Verify that once Shutdown(), behaves that way.
115 EXPECT_TRUE(area
->is_shutdown_
);
116 EXPECT_FALSE(area
->map_
.get());
117 EXPECT_EQ(0u, area
->Length());
118 EXPECT_TRUE(area
->Key(0).is_null());
119 EXPECT_TRUE(area
->GetItem(kKey
).is_null());
120 EXPECT_FALSE(area
->SetItem(kKey
, kValue
, &old_nullable_value
));
121 EXPECT_FALSE(area
->RemoveItem(kKey
, &old_value
));
122 EXPECT_FALSE(area
->Clear());
125 TEST_F(DomStorageAreaTest
, BackingDatabaseOpened
) {
126 const int64 kSessionStorageNamespaceId
= kLocalStorageNamespaceId
+ 1;
127 base::ScopedTempDir temp_dir
;
128 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
129 const base::FilePath kExpectedOriginFilePath
= temp_dir
.path().Append(
130 DomStorageArea::DatabaseFileNameFromOrigin(kOrigin
));
132 // No directory, backing should be null.
134 scoped_refptr
<DomStorageArea
> area(
135 new DomStorageArea(kOrigin
, base::FilePath(), NULL
));
136 EXPECT_EQ(NULL
, area
->backing_
.get());
137 EXPECT_TRUE(area
->is_initial_import_done_
);
138 EXPECT_FALSE(file_util::PathExists(kExpectedOriginFilePath
));
141 // Valid directory and origin but no session storage backing. Backing should
144 scoped_refptr
<DomStorageArea
> area(
145 new DomStorageArea(kSessionStorageNamespaceId
, std::string(), kOrigin
,
147 EXPECT_EQ(NULL
, area
->backing_
.get());
148 EXPECT_TRUE(area
->is_initial_import_done_
);
150 NullableString16 old_value
;
151 EXPECT_TRUE(area
->SetItem(kKey
, kValue
, &old_value
));
152 ASSERT_TRUE(old_value
.is_null());
154 // Check that saving a value has still left us without a backing database.
155 EXPECT_EQ(NULL
, area
->backing_
.get());
156 EXPECT_FALSE(file_util::PathExists(kExpectedOriginFilePath
));
159 // This should set up a DomStorageArea that is correctly backed to disk.
161 scoped_refptr
<DomStorageArea
> area(
162 new DomStorageArea(kOrigin
,
164 new MockDomStorageTaskRunner(base::MessageLoopProxy::current())));
166 EXPECT_TRUE(area
->backing_
.get());
167 DomStorageDatabase
* database
= static_cast<LocalStorageDatabaseAdapter
*>(
168 area
->backing_
.get())->db_
.get();
169 EXPECT_FALSE(database
->IsOpen());
170 EXPECT_FALSE(area
->is_initial_import_done_
);
172 // Inject an in-memory db to speed up the test.
173 // We will verify that something is written into the database but not
174 // that a file is written to disk - DOMStorageDatabase unit tests cover
176 area
->backing_
.reset(new LocalStorageDatabaseAdapter());
178 // Need to write something to ensure that the database is created.
179 NullableString16 old_value
;
180 EXPECT_TRUE(area
->SetItem(kKey
, kValue
, &old_value
));
181 ASSERT_TRUE(old_value
.is_null());
182 EXPECT_TRUE(area
->is_initial_import_done_
);
183 EXPECT_TRUE(area
->commit_batch_
.get());
184 EXPECT_EQ(0, area
->commit_batches_in_flight_
);
186 MessageLoop::current()->RunUntilIdle();
188 EXPECT_FALSE(area
->commit_batch_
.get());
189 EXPECT_EQ(0, area
->commit_batches_in_flight_
);
190 database
= static_cast<LocalStorageDatabaseAdapter
*>(
191 area
->backing_
.get())->db_
.get();
192 EXPECT_TRUE(database
->IsOpen());
193 EXPECT_EQ(1u, area
->Length());
194 EXPECT_EQ(kValue
, area
->GetItem(kKey
).string());
196 // Verify the content made it to the in memory database.
198 area
->backing_
->ReadAllValues(&values
);
199 EXPECT_EQ(1u, values
.size());
200 EXPECT_EQ(kValue
, values
[kKey
].string());
204 TEST_F(DomStorageAreaTest
, CommitTasks
) {
205 base::ScopedTempDir temp_dir
;
206 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
208 scoped_refptr
<DomStorageArea
> area(
209 new DomStorageArea(kOrigin
,
211 new MockDomStorageTaskRunner(base::MessageLoopProxy::current())));
212 // Inject an in-memory db to speed up the test.
213 area
->backing_
.reset(new LocalStorageDatabaseAdapter());
215 // Unrelated to commits, but while we're here, see that querying Length()
216 // causes the backing database to be opened and presumably read from.
217 EXPECT_FALSE(area
->is_initial_import_done_
);
218 EXPECT_EQ(0u, area
->Length());
219 EXPECT_TRUE(area
->is_initial_import_done_
);
222 NullableString16 old_value
;
224 // See that changes are batched up.
225 EXPECT_FALSE(area
->commit_batch_
.get());
226 EXPECT_TRUE(area
->SetItem(kKey
, kValue
, &old_value
));
227 EXPECT_TRUE(area
->HasUncommittedChanges());
228 EXPECT_TRUE(area
->commit_batch_
.get());
229 EXPECT_FALSE(area
->commit_batch_
->clear_all_first
);
230 EXPECT_EQ(1u, area
->commit_batch_
->changed_values
.size());
231 EXPECT_TRUE(area
->SetItem(kKey2
, kValue2
, &old_value
));
232 EXPECT_TRUE(area
->commit_batch_
.get());
233 EXPECT_FALSE(area
->commit_batch_
->clear_all_first
);
234 EXPECT_EQ(2u, area
->commit_batch_
->changed_values
.size());
235 MessageLoop::current()->RunUntilIdle();
236 EXPECT_FALSE(area
->HasUncommittedChanges());
237 EXPECT_FALSE(area
->commit_batch_
.get());
238 EXPECT_EQ(0, area
->commit_batches_in_flight_
);
239 // Verify the changes made it to the database.
241 area
->backing_
->ReadAllValues(&values
);
242 EXPECT_EQ(2u, values
.size());
243 EXPECT_EQ(kValue
, values
[kKey
].string());
244 EXPECT_EQ(kValue2
, values
[kKey2
].string());
246 // See that clear is handled properly.
247 EXPECT_TRUE(area
->Clear());
248 EXPECT_TRUE(area
->commit_batch_
.get());
249 EXPECT_TRUE(area
->commit_batch_
->clear_all_first
);
250 EXPECT_TRUE(area
->commit_batch_
->changed_values
.empty());
251 MessageLoop::current()->RunUntilIdle();
252 EXPECT_FALSE(area
->commit_batch_
.get());
253 EXPECT_EQ(0, area
->commit_batches_in_flight_
);
254 // Verify the changes made it to the database.
256 area
->backing_
->ReadAllValues(&values
);
257 EXPECT_TRUE(values
.empty());
259 // See that if changes accrue while a commit is "in flight"
260 // those will also get committed.
261 EXPECT_TRUE(area
->SetItem(kKey
, kValue
, &old_value
));
262 EXPECT_TRUE(area
->HasUncommittedChanges());
263 // At this point the OnCommitTimer task has been posted. We inject
264 // another task in the queue that will execute after the timer task,
265 // but before the CommitChanges task. From within our injected task,
266 // we'll make an additional SetItem() call.
267 MessageLoop::current()->PostTask(
269 base::Bind(&DomStorageAreaTest::InjectedCommitSequencingTask
,
270 base::Unretained(this), area
));
271 MessageLoop::current()->RunUntilIdle();
272 EXPECT_TRUE(area
->HasOneRef());
273 EXPECT_FALSE(area
->HasUncommittedChanges());
274 // Verify the changes made it to the database.
276 area
->backing_
->ReadAllValues(&values
);
277 EXPECT_EQ(2u, values
.size());
278 EXPECT_EQ(kValue
, values
[kKey
].string());
279 EXPECT_EQ(kValue2
, values
[kKey2
].string());
282 TEST_F(DomStorageAreaTest
, CommitChangesAtShutdown
) {
283 base::ScopedTempDir temp_dir
;
284 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
285 scoped_refptr
<DomStorageArea
> area(
286 new DomStorageArea(kOrigin
,
288 new MockDomStorageTaskRunner(base::MessageLoopProxy::current())));
290 // Inject an in-memory db to speed up the test and also to verify
291 // the final changes are commited in it's dtor.
292 static_cast<LocalStorageDatabaseAdapter
*>(area
->backing_
.get())->db_
.reset(
293 new VerifyChangesCommittedDatabase());
296 NullableString16 old_value
;
297 EXPECT_TRUE(area
->SetItem(kKey
, kValue
, &old_value
));
298 EXPECT_TRUE(area
->HasUncommittedChanges());
299 area
->backing_
->ReadAllValues(&values
);
300 EXPECT_TRUE(values
.empty()); // not committed yet
302 MessageLoop::current()->RunUntilIdle();
303 EXPECT_TRUE(area
->HasOneRef());
304 EXPECT_FALSE(area
->backing_
.get());
305 // The VerifyChangesCommittedDatabase destructor verifies values
309 TEST_F(DomStorageAreaTest
, DeleteOrigin
) {
310 base::ScopedTempDir temp_dir
;
311 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
312 scoped_refptr
<DomStorageArea
> area(
313 new DomStorageArea(kOrigin
,
315 new MockDomStorageTaskRunner(base::MessageLoopProxy::current())));
317 // This test puts files on disk.
318 base::FilePath db_file_path
= static_cast<LocalStorageDatabaseAdapter
*>(
319 area
->backing_
.get())->db_
->file_path();
320 base::FilePath db_journal_file_path
=
321 DomStorageDatabase::GetJournalFilePath(db_file_path
);
323 // Nothing bad should happen when invoked w/o any files on disk.
324 area
->DeleteOrigin();
325 EXPECT_FALSE(file_util::PathExists(db_file_path
));
327 // Commit something in the database and then delete.
328 NullableString16 old_value
;
329 area
->SetItem(kKey
, kValue
, &old_value
);
330 MessageLoop::current()->RunUntilIdle();
331 EXPECT_TRUE(file_util::PathExists(db_file_path
));
332 area
->DeleteOrigin();
333 EXPECT_EQ(0u, area
->Length());
334 EXPECT_FALSE(file_util::PathExists(db_file_path
));
335 EXPECT_FALSE(file_util::PathExists(db_journal_file_path
));
337 // Put some uncommitted changes to a non-existing database in
338 // and then delete. No file ever gets created in this case.
339 area
->SetItem(kKey
, kValue
, &old_value
);
340 EXPECT_TRUE(area
->HasUncommittedChanges());
341 EXPECT_EQ(1u, area
->Length());
342 area
->DeleteOrigin();
343 EXPECT_TRUE(area
->HasUncommittedChanges());
344 EXPECT_EQ(0u, area
->Length());
345 MessageLoop::current()->RunUntilIdle();
346 EXPECT_FALSE(area
->HasUncommittedChanges());
347 EXPECT_FALSE(file_util::PathExists(db_file_path
));
349 // Put some uncommitted changes to a an existing database in
351 area
->SetItem(kKey
, kValue
, &old_value
);
352 MessageLoop::current()->RunUntilIdle();
353 EXPECT_TRUE(file_util::PathExists(db_file_path
));
354 area
->SetItem(kKey2
, kValue2
, &old_value
);
355 EXPECT_TRUE(area
->HasUncommittedChanges());
356 EXPECT_EQ(2u, area
->Length());
357 area
->DeleteOrigin();
358 EXPECT_TRUE(area
->HasUncommittedChanges());
359 EXPECT_EQ(0u, area
->Length());
360 MessageLoop::current()->RunUntilIdle();
361 EXPECT_FALSE(area
->HasUncommittedChanges());
362 // Since the area had uncommitted changes at the time delete
363 // was called, the file will linger until the shutdown time.
364 EXPECT_TRUE(file_util::PathExists(db_file_path
));
366 MessageLoop::current()->RunUntilIdle();
367 EXPECT_FALSE(file_util::PathExists(db_file_path
));
370 TEST_F(DomStorageAreaTest
, PurgeMemory
) {
371 base::ScopedTempDir temp_dir
;
372 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
373 scoped_refptr
<DomStorageArea
> area(
374 new DomStorageArea(kOrigin
,
376 new MockDomStorageTaskRunner(base::MessageLoopProxy::current())));
378 // Inject an in-memory db to speed up the test.
379 area
->backing_
.reset(new LocalStorageDatabaseAdapter());
381 // Unowned ptrs we use to verify that 'purge' has happened.
382 DomStorageDatabase
* original_backing
=
383 static_cast<LocalStorageDatabaseAdapter
*>(
384 area
->backing_
.get())->db_
.get();
385 DomStorageMap
* original_map
= area
->map_
.get();
387 // Should do no harm when called on a newly constructed object.
388 EXPECT_FALSE(area
->is_initial_import_done_
);
390 EXPECT_FALSE(area
->is_initial_import_done_
);
391 DomStorageDatabase
* new_backing
= static_cast<LocalStorageDatabaseAdapter
*>(
392 area
->backing_
.get())->db_
.get();
393 EXPECT_EQ(original_backing
, new_backing
);
394 EXPECT_EQ(original_map
, area
->map_
.get());
396 // Should not do anything when commits are pending.
397 NullableString16 old_value
;
398 area
->SetItem(kKey
, kValue
, &old_value
);
399 EXPECT_TRUE(area
->is_initial_import_done_
);
400 EXPECT_TRUE(area
->HasUncommittedChanges());
402 EXPECT_TRUE(area
->is_initial_import_done_
);
403 EXPECT_TRUE(area
->HasUncommittedChanges());
404 new_backing
= static_cast<LocalStorageDatabaseAdapter
*>(
405 area
->backing_
.get())->db_
.get();
406 EXPECT_EQ(original_backing
, new_backing
);
407 EXPECT_EQ(original_map
, area
->map_
.get());
409 // Commit the changes from above,
410 MessageLoop::current()->RunUntilIdle();
411 EXPECT_FALSE(area
->HasUncommittedChanges());
412 new_backing
= static_cast<LocalStorageDatabaseAdapter
*>(
413 area
->backing_
.get())->db_
.get();
414 EXPECT_EQ(original_backing
, new_backing
);
415 EXPECT_EQ(original_map
, area
->map_
.get());
417 // Should drop caches and reset database connections
418 // when invoked on an area that's loaded up primed.
420 EXPECT_FALSE(area
->is_initial_import_done_
);
421 new_backing
= static_cast<LocalStorageDatabaseAdapter
*>(
422 area
->backing_
.get())->db_
.get();
423 EXPECT_NE(original_backing
, new_backing
);
424 EXPECT_NE(original_map
, area
->map_
.get());
427 TEST_F(DomStorageAreaTest
, DatabaseFileNames
) {
430 const char* file_name
;
431 const char* journal_file_name
;
433 { "https://www.google.com/",
434 "https_www.google.com_0.localstorage",
435 "https_www.google.com_0.localstorage-journal" },
436 { "http://www.google.com:8080/",
437 "http_www.google.com_8080.localstorage",
438 "http_www.google.com_8080.localstorage-journal" },
440 "file__0.localstorage",
441 "file__0.localstorage-journal" },
444 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(kCases
); ++i
) {
445 GURL origin
= GURL(kCases
[i
].origin
).GetOrigin();
446 base::FilePath file_name
= base::FilePath().AppendASCII(kCases
[i
].file_name
);
447 base::FilePath journal_file_name
=
448 base::FilePath().AppendASCII(kCases
[i
].journal_file_name
);
451 DomStorageArea::DatabaseFileNameFromOrigin(origin
));
453 DomStorageArea::OriginFromDatabaseFileName(file_name
));
454 EXPECT_EQ(journal_file_name
,
455 DomStorageDatabase::GetJournalFilePath(file_name
));
458 // Also test some DomStorageDatabase::GetJournalFilePath cases here.
459 base::FilePath parent
= base::FilePath().AppendASCII("a").AppendASCII("b");
461 parent
.AppendASCII("file-journal"),
462 DomStorageDatabase::GetJournalFilePath(parent
.AppendASCII("file")));
464 base::FilePath().AppendASCII("-journal"),
465 DomStorageDatabase::GetJournalFilePath(base::FilePath()));
467 base::FilePath().AppendASCII(".extensiononly-journal"),
468 DomStorageDatabase::GetJournalFilePath(
469 base::FilePath().AppendASCII(".extensiononly")));
472 } // namespace dom_storage