PageRenderTime 57ms CodeModel.GetById 20ms app.highlight 31ms RepoModel.GetById 1ms app.codeStats 0ms

/extensions/cookie/test/unit/test_cookies_async_failure.js

http://github.com/zpao/v8monkey
JavaScript | 597 lines | 342 code | 107 blank | 148 comment | 13 complexity | 3d7ee17042cc5166e487f766e613dc7c MD5 | raw file
  1/* Any copyright is dedicated to the Public Domain.
  2   http://creativecommons.org/publicdomain/zero/1.0/ */
  3
  4// Test the various ways opening a cookie database can fail in an asynchronous
  5// (i.e. after synchronous initialization) manner, and that the database is
  6// renamed and recreated under each circumstance. These circumstances are, in no
  7// particular order:
  8//
  9// 1) A write operation failing after the database has been read in.
 10// 2) Asynchronous read failure due to a corrupt database.
 11// 3) Synchronous read failure due to a corrupt database, when reading:
 12//    a) a single base domain;
 13//    b) the entire database.
 14// 4) Asynchronous read failure, followed by another failure during INSERT but
 15//    before the database closes for rebuilding. (The additional error should be
 16//    ignored.)
 17// 5) Asynchronous read failure, followed by an INSERT failure during rebuild.
 18//    This should result in an abort of the database rebuild; the partially-
 19//    built database should be moved to 'cookies.sqlite.bak-rebuild'.
 20
 21let test_generator = do_run_test();
 22
 23function run_test() {
 24  do_test_pending();
 25  do_run_generator(test_generator);
 26}
 27
 28function finish_test() {
 29  do_execute_soon(function() {
 30    test_generator.close();
 31    do_test_finished();
 32  });
 33}
 34
 35function do_run_test() {
 36  // Set up a profile.
 37  this.profile = do_get_profile();
 38
 39  // Get the cookie file and the backup file.
 40  do_check_false(do_get_cookie_file(profile).exists());
 41  do_check_false(do_get_backup_file(profile).exists());
 42
 43  // Create a cookie object for testing.
 44  this.now = Date.now() * 1000;
 45  this.futureExpiry = Math.round(this.now / 1e6 + 1000);
 46  this.cookie = new Cookie("oh", "hai", "bar.com", "/", this.futureExpiry,
 47    this.now, this.now, false, false, false);
 48
 49  this.sub_generator = run_test_1(test_generator);
 50  sub_generator.next();
 51  yield;
 52
 53  this.sub_generator = run_test_2(test_generator);
 54  sub_generator.next();
 55  yield;
 56
 57  this.sub_generator = run_test_3(test_generator);
 58  sub_generator.next();
 59  yield;
 60
 61  this.sub_generator = run_test_4(test_generator);
 62  sub_generator.next();
 63  yield;
 64
 65  this.sub_generator = run_test_5(test_generator);
 66  sub_generator.next();
 67  yield;
 68
 69  finish_test();
 70  return;
 71}
 72
 73function do_get_backup_file(profile)
 74{
 75  let file = profile.clone();
 76  file.append("cookies.sqlite.bak");
 77  return file;
 78}
 79
 80function do_get_rebuild_backup_file(profile)
 81{
 82  let file = profile.clone();
 83  file.append("cookies.sqlite.bak-rebuild");
 84  return file;
 85}
 86
 87function do_corrupt_db(file)
 88{
 89  // Sanity check: the database size should be larger than 450k, since we've
 90  // written about 460k of data. If it's not, let's make it obvious now.
 91  let size = file.fileSize;
 92  do_check_true(size > 450e3);
 93
 94  // Corrupt the database by writing bad data to the end of the file. We
 95  // assume that the important metadata -- table structure etc -- is stored
 96  // elsewhere, and that doing this will not cause synchronous failure when
 97  // initializing the database connection. This is totally empirical --
 98  // overwriting between 1k and 100k of live data seems to work. (Note that the
 99  // database file will be larger than the actual content requires, since the
100  // cookie service uses a large growth increment. So we calculate the offset
101  // based on the expected size of the content, not just the file size.)
102  let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
103                createInstance(Ci.nsIFileOutputStream);
104  ostream.init(file, 2, -1, 0);
105  let sstream = ostream.QueryInterface(Ci.nsISeekableStream);
106  let n = size - 450e3 + 20e3;
107  sstream.seek(Ci.nsISeekableStream.NS_SEEK_SET, size - n);
108  for (let i = 0; i < n; ++i) {
109    ostream.write("a", 1);
110  }
111  ostream.flush();
112  ostream.close();
113
114  do_check_eq(file.clone().fileSize, size);
115  return size;
116}
117
118function run_test_1(generator)
119{
120  // Load the profile and populate it.
121  let uri = NetUtil.newURI("http://foo.com/");
122  Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
123
124  // Close the profile.
125  do_close_profile(sub_generator);
126  yield;
127
128  // Open a database connection now, before we load the profile and begin
129  // asynchronous write operations. In order to tell when the async delete
130  // statement has completed, we do something tricky: open a schema 2 connection
131  // and add a cookie with null baseDomain. We can then wait until we see it
132  // deleted in the new database.
133  let db2 = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
134  db2.db.executeSimpleSQL("INSERT INTO moz_cookies (baseDomain) VALUES (NULL)");
135  db2.close();
136  let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 4);
137  do_check_eq(do_count_cookies_in_db(db.db), 2);
138
139  // Load the profile, and wait for async read completion...
140  do_load_profile(sub_generator);
141  yield;
142
143  // ... and the DELETE statement to finish.
144  while (do_count_cookies_in_db(db.db) == 2) {
145    do_execute_soon(function() {
146      do_run_generator(sub_generator);
147    });
148    yield;
149  }
150  do_check_eq(do_count_cookies_in_db(db.db), 1);
151
152  // Insert a row.
153  db.insertCookie(cookie);
154  db.close();
155
156  // Attempt to insert a cookie with the same (name, host, path) triplet.
157  Services.cookiemgr.add(cookie.host, cookie.path, cookie.name, "hallo",
158    cookie.isSecure, cookie.isHttpOnly, cookie.isSession, cookie.expiry);
159
160  // Check that the cookie service accepted the new cookie.
161  do_check_eq(Services.cookiemgr.countCookiesFromHost(cookie.host), 1);
162
163  // Wait for the cookie service to rename the old database and rebuild.
164  new _observer(sub_generator, "cookie-db-rebuilding");
165  yield;
166  do_execute_soon(function() { do_run_generator(sub_generator); });
167  yield;
168
169  // At this point, the cookies should still be in memory.
170  do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 1);
171  do_check_eq(Services.cookiemgr.countCookiesFromHost(cookie.host), 1);
172  do_check_eq(do_count_cookies(), 2);
173
174  // Close the profile.
175  do_close_profile(sub_generator);
176  yield;
177
178  // Check that the original database was renamed, and that it contains the
179  // original cookie.
180  do_check_true(do_get_backup_file(profile).exists());
181  let backupdb = Services.storage.openDatabase(do_get_backup_file(profile));
182  do_check_eq(do_count_cookies_in_db(backupdb, "foo.com"), 1);
183  backupdb.close();
184
185  // Load the profile, and check that it contains the new cookie.
186  do_load_profile();
187
188  do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 1);
189  let enumerator = Services.cookiemgr.getCookiesFromHost(cookie.host);
190  do_check_true(enumerator.hasMoreElements());
191  let dbcookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
192  do_check_eq(dbcookie.value, "hallo");
193  do_check_false(enumerator.hasMoreElements());
194
195  // Close the profile.
196  do_close_profile(sub_generator);
197  yield;
198
199  // Clean up.
200  do_get_cookie_file(profile).remove(false);
201  do_get_backup_file(profile).remove(false);
202  do_check_false(do_get_cookie_file(profile).exists());
203  do_check_false(do_get_backup_file(profile).exists());
204  do_run_generator(generator);
205}
206
207function run_test_2(generator)
208{
209  // Load the profile and populate it.
210  do_load_profile();
211  for (let i = 0; i < 3000; ++i) {
212    let uri = NetUtil.newURI("http://" + i + ".com/");
213    Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
214  }
215
216  // Close the profile.
217  do_close_profile(sub_generator);
218  yield;
219
220  // Corrupt the database file.
221  let size = do_corrupt_db(do_get_cookie_file(profile));
222
223  // Load the profile.
224  do_load_profile();
225
226  // At this point, the database connection should be open. Ensure that it
227  // succeeded.
228  do_check_false(do_get_backup_file(profile).exists());
229
230  // Synchronously read in the first cookie. This will cause it to go into the
231  // cookie table, whereupon it will be written out during database rebuild.
232  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
233
234  // Wait for the asynchronous read to choke, at which point the backup file
235  // will be created and the database rebuilt.
236  new _observer(sub_generator, "cookie-db-rebuilding");
237  yield;
238  do_execute_soon(function() { do_run_generator(sub_generator); });
239  yield;
240
241  // At this point, the cookies should still be in memory.
242  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
243  do_check_eq(do_count_cookies(), 1);
244
245  // Close the profile.
246  do_close_profile(sub_generator);
247  yield;
248
249  // Check that the original database was renamed.
250  do_check_true(do_get_backup_file(profile).exists());
251  do_check_eq(do_get_backup_file(profile).fileSize, size);
252  let db = Services.storage.openDatabase(do_get_cookie_file(profile));
253  do_check_eq(do_count_cookies_in_db(db, "0.com"), 1);
254  db.close();
255
256  // Load the profile, and check that it contains the new cookie.
257  do_load_profile();
258  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
259  do_check_eq(do_count_cookies(), 1);
260
261  // Close the profile.
262  do_close_profile(sub_generator);
263  yield;
264
265  // Clean up.
266  do_get_cookie_file(profile).remove(false);
267  do_get_backup_file(profile).remove(false);
268  do_check_false(do_get_cookie_file(profile).exists());
269  do_check_false(do_get_backup_file(profile).exists());
270  do_run_generator(generator);
271}
272
273function run_test_3(generator)
274{
275  // Set the maximum cookies per base domain limit to a large value, so that
276  // corrupting the database is easier.
277  Services.prefs.setIntPref("network.cookie.maxPerHost", 3000);
278
279  // Load the profile and populate it.
280  do_load_profile();
281  for (let i = 0; i < 10; ++i) {
282    let uri = NetUtil.newURI("http://hither.com/");
283    Services.cookies.setCookieString(uri, null, "oh" + i + "=hai; max-age=1000",
284      null);
285  }
286  for (let i = 10; i < 3000; ++i) {
287    let uri = NetUtil.newURI("http://haithur.com/");
288    Services.cookies.setCookieString(uri, null, "oh" + i + "=hai; max-age=1000",
289      null);
290  }
291
292  // Close the profile.
293  do_close_profile(sub_generator);
294  yield;
295
296  // Corrupt the database file.
297  let size = do_corrupt_db(do_get_cookie_file(profile));
298
299  // Load the profile.
300  do_load_profile();
301
302  // At this point, the database connection should be open. Ensure that it
303  // succeeded.
304  do_check_false(do_get_backup_file(profile).exists());
305
306  // Synchronously read in the cookies for our two domains. The first should
307  // succeed, but the second should fail midway through, resulting in none of
308  // those cookies being present.
309  do_check_eq(Services.cookiemgr.countCookiesFromHost("hither.com"), 10);
310  do_check_eq(Services.cookiemgr.countCookiesFromHost("haithur.com"), 0);
311
312  // Wait for the backup file to be created and the database rebuilt.
313  do_check_false(do_get_backup_file(profile).exists());
314  new _observer(sub_generator, "cookie-db-rebuilding");
315  yield;
316  do_execute_soon(function() { do_run_generator(sub_generator); });
317  yield;
318
319  // Close the profile.
320  do_close_profile(sub_generator);
321  yield;
322  let db = Services.storage.openDatabase(do_get_cookie_file(profile));
323  do_check_eq(do_count_cookies_in_db(db, "hither.com"), 10);
324  do_check_eq(do_count_cookies_in_db(db), 10);
325  db.close();
326
327  // Check that the original database was renamed.
328  do_check_true(do_get_backup_file(profile).exists());
329  do_check_eq(do_get_backup_file(profile).fileSize, size);
330
331  // Rename it back, and try loading the entire database synchronously.
332  do_get_backup_file(profile).moveTo(null, "cookies.sqlite");
333  do_load_profile();
334
335  // At this point, the database connection should be open. Ensure that it
336  // succeeded.
337  do_check_false(do_get_backup_file(profile).exists());
338
339  // Synchronously read in everything.
340  do_check_eq(do_count_cookies(), 0);
341
342  // Wait for the backup file to be created and the database rebuilt.
343  do_check_false(do_get_backup_file(profile).exists());
344  new _observer(sub_generator, "cookie-db-rebuilding");
345  yield;
346  do_execute_soon(function() { do_run_generator(sub_generator); });
347  yield;
348
349  // Close the profile.
350  do_close_profile(sub_generator);
351  yield;
352  let db = Services.storage.openDatabase(do_get_cookie_file(profile));
353  do_check_eq(do_count_cookies_in_db(db), 0);
354  db.close();
355
356  // Check that the original database was renamed.
357  do_check_true(do_get_backup_file(profile).exists());
358  do_check_eq(do_get_backup_file(profile).fileSize, size);
359
360  // Clean up.
361  do_get_cookie_file(profile).remove(false);
362  do_get_backup_file(profile).remove(false);
363  do_check_false(do_get_cookie_file(profile).exists());
364  do_check_false(do_get_backup_file(profile).exists());
365  do_run_generator(generator);
366}
367
368function run_test_4(generator)
369{
370  // Load the profile and populate it.
371  do_load_profile();
372  for (let i = 0; i < 3000; ++i) {
373    let uri = NetUtil.newURI("http://" + i + ".com/");
374    Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
375  }
376
377  // Close the profile.
378  do_close_profile(sub_generator);
379  yield;
380
381  // Corrupt the database file.
382  let size = do_corrupt_db(do_get_cookie_file(profile));
383
384  // Load the profile.
385  do_load_profile();
386
387  // At this point, the database connection should be open. Ensure that it
388  // succeeded.
389  do_check_false(do_get_backup_file(profile).exists());
390
391  // Synchronously read in the first cookie. This will cause it to go into the
392  // cookie table, whereupon it will be written out during database rebuild.
393  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
394
395  // Queue up an INSERT for the same base domain. This should also go into
396  // memory and be written out during database rebuild.
397  let uri = NetUtil.newURI("http://0.com/");
398  Services.cookies.setCookieString(uri, null, "oh2=hai; max-age=1000", null);
399
400  // Wait for the asynchronous read to choke and the insert to fail shortly
401  // thereafter, at which point the backup file will be created and the database
402  // rebuilt.
403  new _observer(sub_generator, "cookie-db-rebuilding");
404  yield;
405  do_execute_soon(function() { do_run_generator(sub_generator); });
406  yield;
407
408  // Close the profile.
409  do_close_profile(sub_generator);
410  yield;
411
412  // Check that the original database was renamed.
413  do_check_true(do_get_backup_file(profile).exists());
414  do_check_eq(do_get_backup_file(profile).fileSize, size);
415  let db = Services.storage.openDatabase(do_get_cookie_file(profile));
416  do_check_eq(do_count_cookies_in_db(db, "0.com"), 2);
417  db.close();
418
419  // Load the profile, and check that it contains the new cookie.
420  do_load_profile();
421  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 2);
422  do_check_eq(do_count_cookies(), 2);
423
424  // Close the profile.
425  do_close_profile(sub_generator);
426  yield;
427
428  // Clean up.
429  do_get_cookie_file(profile).remove(false);
430  do_get_backup_file(profile).remove(false);
431  do_check_false(do_get_cookie_file(profile).exists());
432  do_check_false(do_get_backup_file(profile).exists());
433  do_run_generator(generator);
434}
435
436function run_test_4(generator)
437{
438  // Load the profile and populate it.
439  do_load_profile();
440  for (let i = 0; i < 3000; ++i) {
441    let uri = NetUtil.newURI("http://" + i + ".com/");
442    Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
443  }
444
445  // Close the profile.
446  do_close_profile(sub_generator);
447  yield;
448
449  // Corrupt the database file.
450  let size = do_corrupt_db(do_get_cookie_file(profile));
451
452  // Load the profile.
453  do_load_profile();
454
455  // At this point, the database connection should be open. Ensure that it
456  // succeeded.
457  do_check_false(do_get_backup_file(profile).exists());
458
459  // Synchronously read in the first cookie. This will cause it to go into the
460  // cookie table, whereupon it will be written out during database rebuild.
461  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
462
463  // Queue up an INSERT for the same base domain. This should also go into
464  // memory and be written out during database rebuild.
465  let uri = NetUtil.newURI("http://0.com/");
466  Services.cookies.setCookieString(uri, null, "oh2=hai; max-age=1000", null);
467
468  // Wait for the asynchronous read to choke and the insert to fail shortly
469  // thereafter, at which point the backup file will be created and the database
470  // rebuilt.
471  new _observer(sub_generator, "cookie-db-rebuilding");
472  yield;
473  do_execute_soon(function() { do_run_generator(sub_generator); });
474  yield;
475
476  // At this point, the cookies should still be in memory.
477  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 2);
478  do_check_eq(do_count_cookies(), 2);
479
480  // Close the profile.
481  do_close_profile(sub_generator);
482  yield;
483
484  // Check that the original database was renamed.
485  do_check_true(do_get_backup_file(profile).exists());
486  do_check_eq(do_get_backup_file(profile).fileSize, size);
487  let db = Services.storage.openDatabase(do_get_cookie_file(profile));
488  do_check_eq(do_count_cookies_in_db(db, "0.com"), 2);
489  db.close();
490
491  // Load the profile, and check that it contains the new cookie.
492  do_load_profile();
493  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 2);
494  do_check_eq(do_count_cookies(), 2);
495
496  // Close the profile.
497  do_close_profile(sub_generator);
498  yield;
499
500  // Clean up.
501  do_get_cookie_file(profile).remove(false);
502  do_get_backup_file(profile).remove(false);
503  do_check_false(do_get_cookie_file(profile).exists());
504  do_check_false(do_get_backup_file(profile).exists());
505  do_run_generator(generator);
506}
507
508function run_test_5(generator)
509{
510  // Load the profile and populate it.
511  do_load_profile();
512  let uri = NetUtil.newURI("http://bar.com/");
513  Services.cookies.setCookieString(uri, null, "oh=hai; path=/; max-age=1000",
514    null);
515  for (let i = 0; i < 3000; ++i) {
516    let uri = NetUtil.newURI("http://" + i + ".com/");
517    Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
518  }
519
520  // Close the profile.
521  do_close_profile(sub_generator);
522  yield;
523
524  // Corrupt the database file.
525  let size = do_corrupt_db(do_get_cookie_file(profile));
526
527  // Load the profile.
528  do_load_profile();
529
530  // At this point, the database connection should be open. Ensure that it
531  // succeeded.
532  do_check_false(do_get_backup_file(profile).exists());
533
534  // Synchronously read in the first two cookies. This will cause them to go
535  // into the cookie table, whereupon it will be written out during database
536  // rebuild.
537  do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
538  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
539
540  // Wait for the asynchronous read to choke, at which point the backup file
541  // will be created and a new connection opened.
542  new _observer(sub_generator, "cookie-db-rebuilding");
543  yield;
544
545  // At this point, the cookies should still be in memory. (Note that these
546  // calls are re-entrant into the cookie service, but it's OK!)
547  do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
548  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
549  do_check_eq(do_count_cookies(), 2);
550  do_check_true(do_get_backup_file(profile).exists());
551  do_check_eq(do_get_backup_file(profile).fileSize, size);
552  do_check_false(do_get_rebuild_backup_file(profile).exists());
553
554  // Open a database connection, and write a row that will trigger a constraint
555  // violation.
556  let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 4);
557  db.insertCookie(cookie);
558  do_check_eq(do_count_cookies_in_db(db.db, "bar.com"), 1);
559  do_check_eq(do_count_cookies_in_db(db.db), 1);
560  db.close();
561
562  // Wait for the rebuild to bail and the database to be closed.
563  new _observer(sub_generator, "cookie-db-closed");
564  yield;
565
566  // Check that the original backup and the database itself are gone.
567  do_check_true(do_get_rebuild_backup_file(profile).exists());
568  do_check_true(do_get_backup_file(profile).exists());
569  do_check_eq(do_get_backup_file(profile).fileSize, size);
570  do_check_false(do_get_cookie_file(profile).exists());
571
572  // Check that the rebuild backup has the original bar.com cookie, and possibly
573  // a 0.com cookie depending on whether it got written out first or second.
574  db = new CookieDatabaseConnection(do_get_rebuild_backup_file(profile), 4);
575  do_check_eq(do_count_cookies_in_db(db.db, "bar.com"), 1);
576  let count = do_count_cookies_in_db(db.db);
577  do_check_true(count == 1 ||
578    count == 2 && do_count_cookies_in_db(db.db, "0.com") == 1);
579  db.close();
580
581  do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
582  do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
583  do_check_eq(do_count_cookies(), 2);
584
585  // Close the profile. We do not need to wait for completion, because the
586  // database has already been closed.
587  do_close_profile();
588
589  // Clean up.
590  do_get_backup_file(profile).remove(false);
591  do_get_rebuild_backup_file(profile).remove(false);
592  do_check_false(do_get_cookie_file(profile).exists());
593  do_check_false(do_get_backup_file(profile).exists());
594  do_check_false(do_get_rebuild_backup_file(profile).exists());
595  do_run_generator(generator);
596}
597