test_vacuum.js (11328B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ 3 */ 4 5 // This file tests the Vacuum Manager and asyncVacuum(). 6 7 const { VacuumParticipant } = ChromeUtils.importESModule( 8 "resource://testing-common/VacuumParticipant.sys.mjs" 9 ); 10 11 /** 12 * Sends a fake idle-daily notification to the VACUUM Manager. 13 */ 14 function synthesize_idle_daily() { 15 Cc["@mozilla.org/storage/vacuum;1"] 16 .getService(Ci.nsIObserver) 17 .observe(null, "idle-daily", null); 18 } 19 20 /** 21 * Returns a new nsIFile reference for a profile database. 22 * 23 * @param filename for the database, excluded the .sqlite extension. 24 */ 25 function new_db_file(name = "testVacuum") { 26 let file = Services.dirsvc.get("ProfD", Ci.nsIFile); 27 file.append(name + ".sqlite"); 28 return file; 29 } 30 31 function reset_vacuum_date(name = "testVacuum") { 32 let date = parseInt(Date.now() / 1000 - 31 * 86400); 33 // Set last VACUUM to a date in the past. 34 Services.prefs.setIntPref(`storage.vacuum.last.${name}.sqlite`, date); 35 return date; 36 } 37 38 function get_vacuum_date(name = "testVacuum") { 39 return Services.prefs.getIntPref(`storage.vacuum.last.${name}.sqlite`, 0); 40 } 41 42 add_setup(async function () { 43 // turn on Cu.isInAutomation 44 Services.prefs.setBoolPref( 45 "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer", 46 true 47 ); 48 }); 49 50 add_task(async function test_common_vacuum() { 51 let last_vacuum_date = reset_vacuum_date(); 52 info("Test that a VACUUM correctly happens and all notifications are fired."); 53 let promiseTestVacuumBegin = TestUtils.topicObserved("test-begin-vacuum"); 54 let promiseTestVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success"); 55 let promiseVacuumBegin = TestUtils.topicObserved("vacuum-begin"); 56 let promiseVacuumEnd = TestUtils.topicObserved("vacuum-end"); 57 58 let participant = new VacuumParticipant( 59 Services.storage.openDatabase(new_db_file()) 60 ); 61 await participant.promiseRegistered(); 62 synthesize_idle_daily(); 63 // Wait for notifications. 64 await Promise.all([ 65 promiseTestVacuumBegin, 66 promiseTestVacuumEnd, 67 promiseVacuumBegin, 68 promiseVacuumEnd, 69 ]); 70 Assert.greater(get_vacuum_date(), last_vacuum_date); 71 await participant.dispose(); 72 }); 73 74 add_task(async function test_skipped_if_recent_vacuum() { 75 info("Test that a VACUUM is skipped if it was run recently."); 76 Services.prefs.setIntPref( 77 "storage.vacuum.last.testVacuum.sqlite", 78 parseInt(Date.now() / 1000) 79 ); 80 // Wait for VACUUM skipped notification. 81 let promiseSkipped = TestUtils.topicObserved("vacuum-skip"); 82 83 let participant = new VacuumParticipant( 84 Services.storage.openDatabase(new_db_file()) 85 ); 86 await participant.promiseRegistered(); 87 synthesize_idle_daily(); 88 89 // Check that VACUUM has been skipped. 90 await promiseSkipped; 91 92 await participant.dispose(); 93 }); 94 95 add_task(async function test_page_size_change() { 96 info("Test that a VACUUM changes page_size"); 97 reset_vacuum_date(); 98 99 let conn = Services.storage.openDatabase(new_db_file()); 100 info("Check initial page size."); 101 let stmt = conn.createStatement("PRAGMA page_size"); 102 Assert.ok(stmt.executeStep()); 103 Assert.equal(stmt.row.page_size, conn.defaultPageSize); 104 stmt.finalize(); 105 await populateFreeList(conn); 106 107 let participant = new VacuumParticipant(conn, { expectedPageSize: 1024 }); 108 await participant.promiseRegistered(); 109 let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success"); 110 synthesize_idle_daily(); 111 await promiseVacuumEnd; 112 113 info("Check that page size was updated."); 114 stmt = conn.createStatement("PRAGMA page_size"); 115 Assert.ok(stmt.executeStep()); 116 Assert.equal(stmt.row.page_size, 1024); 117 stmt.finalize(); 118 119 await participant.dispose(); 120 }); 121 122 add_task(async function test_skipped_optout_vacuum() { 123 info("Test that a VACUUM is skipped if the participant wants to opt-out."); 124 reset_vacuum_date(); 125 126 let participant = new VacuumParticipant( 127 Services.storage.openDatabase(new_db_file()), 128 { grant: false } 129 ); 130 await participant.promiseRegistered(); 131 // Wait for VACUUM skipped notification. 132 let promiseSkipped = TestUtils.topicObserved("vacuum-skip"); 133 134 synthesize_idle_daily(); 135 136 // Check that VACUUM has been skipped. 137 await promiseSkipped; 138 139 await participant.dispose(); 140 }); 141 142 add_task(async function test_memory_database_crash() { 143 info("Test that we don't crash trying to vacuum a memory database"); 144 reset_vacuum_date(); 145 146 let participant = new VacuumParticipant( 147 Services.storage.openSpecialDatabase("memory") 148 ); 149 await participant.promiseRegistered(); 150 // Wait for VACUUM skipped notification. 151 let promiseSkipped = TestUtils.topicObserved("vacuum-skip"); 152 153 synthesize_idle_daily(); 154 155 // Check that VACUUM has been skipped. 156 await promiseSkipped; 157 158 await participant.dispose(); 159 }); 160 161 add_task(async function test_async_connection() { 162 info("Test we can vacuum an async connection"); 163 reset_vacuum_date(); 164 165 let conn = await openAsyncDatabase(new_db_file()); 166 await populateFreeList(conn); 167 let participant = new VacuumParticipant(conn); 168 await participant.promiseRegistered(); 169 170 let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success"); 171 synthesize_idle_daily(); 172 await promiseVacuumEnd; 173 174 await participant.dispose(); 175 }); 176 177 add_task(async function test_change_to_incremental_vacuum() { 178 info("Test we can change to incremental vacuum"); 179 reset_vacuum_date(); 180 181 let conn = Services.storage.openDatabase(new_db_file()); 182 info("Check initial vacuum."); 183 let stmt = conn.createStatement("PRAGMA auto_vacuum"); 184 Assert.ok(stmt.executeStep()); 185 Assert.equal(stmt.row.auto_vacuum, 0); 186 stmt.finalize(); 187 await populateFreeList(conn); 188 189 let participant = new VacuumParticipant(conn, { useIncrementalVacuum: true }); 190 await participant.promiseRegistered(); 191 let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success"); 192 synthesize_idle_daily(); 193 await promiseVacuumEnd; 194 195 info("Check that auto_vacuum was updated."); 196 stmt = conn.createStatement("PRAGMA auto_vacuum"); 197 Assert.ok(stmt.executeStep()); 198 Assert.equal(stmt.row.auto_vacuum, 2); 199 stmt.finalize(); 200 201 await participant.dispose(); 202 }); 203 204 add_task(async function test_change_from_incremental_vacuum() { 205 info("Test we can change from incremental vacuum"); 206 reset_vacuum_date(); 207 208 let conn = Services.storage.openDatabase(new_db_file()); 209 conn.executeSimpleSQL("PRAGMA auto_vacuum = 2"); 210 info("Check initial vacuum."); 211 let stmt = conn.createStatement("PRAGMA auto_vacuum"); 212 Assert.ok(stmt.executeStep()); 213 Assert.equal(stmt.row.auto_vacuum, 2); 214 stmt.finalize(); 215 await populateFreeList(conn); 216 217 let participant = new VacuumParticipant(conn, { 218 useIncrementalVacuum: false, 219 }); 220 await participant.promiseRegistered(); 221 let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success"); 222 synthesize_idle_daily(); 223 await promiseVacuumEnd; 224 225 info("Check that auto_vacuum was updated."); 226 stmt = conn.createStatement("PRAGMA auto_vacuum"); 227 Assert.ok(stmt.executeStep()); 228 Assert.equal(stmt.row.auto_vacuum, 0); 229 stmt.finalize(); 230 231 await participant.dispose(); 232 }); 233 234 add_task(async function test_attached_vacuum() { 235 info("Test attached database is not a problem"); 236 reset_vacuum_date(); 237 238 let conn = Services.storage.openDatabase(new_db_file()); 239 let conn2 = Services.storage.openDatabase(new_db_file("attached")); 240 241 info("Attach " + conn2.databaseFile.path); 242 conn.executeSimpleSQL( 243 `ATTACH DATABASE '${conn2.databaseFile.path}' AS attached` 244 ); 245 await asyncClose(conn2); 246 let stmt = conn.createStatement("PRAGMA database_list"); 247 let schemas = []; 248 while (stmt.executeStep()) { 249 schemas.push(stmt.row.name); 250 } 251 Assert.deepEqual(schemas, ["main", "attached"]); 252 stmt.finalize(); 253 254 await populateFreeList(conn); 255 await populateFreeList(conn, "attached"); 256 257 let participant = new VacuumParticipant(conn); 258 await participant.promiseRegistered(); 259 let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success"); 260 synthesize_idle_daily(); 261 await promiseVacuumEnd; 262 263 await participant.dispose(); 264 }); 265 266 add_task(async function test_vacuum_fail() { 267 info("Test a failed vacuum"); 268 reset_vacuum_date(); 269 270 let conn = Services.storage.openDatabase(new_db_file()); 271 // Cannot vacuum in a transaction. 272 conn.beginTransaction(); 273 await populateFreeList(conn); 274 275 let participant = new VacuumParticipant(conn); 276 await participant.promiseRegistered(); 277 let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-failure"); 278 synthesize_idle_daily(); 279 await promiseVacuumEnd; 280 281 conn.commitTransaction(); 282 await participant.dispose(); 283 }); 284 285 add_task(async function test_async_vacuum() { 286 // Since previous tests already go through most cases, this only checks 287 // the basics of directly calling asyncVacuum(). 288 info("Test synchronous connection"); 289 let conn = Services.storage.openDatabase(new_db_file()); 290 await populateFreeList(conn); 291 let rv = await new Promise(resolve => { 292 conn.asyncVacuum(status => { 293 resolve(status); 294 }); 295 }); 296 Assert.ok(Components.isSuccessCode(rv)); 297 await asyncClose(conn); 298 299 info("Test asynchronous connection"); 300 conn = await openAsyncDatabase(new_db_file()); 301 await populateFreeList(conn); 302 rv = await new Promise(resolve => { 303 conn.asyncVacuum(status => { 304 resolve(status); 305 }); 306 }); 307 Assert.ok(Components.isSuccessCode(rv)); 308 await asyncClose(conn); 309 }); 310 311 // Chunked growth is disabled on Android, so this test is pointless there. 312 add_task( 313 { skip_if: () => AppConstants.platform == "android" }, 314 async function test_vacuum_growth() { 315 // Tests vacuum doesn't nullify chunked growth. 316 let conn = Services.storage.openDatabase(new_db_file("incremental")); 317 conn.executeSimpleSQL("PRAGMA auto_vacuum = INCREMENTAL"); 318 conn.setGrowthIncrement(2 * conn.defaultPageSize, ""); 319 await populateFreeList(conn); 320 let stmt = conn.createStatement("PRAGMA freelist_count"); 321 let count = 0; 322 Assert.ok(stmt.executeStep()); 323 count = stmt.row.freelist_count; 324 stmt.reset(); 325 Assert.greater(count, 2, "There's more than 2 page in freelist"); 326 327 let rv = await new Promise(resolve => { 328 conn.asyncVacuum(status => { 329 resolve(status); 330 }, true); 331 }); 332 Assert.ok(Components.isSuccessCode(rv)); 333 334 Assert.ok(stmt.executeStep()); 335 Assert.equal( 336 stmt.row.freelist_count, 337 2, 338 "chunked growth space was preserved" 339 ); 340 stmt.reset(); 341 342 // A full vacuuum should not be executed if there's less free pages than 343 // chunked growth. 344 rv = await new Promise(resolve => { 345 conn.asyncVacuum(status => { 346 resolve(status); 347 }); 348 }); 349 Assert.ok(Components.isSuccessCode(rv)); 350 351 Assert.ok(stmt.executeStep()); 352 Assert.equal( 353 stmt.row.freelist_count, 354 2, 355 "chunked growth space was preserved" 356 ); 357 stmt.finalize(); 358 359 await asyncClose(conn); 360 } 361 ); 362 363 async function populateFreeList(conn, schema = "main") { 364 await executeSimpleSQLAsync(conn, `CREATE TABLE ${schema}.test (id TEXT)`); 365 await executeSimpleSQLAsync( 366 conn, 367 `INSERT INTO ${schema}.test 368 VALUES ${Array.from({ length: 3000 }, () => Math.random()).map( 369 v => "('" + v + "')" 370 )}` 371 ); 372 await executeSimpleSQLAsync(conn, `DROP TABLE ${schema}.test`); 373 }