| 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 |