summaryrefslogtreecommitdiffstats
path: root/storage/test/unit/test_vacuum.js
blob: 4971f577e13e800641d9f74c3d919b96cb8cacbc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

// This file tests the Vacuum Manager.

const { MockRegistrar } = ChromeUtils.importESModule(
  "resource://testing-common/MockRegistrar.sys.mjs"
);

/**
 * Loads a test component that will register as a vacuum-participant.
 * If other participants are found they will be unregistered, to avoid conflicts
 * with the test itself.
 */
function load_test_vacuum_component() {
  const CATEGORY_NAME = "vacuum-participant";
  const CONTRACT_ID = "@unit.test.com/test-vacuum-participant;1";

  MockRegistrar.registerESM(
    CONTRACT_ID,
    "resource://test/VacuumParticipant.sys.mjs",
    "VacuumParticipant"
  );

  let { catMan } = Services;
  // Temporary unregister other participants for this test.
  for (let { data: entry } of catMan.enumerateCategory(CATEGORY_NAME)) {
    print("Check if the found category entry (" + entry + ") is expected.");
    catMan.deleteCategoryEntry("vacuum-participant", entry, false);
  }
  catMan.addCategoryEntry(
    CATEGORY_NAME,
    "vacuumParticipant",
    CONTRACT_ID,
    false,
    false
  );
  print("Check the test entry exists.");
}

/**
 * Sends a fake idle-daily notification to the VACUUM Manager.
 */
function synthesize_idle_daily() {
  let vm = Cc["@mozilla.org/storage/vacuum;1"].getService(Ci.nsIObserver);
  vm.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) {
  let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
  file.append(name + ".sqlite");
  return file;
}

function run_test() {
  do_test_pending();

  // Change initial page size.  Do it immediately since it would require an
  // additional vacuum op to do it later.  As a bonus this makes the page size
  // change test really fast since it only has to check results.
  let conn = getDatabase(new_db_file("testVacuum"));
  conn.executeSimpleSQL("PRAGMA page_size = 1024");
  print("Check current page size.");
  let stmt = conn.createStatement("PRAGMA page_size");
  try {
    while (stmt.executeStep()) {
      Assert.equal(stmt.row.page_size, 1024);
    }
  } finally {
    stmt.finalize();
  }

  load_test_vacuum_component();

  run_next_test();
}

const TESTS = [
  function test_common_vacuum() {
    print(
      "\n*** Test that a VACUUM correctly happens and all notifications are fired."
    );
    // Wait for VACUUM begin.
    let beginVacuumReceived = false;
    Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) {
      Services.obs.removeObserver(onVacuum, aTopic);
      beginVacuumReceived = true;
    }, "test-begin-vacuum");

    // Wait for heavy IO notifications.
    let heavyIOTaskBeginReceived = false;
    let heavyIOTaskEndReceived = false;
    Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) {
      if (heavyIOTaskBeginReceived && heavyIOTaskEndReceived) {
        Services.obs.removeObserver(onVacuum, aTopic);
      }

      if (aData == "vacuum-begin") {
        heavyIOTaskBeginReceived = true;
      } else if (aData == "vacuum-end") {
        heavyIOTaskEndReceived = true;
      }
    }, "heavy-io-task");

    // Wait for VACUUM end.
    Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) {
      Services.obs.removeObserver(onVacuum, aTopic);
      print("Check we received onBeginVacuum");
      Assert.ok(beginVacuumReceived);
      print("Check we received heavy-io-task notifications");
      Assert.ok(heavyIOTaskBeginReceived);
      Assert.ok(heavyIOTaskEndReceived);
      print("Received onEndVacuum");
      run_next_test();
    }, "test-end-vacuum");

    synthesize_idle_daily();
  },

  function test_skipped_if_recent_vacuum() {
    print("\n*** 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 begin.
    let vacuumObserver = {
      gotNotification: false,
      observe: function VO_observe(aSubject, aTopic, aData) {
        this.gotNotification = true;
      },
      QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
    };
    Services.obs.addObserver(vacuumObserver, "test-begin-vacuum");

    // Check after a couple seconds that no VACUUM has been run.
    do_timeout(2000, function() {
      print("Check VACUUM did not run.");
      Assert.ok(!vacuumObserver.gotNotification);
      Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum");
      run_next_test();
    });

    synthesize_idle_daily();
  },

  function test_page_size_change() {
    print("\n*** Test that a VACUUM changes page_size");

    // We did setup the database with a small page size, the previous vacuum
    // should have updated it.
    print("Check that page size was updated.");
    let conn = getDatabase(new_db_file("testVacuum"));
    let stmt = conn.createStatement("PRAGMA page_size");
    try {
      while (stmt.executeStep()) {
        Assert.equal(stmt.row.page_size, conn.defaultPageSize);
      }
    } finally {
      stmt.finalize();
    }

    run_next_test();
  },

  function test_skipped_optout_vacuum() {
    print(
      "\n*** Test that a VACUUM is skipped if the participant wants to opt-out."
    );
    Services.obs.notifyObservers(null, "test-options", "opt-out");

    // Wait for VACUUM begin.
    let vacuumObserver = {
      gotNotification: false,
      observe: function VO_observe(aSubject, aTopic, aData) {
        this.gotNotification = true;
      },
      QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
    };
    Services.obs.addObserver(vacuumObserver, "test-begin-vacuum");

    // Check after a couple seconds that no VACUUM has been run.
    do_timeout(2000, function() {
      print("Check VACUUM did not run.");
      Assert.ok(!vacuumObserver.gotNotification);
      Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum");
      run_next_test();
    });

    synthesize_idle_daily();
  },

  /* Changing page size on WAL is not supported till Bug 634374 is properly fixed.
  function test_page_size_change_with_wal()
  {
    print("\n*** Test that a VACUUM changes page_size with WAL mode");
    Services.obs.notifyObservers(null, "test-options", "wal");

    // Set a small page size.
    let conn = getDatabase(new_db_file("testVacuum2"));
    conn.executeSimpleSQL("PRAGMA page_size = 1024");
    let stmt = conn.createStatement("PRAGMA page_size");
    try {
      while (stmt.executeStep()) {
        do_check_eq(stmt.row.page_size, 1024);
      }
    }
    finally {
      stmt.finalize();
    }

    // Use WAL journal mode.
    conn.executeSimpleSQL("PRAGMA journal_mode = WAL");
    stmt = conn.createStatement("PRAGMA journal_mode");
    try {
      while (stmt.executeStep()) {
        do_check_eq(stmt.row.journal_mode, "wal");
      }
    }
    finally {
      stmt.finalize();
    }

    // Wait for VACUUM end.
    let vacuumObserver = {
      observe: function VO_observe(aSubject, aTopic, aData) {
        Services.obs.removeObserver(this, aTopic);
        print("Check page size has been updated.");
        let stmt = conn.createStatement("PRAGMA page_size");
        try {
          while (stmt.executeStep()) {
            do_check_eq(stmt.row.page_size, Ci.mozIStorageConnection.DEFAULT_PAGE_SIZE);
          }
        }
        finally {
          stmt.finalize();
        }

        print("Check journal mode has been restored.");
        stmt = conn.createStatement("PRAGMA journal_mode");
        try {
          while (stmt.executeStep()) {
            do_check_eq(stmt.row.journal_mode, "wal");
          }
        }
        finally {
          stmt.finalize();
        }

        run_next_test();
      },
      QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver])
    }
    Services.obs.addObserver(vacuumObserver, "test-end-vacuum", false);

    synthesize_idle_daily();
  },
  */

  function test_memory_database_crash() {
    print("\n*** Test that we don't crash trying to vacuum a memory database");
    Services.obs.notifyObservers(null, "test-options", "memory");

    // Wait for VACUUM begin.
    let vacuumObserver = {
      gotNotification: false,
      observe: function VO_observe(aSubject, aTopic, aData) {
        this.gotNotification = true;
      },
      QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
    };
    Services.obs.addObserver(vacuumObserver, "test-begin-vacuum");

    // Check after a couple seconds that no VACUUM has been run.
    do_timeout(2000, function() {
      print("Check VACUUM did not run.");
      Assert.ok(!vacuumObserver.gotNotification);
      Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum");
      run_next_test();
    });

    synthesize_idle_daily();
  },

  /* Changing page size on WAL is not supported till Bug 634374 is properly fixed.
  function test_wal_restore_fail()
  {
    print("\n*** Test that a failing WAL restoration notifies failure");
    Services.obs.notifyObservers(null, "test-options", "wal-fail");

    // Wait for VACUUM end.
    let vacuumObserver = {
      observe: function VO_observe(aSubject, aTopic, aData) {
        Services.obs.removeObserver(vacuumObserver, "test-end-vacuum");
        print("Check WAL restoration failed.");
        do_check_false(aData);
        run_next_test();
      },
      QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver])
    }
    Services.obs.addObserver(vacuumObserver, "test-end-vacuum", false);

    synthesize_idle_daily();
  },
  */
];

function run_next_test() {
  if (!TESTS.length) {
    Services.obs.notifyObservers(null, "test-options", "dispose");
    do_test_finished();
  } else {
    // Set last VACUUM to a date in the past.
    Services.prefs.setIntPref(
      "storage.vacuum.last.testVacuum.sqlite",
      parseInt(Date.now() / 1000 - 31 * 86400)
    );
    executeSoon(TESTS.shift());
  }
}