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