372 lines
11 KiB
JavaScript
372 lines
11 KiB
JavaScript
/* 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`);
|
|
}
|