tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }