summaryrefslogtreecommitdiffstats
path: root/dom/system/tests
diff options
context:
space:
mode:
Diffstat (limited to 'dom/system/tests')
-rw-r--r--dom/system/tests/.eslintrc.js5
-rw-r--r--dom/system/tests/chrome.ini6
-rw-r--r--dom/system/tests/file_bug1197901.html16
-rw-r--r--dom/system/tests/ioutils/chrome.ini16
-rw-r--r--dom/system/tests/ioutils/file_ioutils_test_fixtures.js78
-rw-r--r--dom/system/tests/ioutils/file_ioutils_worker.js102
-rw-r--r--dom/system/tests/ioutils/test_ioutils.html26
-rw-r--r--dom/system/tests/ioutils/test_ioutils_copy_move.html366
-rw-r--r--dom/system/tests/ioutils/test_ioutils_dir_iteration.html84
-rw-r--r--dom/system/tests/ioutils/test_ioutils_mkdir.html113
-rw-r--r--dom/system/tests/ioutils/test_ioutils_read_write.html432
-rw-r--r--dom/system/tests/ioutils/test_ioutils_read_write_json.html153
-rw-r--r--dom/system/tests/ioutils/test_ioutils_read_write_utf8.html389
-rw-r--r--dom/system/tests/ioutils/test_ioutils_remove.html97
-rw-r--r--dom/system/tests/ioutils/test_ioutils_set_permissions.html56
-rw-r--r--dom/system/tests/ioutils/test_ioutils_stat_touch.html204
-rw-r--r--dom/system/tests/ioutils/test_ioutils_worker.xhtml40
-rw-r--r--dom/system/tests/location_service.sjs39
-rw-r--r--dom/system/tests/location_services_parent.js20
-rw-r--r--dom/system/tests/mochitest.ini11
-rw-r--r--dom/system/tests/test_bug1197901.html96
-rw-r--r--dom/system/tests/test_constants.xhtml139
-rw-r--r--dom/system/tests/test_location_services_telemetry.html143
-rw-r--r--dom/system/tests/test_pathutils.html446
-rw-r--r--dom/system/tests/worker_constants.js94
25 files changed, 3171 insertions, 0 deletions
diff --git a/dom/system/tests/.eslintrc.js b/dom/system/tests/.eslintrc.js
new file mode 100644
index 0000000000..19d9df957a
--- /dev/null
+++ b/dom/system/tests/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/chrome-test", "plugin:mozilla/mochitest-test"],
+};
diff --git a/dom/system/tests/chrome.ini b/dom/system/tests/chrome.ini
new file mode 100644
index 0000000000..296cf0b133
--- /dev/null
+++ b/dom/system/tests/chrome.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+ worker_constants.js
+
+[test_constants.xhtml]
+[test_pathutils.html]
diff --git a/dom/system/tests/file_bug1197901.html b/dom/system/tests/file_bug1197901.html
new file mode 100644
index 0000000000..e4ca9fd380
--- /dev/null
+++ b/dom/system/tests/file_bug1197901.html
@@ -0,0 +1,16 @@
+<pre>Sensor events testing</pre>
+<script>
+
+window.onmessage = function(event) {
+ if (event.data.command == "addEventListener") {
+ window.addEventListener(
+ "devicemotion", function() {
+ event.source.postMessage({ result: event.data.expected,
+ message: event.data.message },
+ "*");
+ }
+ );
+ }
+};
+
+</script>
diff --git a/dom/system/tests/ioutils/chrome.ini b/dom/system/tests/ioutils/chrome.ini
new file mode 100644
index 0000000000..b3f109a554
--- /dev/null
+++ b/dom/system/tests/ioutils/chrome.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+support-files =
+ file_ioutils_test_fixtures.js
+ file_ioutils_worker.js
+
+[test_ioutils.html]
+[test_ioutils_copy_move.html]
+[test_ioutils_dir_iteration.html]
+[test_ioutils_mkdir.html]
+[test_ioutils_read_write.html]
+[test_ioutils_read_write_json.html]
+[test_ioutils_read_write_utf8.html]
+[test_ioutils_remove.html]
+[test_ioutils_stat_touch.html]
+[test_ioutils_worker.xhtml]
+[test_ioutils_set_permissions.html]
diff --git a/dom/system/tests/ioutils/file_ioutils_test_fixtures.js b/dom/system/tests/ioutils/file_ioutils_test_fixtures.js
new file mode 100644
index 0000000000..5d2e5011c9
--- /dev/null
+++ b/dom/system/tests/ioutils/file_ioutils_test_fixtures.js
@@ -0,0 +1,78 @@
+// Utility functions.
+
+Uint8Array.prototype.equals = function equals(other) {
+ if (this.byteLength !== other.byteLength) {
+ return false;
+ }
+ return this.every((val, i) => val === other[i]);
+};
+
+async function createFile(location, contents = "") {
+ if (typeof contents === "string") {
+ contents = new TextEncoder().encode(contents);
+ }
+ await IOUtils.write(location, contents);
+ const exists = await fileExists(location);
+ ok(exists, `Created temporary file at: ${location}`);
+}
+
+async function createDir(location) {
+ await IOUtils.makeDirectory(location, {
+ ignoreExisting: true,
+ createAncestors: true,
+ });
+ const exists = await dirExists(location);
+ ok(exists, `Created temporary directory at: ${location}`);
+}
+
+async function fileHasBinaryContents(location, expectedContents) {
+ if (!(expectedContents instanceof Uint8Array)) {
+ throw new TypeError("expectedContents must be a byte array");
+ }
+ info(`Opening ${location} for reading`);
+ const bytes = await IOUtils.read(location);
+ return bytes.equals(expectedContents);
+}
+
+async function fileHasTextContents(location, expectedContents) {
+ if (typeof expectedContents !== "string") {
+ throw new TypeError("expectedContents must be a string");
+ }
+ info(`Opening ${location} for reading`);
+ const bytes = await IOUtils.read(location);
+ const contents = new TextDecoder().decode(bytes);
+ return contents === expectedContents;
+}
+
+async function fileExists(file) {
+ try {
+ let { type } = await IOUtils.stat(file);
+ return type === "regular";
+ } catch (ex) {
+ return false;
+ }
+}
+
+async function dirExists(dir) {
+ try {
+ let { type } = await IOUtils.stat(dir);
+ return type === "directory";
+ } catch (ex) {
+ return false;
+ }
+}
+
+async function cleanup(...files) {
+ for (const file of files) {
+ await IOUtils.remove(file, {
+ ignoreAbsent: true,
+ recursive: true,
+ });
+ const exists = await IOUtils.exists(file);
+ ok(!exists, `Removed temporary file: ${file}`);
+ }
+}
+
+function sleep(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
diff --git a/dom/system/tests/ioutils/file_ioutils_worker.js b/dom/system/tests/ioutils/file_ioutils_worker.js
new file mode 100644
index 0000000000..68c0e81289
--- /dev/null
+++ b/dom/system/tests/ioutils/file_ioutils_worker.js
@@ -0,0 +1,102 @@
+// Any copyright is dedicated to the Public Domain.
+// - http://creativecommons.org/publicdomain/zero/1.0/
+
+/* eslint-env mozilla/chrome-worker, node */
+/* global finish, log */
+
+"use strict";
+
+importScripts("chrome://mochikit/content/tests/SimpleTest/WorkerSimpleTest.js");
+importScripts("resource://gre/modules/ObjectUtils.jsm");
+
+// TODO: Remove this import for OS.File. It is currently being used as a
+// stop gap for missing IOUtils functionality.
+importScripts("resource://gre/modules/osfile.jsm");
+importScripts("file_ioutils_test_fixtures.js");
+
+self.onmessage = async function(msg) {
+ const tmpDir = OS.Constants.Path.tmpDir;
+
+ // IOUtils functionality is the same when called from the main thread, or a
+ // web worker. These tests are a modified subset of the main thread tests, and
+ // serve as a confidence check that the implementation is thread-safe.
+ await test_api_is_available_on_worker();
+ await test_full_read_and_write();
+ await test_move_file();
+ await test_copy_file();
+ await test_make_directory();
+
+ finish();
+ info("test_ioutils_worker.xhtml: Test finished");
+
+ async function test_api_is_available_on_worker() {
+ ok(self.IOUtils, "IOUtils is present in web workers");
+ }
+
+ async function test_full_read_and_write() {
+ // Write a file.
+ const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_numbers.tmp");
+ const bytes = Uint8Array.of(...new Array(50).keys());
+ const bytesWritten = await IOUtils.write(tmpFileName, bytes);
+ is(bytesWritten, 50, "IOUtils::write can write entire byte array to file");
+
+ // Read it back.
+ let fileContents = await IOUtils.read(tmpFileName);
+ ok(
+ ObjectUtils.deepEqual(bytes, fileContents) &&
+ bytes.length == fileContents.length,
+ "IOUtils::read can read back entire file"
+ );
+
+ const tooManyBytes = bytes.length + 1;
+ fileContents = await IOUtils.read(tmpFileName, { maxBytes: tooManyBytes });
+ ok(
+ ObjectUtils.deepEqual(bytes, fileContents) &&
+ fileContents.length == bytes.length,
+ "IOUtils::read can read entire file when requested maxBytes is too large"
+ );
+
+ await cleanup(tmpFileName);
+ }
+
+ async function test_move_file() {
+ const src = OS.Path.join(tmpDir, "test_move_file_src.tmp");
+ const dest = OS.Path.join(tmpDir, "test_move_file_dest.tmp");
+ const bytes = Uint8Array.of(...new Array(50).keys());
+ await IOUtils.write(src, bytes);
+
+ await IOUtils.move(src, dest);
+ ok(
+ !(await fileExists(src)) && (await fileExists(dest)),
+ "IOUtils::move can move files from a worker"
+ );
+
+ await cleanup(dest);
+ }
+
+ async function test_copy_file() {
+ const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_orig.tmp");
+ const destFileName = OS.Path.join(tmpDir, "test_ioutils_copy.tmp");
+ await createFile(tmpFileName, "original");
+
+ await IOUtils.copy(tmpFileName, destFileName);
+ ok(
+ (await fileExists(tmpFileName)) &&
+ (await fileHasTextContents(destFileName, "original")),
+ "IOUtils::copy can copy source to dest in same directory"
+ );
+
+ await cleanup(tmpFileName, destFileName);
+ }
+
+ async function test_make_directory() {
+ const dir = OS.Path.join(tmpDir, "test_make_dir.tmp.d");
+ await IOUtils.makeDirectory(dir);
+ ok(
+ OS.File.stat(dir).isDir,
+ "IOUtils::makeDirectory can make a new directory from a worker"
+ );
+
+ await cleanup(dir);
+ }
+};
diff --git a/dom/system/tests/ioutils/test_ioutils.html b/dom/system/tests/ioutils/test_ioutils.html
new file mode 100644
index 0000000000..cf62c4c388
--- /dev/null
+++ b/dom/system/tests/ioutils/test_ioutils.html
@@ -0,0 +1,26 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Test the IOUtils file I/O API</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script>
+ "use strict";
+
+ add_task(async function test_api_is_available_on_window() {
+ ok(window.IOUtils, "IOUtils is present on the window");
+ });
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+
+</html>
diff --git a/dom/system/tests/ioutils/test_ioutils_copy_move.html b/dom/system/tests/ioutils/test_ioutils_copy_move.html
new file mode 100644
index 0000000000..239295bf7e
--- /dev/null
+++ b/dom/system/tests/ioutils/test_ioutils_copy_move.html
@@ -0,0 +1,366 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Test the IOUtils file I/O API</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script src="file_ioutils_test_fixtures.js"></script>
+ <script>
+ "use strict";
+
+ const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
+ const { ObjectUtils } = ChromeUtils.import("resource://gre/modules/ObjectUtils.jsm");
+
+ // TODO: Remove this import for OS.File. It is currently being used as a
+ // stop gap for missing IOUtils functionality.
+ const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+
+
+ const tmpDir = OS.Constants.Path.tmpDir;
+
+ add_task(async function test_move_relative_path() {
+ const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_move_relative_path.tmp");
+ const dest = "relative_to_cwd.tmp";
+ await createFile(tmpFileName, "source");
+
+ info("Test moving a file to a relative destination");
+ await Assert.rejects(
+ IOUtils.move(tmpFileName, dest),
+ /Could not parse path/,
+ "IOUtils::move only works with absolute paths"
+ );
+ ok(
+ await fileHasTextContents(tmpFileName, "source"),
+ "IOUtils::move doesn't change source file when move fails"
+ );
+
+ await cleanup(tmpFileName);
+ });
+
+ add_task(async function test_move_rename() {
+ // Set up.
+ const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_move_src.tmp");
+ const destFileName = OS.Path.join(tmpDir, "test_ioutils_move_dest.tmp");
+ await createFile(tmpFileName, "dest");
+ // Test.
+ info("Test move to new file in same directory");
+ await IOUtils.move(tmpFileName, destFileName);
+ info(`Moved ${tmpFileName} to ${destFileName}`);
+ ok(
+ !await fileExists(tmpFileName)
+ && await fileHasTextContents(destFileName, "dest"),
+ "IOUtils::move can move source to dest in same directory"
+ )
+
+ // Set up.
+ info("Test move to existing file with no overwrite");
+ await createFile(tmpFileName, "source");
+ // Test.
+ await Assert.rejects(
+ IOUtils.move(tmpFileName, destFileName, { noOverwrite: true }),
+ /Could not move source file\(.*\) to destination\(.*\) because the destination already exists and overwrites are not allowed/,
+ "IOUtils::move will refuse to move a file if overwrites are disabled"
+ );
+ ok(
+ await fileExists(tmpFileName)
+ && await fileHasTextContents(destFileName, "dest"),
+ "Failed IOUtils::move doesn't move the source file"
+ );
+
+ // Test.
+ info("Test move to existing file with overwrite");
+ await IOUtils.move(tmpFileName, destFileName, { noOverwrite: false });
+ ok(!await fileExists(tmpFileName), "IOUtils::move moved source");
+ ok(
+ await fileHasTextContents(destFileName, "source"),
+ "IOUtils::move overwrote the destination with the source"
+ );
+
+ // Clean up.
+ await cleanup(tmpFileName, destFileName);
+ });
+
+ add_task(async function test_move_to_dir() {
+ // Set up.
+ info("Test move and rename to non-existing directory");
+ const tmpFileName = OS.Path.join(tmpDir, "test_move_to_dir.tmp");
+ const destDir = OS.Path.join(tmpDir, "test_move_to_dir.tmp.d");
+ const dest = OS.Path.join(destDir, "dest.tmp");
+ await createFile(tmpFileName);
+ // Test.
+ ok(!await IOUtils.exists(destDir), "Expected path not to exist");
+ await IOUtils.move(tmpFileName, dest);
+ ok(
+ !await fileExists(tmpFileName) && await fileExists(dest),
+ "IOUtils::move creates non-existing parents if needed"
+ );
+
+ // Set up.
+ info("Test move and rename to existing directory.")
+ await createFile(tmpFileName);
+ // Test.
+ ok(await dirExists(destDir), `Expected ${destDir} to be a directory`);
+ await IOUtils.move(tmpFileName, dest);
+ ok(
+ !await fileExists(tmpFileName)
+ && await fileExists(dest),
+ "IOUtils::move can move/rename a file into an existing dir"
+ );
+
+ // Set up.
+ info("Test move to existing directory without specifying leaf name.")
+ await createFile(tmpFileName);
+ // Test.
+ await IOUtils.move(tmpFileName, destDir);
+ ok(await dirExists(destDir), `Expected ${destDir} to be a directory`);
+ ok(
+ !await fileExists(tmpFileName)
+ && await fileExists(OS.Path.join(destDir, OS.Path.basename(tmpFileName))),
+ "IOUtils::move can move a file into an existing dir"
+ );
+
+ // Clean up.
+ await cleanup(destDir);
+ });
+
+ add_task(async function test_move_dir() {
+ // Set up.
+ info("Test rename an empty directory");
+ const srcDir = OS.Path.join(tmpDir, "test_move_dir.tmp.d");
+ const destDir = OS.Path.join(tmpDir, "test_move_dir_dest.tmp.d");
+ await createDir(srcDir);
+ // Test.
+ await IOUtils.move(srcDir, destDir);
+ ok(
+ !await IOUtils.exists(srcDir) && await dirExists(destDir),
+ "IOUtils::move can rename directories"
+ );
+
+ // Set up.
+ info("Test move directory and its content into another directory");
+ await createDir(srcDir);
+ await createFile(OS.Path.join(srcDir, "file.tmp"), "foo");
+ // Test.
+ await IOUtils.move(srcDir, destDir);
+ const destFile = OS.Path.join(destDir, OS.Path.basename(srcDir), "file.tmp");
+ ok(
+ !await IOUtils.exists(srcDir)
+ && await dirExists(destDir)
+ && await dirExists(OS.Path.join(destDir, OS.Path.basename(srcDir)))
+ && await fileHasTextContents(destFile, "foo"),
+ "IOUtils::move can move a directory and its contents into another one"
+ )
+
+ // Clean up.
+ await cleanup(srcDir, destDir);
+ });
+
+ add_task(async function test_move_failures() {
+ // Set up.
+ info("Test attempt to rename a non-existent source file");
+ const notExistsSrc = OS.Path.join(tmpDir, "not_exists_src.tmp");
+ const notExistsDest = OS.Path.join(tmpDir, "not_exists_dest.tmp");
+ // Test.
+ await Assert.rejects(
+ IOUtils.move(notExistsSrc, notExistsDest),
+ /Could not move source file\(.*\) because it does not exist/,
+ "IOUtils::move throws if source file does not exist"
+ );
+ ok(
+ !await fileExists(notExistsSrc) && !await fileExists(notExistsDest),
+ "IOUtils::move fails if source file does not exist"
+ );
+
+ // Set up.
+ info("Test attempt to move a directory to a file");
+ const destFile = OS.Path.join(tmpDir, "test_move_failures_file_dest.tmp");
+ const srcDir = OS.Path.join(tmpDir, "test_move_failure_src.tmp.d");
+ await createFile(destFile);
+ await createDir(srcDir);
+ // Test.
+ await Assert.rejects(
+ IOUtils.move(srcDir, destFile),
+ /Could not move the source directory\(.*\) to the destination\(.*\) because the destination is not a directory/,
+ "IOUtils::move throws if try to move dir into an existing file"
+ );
+
+ // Clean up.
+ await cleanup(destFile, srcDir);
+ });
+
+ add_task(async function test_copy() {
+ // Set up.
+ const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_orig.tmp");
+ const destFileName = OS.Path.join(tmpDir, "test_ioutils_copy.tmp");
+ await createFile(tmpFileName, "original");
+ // Test.
+ info("Test copy to new file in same directory");
+ await IOUtils.copy(tmpFileName, destFileName);
+ ok(
+ await fileExists(tmpFileName)
+ && await fileHasTextContents(destFileName, "original"),
+ "IOUtils::copy can copy source to dest in same directory"
+ );
+
+ // Set up.
+ info("Test copy to existing file with no overwrite");
+ await createFile(tmpFileName, "new contents");
+ // Test.
+ await Assert.rejects(
+ IOUtils.copy(tmpFileName, destFileName, { noOverwrite: true }),
+ /Could not copy source file\(.*\) to destination\(.*\) because the destination already exists and overwrites are not allowed/,
+ "IOUtils::copy will refuse to copy to existing destination if overwrites are disabled"
+ );
+ ok(
+ await fileExists(tmpFileName)
+ && await fileHasTextContents(destFileName, "original"),
+ "Failed IOUtils::move doesn't move the source file"
+ );
+
+ // Test.
+ info("Test copy to existing file with overwrite");
+ await IOUtils.copy(tmpFileName, destFileName, { noOverwrite: false });
+ ok(await fileExists(tmpFileName), "IOUtils::copy retains source");
+ ok(
+ await fileHasTextContents(destFileName, "new contents"),
+ "IOUtils::copy overwrote the destination with the source"
+ );
+
+ // Clean up.
+ await cleanup(tmpFileName, destFileName);
+ });
+
+ add_task(async function test_copy_file_to_dir() {
+ // Set up.
+ info("Test copy file to non-existing directory");
+ const tmpFileName = OS.Path.join(tmpDir, "test_copy_file_to_dir.tmp");
+ const destDir = OS.Path.join(tmpDir, "test_copy_file_to_dir.tmp.d");
+ const dest = OS.Path.join(destDir, "dest.tmp");
+ await createFile(tmpFileName);
+ // Test.
+ ok(!await IOUtils.exists(destDir), "Expected path not to exist");
+ await IOUtils.copy(tmpFileName, dest);
+ ok(
+ await fileExists(tmpFileName) && await fileExists(dest),
+ "IOUtils::copy creates non-existing parents if needed"
+ );
+
+ // Set up.
+ info("Test copy file to existing directory")
+ await createFile(tmpFileName);
+ // Test.
+ ok(await dirExists(destDir), `Expected ${destDir} to be a directory`);
+ await IOUtils.copy(tmpFileName, dest);
+ ok(
+ await fileExists(tmpFileName)
+ && await fileExists(dest),
+ "IOUtils::copy can copy a file into an existing dir"
+ );
+
+ // Set up.
+ info("Test copy file to existing directory without specifying leaf name")
+ await createFile(tmpFileName);
+ // Test.
+ await IOUtils.copy(tmpFileName, destDir);
+ ok(await dirExists(destDir), `Expected ${destDir} to be a directory`);
+ ok(
+ await fileExists(tmpFileName)
+ && await fileExists(OS.Path.join(destDir, OS.Path.basename(tmpFileName))),
+ "IOUtils::copy can copy a file into an existing dir"
+ );
+
+ // Clean up.
+ await cleanup(tmpFileName, destDir);
+ });
+
+ add_task(async function test_copy_dir_recursive() {
+ // Set up.
+ info("Test rename an empty directory");
+ const srcDir = OS.Path.join(tmpDir, "test_copy_dir.tmp.d");
+ const destDir = OS.Path.join(tmpDir, "test_copy_dir_dest.tmp.d");
+ await createDir(srcDir);
+ // Test.
+ await IOUtils.copy(srcDir, destDir, { recursive: true });
+ ok(
+ await dirExists(srcDir) && await dirExists(destDir),
+ "IOUtils::copy can recursively copy entire directories"
+ );
+
+ // Set up.
+ info("Test copy directory and its content into another directory");
+ await createDir(srcDir);
+ await createFile(OS.Path.join(srcDir, "file.tmp"), "foo");
+ // Test.
+ await IOUtils.copy(srcDir, destDir, { recursive: true });
+ const destFile = OS.Path.join(destDir, OS.Path.basename(srcDir), "file.tmp");
+ ok(
+ await dirExists(srcDir)
+ && await dirExists(destDir)
+ && await dirExists(OS.Path.join(destDir, OS.Path.basename(srcDir)))
+ && await fileHasTextContents(destFile, "foo"),
+ "IOUtils::copy can move a directory and its contents into another one"
+ )
+
+ // Clean up.
+ await cleanup(srcDir, destDir);
+ });
+
+ add_task(async function test_copy_failures() {
+ // Set up.
+ info("Test attempt to copy a non-existent source file");
+ const notExistsSrc = OS.Path.join(tmpDir, "test_copy_not_exists_src.tmp");
+ const notExistsDest = OS.Path.join(tmpDir, "test_copy_not_exists_dest.tmp");
+ // Test.
+ await Assert.rejects(
+ IOUtils.copy(notExistsSrc, notExistsDest),
+ /Could not copy source file\(.*\) because it does not exist/,
+ "IOUtils::copy throws if source file does not exist"
+ );
+ ok(
+ !await fileExists(notExistsSrc) && !await fileExists(notExistsDest),
+ "IOUtils::copy failure due to missing source file does not affect destination"
+ );
+
+ // Set up.
+ info("Test attempt to copy a directory to a file");
+ const destFile = OS.Path.join(tmpDir, "test_copy_failures_file_dest.tmp");
+ const srcDir = OS.Path.join(tmpDir, "test_copy_failure_src.tmp.d");
+ await createFile(destFile);
+ await createDir(srcDir);
+ // Test.
+ await Assert.rejects(
+ IOUtils.copy(srcDir, destFile, { recursive: true }),
+ /Could not copy the source directory\(.*\) to the destination\(.*\) because the destination is not a directory/,
+ "IOUtils::copy throws if try to move dir into an existing file"
+ );
+ ok(await fileHasTextContents(destFile, ""), "IOUtils::copy failure does not affect destination");
+
+ // Set up.
+ info("Test copy directory without recursive option");
+ await createDir(srcDir);
+ // Test.
+ await Assert.rejects(
+ IOUtils.copy(srcDir, notExistsDest, { recursive: false }),
+ /Refused to copy source directory\(.*\) to the destination\(.*\)/,
+ "IOUtils::copy throws if try to copy a directory with { recursive: false }"
+ );
+ console.log(`${notExistsDest} exists?`, await IOUtils.exists(notExistsDest))
+ ok(!await IOUtils.exists(notExistsDest), "IOUtils::copy failure does not affect destination");
+
+ // Clean up.
+ await cleanup(destFile, srcDir);
+ });
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+
+</html>
diff --git a/dom/system/tests/ioutils/test_ioutils_dir_iteration.html b/dom/system/tests/ioutils/test_ioutils_dir_iteration.html
new file mode 100644
index 0000000000..2f1181fa23
--- /dev/null
+++ b/dom/system/tests/ioutils/test_ioutils_dir_iteration.html
@@ -0,0 +1,84 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Test the IOUtils file I/O API</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script src="file_ioutils_test_fixtures.js"></script>
+ <script>
+ "use strict";
+
+ const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
+ const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+
+ const tmpDir = OS.Constants.Path.tmpDir;
+
+ add_task(async function iterate_dir_failure() {
+ let notExists = OS.Path.join(tmpDir, 'does_not_exist_dir.tmp.d');
+
+ await Assert.rejects(
+ IOUtils.getChildren(notExists),
+ /Could not get children of file\(.*\) because it does not exist/,
+ "IOUtils::getChildren rejects if the file does not exist"
+ );
+ ok(!await fileExists(notExists), `Expected ${notExists} not to exist`);
+
+ info('Try to get the children of a regular file');
+
+ let tmpFileName = OS.Path.join(tmpDir, 'iterator_file.tmp');
+ await createFile(tmpFileName)
+ await Assert.rejects(IOUtils.getChildren(tmpFileName),
+ /Could not get children of file\(.*\) because it is not a directory/,
+ "IOUtils::getChildren rejects if the file is not a dir"
+ );
+
+ await cleanup(tmpFileName);
+ });
+
+ add_task(async function iterate_dir() {
+ info('Try to get the children of a multi-level directory hierarchy');
+
+ let root = OS.Path.join(tmpDir, 'iterator.tmp.d');
+ let child1 = OS.Path.join(root, 'child1.tmp');
+ let child2 = OS.Path.join(root, 'child2.tmp');
+ let grandchild = OS.Path.join(child1, 'grandchild.tmp');
+
+ await createDir(grandchild); // Ancestors will be created.
+ await createDir(child2);
+
+ let entries = await IOUtils.getChildren(root);
+
+ is(entries.length, 2, `Expected 2 entries below the path at ${root}`);
+ ok(!entries.includes(grandchild), "IOUtils::getChildren does not enter subdirectories");
+
+ await cleanup(root);
+ });
+
+ add_task(async function iterate_empty_dir() {
+ info('Try to get the children of an empty directory');
+
+ let emptyDir = OS.Path.join(tmpDir, 'iterator_empty_dir.tmp.d');
+ await createDir(emptyDir);
+
+ is(
+ (await IOUtils.getChildren(emptyDir)).length,
+ 0,
+ "IOUtils::getChildren return an empty array when called on an empty dir"
+ );
+
+ await cleanup(emptyDir);
+ });
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+
+</html>
diff --git a/dom/system/tests/ioutils/test_ioutils_mkdir.html b/dom/system/tests/ioutils/test_ioutils_mkdir.html
new file mode 100644
index 0000000000..2439443e87
--- /dev/null
+++ b/dom/system/tests/ioutils/test_ioutils_mkdir.html
@@ -0,0 +1,113 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Test the IOUtils file I/O API</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script src="file_ioutils_test_fixtures.js"></script>
+ <script>
+ "use strict";
+
+ const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
+ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+
+ add_task(async function test_make_directory() {
+ info("Test creating a new directory");
+ const tmpDir = await PathUtils.getTempDir();
+ const newDirectoryName = PathUtils.join(tmpDir, "test_ioutils_new_dir.tmp.d");
+ await IOUtils.makeDirectory(newDirectoryName);
+ ok(
+ await IOUtils.exists(newDirectoryName),
+ "IOUtils::makeDirectory can create a new directory"
+ );
+
+ info("Test creating an existing directory");
+ await IOUtils.makeDirectory(newDirectoryName, { ignoreExisting: true });
+ ok(
+ await IOUtils.exists(newDirectoryName),
+ "IOUtils::makeDirectory can ignore existing directories"
+ );
+ await Assert.rejects(
+ IOUtils.makeDirectory(newDirectoryName, { ignoreExisting: false }),
+ /Could not create directory because it already exists at .*/,
+ "IOUtils::makeDirectory can throw if the target dir exists"
+ )
+
+ info("Test creating a nested directory");
+ const parentDirName = PathUtils.join(tmpDir, "test_ioutils_mkdir_parent.tmp.d");
+ const nestedDirName = PathUtils.join(
+ parentDirName,
+ "test_ioutils_mkdir_child.tmp.d"
+ );
+ await Assert.rejects(
+ IOUtils.makeDirectory(nestedDirName, { createAncestors: false }),
+ /Could not create directory at .* because the path has missing ancestor components/,
+ "IOUtils::makeDirectory can fail if the target is missing parents"
+ );
+ ok(!await IOUtils.exists(nestedDirName), `Expected ${nestedDirName} not to exist`);
+ await IOUtils.makeDirectory(nestedDirName, { createAncestors: true });
+ ok(
+ await IOUtils.exists(nestedDirName),
+ "IOUtils::makeDirectory can create ancestors of the target directory"
+ );
+
+ await cleanup(newDirectoryName, parentDirName);
+ });
+
+ add_task(async function test_make_directory_failure() {
+ info("Try to create a directory where a file already exists");
+ const tmpDir = await PathUtils.getTempDir();
+ const notADirFileName = PathUtils.join(tmpDir, "test_ioutils_not_a_dir.tmp");
+ await createFile(notADirFileName);
+
+ await Assert.rejects(
+ IOUtils.makeDirectory(notADirFileName, { ignoreExisting: false }),
+ /Could not create directory because the target file\(.*\) exists and is not a directory/,
+ "IOUtils::makeDirectory [ignoreExisting: false] throws when the target is an existing file"
+ );
+ ok(await fileExists(notADirFileName), `Expected ${notADirFileName} to exist`);
+
+ await Assert.rejects(
+ IOUtils.makeDirectory(notADirFileName, { ignoreExisting: true }),
+ /Could not create directory because the target file\(.*\) exists and is not a directory/,
+ "IOUtils::makeDirectory [ignoreExisting: true] throws when the target is an existing file"
+ );
+ ok(await fileExists(notADirFileName), `Expected ${notADirFileName} to exist`);
+
+ await cleanup(notADirFileName);
+ });
+
+ add_task(async function test_make_directory_permissions() {
+ if (Services.appinfo.OS === "WINNT") {
+ ok(true, "Skipping test on unsupported platform (Windows)");
+ return;
+ }
+
+ const tmpDir = await PathUtils.getTempDir();
+ const newDir = PathUtils.join(tmpDir, "test_ioutils_mkdir_perms.tmp.d");
+
+ ok(!await IOUtils.exists(newDir), "Directory does not exist before creation");
+ await IOUtils.makeDirectory(newDir, { permissions: 0o751 });
+ ok(await IOUtils.exists(newDir), "Directory created");
+
+ const stat = await IOUtils.stat(newDir);
+ is(stat.type, "directory", "Directory stat() as directory");
+ is(stat.permissions, 0o751, "Directory created with expected permissions");
+
+ await cleanup(newDir);
+ });
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+
+</html>
diff --git a/dom/system/tests/ioutils/test_ioutils_read_write.html b/dom/system/tests/ioutils/test_ioutils_read_write.html
new file mode 100644
index 0000000000..f888f03449
--- /dev/null
+++ b/dom/system/tests/ioutils/test_ioutils_read_write.html
@@ -0,0 +1,432 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Test the IOUtils file I/O API</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script src="file_ioutils_test_fixtures.js"></script>
+ <script>
+ "use strict";
+
+ const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
+ const { ObjectUtils } = ChromeUtils.import("resource://gre/modules/ObjectUtils.jsm");
+
+ // This is presently only used to test compatability between OS.File and
+ // IOUtils when it comes to writing compressed files. The import and the
+ // test `test_lz4_osfile_compat` can be removed with OS.File is removed.
+ const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+
+ add_task(async function test_read_failure() {
+ const tmpDir = await PathUtils.getTempDir();
+ const doesNotExist = PathUtils.join(tmpDir, "does_not_exist.tmp");
+ await Assert.rejects(
+ IOUtils.read(doesNotExist),
+ /Could not open the file at .*/,
+ "IOUtils::read rejects when file does not exist"
+ );
+ });
+
+ add_task(async function test_write_no_overwrite() {
+ const tmpDir = await PathUtils.getTempDir();
+
+ // Make a new file, and try to write to it with overwrites disabled.
+ const tmpFileName = PathUtils.join(tmpDir, "test_ioutils_overwrite.tmp");
+ const untouchableContents = new TextEncoder().encode("Can't touch this!\n");
+
+ let exists = await IOUtils.exists(tmpFileName);
+ ok(!exists, `File ${tmpFileName} should not exist before writing`);
+
+ await IOUtils.write(tmpFileName, untouchableContents);
+
+ exists = await IOUtils.exists(tmpFileName);
+ ok(exists, `File ${tmpFileName} should exist after writing`);
+
+ const newContents = new TextEncoder().encode("Nah nah nah!\n");
+ await Assert.rejects(
+ IOUtils.write(tmpFileName, newContents, {
+ noOverwrite: true,
+ }),
+ /Refusing to overwrite the file at */,
+ "IOUtils::write rejects writing to existing file if overwrites are disabled"
+ );
+ ok(
+ await fileHasBinaryContents(tmpFileName, untouchableContents),
+ "IOUtils::write doesn't change target file when overwrite is refused"
+ );
+
+ const bytesWritten = await IOUtils.write(
+ tmpFileName,
+ newContents,
+ { noOverwrite: false /* Default. */ }
+ );
+ is(
+ bytesWritten,
+ newContents.length,
+ "IOUtils::write can overwrite files if specified"
+ );
+
+ await cleanup(tmpFileName);
+ });
+
+ add_task(async function test_write_with_backup() {
+ info("Test backup file option with non-existing file");
+
+ const tmpDir = await PathUtils.getTempDir();
+
+ let fileContents = new TextEncoder().encode("Original file contents");
+ let destFileName = PathUtils.join(tmpDir, "test_write_with_backup_option.tmp");
+ let backupFileName = destFileName + ".backup";
+ let bytesWritten =
+ await IOUtils.write(destFileName, fileContents, {
+ backupFile: backupFileName,
+ });
+ ok(
+ await fileHasTextContents(destFileName, "Original file contents"),
+ "IOUtils::write creates a new file with the correct contents"
+ );
+ ok(
+ !await fileExists(backupFileName),
+ "IOUtils::write does not create a backup if the target file does not exist"
+ );
+ is(
+ bytesWritten,
+ fileContents.length,
+ "IOUtils::write correctly writes to a new file without performing a backup"
+ );
+
+ info("Test backup file option with existing destination");
+ let newFileContents = new TextEncoder().encode("New file contents");
+ ok(await fileExists(destFileName), `Expected ${destFileName} to exist`);
+ bytesWritten =
+ await IOUtils.write(destFileName, newFileContents, {
+ backupFile: backupFileName,
+ });
+ ok(
+ await fileHasTextContents(backupFileName, "Original file contents"),
+ "IOUtils::write can backup an existing file before writing"
+ );
+ ok(
+ await fileHasTextContents(destFileName, "New file contents"),
+ "IOUtils::write can create the target with the correct contents"
+ );
+ is(
+ bytesWritten,
+ newFileContents.length,
+ "IOUtils::write correctly writes to the target after taking a backup"
+ );
+
+ await cleanup(destFileName, backupFileName);
+ });
+
+ add_task(async function test_write_with_backup_and_tmp() {
+ info("Test backup with tmp and backup file options, non-existing destination");
+
+ const tmpDir = await PathUtils.getTempDir();
+
+ let fileContents = new TextEncoder().encode("Original file contents");
+ let destFileName = PathUtils.join(tmpDir, "test_write_with_backup_and_tmp_options.tmp");
+ let backupFileName = destFileName + ".backup";
+ let tmpFileName = PathUtils.join(tmpDir, "temp_file.tmp");
+ let bytesWritten =
+ await IOUtils.write(destFileName, fileContents, {
+ backupFile: backupFileName,
+ tmpPath: tmpFileName,
+ });
+ ok(!await fileExists(tmpFileName), "IOUtils::write cleans up the tmpFile");
+ ok(
+ !await fileExists(backupFileName),
+ "IOUtils::write does not create a backup if the target file does not exist"
+ );
+ ok(
+ await fileHasTextContents(destFileName, "Original file contents"),
+ "IOUtils::write can write to the destination when a temporary file is used"
+ );
+ is(
+ bytesWritten,
+ fileContents.length,
+ "IOUtils::write can copy tmp file to destination without performing a backup"
+ );
+
+ info("Test backup with tmp and backup file options, existing destination");
+ let newFileContents = new TextEncoder().encode("New file contents");
+ bytesWritten =
+ await IOUtils.write(destFileName, newFileContents, {
+ backupFile: backupFileName,
+ tmpPath: tmpFileName,
+ });
+
+ ok(!await fileExists(tmpFileName), "IOUtils::write cleans up the tmpFile");
+ ok(
+ await fileHasTextContents(backupFileName, "Original file contents"),
+ "IOUtils::write can create a backup if the target file exists"
+ );
+ ok(
+ await fileHasTextContents(destFileName, "New file contents"),
+ "IOUtils::write can write to the destination when a temporary file is used"
+ );
+ is(
+ bytesWritten,
+ newFileContents.length,
+ "IOUtils::write IOUtils::write can move tmp file to destination after performing a backup"
+ );
+
+ await cleanup(destFileName, backupFileName);
+ });
+
+ add_task(async function test_partial_read() {
+ const tmpDir = await PathUtils.getTempDir();
+
+ const tmpFileName = PathUtils.join(tmpDir, "test_ioutils_partial_read.tmp");
+ const bytes = Uint8Array.of(...new Array(50).keys());
+ const bytesWritten = await IOUtils.write(tmpFileName, bytes);
+ is(
+ bytesWritten,
+ 50,
+ "IOUtils::write can write entire byte array to file"
+ );
+
+ // Read just the first 10 bytes.
+ const first10 = bytes.slice(0, 10);
+ const bytes10 = await IOUtils.read(tmpFileName, { maxBytes: 10 });
+ ok(
+ ObjectUtils.deepEqual(bytes10, first10),
+ "IOUtils::read can read part of a file, up to specified max bytes"
+ );
+
+ // Trying to explicitly read nothing isn't useful, but it should still
+ // succeed.
+ const bytes0 = await IOUtils.read(tmpFileName, { maxBytes: 0 });
+ is(bytes0.length, 0, "IOUtils::read can read 0 bytes");
+
+ await cleanup(tmpFileName);
+ });
+
+ add_task(async function test_empty_read_and_write() {
+ // Trying to write an empty file isn't very useful, but it should still
+ // succeed.
+
+ const tmpDir = await PathUtils.getTempDir();
+
+ const tmpFileName = PathUtils.join(tmpDir, "test_ioutils_empty.tmp");
+ const emptyByteArray = new Uint8Array(0);
+ const bytesWritten = await IOUtils.write(
+ tmpFileName,
+ emptyByteArray
+ );
+ is(bytesWritten, 0, "IOUtils::write can create an empty file");
+
+ // Trying to explicitly read nothing isn't useful, but it should still
+ // succeed.
+ const bytes0 = await IOUtils.read(tmpFileName, { maxBytes: 0 });
+ is(bytes0.length, 0, "IOUtils::read can read 0 bytes");
+
+ // Implicitly try to read nothing.
+ const nothing = await IOUtils.read(tmpFileName);
+ is(nothing.length, 0, "IOUtils:: read can read empty files");
+
+ await cleanup(tmpFileName);
+ });
+
+ add_task(async function test_full_read_and_write() {
+ // Write a file.
+
+ const tmpDir = await PathUtils.getTempDir();
+
+ info("Test writing to a new binary file");
+ const tmpFileName = PathUtils.join(tmpDir, "test_ioutils_numbers.tmp");
+ const bytes = Uint8Array.of(...new Array(50).keys());
+ const bytesWritten = await IOUtils.write(tmpFileName, bytes);
+ is(
+ bytesWritten,
+ 50,
+ "IOUtils::write can write entire byte array to file"
+ );
+
+ // Read it back.
+ info("Test reading a binary file");
+ let fileContents = await IOUtils.read(tmpFileName);
+ ok(
+ ObjectUtils.deepEqual(bytes, fileContents) &&
+ bytes.length == fileContents.length,
+ "IOUtils::read can read back entire file"
+ );
+
+ const tooManyBytes = bytes.length + 1;
+ fileContents = await IOUtils.read(tmpFileName, { maxBytes: tooManyBytes });
+ ok(
+ ObjectUtils.deepEqual(bytes, fileContents) &&
+ fileContents.length == bytes.length,
+ "IOUtils::read can read entire file when requested maxBytes is too large"
+ );
+
+ // Clean up.
+ await cleanup(tmpFileName);
+ });
+
+ add_task(async function test_write_relative_path() {
+ const tmpFileName = "test_ioutils_write_relative_path.tmp";
+ const bytes = Uint8Array.of(...new Array(50).keys());
+
+ info("Test writing a file at a relative destination");
+ await Assert.rejects(
+ IOUtils.write(tmpFileName, bytes),
+ /Could not parse path/,
+ "IOUtils::write only works with absolute paths"
+ );
+ });
+
+ add_task(async function test_read_relative_path() {
+ const tmpFileName = "test_ioutils_read_relative_path.tmp";
+
+ info("Test reading a file at a relative destination");
+ await Assert.rejects(
+ IOUtils.read(tmpFileName),
+ /Could not parse path/,
+ "IOUtils::write only works with absolute paths"
+ );
+ });
+
+ add_task(async function test_lz4() {
+ const tmpDir = await PathUtils.getTempDir();
+ const tmpFileName = PathUtils.join(tmpDir, "test_ioutils_lz4.tmp");
+
+ info("Test writing lz4 encoded data");
+ const varyingBytes = Uint8Array.of(...new Array(50).keys());
+ let bytesWritten = await IOUtils.write(tmpFileName, varyingBytes, { compress: true });
+ is(bytesWritten, 64, "Expected to write 64 bytes");
+
+ info("Test reading lz4 encoded data");
+ let readData = await IOUtils.read(tmpFileName, { decompress: true });
+ ok(readData.equals(varyingBytes), "IOUtils can write and read back LZ4 encoded data");
+
+ info("Test writing lz4 compressed data");
+ const repeatedBytes = Uint8Array.of(...new Array(50).fill(1));
+ bytesWritten = await IOUtils.write(tmpFileName, repeatedBytes, { compress: true });
+ is(bytesWritten, 23, "Expected 50 bytes to compress to 23 bytes");
+
+ info("Test reading lz4 encoded data");
+ readData = await IOUtils.read(tmpFileName, { decompress: true });
+ ok(readData.equals(repeatedBytes), "IOUtils can write and read back LZ4 compressed data");
+
+ info("Test writing empty lz4 compressed data")
+ const empty = new Uint8Array();
+ bytesWritten = await IOUtils.write(tmpFileName, empty, { compress: true });
+ is(bytesWritten, 12, "Expected to write just the LZ4 header, with a content length of 0");
+
+
+ info("Test reading empty lz4 compressed data")
+ const readEmpty = await IOUtils.read(tmpFileName, { decompress: true });
+ ok(readEmpty.equals(empty), "IOUtils can write and read back empty buffers with LZ4");
+ const readEmptyRaw = await IOUtils.read(tmpFileName, { decompress: false });
+ is(readEmptyRaw.length, 12, "Expected to read back just the LZ4 header");
+ const expectedHeader = Uint8Array.of(109, 111, 122, 76, 122, 52, 48, 0, 0, 0, 0, 0); // "mozLz40\0\0\0\0"
+ ok(readEmptyRaw.equals(expectedHeader), "Expected to read header with content length of 0");
+
+ await cleanup(tmpFileName);
+ });
+
+ add_task(async function test_lz4_osfile_compat() {
+ const tmpDir = await PathUtils.getTempDir();
+ const osfileTmpFile = PathUtils.join(tmpDir, "test_ioutils_lz4_compat_osfile.tmp");
+ const ioutilsTmpFile = PathUtils.join(tmpDir, "test_ioutils_lz4_compat_ioutils.tmp");
+
+ info("Test OS.File and IOUtils write the same file with LZ4 compression enabled")
+ const repeatedBytes = Uint8Array.of(...new Array(50).fill(1));
+ let expectedBytes = 23;
+ let ioutilsBytes = await IOUtils.write(ioutilsTmpFile, repeatedBytes, { compress: true });
+ let osfileBytes = await OS.File.writeAtomic(osfileTmpFile, repeatedBytes, { compression: "lz4" });
+ is(ioutilsBytes, expectedBytes, "IOUtils writes the expected number of bytes for compression");
+ is(osfileBytes, ioutilsBytes, "OS.File and IOUtils write the same number of bytes for LZ4 compression");
+
+ info("Test OS.File can read a file compressed by IOUtils");
+ const osfileReadBytes = await OS.File.read(ioutilsTmpFile, { compression: "lz4" });
+ ok(osfileReadBytes.every(byte => byte === 1), "OS.File can read a file compressed by IOUtils");
+ is(osfileReadBytes.length, 50, "OS.File reads the right number of bytes from a file compressed by IOUtils")
+
+ info("Test IOUtils can read a file compressed by OS.File");
+ const ioutilsReadBytes = await IOUtils.read(osfileTmpFile, { decompress: true });
+ ok(ioutilsReadBytes.every(byte => byte === 1), "IOUtils can read a file compressed by OS.File");
+ is(ioutilsReadBytes.length, 50, "IOUtils reads the right number of bytes from a file compressed by OS.File")
+
+ await cleanup(osfileTmpFile, ioutilsTmpFile);
+ });
+
+ add_task(async function test_lz4_bad_call() {
+ const tmpDir = await PathUtils.getTempDir();
+ const tmpFileName = PathUtils.join(tmpDir, "test_ioutils_lz4_bad_call.tmp");
+
+ info("Test decompression with invalid options");
+ const varyingBytes = Uint8Array.of(...new Array(50).keys());
+ let bytesWritten = await IOUtils.write(tmpFileName, varyingBytes, { compress: true });
+ is(bytesWritten, 64, "Expected to write 64 bytes");
+ await Assert.rejects(
+ IOUtils.read(tmpFileName, { maxBytes: 4, decompress: true }),
+ /The `maxBytes` and `decompress` options are not compatible/,
+ "IOUtils::read rejects when maxBytes and decompress options are both used"
+ );
+
+ await cleanup(tmpFileName)
+ });
+
+ add_task(async function test_lz4_failure() {
+ const tmpDir = await PathUtils.getTempDir();
+ const tmpFileName = PathUtils.join(tmpDir, "test_ioutils_lz4_fail.tmp");
+
+ info("Test decompression of non-lz4 data");
+ const repeatedBytes = Uint8Array.of(...new Array(50).fill(1));
+ await IOUtils.write(tmpFileName, repeatedBytes, { compress: false });
+
+ await Assert.rejects(
+ IOUtils.read(tmpFileName, { decompress: true }),
+ (actual) => {
+ is(actual.constructor, DOMException,
+ "rejection reason constructor for decompress with bad header");
+ is(actual.name, "NotReadableError",
+ "rejection error name for decompress with bad header");
+ ok(/Could not decompress file because it has an invalid LZ4 header \(wrong magic number: .*\)/
+ .test(actual.message),
+ "rejection error message for decompress with bad header. Got "
+ + actual.message);
+ return true;
+ },
+ "IOUtils::read fails to decompress LZ4 data with a bad header"
+ );
+
+ info("Test decompression of short byte buffer");
+ const elevenBytes = Uint8Array.of(...new Array(11).fill(1));
+ await IOUtils.write(tmpFileName, elevenBytes, { compress: false });
+
+ await Assert.rejects(
+ IOUtils.read(tmpFileName, { decompress: true }),
+ /Could not decompress file because the buffer is too short/,
+ "IOUtils::read fails to decompress LZ4 data with missing header"
+ );
+
+ info("Test decompression of valid header, but corrupt contents");
+ const headerFor10bytes = [109, 111, 122, 76, 122, 52, 48, 0, 10, 0, 0, 0] // "mozlz40\0" + 4 byte length
+ const badContents = new Array(11).fill(255); // Bad leading byte, followed by uncompressed stream.
+ const goodHeaderBadContents = Uint8Array.of(...headerFor10bytes, ...badContents);
+ await IOUtils.write(tmpFileName, goodHeaderBadContents, { compress: false });
+
+ await Assert.rejects(
+ IOUtils.read(tmpFileName, { decompress: true }),
+ /Could not decompress file contents, the file may be corrupt/,
+ "IOUtils::read fails to read corrupt LZ4 contents with a correct header"
+ );
+
+ await cleanup(tmpFileName);
+ });
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+
+</html>
diff --git a/dom/system/tests/ioutils/test_ioutils_read_write_json.html b/dom/system/tests/ioutils/test_ioutils_read_write_json.html
new file mode 100644
index 0000000000..01d2771888
--- /dev/null
+++ b/dom/system/tests/ioutils/test_ioutils_read_write_json.html
@@ -0,0 +1,153 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Test the IOUtils file I/O API</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script src="file_ioutils_test_fixtures.js"></script>
+ <script>
+ "use strict";
+
+ const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
+ const { ObjectUtils } = ChromeUtils.import(
+ "resource://gre/modules/ObjectUtils.jsm"
+ );
+
+ const OBJECT = {
+ "foo": [
+ "bar",
+ 123,
+ 456.789,
+ true,
+ false,
+ null,
+ ],
+ "bar": {
+ "baz": {},
+ },
+ };
+
+ const ARRAY = [1, 2.3, true, false, null, { "foo": "bar" }];
+
+ const PRIMITIVES = [123, true, false, "hello, world", null];
+
+ add_task(async function read_json() {
+ const tmpDir = await PathUtils.getTempDir();
+ const filename = PathUtils.join(tmpDir, "test_ioutils_read_json.tmp");
+
+ info("Testing IOUtils.readJSON() with a serialized object...");
+ await IOUtils.writeUTF8(filename, JSON.stringify(OBJECT));
+ const readObject = await IOUtils.readJSON(filename);
+ const parsedObject = JSON.parse(await IOUtils.readUTF8(filename));
+ ok(ObjectUtils.deepEqual(OBJECT, readObject), "JSON objects should round-trip");
+ ok(
+ ObjectUtils.deepEqual(parsedObject, readObject),
+ "IOUtils.readJSON() equivalent to JSON.parse() for objects"
+ );
+
+ info("Testing IOUtils.readJSON() with a serialized array...");
+ await IOUtils.writeUTF8(filename, JSON.stringify(ARRAY));
+ const readArray = await IOUtils.readJSON(filename);
+ const parsedArray = JSON.parse(await IOUtils.readUTF8(filename));
+ ok(ObjectUtils.deepEqual(ARRAY, readArray), "JSON arrays should round-trip");
+ ok(
+ ObjectUtils.deepEqual(parsedArray, readArray),
+ "IOUtils.readJSON() equivalent to JSON.parse(IOUtils.readUTF8()) for arrays"
+ );
+
+ info("Testing IOUtils.readJSON() with serialized primitives...");
+ for (const primitive of PRIMITIVES) {
+ await IOUtils.writeUTF8(filename, JSON.stringify(primitive));
+ const readPrimitive = await IOUtils.readJSON(filename);
+ const parsedPrimitive = JSON.parse(await IOUtils.readUTF8(filename));
+ ok(primitive === readPrimitive, `JSON primitive ${primitive} should round trip`);
+ ok(
+ readPrimitive === parsedPrimitive,
+ `${readPrimitive} === ${parsedPrimitive} -- IOUtils.readJSON() equivalent to JSON.parse() for primitive`
+ );
+ }
+
+ info("Testing IOUtils.readJSON() with a file that does not exist...");
+ const notExistsFilename = PathUtils.join(tmpDir, "test_ioutils_read_json_not_exists.tmp");
+ ok(!await IOUtils.exists(notExistsFilename), `${notExistsFilename} should not exist`);
+ await Assert.rejects(
+ IOUtils.readJSON(notExistsFilename),
+ /NotFoundError: Could not open the file at/,
+ "IOUtils::readJSON rejects when file does not exist"
+ );
+
+ info("Testing IOUtils.readJSON() with a file that does not contain JSON");
+ const invalidFilename = PathUtils.join(tmpDir, "test_ioutils_read_json_invalid.tmp");
+ await IOUtils.writeUTF8(invalidFilename, ":)");
+
+ await Assert.rejects(
+ IOUtils.readJSON(invalidFilename),
+ /SyntaxError: JSON\.parse/,
+ "IOUTils::readJSON rejects when the file contains invalid JSON"
+ );
+
+ await cleanup(filename, invalidFilename);
+ });
+
+ add_task(async function write_json() {
+ const tmpDir = await PathUtils.getTempDir();
+ const filename = PathUtils.join(tmpDir, "test_ioutils_write_json.tmp");
+
+ info("Testing IOUtils.writeJSON() with an object...");
+ await IOUtils.writeJSON(filename, OBJECT);
+ const readObject = await IOUtils.readJSON(filename);
+ const readObjectStr = await IOUtils.readUTF8(filename);
+ ok(ObjectUtils.deepEqual(OBJECT, readObject), "JSON objects should round-trip");
+ ok(
+ readObjectStr === JSON.stringify(OBJECT),
+ "IOUtils.writeJSON() eqvuialent to JSON.stringify() for an object"
+ );
+
+ info("Testing IOUtils.writeJSON() with an array...");
+ await IOUtils.writeJSON(filename, ARRAY);
+ const readArray = await IOUtils.readJSON(filename);
+ const readArrayStr = await IOUtils.readUTF8(filename);
+ ok(ObjectUtils.deepEqual(ARRAY, readArray), "JSON arrays should round-trip");
+ ok(
+ readArrayStr === JSON.stringify(ARRAY),
+ "IOUtils.writeJSON() equivalent to JSON.stringify() for an array"
+ );
+
+ info("Testing IOUtils.writeJSON() with primitives...");
+ for (const primitive of PRIMITIVES) {
+ await IOUtils.writeJSON(filename, primitive);
+ const readPrimitive = await IOUtils.readJSON(filename);
+ const readPrimitiveStr = await IOUtils.readUTF8(filename);
+ ok(
+ primitive === readPrimitive,
+ `${primitive} === ${readPrimitive} -- IOUtils.writeJSON() should round trip primitive`
+ );
+ ok(
+ readPrimitiveStr === JSON.stringify(primitive),
+ `${readPrimitiveStr} === ${JSON.stringify(primitive)} -- IOUtils.writeJSON() equivalent to JSON.stringify for primitive`
+ );
+ }
+
+ info("Testing IOUtils.writeJSON() with unserializable objects...");
+ await Assert.rejects(
+ IOUtils.writeJSON(filename, window),
+ /TypeError: cyclic object value/,
+ "IOUtils.writeJSON() cannot write cyclic objects"
+ );
+
+ await cleanup(filename);
+ });
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+
+</html>
diff --git a/dom/system/tests/ioutils/test_ioutils_read_write_utf8.html b/dom/system/tests/ioutils/test_ioutils_read_write_utf8.html
new file mode 100644
index 0000000000..80a384a21e
--- /dev/null
+++ b/dom/system/tests/ioutils/test_ioutils_read_write_utf8.html
@@ -0,0 +1,389 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Test the IOUtils file I/O API</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script src="file_ioutils_test_fixtures.js"></script>
+ <script>
+ "use strict";
+
+ const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
+ const { ObjectUtils } = ChromeUtils.import("resource://gre/modules/ObjectUtils.jsm");
+
+ // TODO: Remove this import for OS.File. It is currently being used as a
+ // stop gap for missing IOUtils functionality.
+ const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+
+
+ const tmpDir = OS.Constants.Path.tmpDir;
+
+ // This is an impossible sequence of bytes in an UTF-8 encoded file.
+ // See section 3.5.3 of this text:
+ // https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
+ const invalidUTF8 = Uint8Array.of(0xfe, 0xfe, 0xff, 0xff);
+
+ add_task(async function test_read_utf8_failure() {
+ info("Test attempt to read non-existent file (UTF8)");
+ const doesNotExist = OS.Path.join(tmpDir, "does_not_exist.tmp");
+ await Assert.rejects(
+ IOUtils.readUTF8(doesNotExist),
+ /Could not open the file at .*/,
+ "IOUtils::readUTF8 rejects when file does not exist"
+ );
+
+ info("Test attempt to read invalid UTF-8");
+ const invalidUTF8File = OS.Path.join(tmpDir, "invalid_utf8.tmp");
+
+ // Deliberately write the invalid byte sequence to file.
+ await IOUtils.write(invalidUTF8File, invalidUTF8);
+
+ await Assert.rejects(
+ IOUtils.readUTF8(invalidUTF8File),
+ /Could not read file\(.*\) because it is not UTF-8 encoded/,
+ "IOUtils::readUTF8 will reject when reading a file that is not valid UTF-8"
+ );
+
+ await cleanup(invalidUTF8File);
+ });
+
+ add_task(async function test_write_utf8_no_overwrite() {
+ // Make a new file, and try to write to it with overwrites disabled.
+ const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_write_utf8_overwrite.tmp");
+ const untouchableContents = "Can't touch this!\n";
+ await IOUtils.writeUTF8(tmpFileName, untouchableContents);
+
+ const newContents = "Nah nah nah!\n";
+ await Assert.rejects(
+ IOUtils.writeUTF8(tmpFileName, newContents, {
+ noOverwrite: true,
+ }),
+ /Refusing to overwrite the file at */,
+ "IOUtils::writeUTF8 rejects writing to existing file if overwrites are disabled"
+ );
+ ok(
+ await fileHasTextContents(tmpFileName, untouchableContents),
+ "IOUtils::writeUTF8 doesn't change target file when overwrite is refused"
+ );
+
+ const bytesWritten = await IOUtils.writeUTF8(
+ tmpFileName,
+ newContents,
+ { noOverwrite: false /* Default. */ }
+ );
+ is(
+ bytesWritten,
+ newContents.length,
+ "IOUtils::writeUTF8 can overwrite files if specified"
+ );
+ ok(
+ await fileHasTextContents(tmpFileName, newContents),
+ "IOUtils::writeUTF8 overwrites with the expected contents"
+ );
+
+ await cleanup(tmpFileName);
+ });
+
+ add_task(async function test_write_with_backup() {
+ info("Test backup file option with non-existing file");
+ let fileContents = "Original file contents";
+ let destFileName = OS.Path.join(tmpDir, "test_write_utf8_with_backup_option.tmp");
+ let backupFileName = destFileName + ".backup";
+ let bytesWritten =
+ await IOUtils.writeUTF8(destFileName, fileContents, {
+ backupFile: backupFileName,
+ });
+ ok(
+ await fileHasTextContents(destFileName, "Original file contents"),
+ "IOUtils::writeUTF8 creates a new file with the correct contents"
+ );
+ ok(
+ !await fileExists(backupFileName),
+ "IOUtils::writeUTF8 does not create a backup if the target file does not exist"
+ );
+ is(
+ bytesWritten,
+ fileContents.length,
+ "IOUtils::write correctly writes to a new file without performing a backup"
+ );
+
+ info("Test backup file option with existing destination");
+ let newFileContents = "New file contents";
+ ok(await fileExists(destFileName), `Expected ${destFileName} to exist`);
+ bytesWritten =
+ await IOUtils.writeUTF8(destFileName, newFileContents, {
+ backupFile: backupFileName,
+ });
+ ok(
+ await fileHasTextContents(backupFileName, "Original file contents"),
+ "IOUtils::writeUTF8 can backup an existing file before writing"
+ );
+ ok(
+ await fileHasTextContents(destFileName, "New file contents"),
+ "IOUtils::writeUTF8 can create the target with the correct contents"
+ );
+ is(
+ bytesWritten,
+ newFileContents.length,
+ "IOUtils::writeUTF8 correctly writes to the target after taking a backup"
+ );
+
+ await cleanup(destFileName, backupFileName);
+ });
+
+ add_task(async function test_write_with_backup_and_tmp() {
+ info("Test backup with tmp and backup file options, non-existing destination");
+ let fileContents = "Original file contents";
+ let destFileName = OS.Path.join(tmpDir, "test_write_utf8_with_backup_and_tmp_options.tmp");
+ let backupFileName = destFileName + ".backup";
+ let tmpFileName = OS.Path.join(tmpDir, "temp_file.tmp");
+ let bytesWritten =
+ await IOUtils.writeUTF8(destFileName, fileContents, {
+ backupFile: backupFileName,
+ tmpPath: tmpFileName,
+ });
+ ok(!await fileExists(tmpFileName), "IOUtils::writeUTF8 cleans up the tmpFile");
+ ok(
+ !await fileExists(backupFileName),
+ "IOUtils::writeUTF8 does not create a backup if the target file does not exist"
+ );
+ ok(
+ await fileHasTextContents(destFileName, "Original file contents"),
+ "IOUtils::writeUTF8 can write to the destination when a temporary file is used"
+ );
+ is(
+ bytesWritten,
+ fileContents.length,
+ "IOUtils::writeUTF8 can copy tmp file to destination without performing a backup"
+ );
+
+ info("Test backup with tmp and backup file options, existing destination");
+ let newFileContents = "New file contents";
+ bytesWritten =
+ await IOUtils.writeUTF8(destFileName, newFileContents, {
+ backupFile: backupFileName,
+ tmpPath: tmpFileName,
+ });
+
+ ok(!await fileExists(tmpFileName), "IOUtils::writeUTF8 cleans up the tmpFile");
+ ok(
+ await fileHasTextContents(backupFileName, "Original file contents"),
+ "IOUtils::writeUTF8 can create a backup if the target file exists"
+ );
+ ok(
+ await fileHasTextContents(destFileName, "New file contents"),
+ "IOUtils::writeUTF8 can write to the destination when a temporary file is used"
+ );
+ is(
+ bytesWritten,
+ newFileContents.length,
+ "IOUtils::writeUTF8 can move tmp file to destination after performing a backup"
+ );
+
+ await cleanup(destFileName, backupFileName);
+ });
+
+ add_task(async function test_empty_read_and_write_utf8() {
+ const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_empty_utf8.tmp");
+ const emptyString = ""
+ const bytesWritten = await IOUtils.writeUTF8(
+ tmpFileName,
+ emptyString
+ );
+ is(bytesWritten, 0, "IOUtils::writeUTF8 can create an empty file");
+
+ const nothing = await IOUtils.readUTF8(tmpFileName);
+ is(nothing.length, 0, "IOUtils::readUTF8 can read empty files");
+
+ await cleanup(tmpFileName);
+ });
+
+ add_task(async function test_full_read_and_write_utf8() {
+ // Write a file.
+ info("Test writing emoji file");
+ const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_emoji.tmp");
+
+ // Make sure non-ASCII text is supported for writing and reading back.
+ // For fun, a sampling of space-separated emoji characters from different
+ // Unicode versions, including multi-byte glyphs that are rendered using
+ // ZWJ sequences.
+ const emoji = "☕️ ⚧️ 😀 🖖🏿 🤠 🏳️‍🌈 🥠 🏴‍☠️ 🪐";
+ const expectedBytes = 71;
+ const bytesWritten = await IOUtils.writeUTF8(tmpFileName, emoji);
+ is(
+ bytesWritten,
+ expectedBytes,
+ "IOUtils::writeUTF8 can write emoji to file"
+ );
+
+ // Read it back.
+ info("Test reading emoji from file");
+ let fileContents = await IOUtils.readUTF8(tmpFileName);
+ ok(
+ emoji == fileContents &&
+ emoji.length == fileContents.length,
+ "IOUtils::readUTF8 can read back entire file"
+ );
+
+ // Clean up.
+ await cleanup(tmpFileName);
+ });
+
+ add_task(async function test_write_utf8_relative_path() {
+ const tmpFileName = "test_ioutils_write_utf8_relative_path.tmp";
+
+ info("Test writing a file at a relative destination");
+ await Assert.rejects(
+ IOUtils.writeUTF8(tmpFileName, "foo"),
+ /Could not parse path/,
+ "IOUtils::writeUTF8 only works with absolute paths"
+ );
+ });
+
+ add_task(async function test_read_utf8_relative_path() {
+ const tmpFileName = "test_ioutils_read_utf8_relative_path.tmp";
+
+ info("Test reading a file at a relative destination");
+ await Assert.rejects(
+ IOUtils.readUTF8(tmpFileName),
+ /Could not parse path/,
+ "IOUtils::readUTF8 only works with absolute paths"
+ );
+ });
+
+
+ add_task(async function test_utf8_lz4() {
+ const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_utf8_lz4.tmp");
+
+ info("Test writing lz4 encoded UTF-8 string");
+ const emoji = "☕️ ⚧️ 😀 🖖🏿 🤠 🏳️‍🌈 🥠 🏴‍☠️ 🪐";
+ let bytesWritten = await IOUtils.writeUTF8(tmpFileName, emoji, { compress: true });
+ is(bytesWritten, 83, "Expected to write 64 bytes");
+
+ info("Test reading lz4 encoded UTF-8 string");
+ let readData = await IOUtils.readUTF8(tmpFileName, { decompress: true });
+ is(readData, emoji, "IOUtils can write and read back UTF-8 LZ4 encoded data");
+
+ info("Test writing lz4 compressed UTF-8 string");
+ const lotsOfCoffee = new Array(24).fill("☕️").join(""); // ☕️ is 3 bytes in UTF-8: \0xe2 \0x98 \0x95
+ bytesWritten = await IOUtils.writeUTF8(tmpFileName, lotsOfCoffee, { compress: true });
+ console.log(bytesWritten);
+ is(bytesWritten, 28, "Expected 72 bytes to compress to 28 bytes");
+
+ info("Test reading lz4 encoded UTF-8 string");
+ readData = await IOUtils.readUTF8(tmpFileName, { decompress: true });
+ is(readData, lotsOfCoffee, "IOUtils can write and read back UTF-8 LZ4 compressed data");
+
+ info("Test writing empty lz4 compressed UTF-8 string")
+ const empty = "";
+ bytesWritten = await IOUtils.writeUTF8(tmpFileName, empty, { compress: true });
+ is(bytesWritten, 12, "Expected to write just the LZ4 header");
+
+ info("Test reading empty lz4 compressed UTF-8 string")
+ const readEmpty = await IOUtils.readUTF8(tmpFileName, { decompress: true });
+ is(readEmpty, empty, "IOUtils can write and read back empty buffers with LZ4");
+ const readEmptyRaw = await IOUtils.readUTF8(tmpFileName, { decompress: false });
+ is(readEmptyRaw.length, 12, "Expected to read back just the LZ4 header");
+
+ await cleanup(tmpFileName);
+ });
+
+ add_task(async function test_utf8_lz4_osfile_compat() {
+ const osfileTmpFile = OS.Path.join(tmpDir, "test_ioutils_utf8_lz4_compat_osfile.tmp");
+ const ioutilsTmpFile = OS.Path.join(tmpDir, "test_ioutils_utf8_lz4_compat_ioutils.tmp");
+
+ info("Test OS.File and IOUtils write the same UTF-8 file with LZ4 compression enabled")
+ const emoji = "☕️ ⚧️ 😀 🖖🏿 🤠 🏳️‍🌈 🥠 🏴‍☠️ 🪐";
+ let expectedBytes = 83;
+ let ioutilsBytes = await IOUtils.writeUTF8(ioutilsTmpFile, emoji, { compress: true });
+ let osfileBytes = await OS.File.writeAtomic(osfileTmpFile, emoji, { compression: "lz4" });
+ is(ioutilsBytes, expectedBytes, "IOUtils writes the expected number of bytes for compression");
+ is(osfileBytes, ioutilsBytes, "OS.File and IOUtils write the same number of bytes for LZ4 compression");
+
+ info("Test OS.File can read an UTF-8 file compressed by IOUtils");
+ const osfileReadStr = await OS.File.read(ioutilsTmpFile, { compression: "lz4", encoding: "utf-8" });
+ is(osfileReadStr, emoji, "OS.File can read an UTF-8 file compressed by IOUtils")
+
+ info("Test IOUtils can read an UTF-8 file compressed by OS.File");
+ const ioutilsReadString = await IOUtils.readUTF8(ioutilsTmpFile, { decompress: true });
+ is(ioutilsReadString, emoji, "IOUtils can read an UTF-8 file compressed by OS.File");
+
+ await cleanup(osfileTmpFile, ioutilsTmpFile);
+ });
+
+ add_task(async function test_utf8_lz4_bad_call() {
+ const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_utf8_lz4_bad_call.tmp");
+
+ info("readUTF8 ignores the maxBytes option if provided");
+ const emoji = "☕️ ⚧️ 😀 🖖🏿 🤠 🏳️‍🌈 🥠 🏴‍☠️ 🪐";
+ let bytesWritten = await IOUtils.writeUTF8(tmpFileName, emoji, { compress: true });
+ is(bytesWritten, 83, "Expected to write 83 bytes");
+
+ let readData = await IOUtils.readUTF8(tmpFileName, { maxBytes: 4, decompress: true });
+ is(readData, emoji, "IOUtils can write and read back UTF-8 LZ4 encoded data");
+
+ await cleanup(tmpFileName)
+ });
+
+ add_task(async function test_utf8_lz4_failure() {
+ const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_utf8_lz4_fail.tmp");
+
+ info("Test decompression of non-lz4 UTF-8 string");
+ const repeatedBytes = Uint8Array.of(...new Array(50).fill(1));
+ await IOUtils.write(tmpFileName, repeatedBytes, { compress: false });
+
+ await Assert.rejects(
+ IOUtils.readUTF8(tmpFileName, { decompress: true }),
+ /Could not decompress file because it has an invalid LZ4 header \(wrong magic number: .*\)/,
+ "IOUtils::readUTF8 fails to decompress LZ4 data with a bad header"
+ );
+
+ info("Test UTF-8 decompression of short byte buffer");
+ const elevenBytes = Uint8Array.of(...new Array(11).fill(1));
+ await IOUtils.write(tmpFileName, elevenBytes, { compress: false });
+
+ await Assert.rejects(
+ IOUtils.readUTF8(tmpFileName, { decompress: true }),
+ /Could not decompress file because the buffer is too short/,
+ "IOUtils::readUTF8 fails to decompress LZ4 data with missing header"
+ );
+
+ info("Test UTF-8 decompression of valid header, but corrupt contents");
+ const headerFor10bytes = [109, 111, 122, 76, 122, 52, 48, 0, 10, 0, 0, 0] // "mozlz40\0" + 4 byte length
+ const badContents = new Array(11).fill(255); // Bad leading byte, followed by uncompressed stream.
+ const goodHeaderBadContents = Uint8Array.of(...headerFor10bytes, ...badContents);
+ await IOUtils.write(tmpFileName, goodHeaderBadContents, { compress: false });
+
+ await Assert.rejects(
+ IOUtils.readUTF8(tmpFileName, { decompress: true }),
+ /Could not decompress file contents, the file may be corrupt/,
+ "IOUtils::readUTF8 fails to read corrupt LZ4 contents with a correct header"
+ );
+
+ info("Testing decompression of an empty file (no header)");
+ {
+ const n = await IOUtils.writeUTF8(tmpFileName, "");
+ ok(n === 0, "Overwrote with empty file");
+ }
+ await Assert.rejects(
+ IOUtils.readUTF8(tmpFileName, { decompress: true }),
+ /Could not decompress file because the buffer is too short/,
+ "IOUtils::readUTF8 fails to decompress empty files"
+ );
+
+ await cleanup(tmpFileName);
+ });
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+
+</html>
diff --git a/dom/system/tests/ioutils/test_ioutils_remove.html b/dom/system/tests/ioutils/test_ioutils_remove.html
new file mode 100644
index 0000000000..b30d86654d
--- /dev/null
+++ b/dom/system/tests/ioutils/test_ioutils_remove.html
@@ -0,0 +1,97 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Test the IOUtils file I/O API</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script src="file_ioutils_test_fixtures.js"></script>
+ <script>
+ "use strict";
+
+ const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
+ const { ObjectUtils } = ChromeUtils.import("resource://gre/modules/ObjectUtils.jsm");
+
+ // TODO: Remove this import for OS.File. It is currently being used as a
+ // stop gap for missing IOUtils functionality.
+ const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+
+
+ const tmpDir = OS.Constants.Path.tmpDir;
+
+ add_task(async function test_create_and_remove_file() {
+ info("Test creating and removing a single file");
+ const tmpFileName = OS.Path.join(tmpDir, "test_ioutils_create_and_remove.tmp");
+ await IOUtils.write(tmpFileName, new Uint8Array(0));
+ ok(await fileExists(tmpFileName), `Expected file ${tmpFileName} to exist`);
+
+ await IOUtils.remove(tmpFileName);
+ ok(!await fileExists(tmpFileName), "IOUtils::remove can remove files");
+
+ info("Test creating and removing an empty directory");
+ const tmpDirName = OS.Path.join(tmpDir, "test_ioutils_create_and_remove.tmp.d");
+ await IOUtils.makeDirectory(tmpDirName);
+ ok(await dirExists(tmpDirName), `Expected directory ${tmpDirName} to exist`);
+
+ await IOUtils.remove(tmpDirName);
+ ok(!await dirExists(tmpDirName), "IOUtils::remove can remove empty directories");
+ });
+
+ add_task(async function test_remove_non_existing() {
+ const tmpFileName = OS.Path.join(tmpDir, "test_ioutil_remove_non_existing.tmp");
+ ok(!await fileExists(tmpFileName), `Expected file ${tmpFileName} not to exist`);
+
+ await IOUtils.remove(tmpFileName, { ignoreAbsent: true });
+ ok(!await fileExists(tmpFileName), "IOUtils::remove can ignore missing files without error");
+
+ await Assert.rejects(
+ IOUtils.remove(tmpFileName, { ignoreAbsent: false }),
+ /Could not remove the file at .* because it does not exist/,
+ "IOUtils::remove can throw an error when target file is missing"
+ );
+ ok(!await fileExists(tmpFileName), `Expected file ${tmpFileName} not to exist`);
+ });
+
+ add_task(async function test_remove_recursive() {
+ const tmpParentDir = OS.Path.join(tmpDir, "test_ioutils_remove.tmp.d");
+ const tmpChildDir = OS.Path.join(tmpParentDir, "child.tmp.d");
+ const tmpTopLevelFileName = OS.Path.join(tmpParentDir, "top.tmp");
+ const tmpNestedFileName = OS.Path.join(tmpChildDir, "nested.tmp");
+ await createDir(tmpChildDir);
+ await createFile(tmpTopLevelFileName, "");
+ await createFile(tmpNestedFileName, "");
+
+ ok(
+ await fileExists(tmpTopLevelFileName),
+ `Expected file ${tmpTopLevelFileName} to exist`
+ );
+ ok(
+ await fileExists(tmpNestedFileName),
+ `Expected file ${tmpNestedFileName} to exist`
+ );
+
+ await Assert.rejects(
+ IOUtils.remove(tmpParentDir, { recursive: false }),
+ /Could not remove the non-empty directory at .*/,
+ "IOUtils::remove fails if non-recursively removing directory with contents"
+ );
+
+ await IOUtils.remove(tmpParentDir, { recursive: true });
+ ok(
+ !await dirExists(tmpParentDir),
+ "IOUtils::remove can recursively remove a directory"
+ );
+ });
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+
+</html>
diff --git a/dom/system/tests/ioutils/test_ioutils_set_permissions.html b/dom/system/tests/ioutils/test_ioutils_set_permissions.html
new file mode 100644
index 0000000000..8cf4494c5d
--- /dev/null
+++ b/dom/system/tests/ioutils/test_ioutils_set_permissions.html
@@ -0,0 +1,56 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Test the IOUtils file I/O API</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script src="file_ioutils_test_fixtures.js"></script>
+ <script>
+ "use strict";
+
+ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ add_task(async function test_setPermissions() {
+ const tempDir = await PathUtils.getTempDir();
+ const tempFile = PathUtils.join(tempDir, "setPermissions.tmp");
+
+ await IOUtils.writeUTF8(tempFile, "");
+ await IOUtils.setPermissions(tempFile, 0o421);
+
+ let stat = await IOUtils.stat(tempFile);
+
+ if (Services.appinfo.OS === "WINNT") {
+ // setPermissions ignores the x bit on Windows.
+ is(stat.permissions, 0o666, "Permissions munged on Windows");
+ } else {
+ is(stat.permissions, 0o421, "Permissions match");
+ }
+
+ await IOUtils.setPermissions(tempFile, 0o400);
+ stat = await IOUtils.stat(tempFile);
+
+ if (Services.appinfo.OS === "WINNT") {
+ is(stat.permissions, 0o444, "Permissions munged on Windows");
+
+ // We need to make the file writable to delete it on Windows.
+ await IOUtils.setPermissions(tempFile, 0o600);
+ } else {
+ is(stat.permissions, 0o400, "Permissions match");
+ }
+
+ await cleanup(tempFile);
+ });
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+
+</html>
diff --git a/dom/system/tests/ioutils/test_ioutils_stat_touch.html b/dom/system/tests/ioutils/test_ioutils_stat_touch.html
new file mode 100644
index 0000000000..c8b052fadb
--- /dev/null
+++ b/dom/system/tests/ioutils/test_ioutils_stat_touch.html
@@ -0,0 +1,204 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Test the IOUtils file I/O API</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script src="file_ioutils_test_fixtures.js"></script>
+ <script>
+ "use strict";
+
+ const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
+ const { ObjectUtils } = ChromeUtils.import("resource://gre/modules/ObjectUtils.jsm");
+ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ add_task(async function test_stat() {
+ info("Test attempt to stat a regular empty file");
+
+ const tmpDir = await PathUtils.getTempDir();
+
+ const emptyFileName = PathUtils.join(tmpDir, "test_stat_empty.tmp");
+ await createFile(emptyFileName);
+
+ const emptyFileInfo = await IOUtils.stat(emptyFileName);
+ is(emptyFileInfo.size, 0, "IOUtils::stat can get correct (empty) file size");
+ is(emptyFileInfo.path, emptyFileName, "IOUtils::stat result contains the path");
+ is(emptyFileInfo.type, "regular", "IOUtils::stat can stat regular (empty) files");
+ Assert.less(
+ (emptyFileInfo.lastModified - new Date().valueOf()),
+ 1000, // Allow for 1 second deviation in case of slow tests.
+ "IOUtils::stat can get the last modification date for a regular file"
+ );
+
+ info("Test attempt to stat a regular binary file");
+ const tempFileName = PathUtils.join(tmpDir, "test_stat_binary.tmp");
+ const bytes = Uint8Array.of(...new Array(50).keys());
+ await createFile(tempFileName, bytes);
+
+ const fileInfo = await IOUtils.stat(tempFileName);
+ is(fileInfo.size, 50, "IOUtils::stat can get correct file size");
+ is(fileInfo.path, tempFileName, "IOUtils::stat result contains the path");
+ is(fileInfo.type, "regular", "IOUtils::stat can stat regular files");
+ Assert.less(
+ (fileInfo.lastModified - new Date().valueOf()),
+ 1000, // Allow for 1 second deviation in case of slow tests.
+ "IOUtils::stat can get the last modification date for a regular file"
+ );
+
+ info("Test attempt to stat a directory");
+ const tempDirName = PathUtils.join(tmpDir, "test_stat_dir.tmp.d");
+ await IOUtils.makeDirectory(tempDirName);
+
+ const dirInfo = await IOUtils.stat(tempDirName);
+ is(dirInfo.size, -1, "IOUtils::stat reports -1 size for directories")
+ is(fileInfo.path, tempFileName, "IOUtils::stat result contains the path");
+ is(fileInfo.type, "regular", "IOUtils::stat can stat directories");
+ Assert.less(
+ (fileInfo.lastModified - new Date().valueOf()),
+ 1000, // Allow for 1 second deviation in case of slow tests.
+ "IOUtils::stat can get the last modification date for a regular file"
+ );
+
+ await cleanup(emptyFileName, tempFileName, tempDirName)
+ });
+
+ add_task(async function test_stat_failures() {
+ info("Test attempt to stat a non-existing file");
+
+ const tmpDir = await PathUtils.getTempDir();
+
+ const notExistsFile = PathUtils.join(tmpDir, "test_stat_not_exists.tmp");
+
+ await Assert.rejects(
+ IOUtils.stat(notExistsFile),
+ /Could not stat file\(.*\) because it does not exist/,
+ "IOUtils::stat throws if the target file does not exist"
+ );
+ });
+
+ add_task(async function test_touch_and_stat() {
+ info("Test attempt to touch a file");
+
+ const tmpDir = await PathUtils.getTempDir();
+
+ const tmpFileName = PathUtils.join(tmpDir, "test_touch_and_stat.tmp");
+ await createFile(tmpFileName);
+
+ const oldFileInfo = await IOUtils.stat(tmpFileName);
+ await sleep(500);
+
+ // Now update the time stamp.
+ const stamp = await IOUtils.touch(tmpFileName);
+ const newFileInfo = await IOUtils.stat(tmpFileName);
+
+ ok(
+ newFileInfo.lastModified > oldFileInfo.lastModified,
+ "IOUtils::touch can update the lastModified time stamp on the file system"
+ );
+ is(
+ stamp,
+ newFileInfo.lastModified,
+ "IOUtils::touch returns the updated time stamp."
+ );
+
+ info("Test attempt to touch a directory");
+ const tmpDirName = PathUtils.join(tmpDir, "test_touch_and_stat.tmp.d");
+ await createDir(tmpDirName);
+
+ await cleanup(tmpFileName, tmpDirName);
+ });
+
+ add_task(async function test_touch_custom_mod_time() {
+ const tmpDir = await PathUtils.getTempDir();
+
+ const tempFileName = PathUtils.join(tmpDir, "test_touch_custom_mod_time.tmp");
+ await createFile(tempFileName);
+ const originalInfo = await IOUtils.stat(tempFileName);
+ const now = originalInfo.lastModified;
+
+ const oneMinute = 60 * 1000; // milliseconds
+
+ info("Test attempt to set modification time to the future");
+ const future = now + oneMinute;
+ let newModTime = await IOUtils.touch(tempFileName, future);
+ const futureInfo = await IOUtils.stat(tempFileName);
+ Assert.less(originalInfo.lastModified, futureInfo.lastModified, "IOUtils::touch can set a future modification time for the file");
+
+ is(newModTime, futureInfo.lastModified, "IOUtils::touch returns the updated time stamp");
+ is(newModTime, future, "IOUtils::touch return value matches the argument value exactly");
+
+ info("Test attempt to set modification time to the past");
+ const past = now - 2 * oneMinute;
+ newModTime = await IOUtils.touch(tempFileName, past);
+ const pastInfo = await IOUtils.stat(tempFileName);
+ Assert.greater(originalInfo.lastModified, pastInfo.lastModified, "IOUtils::touch can set a past modification time for the file");
+
+ is(newModTime, pastInfo.lastModified, "IOUtils::touch returns the updated time stamp");
+ is(newModTime, past, "IOUtils::touch return value matches the argument value exactly");
+
+ await cleanup(tempFileName);
+ });
+
+ add_task(async function test_stat_btime() {
+ if (["Darwin", "WINNT"].includes(Services.appinfo.OS)) {
+ const tmpDir = await PathUtils.getTempDir();
+
+ const tempFileName = PathUtils.join(tmpDir, "test_stat_btime.tmp");
+ await createFile(tempFileName);
+ const originalInfo = await IOUtils.stat(tempFileName);
+
+ const future = originalInfo.lastModified + 6000;
+ await IOUtils.touch(tempFileName, future);
+ const futureInfo = await IOUtils.stat(tempFileName);
+
+ ok(originalInfo.hasOwnProperty("creationTime"), "originalInfo has creationTime field");
+ ok(originalInfo.creationTime !== undefined && originalInfo.creationTime !== null, "originalInfo has non-null creationTime");
+
+ ok(futureInfo.hasOwnProperty("creationTime"), "futureInfo has creationTime field");
+ ok(futureInfo.creationTime !== undefined && futureInfo.creationTime !== null, "futureInfo has non-null creationTime");
+
+ is(originalInfo.creationTime, futureInfo.creationTime, "creationTime matches");
+
+ await cleanup(tempFileName);
+ } else {
+ ok(true, `skipping test_stat_btime() on unsupported platform ${Services.appinfo.OS}`);
+ }
+ });
+
+ add_task(async function test_touch_failures() {
+ info("Test attempt to touch a non-existing file");
+ const tmpDir = await PathUtils.getTempDir();
+ const notExistsFile = PathUtils.join(tmpDir, "test_touch_not_exists.tmp");
+
+ await Assert.rejects(
+ IOUtils.touch(notExistsFile),
+ /Could not touch file\(.*\) because it does not exist/,
+ "IOUtils::touch throws if the target file does not exist"
+ );
+
+ info("Test attempt to set modification time to Epoch");
+ const tempFileName = PathUtils.join(tmpDir, "test_touch_epoch.tmp");
+ await createFile(tempFileName);
+
+ await Assert.rejects(
+ IOUtils.touch(tempFileName, 0),
+ /Refusing to set the modification time of file\(.*\) to 0/,
+ "IOUtils::touch cannot set the file modification time to Epoch"
+ );
+
+ await cleanup(tempFileName);
+ });
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+
+</html>
diff --git a/dom/system/tests/ioutils/test_ioutils_worker.xhtml b/dom/system/tests/ioutils/test_ioutils_worker.xhtml
new file mode 100644
index 0000000000..df67d48676
--- /dev/null
+++ b/dom/system/tests/ioutils/test_ioutils_worker.xhtml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Testing IOUtils on a chrome worker thread"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/WorkerHandler.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ // Test IOUtils in a chrome worker.
+ function test() {
+ // finish() will be called in the worker.
+ SimpleTest.waitForExplicitFinish();
+ info("test_ioutils_worker.xhtml: Starting test");
+
+ const worker = new ChromeWorker("file_ioutils_worker.js");
+ info("test_ioutils_worker.xhtml: Chrome worker created");
+
+ // Set up the worker with testing facilities, and start it.
+ listenForTests(worker, { verbose: false });
+ worker.postMessage(0);
+ info("test_ioutils_worker.xhtml: Test in progress");
+ };
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result" />
+</window>
diff --git a/dom/system/tests/location_service.sjs b/dom/system/tests/location_service.sjs
new file mode 100644
index 0000000000..04779b7d76
--- /dev/null
+++ b/dom/system/tests/location_service.sjs
@@ -0,0 +1,39 @@
+function parseQueryString(str) {
+ if (str == "") {
+ return {};
+ }
+
+ var paramArray = str.split("&");
+ var regex = /^([^=]+)=(.*)$/;
+ var params = {};
+ for (var i = 0, sz = paramArray.length; i < sz; i++) {
+ var match = regex.exec(paramArray[i]);
+ if (!match) {
+ throw new Error("Bad parameter in queryString! '" + paramArray[i] + "'");
+ }
+ params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
+ }
+
+ return params;
+}
+
+function getPosition(params) {
+ var response = {
+ status: "OK",
+ accuracy: 100,
+ location: {
+ lat: params.lat,
+ lng: params.lng,
+ },
+ };
+
+ return JSON.stringify(response);
+}
+
+function handleRequest(request, response) {
+ let params = parseQueryString(request.queryString);
+ response.setStatusLine("1.0", 200, "OK");
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "application/x-javascript", false);
+ response.write(getPosition(params));
+}
diff --git a/dom/system/tests/location_services_parent.js b/dom/system/tests/location_services_parent.js
new file mode 100644
index 0000000000..3112e60117
--- /dev/null
+++ b/dom/system/tests/location_services_parent.js
@@ -0,0 +1,20 @@
+/**
+ * Loaded as a frame script fetch telemetry for
+ * test_location_services_telemetry.html
+ */
+
+/* global addMessageListener, sendAsyncMessage */
+
+"use strict";
+
+const HISTOGRAM_KEY = "REGION_LOCATION_SERVICES_DIFFERENCE";
+
+addMessageListener("getTelemetryEvents", options => {
+ let result = Services.telemetry.getHistogramById(HISTOGRAM_KEY).snapshot();
+ sendAsyncMessage("getTelemetryEvents", result);
+});
+
+addMessageListener("clear", options => {
+ Services.telemetry.getHistogramById(HISTOGRAM_KEY).clear();
+ sendAsyncMessage("clear", true);
+});
diff --git a/dom/system/tests/mochitest.ini b/dom/system/tests/mochitest.ini
new file mode 100644
index 0000000000..724dbf73bb
--- /dev/null
+++ b/dom/system/tests/mochitest.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+scheme = https
+
+support-files =
+ file_bug1197901.html
+ location_services_parent.js
+ location_service.sjs
+
+[test_bug1197901.html]
+[test_location_services_telemetry.html]
+skip-if = os == "android"
diff --git a/dom/system/tests/test_bug1197901.html b/dom/system/tests/test_bug1197901.html
new file mode 100644
index 0000000000..7e1866ffa3
--- /dev/null
+++ b/dom/system/tests/test_bug1197901.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1197901
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1197901</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1197901 **/
+ SimpleTest.requestFlakyTimeout("requestFlakyTimeout is silly");
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ SimpleTest.waitForFocus(function() {
+ SpecialPowers.pushPrefEnv({"set": [["device.sensors.test.events", true]]},
+ doTest);
+ }, window);
+ };
+
+ function doTest() {
+ window.onmessage = function(event) {
+ ok(event.data.result, event.data.message);
+ };
+
+ // Only same-origin iframe should get the events.
+ var xo = document.getElementById("cross-origin");
+ xo.contentWindow.postMessage(
+ { command: "addEventListener",
+ expected: false,
+ message: "Cross-origin iframe shouldn't get the sensor events."},
+ "*");
+
+ var so = document.getElementById("same-origin");
+ so.contentWindow.postMessage(
+ { command: "addEventListener",
+ expected: true,
+ message: "Same-origin iframe should get the sensor events." },
+ "*");
+
+ // We need a timeout here to check that something does not happen.
+ setTimeout(function() {
+ so.remove();
+ xo.remove();
+ doWindowTest();
+ }, 500);
+ }
+
+ function doWindowTest() {
+ var win = window.open("file_bug1197901.html", "w1", "height=100,width=100");
+ win.onload = function() {
+ win.focus();
+ SimpleTest.waitForFocus(function() {
+ var win2 = window.open("file_bug1197901.html", "w2", "height=100,width=100,left=100");
+ win2.onload = function() {
+ win2.focus();
+ SimpleTest.waitForFocus(function() {
+ // Only focused window should get the events.
+ win.postMessage(
+ { command: "addEventListener",
+ expected: false,
+ message: "Only focused window should get the sensor events." },
+ "*");
+ win2.postMessage(
+ { command: "addEventListener",
+ expected: true,
+ message: "Focused window should get the sensor events." },
+ "*");
+ setTimeout(function() {
+ window.onmessage = null;
+ win.close();
+ win2.close();
+ SimpleTest.finish();
+ }, 500);
+ }, win2);
+ };
+ }, win);
+ };
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<iframe src="file_bug1197901.html" id="same-origin"></iframe>
+<iframe src="http://example.com/tests/dom/system/tests/file_bug1197901.html" id="cross-origin"></iframe>
+</body>
+</html>
diff --git a/dom/system/tests/test_constants.xhtml b/dom/system/tests/test_constants.xhtml
new file mode 100644
index 0000000000..e5afc67f1d
--- /dev/null
+++ b/dom/system/tests/test_constants.xhtml
@@ -0,0 +1,139 @@
+<?xml version="1.0"?>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Testing constants on a chrome worker thread"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test();">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript">
+ <![CDATA[
+
+let worker;
+
+function test_xul() {
+ let lib;
+ isnot(null, OS.Constants.Path.libxul, "libxulpath is defined");
+ try {
+ lib = ctypes.open(OS.Constants.Path.libxul);
+ lib.declare("DumpJSStack", ctypes.default_abi, ctypes.void_t);
+ } catch (x) {
+ success = false;
+ ok(false, "Could not open libxul " + x);
+ }
+ if (lib) {
+ lib.close();
+ }
+ ok(true, "test_xul: opened libxul successfully");
+}
+
+// Test that OS.Constants.libc is defined
+function test_libc() {
+ isnot(null, OS.Constants.libc, "OS.Constants.libc is defined");
+ is(0001, OS.Constants.libc.S_IXOTH, "OS.Constants.libc.S_IXOTH is defined");
+ is(0002, OS.Constants.libc.S_IWOTH, "OS.Constants.libc.S_IWOTH is defined");
+ is(0007, OS.Constants.libc.S_IRWXO, "OS.Constants.libc.S_IRWXO is defined");
+ is(0010, OS.Constants.libc.S_IXGRP, "OS.Constants.libc.S_IXGRP is defined");
+ is(0020, OS.Constants.libc.S_IWGRP, "OS.Constants.libc.S_IWGRP is defined");
+ is(0040, OS.Constants.libc.S_IRGRP, "OS.Constants.libc.S_IRGRP is defined");
+ is(0070, OS.Constants.libc.S_IRWXG, "OS.Constants.libc.S_IRWXG is defined");
+ is(0100, OS.Constants.libc.S_IXUSR, "OS.Constants.libc.S_IXUSR is defined");
+ is(0200, OS.Constants.libc.S_IWUSR, "OS.Constants.libc.S_IWUSR is defined");
+ is(0400, OS.Constants.libc.S_IRUSR, "OS.Constants.libc.S_IRUSR is defined");
+ is(0700, OS.Constants.libc.S_IRWXU, "OS.Constants.libc.S_IRWXU is defined");
+}
+
+// Test that OS.Constants.Win is defined
+function test_Win() {
+ var xulRuntime = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime);
+ if(xulRuntime.OS == "Windows") {
+ ok("Win" in OS.Constants, "OS.Constants.Win is defined");
+ is(OS.Constants.Win.INVALID_HANDLE_VALUE, -1,
+ "OS.Constants.Win.INVALID_HANDLE_VALUE is defined and correct");
+ }
+}
+
+// Test that OS.Constants.Sys.DEBUG is set properly on main thread
+function test_debugBuildMainThread(isDebugBuild) {
+ is(isDebugBuild, !!OS.Constants.Sys.DEBUG, "OS.Constants.Sys.DEBUG is set properly on main thread");
+}
+
+// Test that OS.Constants.Sys.umask is set properly on main thread
+function test_umaskMainThread(umask) {
+ is(umask, OS.Constants.Sys.umask,
+ "OS.Constants.Sys.umask is set properly on main thread: " +
+ ("0000"+umask.toString(8)).slice(-4));
+}
+
+var ctypes;
+function test() {
+ ok(true, "test_constants.xhtml: Starting test");
+
+ // Test 1: Load libxul from main thread
+ Cc["@mozilla.org/net/osfileconstantsservice;1"].
+ getService(Ci.nsIOSFileConstantsService).
+ init();
+ ({ctypes} = ChromeUtils.import("resource://gre/modules/ctypes.jsm"));
+ test_xul();
+ test_libc();
+ test_Win();
+
+ let isDebugBuild = Cc["@mozilla.org/xpcom/debug;1"]
+ .getService(Ci.nsIDebug2).isDebugBuild;
+ test_debugBuildMainThread(isDebugBuild);
+
+ let umask = Cc["@mozilla.org/system-info;1"].
+ getService(Ci.nsIPropertyBag2).
+ getProperty("umask");
+ test_umaskMainThread(umask);
+
+ // Test 2: Load libxul from chrome thread
+ worker = new ChromeWorker("worker_constants.js");
+ SimpleTest.waitForExplicitFinish();
+ ok(true, "test_constants.xhtml: Chrome worker created");
+ worker.onerror = function onerror(error) {
+ error.preventDefault();
+ ok(false, "error " + error);
+ }
+ worker.onmessage = function onmessage(msg) {
+ switch (msg.data.kind) {
+ case "is":
+ SimpleTest.is(msg.data.a, msg.data.b, msg.data.description);
+ return;
+ case "isnot":
+ SimpleTest.isnot(msg.data.a, msg.data.b, msg.data.description);
+ return;
+ case "ok":
+ SimpleTest.ok(msg.data.condition, msg.data.description);
+ return;
+ case "finish":
+ SimpleTest.finish();
+ return;
+ default:
+ SimpleTest.ok(false, "test_constants.xhtml: wrong message " + JSON.stringify(msg.data));
+ return;
+ }
+ };
+
+ // pass expected values that are unavailable off-main-thread
+ // to the worker
+ worker.postMessage({
+ isDebugBuild: isDebugBuild,
+ umask: umask
+ });
+ ok(true, "test_constants.xhtml: Test in progress");
+};
+]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display:none;"></div>
+ <pre id="test"></pre>
+ </body>
+ <label id="test-result"/>
+</window>
diff --git a/dom/system/tests/test_location_services_telemetry.html b/dom/system/tests/test_location_services_telemetry.html
new file mode 100644
index 0000000000..dff1efe0f2
--- /dev/null
+++ b/dom/system/tests/test_location_services_telemetry.html
@@ -0,0 +1,143 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1637402
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1637402</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.requestLongerTimeout(2);
+
+ const BASE_GEO_URL = "http://mochi.test:8888/tests/dom/system/tests/location_service.sjs";
+
+ const GEO_PREF = "geo.provider.network.url";
+ const BACKUP_PREF = "geo.provider.network.compare.url";
+
+ const PARENT = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL("location_services_parent.js"));
+
+ function sendToParent(msg, options) {
+ return new Promise(resolve => {
+ PARENT.addMessageListener(msg, events => {
+ PARENT.removeMessageListener(msg);
+ resolve(events);
+ });
+ PARENT.sendAsyncMessage(msg, options);
+ });
+ }
+
+ function getCurrentPosition() {
+ return new Promise(function(resolve, reject) {
+ navigator.geolocation.getCurrentPosition(resolve, reject);
+ });
+ }
+
+ let tries = 0;
+ let MAX_RETRIES = 500;
+ async function waitFor(fun) {
+ let passing = false;
+ while (!passing && ++tries < MAX_RETRIES) {
+ passing = await fun();
+ }
+ tries = 0;
+ if (!passing) {
+ ok(false, "waitFor condition never passed");
+ }
+ }
+
+ // Keeps track of how many telemetry results we have
+ // seen so we can wait for new ones.
+ let telemetryResultCount = 0;
+ async function newTelemetryResult() {
+ let results = await sendToParent("getTelemetryEvents");
+ let total = Object.values(results.values)
+ .reduce((val, acc) => acc + val, 0);
+ if (total <= telemetryResultCount) {
+ return false;
+ }
+ telemetryResultCount++;
+ return true;
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ window.onload = () => {
+ SimpleTest.waitForFocus(() => {
+ SpecialPowers.pushPrefEnv({"set":
+ [
+ ["geo.prompt.testing", true],
+ ["geo.prompt.testing.allow", true],
+ ["geo.provider.network.logging.enabled", true],
+ ["geo.provider.network.debug.requestCache.enabled", false]
+ ],
+ }, doTest);
+ }, window);
+ };
+
+ const BASE_LOCATION = {lat: 55.867055, lng: -4.271041};
+ const LOCATIONS = [
+ {lat: "foo", lng: "bar", skipWait: true}, // Nan
+ {lat: 55.867055, lng: -4.271041}, // 0M
+ {lat: 50.8251639, lng: -0.1622551}, // 623KM
+ {lat: 55.9438948, lng: -3.1845417}, // 68KM
+ {lat: 39.4780911, lng: -0.3821706}, // 1844KM
+ {lat: 55.867160, lng: -4.271041}, // 10M
+ {lat: 41.8769913, lng: 12.4835351}, // 1969KM
+ {lat: 55.867055, lng: -4.271041}, // 0M
+ ]
+
+ async function setLocations(main, backup) {
+ await SpecialPowers.setCharPref(
+ GEO_PREF,
+ `${BASE_GEO_URL}?lat=${main.lat}&lng=${main.lng}`
+ );
+ await SpecialPowers.setCharPref(
+ BACKUP_PREF,
+ `${BASE_GEO_URL}?lat=${backup.lat}&lng=${backup.lng}`
+ );
+ }
+
+ async function doTest() {
+ // Not all treeherder builds can collect telemetry.
+ if (!SpecialPowers.Services.telemetry.canRecordPrereleaseData) {
+ ok(true, "Cant run any tests without telemetry");
+ SimpleTest.finish();
+ return;
+ }
+ await sendToParent("clear");
+
+ for (let location of LOCATIONS) {
+ await setLocations(BASE_LOCATION, location);
+ await getCurrentPosition();
+ // Not all requests (NaN) will report telemetry.
+ if (!location.skipWait) {
+ await waitFor(newTelemetryResult, "");
+ }
+ }
+
+ let res = await sendToParent("getTelemetryEvents");
+ let total = Object.values(res.values)
+ .reduce((val, acc) => acc + val, 0);
+
+ is(total, 7, "Should have correct number of results");
+ is(res.values["0"], 2, "Two results were same location");
+ // Telemetry could change how exact bucketing
+ // implementation, so check the low bucket
+ // and that the rest are spead out.
+ is(
+ Object.keys(res.values).length,
+ 6,
+ "Split the rest of the results across buckets"
+ );
+
+ SimpleTest.finish();
+ }
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1637402">Mozilla Bug </a>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/system/tests/test_pathutils.html b/dom/system/tests/test_pathutils.html
new file mode 100644
index 0000000000..555942e13d
--- /dev/null
+++ b/dom/system/tests/test_pathutils.html
@@ -0,0 +1,446 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>PathUtils tests</title>
+</head>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+<script>
+ "use strict";
+
+ const { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+ );
+ const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
+ const { Services } = ChromeUtils.import(
+ "resource://gre/modules/Services.jsm"
+ );
+
+ const UNRECOGNIZED_PATH = /Could not initialize path: NS_ERROR_FILE_UNRECOGNIZED_PATH/;
+ const EMPTY_PATH = /PathUtils does not support empty paths/;
+ const JOIN = /Could not append to path/;
+
+ add_task(function test_filename() {
+ Assert.throws(
+ () => PathUtils.filename(""),
+ EMPTY_PATH,
+ "PathUtils.filename() does not support empty paths"
+ );
+ Assert.throws(
+ () => PathUtils.filename("foo.txt"),
+ UNRECOGNIZED_PATH,
+ "PathUtils.filename() does not support relative paths"
+ );
+
+ if (Services.appinfo.OS === "WINNT") {
+ is(
+ PathUtils.filename("C:"),
+ "C:",
+ "PathUtils.filename() with a drive path"
+ );
+ is(
+ PathUtils.filename("C:\\"),
+ "C:",
+ "PathUtils.filename() with a drive path"
+ );
+ is(
+ PathUtils.filename("C:\\Windows"),
+ "Windows",
+ "PathUtils.filename() with a path with 2 components"
+ );
+ is(
+ PathUtils.filename("C:\\Windows\\"),
+ "Windows",
+ "PathUtils.filename() with a path with 2 components and a trailing slash"
+ );
+ is(
+ PathUtils.filename("C:\\Windows\\System32"),
+ "System32",
+ "PathUtils.filename() with a path with 3 components"
+ );
+ is(
+ PathUtils.filename("\\\\server"),
+ "\\\\server",
+ "PathUtils.filename() with a UNC server path"
+ );
+ is(
+ PathUtils.filename("C:\\file.dat"),
+ "file.dat",
+ "PathUtils.filename() with a file path"
+ );
+ } else {
+ is(
+ PathUtils.filename("/"),
+ "/",
+ "PathUtils.filename() with a root path"
+ );
+ is(
+ PathUtils.filename("/usr/"),
+ "usr",
+ "PathUtils.filename() with a non-root path"
+ );
+ is(
+ PathUtils.filename("/usr/lib/libfoo.so"),
+ "libfoo.so",
+ "PathUtils.filename() with a path with 3 components"
+ );
+ }
+ });
+
+ add_task(function test_parent() {
+ Assert.throws(
+ () => PathUtils.parent("."),
+ UNRECOGNIZED_PATH,
+ "PathUtils.parent() does not support relative paths"
+ );
+ Assert.throws(
+ () => PathUtils.parent(""),
+ EMPTY_PATH,
+ "PathUtils.parent() does not support empty paths"
+ );
+
+ if (Services.appinfo.OS === "WINNT") {
+ is(
+ PathUtils.parent("C:"),
+ null,
+ "PathUtils.parent() with a drive path"
+ );
+ is(
+ PathUtils.parent("\\\\server"),
+ null,
+ "PathUtils.parent() with a UNC server path"
+ );
+ is(
+ PathUtils.parent("\\\\server\\foo"),
+ "\\\\server",
+ "PathUtils.parent() with a UNC server path and child component"
+ );
+ } else {
+ is(
+ PathUtils.parent("/"),
+ null,
+ "PathUtils.parent() with a root path"
+ );
+ is(
+ PathUtils.parent("/var"),
+ "/",
+ "PathUtils.parent() with a 2 component path"
+ );
+ is(
+ PathUtils.parent("/var/run"),
+ "/var",
+ "PathUtils.parent() with a 3 component path"
+ );
+ }
+ });
+
+ add_task(function test_join() {
+ is(
+ PathUtils.join(),
+ "",
+ "PathUtils.join() with an empty sequence"
+ );
+ Assert.throws(
+ () => PathUtils.join(""),
+ EMPTY_PATH,
+ "PathUtils.join() does not support empty paths"
+ );
+ Assert.throws(
+ () => PathUtils.join("foo", "bar"),
+ UNRECOGNIZED_PATH,
+ "PathUtils.join() does not support relative paths"
+ );
+ Assert.throws(
+ () => PathUtils.join("."),
+ UNRECOGNIZED_PATH,
+ "PathUtils.join() does not support relative paths"
+ );
+
+ if (Services.appinfo.OS === "WINNT") {
+ is(
+ PathUtils.join("C:"),
+ "C:",
+ "PathUtils.join() with a single path"
+ );
+ is(
+ PathUtils.join("C:\\Windows", "System32"),
+ "C:\\Windows\\System32",
+ "PathUtils.join() with a 2 component path and an additional component"
+ );
+ is(
+ PathUtils.join("C:", "Users", "Example"),
+ "C:\\Users\\Example",
+ "PathUtils.join() with a root path and two additional components"
+ );
+ is(
+ PathUtils.join("\\\\server", "Files", "Example.dat"),
+ "\\\\server\\Files\\Example.dat",
+ "PathUtils.join() with a server path"
+ );
+ } else {
+ is(
+ PathUtils.join("/"),
+ "/",
+ "PathUtils.join() with a root path"
+ );
+ is(
+ PathUtils.join("/usr", "lib"),
+ "/usr/lib",
+ "PathUtils.join() with a 2 component path and an additional component"
+ );
+ is(
+ PathUtils.join("/", "home", "example"),
+ "/home/example",
+ "PathUtils.join() with a root path and two additional components"
+ );
+ }
+ });
+
+ add_task(function test_join_relative() {
+ if (Services.appinfo.OS === "WINNT") {
+ is(
+ PathUtils.joinRelative("C:", ""),
+ "C:",
+ "PathUtils.joinRelative() with an empty relative path"
+ );
+
+ is(
+ PathUtils.joinRelative("C:", "foo\\bar\\baz"),
+ "C:\\foo\\bar\\baz",
+ "PathUtils.joinRelative() with a relative path containing path separators"
+ );
+ } else {
+ is(
+ PathUtils.joinRelative("/", ""),
+ "/",
+ "PathUtils.joinRelative() with an empty relative path"
+ );
+
+ is(
+ PathUtils.joinRelative("/", "foo/bar/baz"),
+ "/foo/bar/baz",
+ "PathUtils.joinRelative() with a relative path containing path separators"
+ );
+ }
+ });
+
+ add_task(async function test_normalize() {
+ Assert.throws(
+ () => PathUtils.normalize(""),
+ EMPTY_PATH,
+ "PathUtils.normalize() does not support empty paths"
+ );
+ Assert.throws(
+ () => PathUtils.normalize("."),
+ UNRECOGNIZED_PATH,
+ "PathUtils.normalize() does not support relative paths"
+ );
+
+ if (Services.appinfo.OS === "WINNT") {
+ is(
+ PathUtils.normalize("C:\\\\Windows\\\\..\\\\\\.\\Users\\..\\Windows"),
+ "C:\\Windows",
+ "PathUtils.normalize() with a non-normalized path"
+ );
+ } else {
+ // nsLocalFileUnix::Normalize() calls realpath, which resolves symlinks
+ // and requires the file to exist.
+ //
+ // On Darwin, the temp directory is located in `/private/var`, which is a
+ // symlink to `/var`, so we need to pre-normalize our temporary directory
+ // or expected paths won't match.
+ const tmpDir = PathUtils.join(
+ PathUtils.normalize(await PathUtils.getTempDir()),
+ "pathutils_test"
+ );
+
+ await IOUtils.makeDirectory(tmpDir, { ignoreExisting: true });
+ info(`created tmpDir ${tmpDir}`);
+ SimpleTest.registerCleanupFunction(async () => {
+ await IOUtils.remove(tmpDir, {
+ recursive: true,
+ });
+ });
+
+ await IOUtils.makeDirectory(PathUtils.join(tmpDir, "foo", "bar"), {
+ createAncestors: true,
+ });
+
+ is(
+ PathUtils.normalize("/"),
+ "/",
+ "PathUtils.normalize() with a normalized path"
+ );
+
+ is(
+ PathUtils.normalize(
+ PathUtils.join(
+ tmpDir,
+ "foo",
+ ".",
+ "..",
+ "foo",
+ ".",
+ "bar",
+ "..",
+ "bar"
+ )
+ ),
+ PathUtils.join(tmpDir, "foo", "bar"),
+ "PathUtils.normalize() with a non-normalized path"
+ );
+ }
+ });
+
+ add_task(function test_split() {
+ Assert.throws(
+ () => PathUtils.split("foo"),
+ UNRECOGNIZED_PATH,
+ "PathUtils.split() does not support relative paths"
+ );
+ Assert.throws(
+ () => PathUtils.split(""),
+ EMPTY_PATH,
+ "PathUtils.split() does not support empty paths"
+ );
+
+ if (Services.appinfo.OS === "WINNT") {
+ Assert.deepEqual(
+ PathUtils.split("C:\\Users\\Example"),
+ ["C:", "Users", "Example"],
+ "PathUtils.split() on an absolute path"
+ );
+
+ Assert.deepEqual(
+ PathUtils.split("C:\\Users\\Example\\"),
+ ["C:", "Users", "Example"],
+ "PathUtils.split() on an absolute path with a trailing slash"
+ );
+
+ Assert.deepEqual(
+ PathUtils.split("\\\\server\\Files\\Example.dat"),
+ ["\\\\server", "Files", "Example.dat"],
+ "PathUtils.split() with a server as the root"
+ );
+ } else {
+ Assert.deepEqual(
+ PathUtils.split("/home/foo"),
+ ["/", "home", "foo"],
+ "PathUtils.split() on absolute path"
+ );
+
+ Assert.deepEqual(
+ PathUtils.split("/home/foo/"),
+ ["/", "home", "foo"],
+ "PathUtils.split() on absolute path with trailing slash"
+ );
+ }
+ });
+
+ add_task(function test_toFileURI() {
+ Assert.throws(
+ () => PathUtils.toFileURI("."),
+ UNRECOGNIZED_PATH,
+ "PathUtils.toFileURI() does not support relative paths"
+ );
+ Assert.throws(
+ () => PathUtils.toFileURI(""),
+ EMPTY_PATH,
+ "PathUtils.toFileURI() does not support empty paths"
+ );
+
+ if (Services.appinfo.OS === "WINNT") {
+ is(
+ PathUtils.toFileURI("C:\\"),
+ "file:///C:/",
+ "PathUtils.toFileURI() with a root path"
+ );
+
+ is(
+ PathUtils.toFileURI("C:\\Windows\\"),
+ "file:///C:/Windows/",
+ "PathUtils.toFileURI() with a non-root directory path"
+ );
+
+ is(
+ PathUtils.toFileURI("C:\\Windows\\system32\\notepad.exe"),
+ "file:///C:/Windows/system32/notepad.exe",
+ "PathUtils.toFileURI() with a file path"
+ );
+ } else {
+ is(
+ PathUtils.toFileURI("/"),
+ "file:///",
+ "PathUtils.toFileURI() with a root path"
+ );
+
+ is(
+ PathUtils.toFileURI("/bin"),
+ "file:///bin/",
+ "PathUtils.toFileURI() with a non-root directory path"
+ );
+
+ is(
+ PathUtils.toFileURI("/bin/ls"),
+ "file:///bin/ls",
+ "PathUtils.toFileURI() with a file path"
+ );
+ }
+ });
+
+ add_task(async function test_getDirectories() {
+ const profile = await PathUtils.getProfileDir();
+ is(
+ profile,
+ Services.dirsvc.get("ProfD", Ci.nsIFile).path,
+ "PathUtils.getProfileDir() should match dirsvc"
+ );
+
+ const localProfile = await PathUtils.getLocalProfileDir();
+ is(
+ localProfile,
+ Services.dirsvc.get("ProfLD", Ci.nsIFile).path,
+ "PathUtils.getLocalProfileDir() should match dirsvc"
+ );
+
+ // See: nsAppDirectoryServiceDefs.h
+ const tempDir = await PathUtils.getTempDir();
+ if (AppConstants.MOZ_SANDBOX) {
+ is(
+ tempDir,
+ Services.dirsvc.get("ContentTmpD", Ci.nsIFile).path,
+ "PathUtils.getTempDir() should match dirsvc"
+ );
+ } else {
+ is(
+ tempDir,
+ Services.dirsvc.get("TmpD", Ci.nsIFile).path,
+ "PathUtils.getTempDir() should match dirsvc"
+ );
+ }
+ });
+
+ add_task(async function test_createUniquePath() {
+ let path = PathUtils.join(await PathUtils.getProfileDir(), ".test");
+
+ let firstPath = PathUtils.createUniquePath(path);
+ let secondPath = PathUtils.createUniquePath(path);
+ SimpleTest.registerCleanupFunction(async () => {
+ await IOUtils.remove(firstPath);
+ await IOUtils.remove(secondPath);
+ });
+
+ isnot(firstPath, secondPath, "Create unique paths returns different paths");
+ is(PathUtils.filename(firstPath), ".test", "PathUtils.createUniquePath() matches filename for first path");
+ is(PathUtils.filename(secondPath), "-1.test", "PathUtils.createUniquePath() has unique filename for second path");
+ });
+</script>
+
+<body>
+</body>
+
+</html>
diff --git a/dom/system/tests/worker_constants.js b/dom/system/tests/worker_constants.js
new file mode 100644
index 0000000000..4894667dfd
--- /dev/null
+++ b/dom/system/tests/worker_constants.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint-env mozilla/chrome-worker */
+
+function log(text) {
+ dump("WORKER " + text + "\n");
+}
+
+function send(message) {
+ self.postMessage(message);
+}
+
+self.onmessage = function(msg) {
+ self.onmessage = function(msgInner) {
+ log("ignored message " + JSON.stringify(msgInner.data));
+ };
+ let { isDebugBuild, umask } = msg.data;
+ try {
+ test_name();
+ test_xul();
+ test_debugBuildWorkerThread(isDebugBuild);
+ test_umaskWorkerThread(umask);
+ test_bits();
+ } catch (x) {
+ log("Catching error: " + x);
+ log("Stack: " + x.stack);
+ log("Source: " + x.toSource());
+ ok(false, x.toString() + "\n" + x.stack);
+ }
+ finish();
+};
+
+function finish() {
+ send({ kind: "finish" });
+}
+
+function ok(condition, description) {
+ send({ kind: "ok", condition, description });
+}
+function is(a, b, description) {
+ send({ kind: "is", a, b, description });
+}
+function isnot(a, b, description) {
+ send({ kind: "isnot", a, b, description });
+}
+
+// Test that OS.Constants.Sys.Name is defined
+function test_name() {
+ isnot(null, OS.Constants.Sys.Name, "OS.Constants.Sys.Name is defined");
+}
+
+// Test that OS.Constants.Sys.DEBUG is set properly in ChromeWorker thread
+function test_debugBuildWorkerThread(isDebugBuild) {
+ is(
+ isDebugBuild,
+ !!OS.Constants.Sys.DEBUG,
+ "OS.Constants.Sys.DEBUG is set properly on worker thread"
+ );
+}
+
+// Test that OS.Constants.Sys.umask is set properly in ChromeWorker thread
+function test_umaskWorkerThread(umask) {
+ is(
+ umask,
+ OS.Constants.Sys.umask,
+ "OS.Constants.Sys.umask is set properly on worker thread: " +
+ ("0000" + umask.toString(8)).slice(-4)
+ );
+}
+
+// Test that OS.Constants.Path.libxul lets us open libxul
+function test_xul() {
+ let lib;
+ isnot(null, OS.Constants.Path.libxul, "libxul is defined");
+ try {
+ lib = ctypes.open(OS.Constants.Path.libxul);
+ lib.declare("DumpJSStack", ctypes.default_abi, ctypes.void_t);
+ } catch (x) {
+ ok(false, "test_xul: Could not open libxul: " + x);
+ }
+ if (lib) {
+ lib.close();
+ }
+ ok(true, "test_xul: opened libxul successfully");
+}
+
+// Check if the value of OS.Constants.Sys.bits is 32 or 64
+function test_bits() {
+ is(
+ OS.Constants.Sys.bits,
+ ctypes.int.ptr.size * 8,
+ "OS.Constants.Sys.bits is either 32 or 64"
+ );
+}