OLD | NEW |
| (Empty) |
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 "webkit/browser/dom_storage/dom_storage_database.h" | |
6 | |
7 #include "base/file_util.h" | |
8 #include "base/files/file_path.h" | |
9 #include "base/files/scoped_temp_dir.h" | |
10 #include "base/path_service.h" | |
11 #include "base/strings/utf_string_conversions.h" | |
12 #include "sql/statement.h" | |
13 #include "sql/test/scoped_error_ignorer.h" | |
14 #include "testing/gtest/include/gtest/gtest.h" | |
15 #include "third_party/sqlite/sqlite3.h" | |
16 | |
17 namespace dom_storage { | |
18 | |
19 void CreateV1Table(sql::Connection* db) { | |
20 ASSERT_TRUE(db->is_open()); | |
21 ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable")); | |
22 ASSERT_TRUE(db->Execute( | |
23 "CREATE TABLE ItemTable (" | |
24 "key TEXT UNIQUE ON CONFLICT REPLACE, " | |
25 "value TEXT NOT NULL ON CONFLICT FAIL)")); | |
26 } | |
27 | |
28 void CreateV2Table(sql::Connection* db) { | |
29 ASSERT_TRUE(db->is_open()); | |
30 ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable")); | |
31 ASSERT_TRUE(db->Execute( | |
32 "CREATE TABLE ItemTable (" | |
33 "key TEXT UNIQUE ON CONFLICT REPLACE, " | |
34 "value BLOB NOT NULL ON CONFLICT FAIL)")); | |
35 } | |
36 | |
37 void CreateInvalidKeyColumnTable(sql::Connection* db) { | |
38 // Create a table with the key type as FLOAT - this is "invalid" | |
39 // as far as the DOM Storage db is concerned. | |
40 ASSERT_TRUE(db->is_open()); | |
41 ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable")); | |
42 ASSERT_TRUE(db->Execute( | |
43 "CREATE TABLE IF NOT EXISTS ItemTable (" | |
44 "key FLOAT UNIQUE ON CONFLICT REPLACE, " | |
45 "value BLOB NOT NULL ON CONFLICT FAIL)")); | |
46 } | |
47 void CreateInvalidValueColumnTable(sql::Connection* db) { | |
48 // Create a table with the value type as FLOAT - this is "invalid" | |
49 // as far as the DOM Storage db is concerned. | |
50 ASSERT_TRUE(db->is_open()); | |
51 ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable")); | |
52 ASSERT_TRUE(db->Execute( | |
53 "CREATE TABLE IF NOT EXISTS ItemTable (" | |
54 "key TEXT UNIQUE ON CONFLICT REPLACE, " | |
55 "value FLOAT NOT NULL ON CONFLICT FAIL)")); | |
56 } | |
57 | |
58 void InsertDataV1(sql::Connection* db, | |
59 const base::string16& key, | |
60 const base::string16& value) { | |
61 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, | |
62 "INSERT INTO ItemTable VALUES (?,?)")); | |
63 statement.BindString16(0, key); | |
64 statement.BindString16(1, value); | |
65 ASSERT_TRUE(statement.is_valid()); | |
66 statement.Run(); | |
67 } | |
68 | |
69 void CheckValuesMatch(DomStorageDatabase* db, | |
70 const ValuesMap& expected) { | |
71 ValuesMap values_read; | |
72 db->ReadAllValues(&values_read); | |
73 EXPECT_EQ(expected.size(), values_read.size()); | |
74 | |
75 ValuesMap::const_iterator it = values_read.begin(); | |
76 for (; it != values_read.end(); ++it) { | |
77 base::string16 key = it->first; | |
78 base::NullableString16 value = it->second; | |
79 base::NullableString16 expected_value = expected.find(key)->second; | |
80 EXPECT_EQ(expected_value.string(), value.string()); | |
81 EXPECT_EQ(expected_value.is_null(), value.is_null()); | |
82 } | |
83 } | |
84 | |
85 void CreateMapWithValues(ValuesMap* values) { | |
86 base::string16 kCannedKeys[] = { | |
87 ASCIIToUTF16("test"), | |
88 ASCIIToUTF16("company"), | |
89 ASCIIToUTF16("date"), | |
90 ASCIIToUTF16("empty") | |
91 }; | |
92 base::NullableString16 kCannedValues[] = { | |
93 base::NullableString16(ASCIIToUTF16("123"), false), | |
94 base::NullableString16(ASCIIToUTF16("Google"), false), | |
95 base::NullableString16(ASCIIToUTF16("18-01-2012"), false), | |
96 base::NullableString16(base::string16(), false) | |
97 }; | |
98 for (unsigned i = 0; i < sizeof(kCannedKeys) / sizeof(kCannedKeys[0]); i++) | |
99 (*values)[kCannedKeys[i]] = kCannedValues[i]; | |
100 } | |
101 | |
102 TEST(DomStorageDatabaseTest, SimpleOpenAndClose) { | |
103 DomStorageDatabase db; | |
104 EXPECT_FALSE(db.IsOpen()); | |
105 ASSERT_TRUE(db.LazyOpen(true)); | |
106 EXPECT_TRUE(db.IsOpen()); | |
107 EXPECT_EQ(DomStorageDatabase::V2, db.DetectSchemaVersion()); | |
108 db.Close(); | |
109 EXPECT_FALSE(db.IsOpen()); | |
110 } | |
111 | |
112 TEST(DomStorageDatabaseTest, CloseEmptyDatabaseDeletesFile) { | |
113 base::ScopedTempDir temp_dir; | |
114 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
115 base::FilePath file_name = | |
116 temp_dir.path().AppendASCII("TestDomStorageDatabase.db"); | |
117 ValuesMap storage; | |
118 CreateMapWithValues(&storage); | |
119 | |
120 // First test the case that explicitly clearing the database will | |
121 // trigger its deletion from disk. | |
122 { | |
123 DomStorageDatabase db(file_name); | |
124 EXPECT_EQ(file_name, db.file_path()); | |
125 ASSERT_TRUE(db.CommitChanges(false, storage)); | |
126 } | |
127 EXPECT_TRUE(base::PathExists(file_name)); | |
128 | |
129 { | |
130 // Check that reading an existing db with data in it | |
131 // keeps the DB on disk on close. | |
132 DomStorageDatabase db(file_name); | |
133 ValuesMap values; | |
134 db.ReadAllValues(&values); | |
135 EXPECT_EQ(storage.size(), values.size()); | |
136 } | |
137 | |
138 EXPECT_TRUE(base::PathExists(file_name)); | |
139 storage.clear(); | |
140 | |
141 { | |
142 DomStorageDatabase db(file_name); | |
143 ASSERT_TRUE(db.CommitChanges(true, storage)); | |
144 } | |
145 EXPECT_FALSE(base::PathExists(file_name)); | |
146 | |
147 // Now ensure that a series of updates and removals whose net effect | |
148 // is an empty database also triggers deletion. | |
149 CreateMapWithValues(&storage); | |
150 { | |
151 DomStorageDatabase db(file_name); | |
152 ASSERT_TRUE(db.CommitChanges(false, storage)); | |
153 } | |
154 | |
155 EXPECT_TRUE(base::PathExists(file_name)); | |
156 | |
157 { | |
158 DomStorageDatabase db(file_name); | |
159 ASSERT_TRUE(db.CommitChanges(false, storage)); | |
160 ValuesMap::iterator it = storage.begin(); | |
161 for (; it != storage.end(); ++it) | |
162 it->second = base::NullableString16(); | |
163 ASSERT_TRUE(db.CommitChanges(false, storage)); | |
164 } | |
165 EXPECT_FALSE(base::PathExists(file_name)); | |
166 } | |
167 | |
168 TEST(DomStorageDatabaseTest, TestLazyOpenIsLazy) { | |
169 // This test needs to operate with a file on disk to ensure that we will | |
170 // open a file that already exists when only invoking ReadAllValues. | |
171 base::ScopedTempDir temp_dir; | |
172 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
173 base::FilePath file_name = | |
174 temp_dir.path().AppendASCII("TestDomStorageDatabase.db"); | |
175 | |
176 DomStorageDatabase db(file_name); | |
177 EXPECT_FALSE(db.IsOpen()); | |
178 ValuesMap values; | |
179 db.ReadAllValues(&values); | |
180 // Reading an empty db should not open the database. | |
181 EXPECT_FALSE(db.IsOpen()); | |
182 | |
183 values[ASCIIToUTF16("key")] = | |
184 base::NullableString16(ASCIIToUTF16("value"), false); | |
185 db.CommitChanges(false, values); | |
186 // Writing content should open the database. | |
187 EXPECT_TRUE(db.IsOpen()); | |
188 | |
189 db.Close(); | |
190 ASSERT_FALSE(db.IsOpen()); | |
191 | |
192 // Reading from an existing database should open the database. | |
193 CheckValuesMatch(&db, values); | |
194 EXPECT_TRUE(db.IsOpen()); | |
195 } | |
196 | |
197 TEST(DomStorageDatabaseTest, TestDetectSchemaVersion) { | |
198 DomStorageDatabase db; | |
199 db.db_.reset(new sql::Connection()); | |
200 ASSERT_TRUE(db.db_->OpenInMemory()); | |
201 | |
202 CreateInvalidValueColumnTable(db.db_.get()); | |
203 EXPECT_EQ(DomStorageDatabase::INVALID, db.DetectSchemaVersion()); | |
204 | |
205 CreateInvalidKeyColumnTable(db.db_.get()); | |
206 EXPECT_EQ(DomStorageDatabase::INVALID, db.DetectSchemaVersion()); | |
207 | |
208 CreateV1Table(db.db_.get()); | |
209 EXPECT_EQ(DomStorageDatabase::V1, db.DetectSchemaVersion()); | |
210 | |
211 CreateV2Table(db.db_.get()); | |
212 EXPECT_EQ(DomStorageDatabase::V2, db.DetectSchemaVersion()); | |
213 } | |
214 | |
215 TEST(DomStorageDatabaseTest, TestLazyOpenUpgradesDatabase) { | |
216 // This test needs to operate with a file on disk so that we | |
217 // can create a table at version 1 and then close it again | |
218 // so that LazyOpen sees there is work to do (LazyOpen will return | |
219 // early if the database is already open). | |
220 base::ScopedTempDir temp_dir; | |
221 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
222 base::FilePath file_name = | |
223 temp_dir.path().AppendASCII("TestDomStorageDatabase.db"); | |
224 | |
225 DomStorageDatabase db(file_name); | |
226 db.db_.reset(new sql::Connection()); | |
227 ASSERT_TRUE(db.db_->Open(file_name)); | |
228 CreateV1Table(db.db_.get()); | |
229 db.Close(); | |
230 | |
231 EXPECT_TRUE(db.LazyOpen(true)); | |
232 EXPECT_EQ(DomStorageDatabase::V2, db.DetectSchemaVersion()); | |
233 } | |
234 | |
235 TEST(DomStorageDatabaseTest, SimpleWriteAndReadBack) { | |
236 DomStorageDatabase db; | |
237 | |
238 ValuesMap storage; | |
239 CreateMapWithValues(&storage); | |
240 | |
241 EXPECT_TRUE(db.CommitChanges(false, storage)); | |
242 CheckValuesMatch(&db, storage); | |
243 } | |
244 | |
245 TEST(DomStorageDatabaseTest, WriteWithClear) { | |
246 DomStorageDatabase db; | |
247 | |
248 ValuesMap storage; | |
249 CreateMapWithValues(&storage); | |
250 | |
251 ASSERT_TRUE(db.CommitChanges(false, storage)); | |
252 CheckValuesMatch(&db, storage); | |
253 | |
254 // Insert some values, clearing the database first. | |
255 storage.clear(); | |
256 storage[ASCIIToUTF16("another_key")] = | |
257 base::NullableString16(ASCIIToUTF16("test"), false); | |
258 ASSERT_TRUE(db.CommitChanges(true, storage)); | |
259 CheckValuesMatch(&db, storage); | |
260 | |
261 // Now clear the values without inserting any new ones. | |
262 storage.clear(); | |
263 ASSERT_TRUE(db.CommitChanges(true, storage)); | |
264 CheckValuesMatch(&db, storage); | |
265 } | |
266 | |
267 TEST(DomStorageDatabaseTest, UpgradeFromV1ToV2WithData) { | |
268 const base::string16 kCannedKey = ASCIIToUTF16("foo"); | |
269 const base::NullableString16 kCannedValue(ASCIIToUTF16("bar"), false); | |
270 ValuesMap expected; | |
271 expected[kCannedKey] = kCannedValue; | |
272 | |
273 DomStorageDatabase db; | |
274 db.db_.reset(new sql::Connection()); | |
275 ASSERT_TRUE(db.db_->OpenInMemory()); | |
276 CreateV1Table(db.db_.get()); | |
277 InsertDataV1(db.db_.get(), kCannedKey, kCannedValue.string()); | |
278 | |
279 ASSERT_TRUE(db.UpgradeVersion1To2()); | |
280 | |
281 EXPECT_EQ(DomStorageDatabase::V2, db.DetectSchemaVersion()); | |
282 | |
283 CheckValuesMatch(&db, expected); | |
284 } | |
285 | |
286 TEST(DomStorageDatabaseTest, TestSimpleRemoveOneValue) { | |
287 DomStorageDatabase db; | |
288 | |
289 ASSERT_TRUE(db.LazyOpen(true)); | |
290 const base::string16 kCannedKey = ASCIIToUTF16("test"); | |
291 const base::NullableString16 kCannedValue(ASCIIToUTF16("data"), false); | |
292 ValuesMap expected; | |
293 expected[kCannedKey] = kCannedValue; | |
294 | |
295 // First write some data into the database. | |
296 ASSERT_TRUE(db.CommitChanges(false, expected)); | |
297 CheckValuesMatch(&db, expected); | |
298 | |
299 ValuesMap values; | |
300 // A null string in the map should mean that that key gets | |
301 // removed. | |
302 values[kCannedKey] = base::NullableString16(); | |
303 EXPECT_TRUE(db.CommitChanges(false, values)); | |
304 | |
305 expected.clear(); | |
306 CheckValuesMatch(&db, expected); | |
307 } | |
308 | |
309 TEST(DomStorageDatabaseTest, TestCanOpenAndReadWebCoreDatabase) { | |
310 base::FilePath webcore_database; | |
311 PathService::Get(base::DIR_SOURCE_ROOT, &webcore_database); | |
312 webcore_database = webcore_database.AppendASCII("webkit"); | |
313 webcore_database = webcore_database.AppendASCII("data"); | |
314 webcore_database = webcore_database.AppendASCII("dom_storage"); | |
315 webcore_database = | |
316 webcore_database.AppendASCII("webcore_test_database.localstorage"); | |
317 | |
318 ASSERT_TRUE(base::PathExists(webcore_database)); | |
319 | |
320 DomStorageDatabase db(webcore_database); | |
321 ValuesMap values; | |
322 db.ReadAllValues(&values); | |
323 EXPECT_TRUE(db.IsOpen()); | |
324 EXPECT_EQ(2u, values.size()); | |
325 | |
326 ValuesMap::const_iterator it = | |
327 values.find(ASCIIToUTF16("value")); | |
328 EXPECT_TRUE(it != values.end()); | |
329 EXPECT_EQ(ASCIIToUTF16("I am in local storage!"), it->second.string()); | |
330 | |
331 it = values.find(ASCIIToUTF16("timestamp")); | |
332 EXPECT_TRUE(it != values.end()); | |
333 EXPECT_EQ(ASCIIToUTF16("1326738338841"), it->second.string()); | |
334 | |
335 it = values.find(ASCIIToUTF16("not_there")); | |
336 EXPECT_TRUE(it == values.end()); | |
337 } | |
338 | |
339 TEST(DomStorageDatabaseTest, TestCanOpenFileThatIsNotADatabase) { | |
340 // Write into the temporary file first. | |
341 base::ScopedTempDir temp_dir; | |
342 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
343 base::FilePath file_name = | |
344 temp_dir.path().AppendASCII("TestDomStorageDatabase.db"); | |
345 | |
346 const char kData[] = "I am not a database."; | |
347 file_util::WriteFile(file_name, kData, strlen(kData)); | |
348 | |
349 { | |
350 sql::ScopedErrorIgnorer ignore_errors; | |
351 ignore_errors.IgnoreError(SQLITE_IOERR_SHORT_READ); | |
352 | |
353 // Try and open the file. As it's not a database, we should end up deleting | |
354 // it and creating a new, valid file, so everything should actually | |
355 // succeed. | |
356 DomStorageDatabase db(file_name); | |
357 ValuesMap values; | |
358 CreateMapWithValues(&values); | |
359 EXPECT_TRUE(db.CommitChanges(true, values)); | |
360 EXPECT_TRUE(db.CommitChanges(false, values)); | |
361 EXPECT_TRUE(db.IsOpen()); | |
362 | |
363 CheckValuesMatch(&db, values); | |
364 | |
365 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); | |
366 } | |
367 | |
368 { | |
369 sql::ScopedErrorIgnorer ignore_errors; | |
370 ignore_errors.IgnoreError(SQLITE_CANTOPEN); | |
371 | |
372 // Try to open a directory, we should fail gracefully and not attempt | |
373 // to delete it. | |
374 DomStorageDatabase db(temp_dir.path()); | |
375 ValuesMap values; | |
376 CreateMapWithValues(&values); | |
377 EXPECT_FALSE(db.CommitChanges(true, values)); | |
378 EXPECT_FALSE(db.CommitChanges(false, values)); | |
379 EXPECT_FALSE(db.IsOpen()); | |
380 | |
381 values.clear(); | |
382 | |
383 db.ReadAllValues(&values); | |
384 EXPECT_EQ(0u, values.size()); | |
385 EXPECT_FALSE(db.IsOpen()); | |
386 | |
387 EXPECT_TRUE(base::PathExists(temp_dir.path())); | |
388 | |
389 ASSERT_TRUE(ignore_errors.CheckIgnoredErrors()); | |
390 } | |
391 } | |
392 | |
393 } // namespace dom_storage | |
OLD | NEW |