summaryrefslogtreecommitdiffstats
path: root/storage/test/unit/test_vacuum.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /storage/test/unit/test_vacuum.js
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'storage/test/unit/test_vacuum.js')
-rw-r--r--storage/test/unit/test_vacuum.js372
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`);
+}