summaryrefslogtreecommitdiffstats
path: root/toolkit/modules/tests/xpcshell/test_JSONFile.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/modules/tests/xpcshell/test_JSONFile.js')
-rw-r--r--toolkit/modules/tests/xpcshell/test_JSONFile.js326
1 files changed, 326 insertions, 0 deletions
diff --git a/toolkit/modules/tests/xpcshell/test_JSONFile.js b/toolkit/modules/tests/xpcshell/test_JSONFile.js
new file mode 100644
index 0000000000..bacbe7895f
--- /dev/null
+++ b/toolkit/modules/tests/xpcshell/test_JSONFile.js
@@ -0,0 +1,326 @@
+/**
+ * Tests the JSONFile object.
+ */
+
+"use strict";
+
+// Globals
+ChromeUtils.defineESModuleGetters(this, {
+ AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
+ FileTestUtils: "resource://testing-common/FileTestUtils.sys.mjs",
+ JSONFile: "resource://gre/modules/JSONFile.sys.mjs",
+});
+
+/**
+ * Returns a reference to a temporary file that is guaranteed not to exist and
+ * is cleaned up later. See FileTestUtils.getTempFile for details.
+ */
+function getTempFile(leafName) {
+ return FileTestUtils.getTempFile(leafName);
+}
+
+const TEST_STORE_FILE_NAME = "test-store.json";
+
+const TEST_DATA = {
+ number: 123,
+ string: "test",
+ object: {
+ prop1: 1,
+ prop2: 2,
+ },
+};
+
+// Tests
+
+add_task(async function test_save_reload() {
+ let storeForSave = new JSONFile({
+ path: getTempFile(TEST_STORE_FILE_NAME).path,
+ });
+
+ await storeForSave.load();
+
+ Assert.ok(storeForSave.dataReady);
+ Assert.deepEqual(storeForSave.data, {});
+
+ Object.assign(storeForSave.data, TEST_DATA);
+
+ await new Promise(resolve => {
+ let save = storeForSave._save.bind(storeForSave);
+ storeForSave._save = () => {
+ save();
+ resolve();
+ };
+ storeForSave.saveSoon();
+ });
+
+ let storeForLoad = new JSONFile({
+ path: storeForSave.path,
+ });
+
+ await storeForLoad.load();
+
+ Assert.deepEqual(storeForLoad.data, TEST_DATA);
+});
+
+add_task(async function test_load_sync() {
+ let storeForSave = new JSONFile({
+ path: getTempFile(TEST_STORE_FILE_NAME).path,
+ });
+ await storeForSave.load();
+ Object.assign(storeForSave.data, TEST_DATA);
+ await storeForSave._save();
+
+ let storeForLoad = new JSONFile({
+ path: storeForSave.path,
+ });
+ storeForLoad.ensureDataReady();
+
+ Assert.deepEqual(storeForLoad.data, TEST_DATA);
+});
+
+add_task(async function test_load_with_dataPostProcessor() {
+ let storeForSave = new JSONFile({
+ path: getTempFile(TEST_STORE_FILE_NAME).path,
+ });
+ await storeForSave.load();
+ Object.assign(storeForSave.data, TEST_DATA);
+ await storeForSave._save();
+
+ let random = Math.random();
+ let storeForLoad = new JSONFile({
+ path: storeForSave.path,
+ dataPostProcessor: data => {
+ Assert.deepEqual(data, TEST_DATA);
+
+ data.test = random;
+ return data;
+ },
+ });
+
+ await storeForLoad.load();
+
+ Assert.equal(storeForLoad.data.test, random);
+});
+
+add_task(async function test_load_with_dataPostProcessor_fails() {
+ let store = new JSONFile({
+ path: getTempFile(TEST_STORE_FILE_NAME).path,
+ dataPostProcessor: () => {
+ throw new Error("dataPostProcessor fails.");
+ },
+ });
+
+ await Assert.rejects(store.load(), /dataPostProcessor fails\./);
+
+ Assert.ok(!store.dataReady);
+});
+
+add_task(async function test_load_sync_with_dataPostProcessor_fails() {
+ let store = new JSONFile({
+ path: getTempFile(TEST_STORE_FILE_NAME).path,
+ dataPostProcessor: () => {
+ throw new Error("dataPostProcessor fails.");
+ },
+ });
+
+ Assert.throws(() => store.ensureDataReady(), /dataPostProcessor fails\./);
+
+ Assert.ok(!store.dataReady);
+});
+
+/**
+ * Loads data from a string in a predefined format. The purpose of this test is
+ * to verify that the JSON format used in previous versions can be loaded.
+ */
+add_task(async function test_load_string_predefined() {
+ let store = new JSONFile({
+ path: getTempFile(TEST_STORE_FILE_NAME).path,
+ });
+
+ let string = '{"number":123,"string":"test","object":{"prop1":1,"prop2":2}}';
+
+ await IOUtils.writeUTF8(store.path, string, {
+ tmpPath: store.path + ".tmp",
+ });
+
+ await store.load();
+
+ Assert.deepEqual(store.data, TEST_DATA);
+});
+
+/**
+ * Loads data from a malformed JSON string.
+ */
+add_task(async function test_load_string_malformed() {
+ let store = new JSONFile({
+ path: getTempFile(TEST_STORE_FILE_NAME).path,
+ });
+
+ let string = '{"number":123,"string":"test","object":{"prop1":1,';
+
+ await IOUtils.writeUTF8(store.path, string, {
+ tmpPath: store.path + ".tmp",
+ });
+
+ await store.load();
+
+ // A backup file should have been created.
+ Assert.ok(await IOUtils.exists(store.path + ".corrupt"));
+ await IOUtils.remove(store.path + ".corrupt");
+
+ // The store should be ready to accept new data.
+ Assert.ok(store.dataReady);
+ Assert.deepEqual(store.data, {});
+});
+
+/**
+ * Loads data from a malformed JSON string, using the synchronous initialization
+ * path.
+ */
+add_task(async function test_load_string_malformed_sync() {
+ let store = new JSONFile({
+ path: getTempFile(TEST_STORE_FILE_NAME).path,
+ });
+
+ let string = '{"number":123,"string":"test","object":{"prop1":1,';
+
+ await IOUtils.writeUTF8(store.path, string, {
+ tmpPath: store.path + ".tmp",
+ });
+
+ store.ensureDataReady();
+
+ // A backup file should have been created.
+ Assert.ok(await IOUtils.exists(store.path + ".corrupt"));
+ await IOUtils.remove(store.path + ".corrupt");
+
+ // The store should be ready to accept new data.
+ Assert.ok(store.dataReady);
+ Assert.deepEqual(store.data, {});
+});
+
+add_task(async function test_overwrite_data() {
+ let storeForSave = new JSONFile({
+ path: getTempFile(TEST_STORE_FILE_NAME).path,
+ });
+
+ let string = `{"number":456,"string":"tset","object":{"prop1":3,"prop2":4}}`;
+
+ await IOUtils.writeUTF8(storeForSave.path, string, {
+ tmpPath: storeForSave.path + ".tmp",
+ });
+
+ Assert.ok(!storeForSave.dataReady);
+ storeForSave.data = TEST_DATA;
+ Assert.ok(storeForSave.dataReady);
+ Assert.equal(storeForSave.data, TEST_DATA);
+
+ await new Promise(resolve => {
+ let save = storeForSave._save.bind(storeForSave);
+ storeForSave._save = () => {
+ save();
+ resolve();
+ };
+ storeForSave.saveSoon();
+ });
+
+ let storeForLoad = new JSONFile({
+ path: storeForSave.path,
+ });
+
+ await storeForLoad.load();
+
+ Assert.deepEqual(storeForLoad.data, TEST_DATA);
+});
+
+add_task(async function test_beforeSave() {
+ let store;
+ let promiseBeforeSave = new Promise(resolve => {
+ store = new JSONFile({
+ path: getTempFile(TEST_STORE_FILE_NAME).path,
+ beforeSave: resolve,
+ saveDelayMs: 250,
+ });
+ });
+
+ store.saveSoon();
+
+ await promiseBeforeSave;
+});
+
+add_task(async function test_beforeSave_rejects() {
+ let storeForSave = new JSONFile({
+ path: getTempFile(TEST_STORE_FILE_NAME).path,
+ beforeSave() {
+ return Promise.reject(new Error("oops"));
+ },
+ saveDelayMs: 250,
+ });
+
+ let promiseSave = new Promise((resolve, reject) => {
+ let save = storeForSave._save.bind(storeForSave);
+ storeForSave._save = () => {
+ save().then(resolve, reject);
+ };
+ storeForSave.saveSoon();
+ });
+
+ await Assert.rejects(promiseSave, function(ex) {
+ return ex.message == "oops";
+ });
+});
+
+add_task(async function test_finalize() {
+ let path = getTempFile(TEST_STORE_FILE_NAME).path;
+
+ let barrier = new AsyncShutdown.Barrier("test-auto-finalize");
+ let storeForSave = new JSONFile({
+ path,
+ saveDelayMs: 2000,
+ finalizeAt: barrier.client,
+ });
+ await storeForSave.load();
+ storeForSave.data = TEST_DATA;
+ storeForSave.saveSoon();
+
+ let promiseFinalize = storeForSave.finalize();
+ await Assert.rejects(storeForSave.finalize(), /has already been finalized$/);
+ await promiseFinalize;
+ Assert.ok(!storeForSave.dataReady);
+
+ // Finalization removes the blocker, so waiting should not log an unhandled
+ // error even though the object has been explicitly finalized.
+ await barrier.wait();
+
+ let storeForLoad = new JSONFile({ path });
+ await storeForLoad.load();
+ Assert.deepEqual(storeForLoad.data, TEST_DATA);
+});
+
+add_task(async function test_finalize_on_shutdown() {
+ let path = getTempFile(TEST_STORE_FILE_NAME).path;
+
+ let barrier = new AsyncShutdown.Barrier("test-finalize-shutdown");
+ let storeForSave = new JSONFile({
+ path,
+ saveDelayMs: 2000,
+ finalizeAt: barrier.client,
+ });
+ await storeForSave.load();
+ storeForSave.data = TEST_DATA;
+ // Arm the saver, then simulate shutdown and ensure the file is
+ // automatically finalized.
+ storeForSave.saveSoon();
+
+ await barrier.wait();
+ // It's possible for `finalize` to reject when called concurrently with
+ // shutdown. We don't distinguish between explicit `finalize` calls and
+ // finalization on shutdown because we expect most consumers to rely on the
+ // latter. However, this behavior can be safely changed if needed.
+ await Assert.rejects(storeForSave.finalize(), /has already been finalized$/);
+ Assert.ok(!storeForSave.dataReady);
+
+ let storeForLoad = new JSONFile({ path });
+ await storeForLoad.load();
+ Assert.deepEqual(storeForLoad.data, TEST_DATA);
+});