- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / dom_storage / dom_storage_area_unittest.cc
1 // Copyright 2013 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/file_util.h"
7 #include "base/files/scoped_temp_dir.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/message_loop/message_loop_proxy.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/threading/sequenced_worker_pool.h"
12 #include "base/time/time.h"
13 #include "content/browser/dom_storage/dom_storage_area.h"
14 #include "content/browser/dom_storage/dom_storage_database.h"
15 #include "content/browser/dom_storage/dom_storage_database_adapter.h"
16 #include "content/browser/dom_storage/dom_storage_task_runner.h"
17 #include "content/browser/dom_storage/local_storage_database_adapter.h"
18 #include "content/common/dom_storage/dom_storage_types.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20
21 namespace content {
22
23
24 class DOMStorageAreaTest : public testing::Test {
25  public:
26   DOMStorageAreaTest()
27     : kOrigin(GURL("http://dom_storage/")),
28       kKey(ASCIIToUTF16("key")),
29       kValue(ASCIIToUTF16("value")),
30       kKey2(ASCIIToUTF16("key2")),
31       kValue2(ASCIIToUTF16("value2")) {
32   }
33
34   const GURL kOrigin;
35   const base::string16 kKey;
36   const base::string16 kValue;
37   const base::string16 kKey2;
38   const base::string16 kValue2;
39
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     base::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());
54   }
55
56   // Class used in the CommitChangesAtShutdown test case.
57   class VerifyChangesCommittedDatabase : public DOMStorageDatabase {
58    public:
59     VerifyChangesCommittedDatabase() {}
60     virtual ~VerifyChangesCommittedDatabase() {
61       const base::string16 kKey(ASCIIToUTF16("key"));
62       const base::string16 kValue(ASCIIToUTF16("value"));
63       DOMStorageValuesMap values;
64       ReadAllValues(&values);
65       EXPECT_EQ(1u, values.size());
66       EXPECT_EQ(kValue, values[kKey].string());
67     }
68   };
69
70  private:
71   base::MessageLoop message_loop_;
72 };
73
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   base::NullableString16 old_nullable_value;
79   scoped_refptr<DOMStorageArea> copy;
80
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());
89
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());
98
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());
112
113   // Verify that once Shutdown(), behaves that way.
114   area->Shutdown();
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());
123 }
124
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));
131
132   // No directory, backing should be null.
133   {
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(base::PathExists(kExpectedOriginFilePath));
139   }
140
141   // Valid directory and origin but no session storage backing. Backing should
142   // be null.
143   {
144     scoped_refptr<DOMStorageArea> area(
145         new DOMStorageArea(kSessionStorageNamespaceId, std::string(), kOrigin,
146                            NULL, NULL));
147     EXPECT_EQ(NULL, area->backing_.get());
148     EXPECT_TRUE(area->is_initial_import_done_);
149
150     base::NullableString16 old_value;
151     EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value));
152     ASSERT_TRUE(old_value.is_null());
153
154     // Check that saving a value has still left us without a backing database.
155     EXPECT_EQ(NULL, area->backing_.get());
156     EXPECT_FALSE(base::PathExists(kExpectedOriginFilePath));
157   }
158
159   // This should set up a DOMStorageArea that is correctly backed to disk.
160   {
161     scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
162         kOrigin,
163         temp_dir.path(),
164         new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
165
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_);
171
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
175     // that.
176     area->backing_.reset(new LocalStorageDatabaseAdapter());
177
178     // Need to write something to ensure that the database is created.
179     base::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_);
185
186     base::MessageLoop::current()->RunUntilIdle();
187
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());
195
196     // Verify the content made it to the in memory database.
197     DOMStorageValuesMap values;
198     area->backing_->ReadAllValues(&values);
199     EXPECT_EQ(1u, values.size());
200     EXPECT_EQ(kValue, values[kKey].string());
201   }
202 }
203
204 TEST_F(DOMStorageAreaTest, CommitTasks) {
205   base::ScopedTempDir temp_dir;
206   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
207
208   scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
209       kOrigin,
210       temp_dir.path(),
211       new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
212   // Inject an in-memory db to speed up the test.
213   area->backing_.reset(new LocalStorageDatabaseAdapter());
214
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_);
220
221   DOMStorageValuesMap values;
222   base::NullableString16 old_value;
223
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   base::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.
240   values.clear();
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());
245
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   base::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.
255   values.clear();
256   area->backing_->ReadAllValues(&values);
257   EXPECT_TRUE(values.empty());
258
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   base::MessageLoop::current()->PostTask(
268       FROM_HERE,
269       base::Bind(&DOMStorageAreaTest::InjectedCommitSequencingTask,
270                  base::Unretained(this),
271                  area));
272   base::MessageLoop::current()->RunUntilIdle();
273   EXPECT_TRUE(area->HasOneRef());
274   EXPECT_FALSE(area->HasUncommittedChanges());
275   // Verify the changes made it to the database.
276   values.clear();
277   area->backing_->ReadAllValues(&values);
278   EXPECT_EQ(2u, values.size());
279   EXPECT_EQ(kValue, values[kKey].string());
280   EXPECT_EQ(kValue2, values[kKey2].string());
281 }
282
283 TEST_F(DOMStorageAreaTest, CommitChangesAtShutdown) {
284   base::ScopedTempDir temp_dir;
285   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
286   scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
287       kOrigin,
288       temp_dir.path(),
289       new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
290
291   // Inject an in-memory db to speed up the test and also to verify
292   // the final changes are commited in it's dtor.
293   static_cast<LocalStorageDatabaseAdapter*>(area->backing_.get())->db_.reset(
294       new VerifyChangesCommittedDatabase());
295
296   DOMStorageValuesMap values;
297   base::NullableString16 old_value;
298   EXPECT_TRUE(area->SetItem(kKey, kValue, &old_value));
299   EXPECT_TRUE(area->HasUncommittedChanges());
300   area->backing_->ReadAllValues(&values);
301   EXPECT_TRUE(values.empty());  // not committed yet
302   area->Shutdown();
303   base::MessageLoop::current()->RunUntilIdle();
304   EXPECT_TRUE(area->HasOneRef());
305   EXPECT_FALSE(area->backing_.get());
306   // The VerifyChangesCommittedDatabase destructor verifies values
307   // were committed.
308 }
309
310 TEST_F(DOMStorageAreaTest, DeleteOrigin) {
311   base::ScopedTempDir temp_dir;
312   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
313   scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
314       kOrigin,
315       temp_dir.path(),
316       new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
317
318   // This test puts files on disk.
319   base::FilePath db_file_path = static_cast<LocalStorageDatabaseAdapter*>(
320       area->backing_.get())->db_->file_path();
321   base::FilePath db_journal_file_path =
322       DOMStorageDatabase::GetJournalFilePath(db_file_path);
323
324   // Nothing bad should happen when invoked w/o any files on disk.
325   area->DeleteOrigin();
326   EXPECT_FALSE(base::PathExists(db_file_path));
327
328   // Commit something in the database and then delete.
329   base::NullableString16 old_value;
330   area->SetItem(kKey, kValue, &old_value);
331   base::MessageLoop::current()->RunUntilIdle();
332   EXPECT_TRUE(base::PathExists(db_file_path));
333   area->DeleteOrigin();
334   EXPECT_EQ(0u, area->Length());
335   EXPECT_FALSE(base::PathExists(db_file_path));
336   EXPECT_FALSE(base::PathExists(db_journal_file_path));
337
338   // Put some uncommitted changes to a non-existing database in
339   // and then delete. No file ever gets created in this case.
340   area->SetItem(kKey, kValue, &old_value);
341   EXPECT_TRUE(area->HasUncommittedChanges());
342   EXPECT_EQ(1u, area->Length());
343   area->DeleteOrigin();
344   EXPECT_TRUE(area->HasUncommittedChanges());
345   EXPECT_EQ(0u, area->Length());
346   base::MessageLoop::current()->RunUntilIdle();
347   EXPECT_FALSE(area->HasUncommittedChanges());
348   EXPECT_FALSE(base::PathExists(db_file_path));
349
350   // Put some uncommitted changes to a an existing database in
351   // and then delete.
352   area->SetItem(kKey, kValue, &old_value);
353   base::MessageLoop::current()->RunUntilIdle();
354   EXPECT_TRUE(base::PathExists(db_file_path));
355   area->SetItem(kKey2, kValue2, &old_value);
356   EXPECT_TRUE(area->HasUncommittedChanges());
357   EXPECT_EQ(2u, area->Length());
358   area->DeleteOrigin();
359   EXPECT_TRUE(area->HasUncommittedChanges());
360   EXPECT_EQ(0u, area->Length());
361   base::MessageLoop::current()->RunUntilIdle();
362   EXPECT_FALSE(area->HasUncommittedChanges());
363   // Since the area had uncommitted changes at the time delete
364   // was called, the file will linger until the shutdown time.
365   EXPECT_TRUE(base::PathExists(db_file_path));
366   area->Shutdown();
367   base::MessageLoop::current()->RunUntilIdle();
368   EXPECT_FALSE(base::PathExists(db_file_path));
369 }
370
371 TEST_F(DOMStorageAreaTest, PurgeMemory) {
372   base::ScopedTempDir temp_dir;
373   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
374   scoped_refptr<DOMStorageArea> area(new DOMStorageArea(
375       kOrigin,
376       temp_dir.path(),
377       new MockDOMStorageTaskRunner(base::MessageLoopProxy::current().get())));
378
379   // Inject an in-memory db to speed up the test.
380   area->backing_.reset(new LocalStorageDatabaseAdapter());
381
382   // Unowned ptrs we use to verify that 'purge' has happened.
383   DOMStorageDatabase* original_backing =
384       static_cast<LocalStorageDatabaseAdapter*>(
385           area->backing_.get())->db_.get();
386   DOMStorageMap* original_map = area->map_.get();
387
388   // Should do no harm when called on a newly constructed object.
389   EXPECT_FALSE(area->is_initial_import_done_);
390   area->PurgeMemory();
391   EXPECT_FALSE(area->is_initial_import_done_);
392   DOMStorageDatabase* new_backing = static_cast<LocalStorageDatabaseAdapter*>(
393       area->backing_.get())->db_.get();
394   EXPECT_EQ(original_backing, new_backing);
395   EXPECT_EQ(original_map, area->map_.get());
396
397   // Should not do anything when commits are pending.
398   base::NullableString16 old_value;
399   area->SetItem(kKey, kValue, &old_value);
400   EXPECT_TRUE(area->is_initial_import_done_);
401   EXPECT_TRUE(area->HasUncommittedChanges());
402   area->PurgeMemory();
403   EXPECT_TRUE(area->is_initial_import_done_);
404   EXPECT_TRUE(area->HasUncommittedChanges());
405   new_backing = static_cast<LocalStorageDatabaseAdapter*>(
406       area->backing_.get())->db_.get();
407   EXPECT_EQ(original_backing, new_backing);
408   EXPECT_EQ(original_map, area->map_.get());
409
410   // Commit the changes from above,
411   base::MessageLoop::current()->RunUntilIdle();
412   EXPECT_FALSE(area->HasUncommittedChanges());
413   new_backing = static_cast<LocalStorageDatabaseAdapter*>(
414       area->backing_.get())->db_.get();
415   EXPECT_EQ(original_backing, new_backing);
416   EXPECT_EQ(original_map, area->map_.get());
417
418   // Should drop caches and reset database connections
419   // when invoked on an area that's loaded up primed.
420   area->PurgeMemory();
421   EXPECT_FALSE(area->is_initial_import_done_);
422   new_backing = static_cast<LocalStorageDatabaseAdapter*>(
423       area->backing_.get())->db_.get();
424   EXPECT_NE(original_backing, new_backing);
425   EXPECT_NE(original_map, area->map_.get());
426 }
427
428 TEST_F(DOMStorageAreaTest, DatabaseFileNames) {
429   struct {
430     const char* origin;
431     const char* file_name;
432     const char* journal_file_name;
433   } kCases[] = {
434     { "https://www.google.com/",
435       "https_www.google.com_0.localstorage",
436       "https_www.google.com_0.localstorage-journal" },
437     { "http://www.google.com:8080/",
438       "http_www.google.com_8080.localstorage",
439       "http_www.google.com_8080.localstorage-journal" },
440     { "file:///",
441       "file__0.localstorage",
442       "file__0.localstorage-journal" },
443   };
444
445   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kCases); ++i) {
446     GURL origin = GURL(kCases[i].origin).GetOrigin();
447     base::FilePath file_name =
448         base::FilePath().AppendASCII(kCases[i].file_name);
449     base::FilePath journal_file_name =
450         base::FilePath().AppendASCII(kCases[i].journal_file_name);
451
452     EXPECT_EQ(file_name,
453               DOMStorageArea::DatabaseFileNameFromOrigin(origin));
454     EXPECT_EQ(origin,
455               DOMStorageArea::OriginFromDatabaseFileName(file_name));
456     EXPECT_EQ(journal_file_name,
457               DOMStorageDatabase::GetJournalFilePath(file_name));
458   }
459
460   // Also test some DOMStorageDatabase::GetJournalFilePath cases here.
461   base::FilePath parent = base::FilePath().AppendASCII("a").AppendASCII("b");
462   EXPECT_EQ(
463       parent.AppendASCII("file-journal"),
464       DOMStorageDatabase::GetJournalFilePath(parent.AppendASCII("file")));
465   EXPECT_EQ(
466       base::FilePath().AppendASCII("-journal"),
467       DOMStorageDatabase::GetJournalFilePath(base::FilePath()));
468   EXPECT_EQ(
469       base::FilePath().AppendASCII(".extensiononly-journal"),
470       DOMStorageDatabase::GetJournalFilePath(
471           base::FilePath().AppendASCII(".extensiononly")));
472 }
473
474 }  // namespace content