summaryrefslogtreecommitdiffstats
path: root/webkit/browser/dom_storage/dom_storage_database_unittest.cc
blob: 363d438d698b656f62fd8b67ffd3aab0de1139ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "webkit/browser/dom_storage/dom_storage_database.h"

#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/utf_string_conversions.h"
#include "sql/statement.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace dom_storage {

void CreateV1Table(sql::Connection* db) {
  ASSERT_TRUE(db->is_open());
  ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable"));
  ASSERT_TRUE(db->Execute(
      "CREATE TABLE ItemTable ("
      "key TEXT UNIQUE ON CONFLICT REPLACE, "
      "value TEXT NOT NULL ON CONFLICT FAIL)"));
}

void CreateV2Table(sql::Connection* db) {
  ASSERT_TRUE(db->is_open());
  ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable"));
  ASSERT_TRUE(db->Execute(
      "CREATE TABLE ItemTable ("
      "key TEXT UNIQUE ON CONFLICT REPLACE, "
      "value BLOB NOT NULL ON CONFLICT FAIL)"));
}

void CreateInvalidKeyColumnTable(sql::Connection* db) {
  // Create a table with the key type as FLOAT - this is "invalid"
  // as far as the DOM Storage db is concerned.
  ASSERT_TRUE(db->is_open());
  ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable"));
  ASSERT_TRUE(db->Execute(
      "CREATE TABLE IF NOT EXISTS ItemTable ("
      "key FLOAT UNIQUE ON CONFLICT REPLACE, "
      "value BLOB NOT NULL ON CONFLICT FAIL)"));
}
void CreateInvalidValueColumnTable(sql::Connection* db) {
  // Create a table with the value type as FLOAT - this is "invalid"
  // as far as the DOM Storage db is concerned.
  ASSERT_TRUE(db->is_open());
  ASSERT_TRUE(db->Execute("DROP TABLE IF EXISTS ItemTable"));
  ASSERT_TRUE(db->Execute(
      "CREATE TABLE IF NOT EXISTS ItemTable ("
      "key TEXT UNIQUE ON CONFLICT REPLACE, "
      "value FLOAT NOT NULL ON CONFLICT FAIL)"));
}

void InsertDataV1(sql::Connection* db,
                  const base::string16& key,
                  const base::string16& value) {
  sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE,
      "INSERT INTO ItemTable VALUES (?,?)"));
  statement.BindString16(0, key);
  statement.BindString16(1, value);
  ASSERT_TRUE(statement.is_valid());
  statement.Run();
}

void CheckValuesMatch(DomStorageDatabase* db,
                      const ValuesMap& expected) {
  ValuesMap values_read;
  db->ReadAllValues(&values_read);
  EXPECT_EQ(expected.size(), values_read.size());

  ValuesMap::const_iterator it = values_read.begin();
  for (; it != values_read.end(); ++it) {
    base::string16 key = it->first;
    NullableString16 value = it->second;
    NullableString16 expected_value = expected.find(key)->second;
    EXPECT_EQ(expected_value.string(), value.string());
    EXPECT_EQ(expected_value.is_null(), value.is_null());
  }
}

void CreateMapWithValues(ValuesMap* values) {
  base::string16 kCannedKeys[] = {
      ASCIIToUTF16("test"),
      ASCIIToUTF16("company"),
      ASCIIToUTF16("date"),
      ASCIIToUTF16("empty")
  };
  NullableString16 kCannedValues[] = {
      NullableString16(ASCIIToUTF16("123"), false),
      NullableString16(ASCIIToUTF16("Google"), false),
      NullableString16(ASCIIToUTF16("18-01-2012"), false),
      NullableString16(base::string16(), false)
  };
  for (unsigned i = 0; i < sizeof(kCannedKeys) / sizeof(kCannedKeys[0]); i++)
    (*values)[kCannedKeys[i]] = kCannedValues[i];
}

TEST(DomStorageDatabaseTest, SimpleOpenAndClose) {
  DomStorageDatabase db;
  EXPECT_FALSE(db.IsOpen());
  ASSERT_TRUE(db.LazyOpen(true));
  EXPECT_TRUE(db.IsOpen());
  EXPECT_EQ(DomStorageDatabase::V2, db.DetectSchemaVersion());
  db.Close();
  EXPECT_FALSE(db.IsOpen());
}

TEST(DomStorageDatabaseTest, CloseEmptyDatabaseDeletesFile) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  base::FilePath file_name = temp_dir.path().AppendASCII("TestDomStorageDatabase.db");
  ValuesMap storage;
  CreateMapWithValues(&storage);

  // First test the case that explicitly clearing the database will
  // trigger its deletion from disk.
  {
    DomStorageDatabase db(file_name);
    EXPECT_EQ(file_name, db.file_path());
    ASSERT_TRUE(db.CommitChanges(false, storage));
  }
  EXPECT_TRUE(file_util::PathExists(file_name));

  {
    // Check that reading an existing db with data in it
    // keeps the DB on disk on close.
    DomStorageDatabase db(file_name);
    ValuesMap values;
    db.ReadAllValues(&values);
    EXPECT_EQ(storage.size(), values.size());
  }

  EXPECT_TRUE(file_util::PathExists(file_name));
  storage.clear();

  {
    DomStorageDatabase db(file_name);
    ASSERT_TRUE(db.CommitChanges(true, storage));
  }
  EXPECT_FALSE(file_util::PathExists(file_name));

  // Now ensure that a series of updates and removals whose net effect
  // is an empty database also triggers deletion.
  CreateMapWithValues(&storage);
  {
    DomStorageDatabase db(file_name);
    ASSERT_TRUE(db.CommitChanges(false, storage));
  }

  EXPECT_TRUE(file_util::PathExists(file_name));

  {
    DomStorageDatabase db(file_name);
    ASSERT_TRUE(db.CommitChanges(false, storage));
    ValuesMap::iterator it = storage.begin();
    for (; it != storage.end(); ++it)
      it->second = NullableString16(true);
    ASSERT_TRUE(db.CommitChanges(false, storage));
  }
  EXPECT_FALSE(file_util::PathExists(file_name));
}

TEST(DomStorageDatabaseTest, TestLazyOpenIsLazy) {
  // This test needs to operate with a file on disk to ensure that we will
  // open a file that already exists when only invoking ReadAllValues.
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  base::FilePath file_name = temp_dir.path().AppendASCII("TestDomStorageDatabase.db");

  DomStorageDatabase db(file_name);
  EXPECT_FALSE(db.IsOpen());
  ValuesMap values;
  db.ReadAllValues(&values);
  // Reading an empty db should not open the database.
  EXPECT_FALSE(db.IsOpen());

  values[ASCIIToUTF16("key")] = NullableString16(ASCIIToUTF16("value"), false);
  db.CommitChanges(false, values);
  // Writing content should open the database.
  EXPECT_TRUE(db.IsOpen());

  db.Close();
  ASSERT_FALSE(db.IsOpen());

  // Reading from an existing database should open the database.
  CheckValuesMatch(&db, values);
  EXPECT_TRUE(db.IsOpen());
}

TEST(DomStorageDatabaseTest, TestDetectSchemaVersion) {
  DomStorageDatabase db;
  db.db_.reset(new sql::Connection());
  ASSERT_TRUE(db.db_->OpenInMemory());

  CreateInvalidValueColumnTable(db.db_.get());
  EXPECT_EQ(DomStorageDatabase::INVALID, db.DetectSchemaVersion());

  CreateInvalidKeyColumnTable(db.db_.get());
  EXPECT_EQ(DomStorageDatabase::INVALID, db.DetectSchemaVersion());

  CreateV1Table(db.db_.get());
  EXPECT_EQ(DomStorageDatabase::V1, db.DetectSchemaVersion());

  CreateV2Table(db.db_.get());
  EXPECT_EQ(DomStorageDatabase::V2, db.DetectSchemaVersion());
}

TEST(DomStorageDatabaseTest, TestLazyOpenUpgradesDatabase) {
  // This test needs to operate with a file on disk so that we
  // can create a table at version 1 and then close it again
  // so that LazyOpen sees there is work to do (LazyOpen will return
  // early if the database is already open).
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  base::FilePath file_name = temp_dir.path().AppendASCII("TestDomStorageDatabase.db");

  DomStorageDatabase db(file_name);
  db.db_.reset(new sql::Connection());
  ASSERT_TRUE(db.db_->Open(file_name));
  CreateV1Table(db.db_.get());
  db.Close();

  EXPECT_TRUE(db.LazyOpen(true));
  EXPECT_EQ(DomStorageDatabase::V2, db.DetectSchemaVersion());
}

TEST(DomStorageDatabaseTest, SimpleWriteAndReadBack) {
  DomStorageDatabase db;

  ValuesMap storage;
  CreateMapWithValues(&storage);

  EXPECT_TRUE(db.CommitChanges(false, storage));
  CheckValuesMatch(&db, storage);
}

TEST(DomStorageDatabaseTest, WriteWithClear) {
  DomStorageDatabase db;

  ValuesMap storage;
  CreateMapWithValues(&storage);

  ASSERT_TRUE(db.CommitChanges(false, storage));
  CheckValuesMatch(&db, storage);

  // Insert some values, clearing the database first.
  storage.clear();
  storage[ASCIIToUTF16("another_key")] =
      NullableString16(ASCIIToUTF16("test"), false);
  ASSERT_TRUE(db.CommitChanges(true, storage));
  CheckValuesMatch(&db, storage);

  // Now clear the values without inserting any new ones.
  storage.clear();
  ASSERT_TRUE(db.CommitChanges(true, storage));
  CheckValuesMatch(&db, storage);
}

TEST(DomStorageDatabaseTest, UpgradeFromV1ToV2WithData) {
  const base::string16 kCannedKey = ASCIIToUTF16("foo");
  const NullableString16 kCannedValue(ASCIIToUTF16("bar"), false);
  ValuesMap expected;
  expected[kCannedKey] = kCannedValue;

  DomStorageDatabase db;
  db.db_.reset(new sql::Connection());
  ASSERT_TRUE(db.db_->OpenInMemory());
  CreateV1Table(db.db_.get());
  InsertDataV1(db.db_.get(), kCannedKey, kCannedValue.string());

  ASSERT_TRUE(db.UpgradeVersion1To2());

  EXPECT_EQ(DomStorageDatabase::V2, db.DetectSchemaVersion());

  CheckValuesMatch(&db, expected);
}

TEST(DomStorageDatabaseTest, TestSimpleRemoveOneValue) {
  DomStorageDatabase db;

  ASSERT_TRUE(db.LazyOpen(true));
  const base::string16 kCannedKey = ASCIIToUTF16("test");
  const NullableString16 kCannedValue(ASCIIToUTF16("data"), false);
  ValuesMap expected;
  expected[kCannedKey] = kCannedValue;

  // First write some data into the database.
  ASSERT_TRUE(db.CommitChanges(false, expected));
  CheckValuesMatch(&db, expected);

  ValuesMap values;
  // A null string in the map should mean that that key gets
  // removed.
  values[kCannedKey] = NullableString16(true);
  EXPECT_TRUE(db.CommitChanges(false, values));

  expected.clear();
  CheckValuesMatch(&db, expected);
}

TEST(DomStorageDatabaseTest, TestCanOpenAndReadWebCoreDatabase) {
  base::FilePath webcore_database;
  PathService::Get(base::DIR_SOURCE_ROOT, &webcore_database);
  webcore_database = webcore_database.AppendASCII("webkit");
  webcore_database = webcore_database.AppendASCII("data");
  webcore_database = webcore_database.AppendASCII("dom_storage");
  webcore_database =
      webcore_database.AppendASCII("webcore_test_database.localstorage");

  ASSERT_TRUE(file_util::PathExists(webcore_database));

  DomStorageDatabase db(webcore_database);
  ValuesMap values;
  db.ReadAllValues(&values);
  EXPECT_TRUE(db.IsOpen());
  EXPECT_EQ(2u, values.size());

  ValuesMap::const_iterator it =
      values.find(ASCIIToUTF16("value"));
  EXPECT_TRUE(it != values.end());
  EXPECT_EQ(ASCIIToUTF16("I am in local storage!"), it->second.string());

  it = values.find(ASCIIToUTF16("timestamp"));
  EXPECT_TRUE(it != values.end());
  EXPECT_EQ(ASCIIToUTF16("1326738338841"), it->second.string());

  it = values.find(ASCIIToUTF16("not_there"));
  EXPECT_TRUE(it == values.end());
}

TEST(DomStorageDatabaseTest, TestCanOpenFileThatIsNotADatabase) {
  // Write into the temporary file first.
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  base::FilePath file_name = temp_dir.path().AppendASCII("TestDomStorageDatabase.db");

  const char kData[] = "I am not a database.";
  file_util::WriteFile(file_name, kData, strlen(kData));

  {
    // Try and open the file. As it's not a database, we should end up deleting
    // it and creating a new, valid file, so everything should actually
    // succeed.
    DomStorageDatabase db(file_name);
    ValuesMap values;
    CreateMapWithValues(&values);
    EXPECT_TRUE(db.CommitChanges(true, values));
    EXPECT_TRUE(db.CommitChanges(false, values));
    EXPECT_TRUE(db.IsOpen());

    CheckValuesMatch(&db, values);
  }

  {
    // Try to open a directory, we should fail gracefully and not attempt
    // to delete it.
    DomStorageDatabase db(temp_dir.path());
    ValuesMap values;
    CreateMapWithValues(&values);
    EXPECT_FALSE(db.CommitChanges(true, values));
    EXPECT_FALSE(db.CommitChanges(false, values));
    EXPECT_FALSE(db.IsOpen());

    values.clear();

    db.ReadAllValues(&values);
    EXPECT_EQ(0u, values.size());
    EXPECT_FALSE(db.IsOpen());

    EXPECT_TRUE(file_util::PathExists(temp_dir.path()));
  }
}

}  // namespace dom_storage