diff options
Diffstat (limited to '')
-rw-r--r-- | storage/test/unit/test_vacuum.js | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/storage/test/unit/test_vacuum.js b/storage/test/unit/test_vacuum.js new file mode 100644 index 0000000000..a4cfdf714f --- /dev/null +++ b/storage/test/unit/test_vacuum.js @@ -0,0 +1,372 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This file tests the Vacuum Manager and asyncVacuum(). + +const { VacuumParticipant } = ChromeUtils.importESModule( + "resource://testing-common/VacuumParticipant.sys.mjs" +); + +/** + * Sends a fake idle-daily notification to the VACUUM Manager. + */ +function synthesize_idle_daily() { + Cc["@mozilla.org/storage/vacuum;1"] + .getService(Ci.nsIObserver) + .observe(null, "idle-daily", null); +} + +/** + * Returns a new nsIFile reference for a profile database. + * @param filename for the database, excluded the .sqlite extension. + */ +function new_db_file(name = "testVacuum") { + let file = Services.dirsvc.get("ProfD", Ci.nsIFile); + file.append(name + ".sqlite"); + return file; +} + +function reset_vacuum_date(name = "testVacuum") { + let date = parseInt(Date.now() / 1000 - 31 * 86400); + // Set last VACUUM to a date in the past. + Services.prefs.setIntPref(`storage.vacuum.last.${name}.sqlite`, date); + return date; +} + +function get_vacuum_date(name = "testVacuum") { + return Services.prefs.getIntPref(`storage.vacuum.last.${name}.sqlite`, 0); +} + +add_setup(async function () { + // turn on Cu.isInAutomation + Services.prefs.setBoolPref( + "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer", + true + ); +}); + +add_task(async function test_common_vacuum() { + let last_vacuum_date = reset_vacuum_date(); + info("Test that a VACUUM correctly happens and all notifications are fired."); + let promiseTestVacuumBegin = TestUtils.topicObserved("test-begin-vacuum"); + let promiseTestVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success"); + let promiseVacuumBegin = TestUtils.topicObserved("vacuum-begin"); + let promiseVacuumEnd = TestUtils.topicObserved("vacuum-end"); + + let participant = new VacuumParticipant( + Services.storage.openDatabase(new_db_file()) + ); + await participant.promiseRegistered(); + synthesize_idle_daily(); + // Wait for notifications. + await Promise.all([ + promiseTestVacuumBegin, + promiseTestVacuumEnd, + promiseVacuumBegin, + promiseVacuumEnd, + ]); + Assert.greater(get_vacuum_date(), last_vacuum_date); + await participant.dispose(); +}); + +add_task(async function test_skipped_if_recent_vacuum() { + info("Test that a VACUUM is skipped if it was run recently."); + Services.prefs.setIntPref( + "storage.vacuum.last.testVacuum.sqlite", + parseInt(Date.now() / 1000) + ); + // Wait for VACUUM skipped notification. + let promiseSkipped = TestUtils.topicObserved("vacuum-skip"); + + let participant = new VacuumParticipant( + Services.storage.openDatabase(new_db_file()) + ); + await participant.promiseRegistered(); + synthesize_idle_daily(); + + // Check that VACUUM has been skipped. + await promiseSkipped; + + await participant.dispose(); +}); + +add_task(async function test_page_size_change() { + info("Test that a VACUUM changes page_size"); + reset_vacuum_date(); + + let conn = Services.storage.openDatabase(new_db_file()); + info("Check initial page size."); + let stmt = conn.createStatement("PRAGMA page_size"); + Assert.ok(stmt.executeStep()); + Assert.equal(stmt.row.page_size, conn.defaultPageSize); + stmt.finalize(); + await populateFreeList(conn); + + let participant = new VacuumParticipant(conn, { expectedPageSize: 1024 }); + await participant.promiseRegistered(); + let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success"); + synthesize_idle_daily(); + await promiseVacuumEnd; + + info("Check that page size was updated."); + stmt = conn.createStatement("PRAGMA page_size"); + Assert.ok(stmt.executeStep()); + Assert.equal(stmt.row.page_size, 1024); + stmt.finalize(); + + await participant.dispose(); +}); + +add_task(async function test_skipped_optout_vacuum() { + info("Test that a VACUUM is skipped if the participant wants to opt-out."); + reset_vacuum_date(); + + let participant = new VacuumParticipant( + Services.storage.openDatabase(new_db_file()), + { grant: false } + ); + await participant.promiseRegistered(); + // Wait for VACUUM skipped notification. + let promiseSkipped = TestUtils.topicObserved("vacuum-skip"); + + synthesize_idle_daily(); + + // Check that VACUUM has been skipped. + await promiseSkipped; + + await participant.dispose(); +}); + +add_task(async function test_memory_database_crash() { + info("Test that we don't crash trying to vacuum a memory database"); + reset_vacuum_date(); + + let participant = new VacuumParticipant( + Services.storage.openSpecialDatabase("memory") + ); + await participant.promiseRegistered(); + // Wait for VACUUM skipped notification. + let promiseSkipped = TestUtils.topicObserved("vacuum-skip"); + + synthesize_idle_daily(); + + // Check that VACUUM has been skipped. + await promiseSkipped; + + await participant.dispose(); +}); + +add_task(async function test_async_connection() { + info("Test we can vacuum an async connection"); + reset_vacuum_date(); + + let conn = await openAsyncDatabase(new_db_file()); + await populateFreeList(conn); + let participant = new VacuumParticipant(conn); + await participant.promiseRegistered(); + + let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success"); + synthesize_idle_daily(); + await promiseVacuumEnd; + + await participant.dispose(); +}); + +add_task(async function test_change_to_incremental_vacuum() { + info("Test we can change to incremental vacuum"); + reset_vacuum_date(); + + let conn = Services.storage.openDatabase(new_db_file()); + info("Check initial vacuum."); + let stmt = conn.createStatement("PRAGMA auto_vacuum"); + Assert.ok(stmt.executeStep()); + Assert.equal(stmt.row.auto_vacuum, 0); + stmt.finalize(); + await populateFreeList(conn); + + let participant = new VacuumParticipant(conn, { useIncrementalVacuum: true }); + await participant.promiseRegistered(); + let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success"); + synthesize_idle_daily(); + await promiseVacuumEnd; + + info("Check that auto_vacuum was updated."); + stmt = conn.createStatement("PRAGMA auto_vacuum"); + Assert.ok(stmt.executeStep()); + Assert.equal(stmt.row.auto_vacuum, 2); + stmt.finalize(); + + await participant.dispose(); +}); + +add_task(async function test_change_from_incremental_vacuum() { + info("Test we can change from incremental vacuum"); + reset_vacuum_date(); + + let conn = Services.storage.openDatabase(new_db_file()); + conn.executeSimpleSQL("PRAGMA auto_vacuum = 2"); + info("Check initial vacuum."); + let stmt = conn.createStatement("PRAGMA auto_vacuum"); + Assert.ok(stmt.executeStep()); + Assert.equal(stmt.row.auto_vacuum, 2); + stmt.finalize(); + await populateFreeList(conn); + + let participant = new VacuumParticipant(conn, { + useIncrementalVacuum: false, + }); + await participant.promiseRegistered(); + let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success"); + synthesize_idle_daily(); + await promiseVacuumEnd; + + info("Check that auto_vacuum was updated."); + stmt = conn.createStatement("PRAGMA auto_vacuum"); + Assert.ok(stmt.executeStep()); + Assert.equal(stmt.row.auto_vacuum, 0); + stmt.finalize(); + + await participant.dispose(); +}); + +add_task(async function test_attached_vacuum() { + info("Test attached database is not a problem"); + reset_vacuum_date(); + + let conn = Services.storage.openDatabase(new_db_file()); + let conn2 = Services.storage.openDatabase(new_db_file("attached")); + + info("Attach " + conn2.databaseFile.path); + conn.executeSimpleSQL( + `ATTACH DATABASE '${conn2.databaseFile.path}' AS attached` + ); + await asyncClose(conn2); + let stmt = conn.createStatement("PRAGMA database_list"); + let schemas = []; + while (stmt.executeStep()) { + schemas.push(stmt.row.name); + } + Assert.deepEqual(schemas, ["main", "attached"]); + stmt.finalize(); + + await populateFreeList(conn); + await populateFreeList(conn, "attached"); + + let participant = new VacuumParticipant(conn); + await participant.promiseRegistered(); + let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-success"); + synthesize_idle_daily(); + await promiseVacuumEnd; + + await participant.dispose(); +}); + +add_task(async function test_vacuum_fail() { + info("Test a failed vacuum"); + reset_vacuum_date(); + + let conn = Services.storage.openDatabase(new_db_file()); + // Cannot vacuum in a transaction. + conn.beginTransaction(); + await populateFreeList(conn); + + let participant = new VacuumParticipant(conn); + await participant.promiseRegistered(); + let promiseVacuumEnd = TestUtils.topicObserved("test-end-vacuum-failure"); + synthesize_idle_daily(); + await promiseVacuumEnd; + + conn.commitTransaction(); + await participant.dispose(); +}); + +add_task(async function test_async_vacuum() { + // Since previous tests already go through most cases, this only checks + // the basics of directly calling asyncVacuum(). + info("Test synchronous connection"); + let conn = Services.storage.openDatabase(new_db_file()); + await populateFreeList(conn); + let rv = await new Promise(resolve => { + conn.asyncVacuum(status => { + resolve(status); + }); + }); + Assert.ok(Components.isSuccessCode(rv)); + await asyncClose(conn); + + info("Test asynchronous connection"); + conn = await openAsyncDatabase(new_db_file()); + await populateFreeList(conn); + rv = await new Promise(resolve => { + conn.asyncVacuum(status => { + resolve(status); + }); + }); + Assert.ok(Components.isSuccessCode(rv)); + await asyncClose(conn); +}); + +// Chunked growth is disabled on Android, so this test is pointless there. +add_task( + { skip_if: () => AppConstants.platform == "android" }, + async function test_vacuum_growth() { + // Tests vacuum doesn't nullify chunked growth. + let conn = Services.storage.openDatabase(new_db_file("incremental")); + conn.executeSimpleSQL("PRAGMA auto_vacuum = INCREMENTAL"); + conn.setGrowthIncrement(2 * conn.defaultPageSize, ""); + await populateFreeList(conn); + let stmt = conn.createStatement("PRAGMA freelist_count"); + let count = 0; + Assert.ok(stmt.executeStep()); + count = stmt.row.freelist_count; + stmt.reset(); + Assert.greater(count, 2, "There's more than 2 page in freelist"); + + let rv = await new Promise(resolve => { + conn.asyncVacuum(status => { + resolve(status); + }, true); + }); + Assert.ok(Components.isSuccessCode(rv)); + + Assert.ok(stmt.executeStep()); + Assert.equal( + stmt.row.freelist_count, + 2, + "chunked growth space was preserved" + ); + stmt.reset(); + + // A full vacuuum should not be executed if there's less free pages than + // chunked growth. + rv = await new Promise(resolve => { + conn.asyncVacuum(status => { + resolve(status); + }); + }); + Assert.ok(Components.isSuccessCode(rv)); + + Assert.ok(stmt.executeStep()); + Assert.equal( + stmt.row.freelist_count, + 2, + "chunked growth space was preserved" + ); + stmt.finalize(); + + await asyncClose(conn); + } +); + +async function populateFreeList(conn, schema = "main") { + await executeSimpleSQLAsync(conn, `CREATE TABLE ${schema}.test (id TEXT)`); + await executeSimpleSQLAsync( + conn, + `INSERT INTO ${schema}.test + VALUES ${Array.from({ length: 3000 }, () => Math.random()).map( + v => "('" + v + "')" + )}` + ); + await executeSimpleSQLAsync(conn, `DROP TABLE ${schema}.test`); +} |