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 "content/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 17namespace content { 18 19void 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 28void 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 37void 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} 47void 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 58void 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 69void CheckValuesMatch(DOMStorageDatabase* db, 70 const DOMStorageValuesMap& expected) { 71 DOMStorageValuesMap values_read; 72 db->ReadAllValues(&values_read); 73 EXPECT_EQ(expected.size(), values_read.size()); 74 75 DOMStorageValuesMap::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 85void CreateMapWithValues(DOMStorageValuesMap* 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 102TEST(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 112TEST(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 DOMStorageValuesMap 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 DOMStorageValuesMap 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 DOMStorageValuesMap::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 168TEST(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 DOMStorageValuesMap 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 197TEST(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 215TEST(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 235TEST(DOMStorageDatabaseTest, SimpleWriteAndReadBack) { 236 DOMStorageDatabase db; 237 238 DOMStorageValuesMap storage; 239 CreateMapWithValues(&storage); 240 241 EXPECT_TRUE(db.CommitChanges(false, storage)); 242 CheckValuesMatch(&db, storage); 243} 244 245TEST(DOMStorageDatabaseTest, WriteWithClear) { 246 DOMStorageDatabase db; 247 248 DOMStorageValuesMap 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 267TEST(DOMStorageDatabaseTest, UpgradeFromV1ToV2WithData) { 268 const base::string16 kCannedKey = ASCIIToUTF16("foo"); 269 const base::NullableString16 kCannedValue(ASCIIToUTF16("bar"), false); 270 DOMStorageValuesMap 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 286TEST(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 DOMStorageValuesMap 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 DOMStorageValuesMap 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 309TEST(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 DOMStorageValuesMap values; 322 db.ReadAllValues(&values); 323 EXPECT_TRUE(db.IsOpen()); 324 EXPECT_EQ(2u, values.size()); 325 326 DOMStorageValuesMap::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 339TEST(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 DOMStorageValuesMap 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 DOMStorageValuesMap 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 content 394