diff options
Diffstat (limited to 'toolkit/components/osfile/tests')
53 files changed, 5129 insertions, 0 deletions
diff --git a/toolkit/components/osfile/tests/mochi/.eslintrc.js b/toolkit/components/osfile/tests/mochi/.eslintrc.js new file mode 100644 index 000000000..8c0f4f574 --- /dev/null +++ b/toolkit/components/osfile/tests/mochi/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/chrome.eslintrc.js" + ] +}; diff --git a/toolkit/components/osfile/tests/mochi/chrome.ini b/toolkit/components/osfile/tests/mochi/chrome.ini new file mode 100644 index 000000000..1da463316 --- /dev/null +++ b/toolkit/components/osfile/tests/mochi/chrome.ini @@ -0,0 +1,15 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = + main_test_osfile_async.js + worker_handler.js + worker_test_osfile_comms.js + worker_test_osfile_front.js + worker_test_osfile_shared.js + worker_test_osfile_unix.js + worker_test_osfile_win.js + +[test_osfile_async.xul] +[test_osfile_back.xul] +[test_osfile_comms.xul] +[test_osfile_front.xul] diff --git a/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js b/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js new file mode 100644 index 000000000..b940a032a --- /dev/null +++ b/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js @@ -0,0 +1,443 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Promise.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); +Components.utils.import("resource://gre/modules/AsyncShutdown.jsm"); + +// The following are used to compare against a well-tested reference +// implementation of file I/O. +Components.utils.import("resource://gre/modules/NetUtil.jsm"); +Components.utils.import("resource://gre/modules/FileUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +var myok = ok; +var myis = is; +var myinfo = info; +var myisnot = isnot; + +var isPromise = function ispromise(value) { + return value != null && typeof value == "object" && "then" in value; +}; + +var maketest = function(prefix, test) { + let utils = { + ok: function ok(t, m) { + myok(t, prefix + ": " + m); + }, + is: function is(l, r, m) { + myis(l, r, prefix + ": " + m); + }, + isnot: function isnot(l, r, m) { + myisnot(l, r, prefix + ": " + m); + }, + info: function info(m) { + myinfo(prefix + ": " + m); + }, + fail: function fail(m) { + utils.ok(false, m); + }, + okpromise: function okpromise(t, m) { + return t.then( + function onSuccess() { + util.ok(true, m); + }, + function onFailure() { + util.ok(false, m); + } + ); + } + }; + return function runtest() { + utils.info("Entering"); + try { + let result = test.call(this, utils); + if (!isPromise(result)) { + throw new TypeError("The test did not return a promise"); + } + utils.info("This was a promise"); + // The test returns a promise + result = result.then(function test_complete() { + utils.info("Complete"); + }, function catch_uncaught_errors(err) { + utils.fail("Uncaught error " + err); + if (err && typeof err == "object" && "message" in err) { + utils.fail("(" + err.message + ")"); + } + if (err && typeof err == "object" && "stack" in err) { + utils.fail("at " + err.stack); + } + }); + return result; + } catch (x) { + utils.fail("Error " + x + " at " + x.stack); + return null; + } + }; +}; + +/** + * Fetch asynchronously the contents of a file using xpcom. + * + * Used for comparing xpcom-based results to os.file-based results. + * + * @param {string} path The _absolute_ path to the file. + * @return {promise} + * @resolves {string} The contents of the file. + */ +var reference_fetch_file = function reference_fetch_file(path, test) { + test.info("Fetching file " + path); + let promise = Promise.defer(); + let file = new FileUtils.File(path); + NetUtil.asyncFetch({ + uri: NetUtil.newURI(file), + loadUsingSystemPrincipal: true + }, function(stream, status) { + if (!Components.isSuccessCode(status)) { + promise.reject(status); + return; + } + let result, reject; + try { + result = NetUtil.readInputStreamToString(stream, stream.available()); + } catch (x) { + reject = x; + } + stream.close(); + if (reject) { + promise.reject(reject); + } else { + promise.resolve(result); + } + }); + + return promise.promise; +}; + +/** + * Compare asynchronously the contents two files using xpcom. + * + * Used for comparing xpcom-based results to os.file-based results. + * + * @param {string} a The _absolute_ path to the first file. + * @param {string} b The _absolute_ path to the second file. + * + * @resolves {null} + */ +var reference_compare_files = function reference_compare_files(a, b, test) { + test.info("Comparing files " + a + " and " + b); + let a_contents = yield reference_fetch_file(a, test); + let b_contents = yield reference_fetch_file(b, test); + is(a_contents, b_contents, "Contents of files " + a + " and " + b + " match"); +}; + +var reference_dir_contents = function reference_dir_contents(path) { + let result = []; + let entries = new FileUtils.File(path).directoryEntries; + while (entries.hasMoreElements()) { + let entry = entries.getNext().QueryInterface(Components.interfaces.nsILocalFile); + result.push(entry.path); + } + return result; +}; + +// Set/Unset OS.Shared.DEBUG, OS.Shared.TEST and a console listener. +function toggleDebugTest (pref, consoleListener) { + Services.prefs.setBoolPref("toolkit.osfile.log", pref); + Services.prefs.setBoolPref("toolkit.osfile.log.redirect", pref); + Services.console[pref ? "registerListener" : "unregisterListener"]( + consoleListener); +} + +var test = maketest("Main", function main(test) { + return Task.spawn(function() { + SimpleTest.waitForExplicitFinish(); + yield test_stat(); + yield test_debug(); + yield test_info_features_detect(); + yield test_position(); + yield test_iter(); + yield test_exists(); + yield test_debug_test(); + info("Test is over"); + SimpleTest.finish(); + }); +}); + +/** + * A file that we know exists and that can be used for reading. + */ +var EXISTING_FILE = OS.Path.join("chrome", "toolkit", "components", + "osfile", "tests", "mochi", "main_test_osfile_async.js"); + +/** + * Test OS.File.stat and OS.File.prototype.stat + */ +var test_stat = maketest("stat", function stat(test) { + return Task.spawn(function() { + // Open a file and stat it + let file = yield OS.File.open(EXISTING_FILE); + let stat1; + + try { + test.info("Stating file"); + stat1 = yield file.stat(); + test.ok(true, "stat has worked " + stat1); + test.ok(stat1, "stat is not empty"); + } finally { + yield file.close(); + } + + // Stat the same file without opening it + test.info("Stating a file without opening it"); + let stat2 = yield OS.File.stat(EXISTING_FILE); + test.ok(true, "stat 2 has worked " + stat2); + test.ok(stat2, "stat 2 is not empty"); + for (let key in stat2) { + test.is("" + stat1[key], "" + stat2[key], "Stat field " + key + "is the same"); + } + }); +}); + +/** + * Test feature detection using OS.File.Info.prototype on main thread + */ +var test_info_features_detect = maketest("features_detect", function features_detect(test) { + return Task.spawn(function() { + if (OS.Constants.Win) { + // see if winBirthDate is defined + if ("winBirthDate" in OS.File.Info.prototype) { + test.ok(true, "winBirthDate is defined"); + } else { + test.fail("winBirthDate not defined though we are under Windows"); + } + } else if (OS.Constants.libc) { + // see if unixGroup is defined + if ("unixGroup" in OS.File.Info.prototype) { + test.ok(true, "unixGroup is defined"); + } else { + test.fail("unixGroup is not defined though we are under Unix"); + } + } + }); +}); + +/** + * Test file.{getPosition, setPosition} + */ +var test_position = maketest("position", function position(test) { + return Task.spawn(function() { + let file = yield OS.File.open(EXISTING_FILE); + + try { + let view = yield file.read(); + test.info("First batch of content read"); + let CHUNK_SIZE = 178;// An arbitrary number of bytes to read from the file + let pos = yield file.getPosition(); + test.info("Obtained position"); + test.is(pos, view.byteLength, "getPosition returned the end of the file"); + pos = yield file.setPosition(-CHUNK_SIZE, OS.File.POS_END); + test.info("Changed position"); + test.is(pos, view.byteLength - CHUNK_SIZE, "setPosition returned the correct position"); + + let view2 = yield file.read(); + test.info("Read the end of the file"); + for (let i = 0; i < CHUNK_SIZE; ++i) { + if (view2[i] != view[i + view.byteLength - CHUNK_SIZE]) { + test.is(view2[i], view[i], "setPosition put us in the right position"); + } + } + } finally { + yield file.close(); + } + }); +}); + +/** + * Test OS.File.prototype.{DirectoryIterator} + */ +var test_iter = maketest("iter", function iter(test) { + return Task.spawn(function() { + let currentDir = yield OS.File.getCurrentDirectory(); + + // Trivial walks through the directory + test.info("Preparing iteration"); + let iterator = new OS.File.DirectoryIterator(currentDir); + let temporary_file_name = OS.Path.join(currentDir, "empty-temporary-file.tmp"); + try { + yield OS.File.remove(temporary_file_name); + } catch (err) { + // Ignore errors removing file + } + let allFiles1 = yield iterator.nextBatch(); + test.info("Obtained all files through nextBatch"); + test.isnot(allFiles1.length, 0, "There is at least one file"); + test.isnot(allFiles1[0].path, null, "Files have a path"); + + // Ensure that we have the same entries with |reference_dir_contents| + let referenceEntries = new Set(); + for (let entry of reference_dir_contents(currentDir)) { + referenceEntries.add(entry); + } + test.is(referenceEntries.size, allFiles1.length, "All the entries in the directory have been listed"); + for (let entry of allFiles1) { + test.ok(referenceEntries.has(entry.path), "File " + entry.path + " effectively exists"); + // Ensure that we have correct isDir and isSymLink + // Current directory is {objdir}/_tests/testing/mochitest/, assume it has some dirs and symlinks. + var f = new FileUtils.File(entry.path); + test.is(entry.isDir, f.isDirectory(), "Get file " + entry.path + " isDir correctly"); + test.is(entry.isSymLink, f.isSymlink(), "Get file " + entry.path + " isSymLink correctly"); + } + + yield iterator.close(); + test.info("Closed iterator"); + + test.info("Double closing DirectoryIterator"); + iterator = new OS.File.DirectoryIterator(currentDir); + yield iterator.close(); + yield iterator.close(); //double closing |DirectoryIterator| + test.ok(true, "|DirectoryIterator| was closed twice successfully"); + + let allFiles2 = []; + let i = 0; + iterator = new OS.File.DirectoryIterator(currentDir); + yield iterator.forEach(function(entry, index) { + test.is(i++, index, "Getting the correct index"); + allFiles2.push(entry); + }); + test.info("Obtained all files through forEach"); + is(allFiles1.length, allFiles2.length, "Both runs returned the same number of files"); + for (let i = 0; i < allFiles1.length; ++i) { + if (allFiles1[i].path != allFiles2[i].path) { + test.is(allFiles1[i].path, allFiles2[i].path, "Both runs return the same files"); + break; + } + } + + // Testing batch iteration + whether an iteration can be stopped early + let BATCH_LENGTH = 10; + test.info("Getting some files through nextBatch"); + yield iterator.close(); + + iterator = new OS.File.DirectoryIterator(currentDir); + let someFiles1 = yield iterator.nextBatch(BATCH_LENGTH); + let someFiles2 = yield iterator.nextBatch(BATCH_LENGTH); + yield iterator.close(); + + iterator = new OS.File.DirectoryIterator(currentDir); + yield iterator.forEach(function cb(entry, index, iterator) { + if (index < BATCH_LENGTH) { + test.is(entry.path, someFiles1[index].path, "Both runs return the same files (part 1)"); + } else if (index < 2*BATCH_LENGTH) { + test.is(entry.path, someFiles2[index - BATCH_LENGTH].path, "Both runs return the same files (part 2)"); + } else if (index == 2 * BATCH_LENGTH) { + test.info("Attempting to stop asynchronous forEach"); + return iterator.close(); + } else { + test.fail("Can we stop an asynchronous forEach? " + index); + } + return null; + }); + yield iterator.close(); + + // Ensuring that we find new files if they appear + let file = yield OS.File.open(temporary_file_name, { write: true } ); + file.close(); + iterator = new OS.File.DirectoryIterator(currentDir); + try { + let files = yield iterator.nextBatch(); + is(files.length, allFiles1.length + 1, "The directory iterator has noticed the new file"); + let exists = yield iterator.exists(); + test.ok(exists, "After nextBatch, iterator detects that the directory exists"); + } finally { + yield iterator.close(); + } + + // Ensuring that opening a non-existing directory fails consistently + // once iteration starts. + try { + iterator = null; + iterator = new OS.File.DirectoryIterator("/I do not exist"); + let exists = yield iterator.exists(); + test.ok(!exists, "Before any iteration, iterator detects that the directory doesn't exist"); + let exn = null; + try { + yield iterator.next(); + } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) { + exn = ex; + let exists = yield iterator.exists(); + test.ok(!exists, "After one iteration, iterator detects that the directory doesn't exist"); + } + test.ok(exn, "Iterating through a directory that does not exist has failed with becauseNoSuchFile"); + } finally { + if (iterator) { + iterator.close(); + } + } + test.ok(!!iterator, "The directory iterator for a non-existing directory was correctly created"); + }); +}); + +/** + * Test OS.File.prototype.{exists} + */ +var test_exists = maketest("exists", function exists(test) { + return Task.spawn(function() { + let fileExists = yield OS.File.exists(EXISTING_FILE); + test.ok(fileExists, "file exists"); + fileExists = yield OS.File.exists(EXISTING_FILE + ".tmp"); + test.ok(!fileExists, "file does not exists"); + }); +}); + +/** + * Test changes to OS.Shared.DEBUG flag. + */ +var test_debug = maketest("debug", function debug(test) { + return Task.spawn(function() { + function testSetDebugPref (pref) { + try { + Services.prefs.setBoolPref("toolkit.osfile.log", pref); + } catch (x) { + test.fail("Setting OS.Shared.DEBUG to " + pref + + " should not cause error."); + } finally { + test.is(OS.Shared.DEBUG, pref, "OS.Shared.DEBUG is set correctly."); + } + } + testSetDebugPref(true); + let workerDEBUG = yield OS.File.GET_DEBUG(); + test.is(workerDEBUG, true, "Worker's DEBUG is set."); + testSetDebugPref(false); + workerDEBUG = yield OS.File.GET_DEBUG(); + test.is(workerDEBUG, false, "Worker's DEBUG is unset."); + }); +}); + +/** + * Test logging in the main thread with set OS.Shared.DEBUG and + * OS.Shared.TEST flags. + */ +var test_debug_test = maketest("debug_test", function debug_test(test) { + return Task.spawn(function () { + // Create a console listener. + let consoleListener = { + observe: function (aMessage) { + // Ignore unexpected messages. + if (!(aMessage instanceof Components.interfaces.nsIConsoleMessage)) { + return; + } + if (aMessage.message.indexOf("TEST OS") < 0) { + return; + } + test.ok(true, "DEBUG TEST messages are logged correctly."); + } + }; + toggleDebugTest(true, consoleListener); + // Execution of OS.File.exist method will trigger OS.File.LOG several times. + let fileExists = yield OS.File.exists(EXISTING_FILE); + toggleDebugTest(false, consoleListener); + }); +}); + + diff --git a/toolkit/components/osfile/tests/mochi/test_osfile_async.xul b/toolkit/components/osfile/tests/mochi/test_osfile_async.xul new file mode 100644 index 000000000..1ef103f02 --- /dev/null +++ b/toolkit/components/osfile/tests/mochi/test_osfile_async.xul @@ -0,0 +1,23 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Testing async I/O with OS.File" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" + src="main_test_osfile_async.js"/> + + <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/toolkit/components/osfile/tests/mochi/test_osfile_back.xul b/toolkit/components/osfile/tests/mochi/test_osfile_back.xul new file mode 100644 index 000000000..b6af6303f --- /dev/null +++ b/toolkit/components/osfile/tests/mochi/test_osfile_back.xul @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Testing OS.File on a chrome worker thread" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" + src="worker_handler.js"/> + <script type="application/javascript"> + <![CDATA[ + +let worker; + +function test() { + ok(true, "test_osfile.xul: Starting test"); + if (navigator.platform.indexOf("Win") != -1) { + ok(true, "test_osfile.xul: Using Windows test suite"); + worker = new ChromeWorker("worker_test_osfile_win.js"); + } else { + ok(true, "test_osfile.xul: Using Unix test suite"); + worker = new ChromeWorker("worker_test_osfile_unix.js"); + } + SimpleTest.waitForExplicitFinish(); + ok(true, "test_osfile.xul: Chrome worker created"); + dump("MAIN: go\n"); + worker_handler(worker); + worker.postMessage(0); + ok(true, "test_osfile.xul: 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/toolkit/components/osfile/tests/mochi/test_osfile_comms.xul b/toolkit/components/osfile/tests/mochi/test_osfile_comms.xul new file mode 100644 index 000000000..88e474ce2 --- /dev/null +++ b/toolkit/components/osfile/tests/mochi/test_osfile_comms.xul @@ -0,0 +1,84 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Testing OS.File on a chrome worker thread" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript"> + <![CDATA[ + +"use strict"; + +let worker; + +let test = function test() { + SimpleTest.info("test_osfile_comms.xul: Starting test"); + Components.utils.import("resource://gre/modules/ctypes.jsm"); + Components.utils.import("resource://gre/modules/osfile.jsm"); + worker = new ChromeWorker("worker_test_osfile_comms.js"); + SimpleTest.waitForExplicitFinish(); + try { + worker.onerror = function onerror(error) { + SimpleTest.ok(false, "received error "+error); + } + worker.onmessage = function onmessage(msg) { + Components.utils.forceShrinkingGC(); + switch (msg.data.kind) { + case "is": + SimpleTest.ok(msg.data.outcome, msg.data.description + + " ("+ msg.data.a + " ==? " + msg.data.b + ")" ); + return; + case "isnot": + SimpleTest.ok(msg.data.outcome, msg.data.description + + " ("+ msg.data.a + " !=? " + msg.data.b + ")" ); + return; + case "ok": + SimpleTest.ok(msg.data.condition, msg.data.description); + return; + case "info": + SimpleTest.info(msg.data.description); + return; + case "finish": + SimpleTest.finish(); + return; + case "value": + SimpleTest.ok(true, "test_osfile_comms.xul: Received value " + JSON.stringify(msg.data.value)); + let type = eval(msg.data.typename); + let check = eval(msg.data.check); + let value = msg.data.value; + let deserialized = type.fromMsg(value); + check(deserialized, "Main thread test: "); + return; + default: + SimpleTest.ok(false, "test_osfile_comms.xul: wrong message "+JSON.stringify(msg.data)); + return; + } + }; + worker.postMessage(0) + ok(true, "Worker launched"); + } catch(x) { + // As we have set |waitForExplicitFinish|, we add this fallback + // in case of uncaught error, to ensure that the test does not + // just freeze. + ok(false, "Caught exception " + x + " at " + x.stack); + SimpleTest.finish(); + } +}; + +]]> + </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/toolkit/components/osfile/tests/mochi/test_osfile_front.xul b/toolkit/components/osfile/tests/mochi/test_osfile_front.xul new file mode 100644 index 000000000..10d3fbd01 --- /dev/null +++ b/toolkit/components/osfile/tests/mochi/test_osfile_front.xul @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Testing OS.File on a chrome worker thread" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" + src="worker_handler.js"/> + <script type="application/javascript"> + <![CDATA[ + +let worker; +let main = this; + +function test() { + info("test_osfile_front.xul: Starting test"); + + // Test the OS.File worker + + worker = new ChromeWorker("worker_test_osfile_front.js"); + SimpleTest.waitForExplicitFinish(); + info("test_osfile_front.xul: Chrome worker created"); + dump("MAIN: go\n"); + worker_handler(worker); + worker.postMessage(0); + ok(true, "test_osfile_front.xul: 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/toolkit/components/osfile/tests/mochi/worker_handler.js b/toolkit/components/osfile/tests/mochi/worker_handler.js new file mode 100644 index 000000000..6c513e6ba --- /dev/null +++ b/toolkit/components/osfile/tests/mochi/worker_handler.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function worker_handler(worker) { + worker.onerror = function(error) { + error.preventDefault(); + ok(false, "Worker error " + error.message); + } + worker.onmessage = function(msg) { + ok(true, "MAIN: onmessage " + JSON.stringify(msg.data)); + switch (msg.data.kind) { + case "is": + SimpleTest.ok(msg.data.outcome, msg.data.description + + "( "+ msg.data.a + " ==? " + msg.data.b + ")" ); + return; + case "isnot": + SimpleTest.ok(msg.data.outcome, msg.data.description + + "( "+ msg.data.a + " !=? " + msg.data.b + ")" ); + return; + case "ok": + SimpleTest.ok(msg.data.condition, msg.data.description); + return; + case "info": + SimpleTest.info(msg.data.description); + return; + case "finish": + SimpleTest.finish(); + return; + default: + SimpleTest.ok(false, "test_osfile.xul: wrong message " + JSON.stringify(msg.data)); + return; + } + }; +} diff --git a/toolkit/components/osfile/tests/mochi/worker_test_osfile_comms.js b/toolkit/components/osfile/tests/mochi/worker_test_osfile_comms.js new file mode 100644 index 000000000..5e8bdd9ca --- /dev/null +++ b/toolkit/components/osfile/tests/mochi/worker_test_osfile_comms.js @@ -0,0 +1,145 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +importScripts('worker_test_osfile_shared.js'); + +// The set of samples for communications test. Declare as a global +// variable to prevent this from being garbage-collected too early. +var samples; + +self.onmessage = function(msg) { + info("Initializing"); + self.onmessage = function on_unexpected_message(msg) { + throw new Error("Unexpected message " + JSON.stringify(msg.data)); + }; + importScripts("resource://gre/modules/osfile.jsm"); + info("Initialization complete"); + + samples = [ + { typename: "OS.Shared.Type.char.in_ptr", + valuedescr: "String", + value: "This is a test", + type: OS.Shared.Type.char.in_ptr, + check: function check_string(candidate, prefix) { + is(candidate, "This is a test", prefix); + }}, + { typename: "OS.Shared.Type.char.in_ptr", + valuedescr: "Typed array", + value: (function() { + let view = new Uint8Array(15); + for (let i = 0; i < 15; ++i) { + view[i] = i; + } + return view; + })(), + type: OS.Shared.Type.char.in_ptr, + check: function check_ArrayBuffer(candidate, prefix) { + for (let i = 0; i < 15; ++i) { + is(candidate[i], i % 256, prefix + "Checking that the contents of the ArrayBuffer were preserved"); + } + }}, + { typename: "OS.Shared.Type.char.in_ptr", + valuedescr: "Pointer", + value: new OS.Shared.Type.char.in_ptr.implementation(1), + type: OS.Shared.Type.char.in_ptr, + check: function check_ptr(candidate, prefix) { + let address = ctypes.cast(candidate, ctypes.uintptr_t).value.toString(); + is(address, "1", prefix + "Checking that the pointer address was preserved"); + }}, + { typename: "OS.Shared.Type.char.in_ptr", + valuedescr: "C array", + value: (function() { + let buf = new (ctypes.ArrayType(ctypes.uint8_t, 15))(); + for (let i = 0; i < 15; ++i) { + buf[i] = i % 256; + } + return buf; + })(), + type: OS.Shared.Type.char.in_ptr, + check: function check_array(candidate, prefix) { + let cast = ctypes.cast(candidate, ctypes.uint8_t.ptr); + for (let i = 0; i < 15; ++i) { + is(cast.contents, i % 256, prefix + "Checking that the contents of the C array were preserved, index " + i); + cast = cast.increment(); + } + } + }, + { typename: "OS.File.Error", + valuedescr: "OS Error", + type: OS.File.Error, + value: new OS.File.Error("foo", 1), + check: function check_error(candidate, prefix) { + ok(candidate instanceof OS.File.Error, + prefix + "Error is an OS.File.Error"); + ok(candidate.unixErrno == 1 || candidate.winLastError == 1, + prefix + "Error code is correct"); + try { + let string = candidate.toString(); + info(prefix + ".toString() works " + string); + } catch (x) { + ok(false, prefix + ".toString() fails " + x); + } + } + } + ]; + samples.forEach(function test(sample) { + let type = sample.type; + let value = sample.value; + let check = sample.check; + info("Testing handling of type " + sample.typename + " communicating " + sample.valuedescr); + + // 1. Test serialization + let serialized; + let exn; + try { + serialized = type.toMsg(value); + } catch (ex) { + exn = ex; + } + is(exn, null, "Can I serialize the following value? " + value + + " aka " + JSON.stringify(value)); + if (exn) { + return; + } + + if ("data" in serialized) { + // Unwrap from `Meta` + serialized = serialized.data; + } + + // 2. Test deserialization + let deserialized; + try { + deserialized = type.fromMsg(serialized); + } catch (ex) { + exn = ex; + } + is(exn, null, "Can I deserialize the following message? " + serialized + + " aka " + JSON.stringify(serialized)); + if (exn) { + return; + } + + // 3. Local test deserialized value + info("Running test on deserialized value " + deserialized + + " aka " + JSON.stringify(deserialized)); + check(deserialized, "Local test: "); + + // 4. Test sending serialized + info("Attempting to send message"); + try { + self.postMessage({kind:"value", + typename: sample.typename, + value: serialized, + check: check.toSource()}); + } catch (ex) { + exn = ex; + } + is(exn, null, "Can I send the following message? " + serialized + + " aka " + JSON.stringify(serialized)); + }); + + finish(); + }; diff --git a/toolkit/components/osfile/tests/mochi/worker_test_osfile_front.js b/toolkit/components/osfile/tests/mochi/worker_test_osfile_front.js new file mode 100644 index 000000000..29e613510 --- /dev/null +++ b/toolkit/components/osfile/tests/mochi/worker_test_osfile_front.js @@ -0,0 +1,566 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +importScripts('worker_test_osfile_shared.js'); +importScripts("resource://gre/modules/workers/require.js"); + +var SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); +SharedAll.Config.DEBUG = true; + +function should_throw(f) { + try { + f(); + } catch (x) { + return x; + } + return null; +} + +self.onmessage = function onmessage_start(msg) { + self.onmessage = function onmessage_ignored(msg) { + log("ignored message " + JSON.stringify(msg.data)); + }; + try { + test_init(); + test_open_existing_file(); + test_open_non_existing_file(); + test_flush_open_file(); + test_copy_existing_file(); + test_position(); + test_move_file(); + test_iter_dir(); + test_info(); + test_path(); + test_exists_file(); + test_remove_file(); + } catch (x) { + log("Catching error: " + x); + log("Stack: " + x.stack); + log("Source: " + x.toSource()); + ok(false, x.toString() + "\n" + x.stack); + } + finish(); +}; + +function test_init() { + info("Starting test_init"); + importScripts("resource://gre/modules/osfile.jsm"); +} + +/** + * Test that we can open an existing file. + */ +function test_open_existing_file() +{ + info("Starting test_open_existing"); + let file = OS.File.open("chrome/toolkit/components/osfile/tests/mochi/worker_test_osfile_unix.js"); + file.close(); +} + +/** + * Test that opening a file that does not exist fails with the right error. + */ +function test_open_non_existing_file() +{ + info("Starting test_open_non_existing"); + let exn; + try { + let file = OS.File.open("/I do not exist"); + } catch (x) { + exn = x; + info("test_open_non_existing_file: Exception detail " + exn); + } + ok(!!exn, "test_open_non_existing_file: Exception was raised "); + ok(exn instanceof OS.File.Error, "test_open_non_existing_file: Exception was a OS.File.Error"); + ok(exn.becauseNoSuchFile, "test_open_non_existing_file: Exception confirms that the file does not exist"); +} + +/** + * Test that to ensure that |foo.flush()| does not + * cause an error, where |foo| is an open file. + */ +function test_flush_open_file() +{ + info("Starting test_flush_open_file"); + let tmp = "test_flush.tmp"; + let file = OS.File.open(tmp, {create: true, write: true}); + file.flush(); + file.close(); + OS.File.remove(tmp); +} + +/** + * Utility function for comparing two files (or a prefix of two files). + * + * This function returns nothing but fails of both files (or prefixes) + * are not identical. + * + * @param {string} test The name of the test (used for logging). + * @param {string} sourcePath The name of the first file. + * @param {string} destPath The name of the second file. + * @param {number=} prefix If specified, only compare the |prefix| + * first bytes of |sourcePath| and |destPath|. + */ +function compare_files(test, sourcePath, destPath, prefix) +{ + info(test + ": Comparing " + sourcePath + " and " + destPath); + let source = OS.File.open(sourcePath); + let dest = OS.File.open(destPath); + info("Files are open"); + let sourceResult, destResult; + try { + if (prefix != undefined) { + sourceResult = source.read(prefix); + destResult = dest.read(prefix); + } else { + sourceResult = source.read(); + destResult = dest.read(); + } + is(sourceResult.length, destResult.length, test + ": Both files have the same size"); + for (let i = 0; i < sourceResult.length; ++i) { + if (sourceResult[i] != destResult[i]) { + is(sourceResult[i] != destResult[i], test + ": Comparing char " + i); + break; + } + } + } finally { + source.close(); + dest.close(); + } + info(test + ": Comparison complete"); +} + +/** + * Test that copying a file using |copy| works. + */ +function test_copy_existing_file() +{ + let src_file_name = + OS.Path.join("chrome", "toolkit", "components", "osfile", "tests", "mochi", + "worker_test_osfile_front.js"); + let tmp_file_name = "test_osfile_front.tmp"; + info("Starting test_copy_existing"); + OS.File.copy(src_file_name, tmp_file_name); + + info("test_copy_existing: Copy complete"); + compare_files("test_copy_existing", src_file_name, tmp_file_name); + + // Create a bogus file with arbitrary content, then attempt to overwrite + // it with |copy|. + let dest = OS.File.open(tmp_file_name, {trunc: true}); + let buf = new Uint8Array(50); + dest.write(buf); + dest.close(); + + OS.File.copy(src_file_name, tmp_file_name); + + compare_files("test_copy_existing 2", src_file_name, tmp_file_name); + + // Attempt to overwrite with noOverwrite + let exn; + try { + OS.File.copy(src_file_name, tmp_file_name, {noOverwrite: true}); + } catch(x) { + exn = x; + } + ok(!!exn, "test_copy_existing: noOverwrite prevents overwriting existing files"); + + info("test_copy_existing: Cleaning up"); + OS.File.remove(tmp_file_name); +} + +/** + * Test that moving a file works. + */ +function test_move_file() +{ + info("test_move_file: Starting"); + // 1. Copy file into a temporary file + let src_file_name = + OS.Path.join("chrome", "toolkit", "components", "osfile", "tests", "mochi", + "worker_test_osfile_front.js"); + let tmp_file_name = "test_osfile_front.tmp"; + let tmp2_file_name = "test_osfile_front.tmp2"; + OS.File.copy(src_file_name, tmp_file_name); + + info("test_move_file: Copy complete"); + + // 2. Move + OS.File.move(tmp_file_name, tmp2_file_name); + + info("test_move_file: Move complete"); + + // 3. Check that destination exists + compare_files("test_move_file", src_file_name, tmp2_file_name); + + // 4. Check that original file does not exist anymore + let exn; + try { + OS.File.open(tmp_file_name); + } catch (x) { + exn = x; + } + ok(!!exn, "test_move_file: Original file has been removed"); + + info("test_move_file: Cleaning up"); + OS.File.remove(tmp2_file_name); +} + +function test_iter_dir() +{ + info("test_iter_dir: Starting"); + + // Create a file, to be sure that it exists + let tmp_file_name = "test_osfile_front.tmp"; + let tmp_file = OS.File.open(tmp_file_name, {write: true, trunc:true}); + tmp_file.close(); + + let parent = OS.File.getCurrentDirectory(); + info("test_iter_dir: directory " + parent); + let iterator = new OS.File.DirectoryIterator(parent); + info("test_iter_dir: iterator created"); + let encountered_tmp_file = false; + for (let entry in iterator) { + // Checking that |name| can be decoded properly + info("test_iter_dir: encountering entry " + entry.name); + + if (entry.name == tmp_file_name) { + encountered_tmp_file = true; + isnot(entry.isDir, "test_iter_dir: The temporary file is not a directory"); + isnot(entry.isSymLink, "test_iter_dir: The temporary file is not a link"); + } + + let file; + let success = true; + try { + file = OS.File.open(entry.path); + } catch (x) { + if (x.becauseNoSuchFile) { + success = false; + } + } + if (file) { + file.close(); + } + ok(success, "test_iter_dir: Entry " + entry.path + " exists"); + + if (OS.Win) { + // We assume that the files are at least as recent as 2009. + // Since this test was written in 2011 and some of our packaging + // sets dates arbitrarily to 2010, this should be safe. + let year = new Date().getFullYear(); + let creation = entry.winCreationDate; + ok(creation, "test_iter_dir: Windows creation date exists: " + creation); + ok(creation.getFullYear() >= 2009 && creation.getFullYear() <= year, "test_iter_dir: consistent creation date"); + + let lastWrite = entry.winLastWriteDate; + ok(lastWrite, "test_iter_dir: Windows lastWrite date exists: " + lastWrite); + ok(lastWrite.getFullYear() >= 2009 && lastWrite.getFullYear() <= year, "test_iter_dir: consistent lastWrite date"); + + let lastAccess = entry.winLastAccessDate; + ok(lastAccess, "test_iter_dir: Windows lastAccess date exists: " + lastAccess); + ok(lastAccess.getFullYear() >= 2009 && lastAccess.getFullYear() <= year, "test_iter_dir: consistent lastAccess date"); + } + + } + ok(encountered_tmp_file, "test_iter_dir: We have found the temporary file"); + + info("test_iter_dir: Cleaning up"); + iterator.close(); + + // Testing nextBatch() + iterator = new OS.File.DirectoryIterator(parent); + let allentries = []; + for (let x in iterator) { + allentries.push(x); + } + iterator.close(); + + ok(allentries.length >= 14, "test_iter_dir: Meta-check: the test directory should contain at least 14 items"); + + iterator = new OS.File.DirectoryIterator(parent); + let firstten = iterator.nextBatch(10); + is(firstten.length, 10, "test_iter_dir: nextBatch(10) returns 10 items"); + for (let i = 0; i < firstten.length; ++i) { + is(allentries[i].path, firstten[i].path, "test_iter_dir: Checking that batch returns the correct entries"); + } + let nextthree = iterator.nextBatch(3); + is(nextthree.length, 3, "test_iter_dir: nextBatch(3) returns 3 items"); + for (let i = 0; i < nextthree.length; ++i) { + is(allentries[i + firstten.length].path, nextthree[i].path, "test_iter_dir: Checking that batch 2 returns the correct entries"); + } + let everythingelse = iterator.nextBatch(); + ok(everythingelse.length >= 1, "test_iter_dir: nextBatch() returns at least one item"); + for (let i = 0; i < everythingelse.length; ++i) { + is(allentries[i + firstten.length + nextthree.length].path, everythingelse[i].path, "test_iter_dir: Checking that batch 3 returns the correct entries"); + } + is(iterator.nextBatch().length, 0, "test_iter_dir: Once there is nothing left, nextBatch returns an empty array"); + iterator.close(); + + iterator = new OS.File.DirectoryIterator(parent); + iterator.close(); + is(iterator.nextBatch().length, 0, "test_iter_dir: nextBatch on closed iterator returns an empty array"); + + iterator = new OS.File.DirectoryIterator(parent); + let allentries2 = iterator.nextBatch(); + is(allentries.length, allentries2.length, "test_iter_dir: Checking that getBatch(null) returns the right number of entries"); + for (let i = 0; i < allentries.length; ++i) { + is(allentries[i].path, allentries2[i].path, "test_iter_dir: Checking that getBatch(null) returns everything in the right order"); + } + iterator.close(); + + // Test forEach + iterator = new OS.File.DirectoryIterator(parent); + let index = 0; + iterator.forEach( + function cb(entry, aIndex, aIterator) { + is(index, aIndex, "test_iter_dir: Checking that forEach index is correct"); + ok(iterator == aIterator, "test_iter_dir: Checking that right iterator is passed"); + if (index < 10) { + is(allentries[index].path, entry.path, "test_iter_dir: Checking that forEach entry is correct"); + } else if (index == 10) { + iterator.close(); + } else { + ok(false, "test_iter_dir: Checking that forEach can be stopped early"); + } + ++index; + }); + iterator.close(); + + //test for prototype |OS.File.DirectoryIterator.unixAsFile| + if ("unixAsFile" in OS.File.DirectoryIterator.prototype) { + info("testing property unixAsFile"); + let path = OS.Path.join("chrome", "toolkit", "components", "osfile", "tests", "mochi"); + iterator = new OS.File.DirectoryIterator(path); + + let dir_file = iterator.unixAsFile();// return |File| + let stat0 = dir_file.stat(); + let stat1 = OS.File.stat(path); + + let unix_info_to_string = function unix_info_to_string(info) { + return "| " + info.unixMode + " | " + info.unixOwner + " | " + info.unixGroup + " | " + info.creationDate + " | " + info.lastModificationDate + " | " + info.lastAccessDate + " | " + info.size + " |"; + }; + + let s0_string = unix_info_to_string(stat0); + let s1_string = unix_info_to_string(stat1); + + ok(stat0.isDir, "unixAsFile returned a directory"); + is(s0_string, s1_string, "unixAsFile returned the correct file"); + dir_file.close(); + iterator.close(); + } + info("test_iter_dir: Complete"); +} + +function test_position() { + info("test_position: Starting"); + + ok("POS_START" in OS.File, "test_position: POS_START exists"); + ok("POS_CURRENT" in OS.File, "test_position: POS_CURRENT exists"); + ok("POS_END" in OS.File, "test_position: POS_END exists"); + + let ARBITRARY_POSITION = 321; + let src_file_name = + OS.Path.join("chrome", "toolkit", "components", "osfile", "tests", "mochi", + "worker_test_osfile_front.js"); + + let file = OS.File.open(src_file_name); + is(file.getPosition(), 0, "test_position: Initial position is 0"); + + let size = 0 + file.stat().size; // Hack: We can remove this 0 + once 776259 has landed + + file.setPosition(ARBITRARY_POSITION, OS.File.POS_START); + is(file.getPosition(), ARBITRARY_POSITION, "test_position: Setting position from start"); + + file.setPosition(0, OS.File.POS_START); + is(file.getPosition(), 0, "test_position: Setting position from start back to 0"); + + file.setPosition(ARBITRARY_POSITION); + is(file.getPosition(), ARBITRARY_POSITION, "test_position: Setting position without argument"); + + file.setPosition(-ARBITRARY_POSITION, OS.File.POS_END); + is(file.getPosition(), size - ARBITRARY_POSITION, "test_position: Setting position from end"); + + file.setPosition(ARBITRARY_POSITION, OS.File.POS_CURRENT); + is(file.getPosition(), size, "test_position: Setting position from current"); + + file.close(); + info("test_position: Complete"); +} + +function test_info() { + info("test_info: Starting"); + + let filename = "test_info.tmp"; + let size = 261;// An arbitrary file length + let start = new Date(); + + // Cleanup any leftover from previous tests + try { + OS.File.remove(filename); + info("test_info: Cleaned up previous garbage"); + } catch (x) { + if (!x.becauseNoSuchFile) { + throw x; + } + info("test_info: No previous garbage"); + } + + let file = OS.File.open(filename, {trunc: true}); + let buf = new ArrayBuffer(size); + file._write(buf, size); + file.close(); + + // Test OS.File.stat on new file + let stat = OS.File.stat(filename); + ok(!!stat, "test_info: info acquired"); + ok(!stat.isDir, "test_info: file is not a directory"); + is(stat.isSymLink, false, "test_info: file is not a link"); + is(stat.size.toString(), size, "test_info: correct size"); + + let stop = new Date(); + + // We round down/up by 1s as file system precision is lower than + // Date precision (no clear specifications about that, but it seems + // that this can be a little over 1 second under ext3 and 2 seconds + // under FAT). + let SLOPPY_FILE_SYSTEM_ADJUSTMENT = 3000; + let startMs = start.getTime() - SLOPPY_FILE_SYSTEM_ADJUSTMENT; + let stopMs = stop.getTime() + SLOPPY_FILE_SYSTEM_ADJUSTMENT; + info("Testing stat with bounds [ " + startMs + ", " + stopMs +" ]"); + + (function() { + let birth; + if ("winBirthDate" in stat) { + birth = stat.winBirthDate; + } else if ("macBirthDate" in stat) { + birth = stat.macBirthDate; + } else { + ok(true, "Skipping birthdate test"); + return; + } + ok(birth.getTime() <= stopMs, + "test_info: platformBirthDate is consistent"); + // Note: Previous versions of this test checked whether the file + // has been created after the start of the test. Unfortunately, + // this sometimes failed under Windows, in specific circumstances: + // if the file has been removed at the start of the test and + // recreated immediately, the Windows file system detects this and + // decides that the file was actually truncated rather than + // recreated, hence that it should keep its previous creation + // date. Debugging hilarity ensues. + }); + + let change = stat.lastModificationDate; + info("Testing lastModificationDate: " + change); + ok(change.getTime() >= startMs && change.getTime() <= stopMs, + "test_info: lastModificationDate is consistent"); + + // Test OS.File.prototype.stat on new file + file = OS.File.open(filename); + try { + stat = file.stat(); + } finally { + file.close(); + } + + ok(!!stat, "test_info: info acquired 2"); + ok(!stat.isDir, "test_info: file is not a directory 2"); + ok(!stat.isSymLink, "test_info: file is not a link 2"); + is(stat.size.toString(), size, "test_info: correct size 2"); + + stop = new Date(); + + // Round up/down as above + startMs = start.getTime() - SLOPPY_FILE_SYSTEM_ADJUSTMENT; + stopMs = stop.getTime() + SLOPPY_FILE_SYSTEM_ADJUSTMENT; + info("Testing stat 2 with bounds [ " + startMs + ", " + stopMs +" ]"); + + let access = stat.lastAccessDate; + info("Testing lastAccessDate: " + access); + ok(access.getTime() >= startMs && access.getTime() <= stopMs, + "test_info: lastAccessDate is consistent"); + + change = stat.lastModificationDate; + info("Testing lastModificationDate 2: " + change); + ok(change.getTime() >= startMs && change.getTime() <= stopMs, + "test_info: lastModificationDate 2 is consistent"); + + // Test OS.File.stat on directory + stat = OS.File.stat(OS.File.getCurrentDirectory()); + ok(!!stat, "test_info: info on directory acquired"); + ok(stat.isDir, "test_info: directory is a directory"); + + info("test_info: Complete"); +} + +// Note that most of the features of path are tested in +// worker_test_osfile_{unix, win}.js +function test_path() +{ + info("test_path: starting"); + let abcd = OS.Path.join("a", "b", "c", "d"); + is(OS.Path.basename(abcd), "d", "basename of a/b/c/d"); + + let abc = OS.Path.join("a", "b", "c"); + is(OS.Path.dirname(abcd), abc, "dirname of a/b/c/d"); + + let abdotsc = OS.Path.join("a", "b", "..", "c"); + is(OS.Path.normalize(abdotsc), OS.Path.join("a", "c"), "normalize a/b/../c"); + + let adotsdotsdots = OS.Path.join("a", "..", "..", ".."); + is(OS.Path.normalize(adotsdotsdots), OS.Path.join("..", ".."), "normalize a/../../.."); + + info("test_path: Complete"); +} + +/** + * Test the file |exists| method. + */ +function test_exists_file() +{ + let file_name = OS.Path.join("chrome", "toolkit", "components" ,"osfile", + "tests", "mochi", "test_osfile_front.xul"); + info("test_exists_file: starting"); + ok(OS.File.exists(file_name), "test_exists_file: file exists (OS.File.exists)"); + ok(!OS.File.exists(file_name + ".tmp"), "test_exists_file: file does not exists (OS.File.exists)"); + + let dir_name = OS.Path.join("chrome", "toolkit", "components" ,"osfile", + "tests", "mochi"); + ok(OS.File.exists(dir_name), "test_exists_file: directory exists"); + ok(!OS.File.exists(dir_name) + ".tmp", "test_exists_file: directory does not exist"); + + info("test_exists_file: complete"); +} + +/** + * Test the file |remove| method. + */ +function test_remove_file() +{ + let absent_file_name = "test_osfile_front_absent.tmp"; + + // Check that removing absent files is handled correctly + let exn = should_throw(function() { + OS.File.remove(absent_file_name, {ignoreAbsent: false}); + }); + ok(!!exn, "test_remove_file: throws if there is no such file"); + + exn = should_throw(function() { + OS.File.remove(absent_file_name, {ignoreAbsent: true}); + OS.File.remove(absent_file_name); + }); + ok(!exn, "test_remove_file: ignoreAbsent works"); + + if (OS.Win) { + let file_name = "test_osfile_front_file_to_remove.tmp"; + let file = OS.File.open(file_name, {write: true}); + file.close(); + ok(OS.File.exists(file_name), "test_remove_file: test file exists"); + OS.Win.File.SetFileAttributes(file_name, + OS.Constants.Win.FILE_ATTRIBUTE_READONLY); + OS.File.remove(file_name); + ok(!OS.File.exists(file_name), + "test_remove_file: test file has been removed"); + } +} diff --git a/toolkit/components/osfile/tests/mochi/worker_test_osfile_shared.js b/toolkit/components/osfile/tests/mochi/worker_test_osfile_shared.js new file mode 100644 index 000000000..da82d4b0a --- /dev/null +++ b/toolkit/components/osfile/tests/mochi/worker_test_osfile_shared.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function log(text) { + dump("WORKER " + text + "\n"); +} + +function send(message) { + self.postMessage(message); +} + +function finish() { + send({kind: "finish"}); +} + +function ok(condition, description) { + send({kind: "ok", condition: !!condition, description: "" + description}); +} + +function is(a, b, description) { + let outcome = a == b; // Need to decide outcome here, as not everything can be serialized + send({kind: "is", outcome: outcome, description: "" + description, a: "" + a, b: "" + b}); +} + +function isnot(a, b, description) { + let outcome = a != b; // Need to decide outcome here, as not everything can be serialized + send({kind: "isnot", outcome: outcome, description: "" + description, a: "" + a, b: "" + b}); +} + +function info(description) { + send({kind: "info", description: "" + description}); +} diff --git a/toolkit/components/osfile/tests/mochi/worker_test_osfile_unix.js b/toolkit/components/osfile/tests/mochi/worker_test_osfile_unix.js new file mode 100644 index 000000000..9fe2d0b4e --- /dev/null +++ b/toolkit/components/osfile/tests/mochi/worker_test_osfile_unix.js @@ -0,0 +1,201 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +importScripts('worker_test_osfile_shared.js'); + +self.onmessage = function(msg) { + log("received message "+JSON.stringify(msg.data)); + self.onmessage = function(msg) { + log("ignored message "+JSON.stringify(msg.data)); + }; + test_init(); + test_getcwd(); + test_open_close(); + test_create_file(); + test_access(); + test_read_write(); + test_passing_undefined(); + finish(); +}; + +function test_init() { + info("Starting test_init"); + importScripts("resource://gre/modules/osfile.jsm"); +} + +function test_open_close() { + info("Starting test_open_close"); + is(typeof OS.Unix.File.open, "function", "OS.Unix.File.open is a function"); + let file = OS.Unix.File.open("chrome/toolkit/components/osfile/tests/mochi/worker_test_osfile_unix.js", OS.Constants.libc.O_RDONLY, 0); + isnot(file, -1, "test_open_close: opening succeeded"); + info("Close: "+OS.Unix.File.close.toSource()); + let result = OS.Unix.File.close(file); + is(result, 0, "test_open_close: close succeeded"); + + file = OS.Unix.File.open("/i do not exist", OS.Constants.libc.O_RDONLY, 0); + is(file, -1, "test_open_close: opening of non-existing file failed"); + is(ctypes.errno, OS.Constants.libc.ENOENT, "test_open_close: error is ENOENT"); +} + +function test_create_file() +{ + info("Starting test_create_file"); + let file = OS.Unix.File.open("test.tmp", OS.Constants.libc.O_RDWR + | OS.Constants.libc.O_CREAT + | OS.Constants.libc.O_TRUNC, + OS.Constants.libc.S_IRWXU); + isnot(file, -1, "test_create_file: file created"); + OS.Unix.File.close(file); +} + +function test_access() +{ + info("Starting test_access"); + let file = OS.Unix.File.open("test1.tmp", OS.Constants.libc.O_RDWR + | OS.Constants.libc.O_CREAT + | OS.Constants.libc.O_TRUNC, + OS.Constants.libc.S_IRWXU); + let result = OS.Unix.File.access("test1.tmp", OS.Constants.libc.R_OK | OS.Constants.libc.W_OK | OS.Constants.libc.X_OK | OS.Constants.libc.F_OK); + is(result, 0, "first call to access() succeeded"); + OS.Unix.File.close(file); + + file = OS.Unix.File.open("test1.tmp", OS.Constants.libc.O_WRONLY + | OS.Constants.libc.O_CREAT + | OS.Constants.libc.O_TRUNC, + OS.Constants.libc.S_IWUSR); + + info("test_access: preparing second call to access()"); + result = OS.Unix.File.access("test2.tmp", OS.Constants.libc.R_OK + | OS.Constants.libc.W_OK + | OS.Constants.libc.X_OK + | OS.Constants.libc.F_OK); + is(result, -1, "test_access: second call to access() failed as expected"); + is(ctypes.errno, OS.Constants.libc.ENOENT, "This is the correct error"); + OS.Unix.File.close(file); +} + +function test_getcwd() +{ + let array = new (ctypes.ArrayType(ctypes.char, 32768))(); + let path = OS.Unix.File.getcwd(array, array.length); + if (ctypes.char.ptr(path).isNull()) { + ok(false, "test_get_cwd: getcwd returned null, errno: " + ctypes.errno); + } + let path2; + if (OS.Unix.File.get_current_dir_name) { + path2 = OS.Unix.File.get_current_dir_name(); + } else { + path2 = OS.Unix.File.getwd_auto(null); + } + if (ctypes.char.ptr(path2).isNull()) { + ok(false, "test_get_cwd: getwd_auto/get_current_dir_name returned null, errno: " + ctypes.errno); + } + is(path.readString(), path2.readString(), "test_get_cwd: getcwd and getwd return the same path"); +} + +function test_read_write() +{ + let output_name = "osfile_copy.tmp"; + // Copy file + let input = OS.Unix.File.open( + "chrome/toolkit/components/osfile/tests/mochi/worker_test_osfile_unix.js", + OS.Constants.libc.O_RDONLY, 0); + isnot(input, -1, "test_read_write: input file opened"); + let output = OS.Unix.File.open("osfile_copy.tmp", OS.Constants.libc.O_RDWR + | OS.Constants.libc.O_CREAT + | OS.Constants.libc.O_TRUNC, + OS.Constants.libc.S_IRWXU); + isnot(output, -1, "test_read_write: output file opened"); + + let array = new (ctypes.ArrayType(ctypes.char, 4096))(); + let bytes = -1; + let total = 0; + while (true) { + bytes = OS.Unix.File.read(input, array, 4096); + ok(bytes != undefined, "test_read_write: bytes is defined"); + isnot(bytes, -1, "test_read_write: no read error"); + let write_from = 0; + if (bytes == 0) { + break; + } + while (bytes > 0) { + let ptr = array.addressOfElement(write_from); + // Note: |write| launches an exception in case of error + let written = OS.Unix.File.write(output, array, bytes); + isnot(written, -1, "test_read_write: no write error"); + write_from += written; + bytes -= written; + } + total += write_from; + } + info("test_read_write: copy complete " + total); + + // Compare files + let result; + info("SEEK_SET: " + OS.Constants.libc.SEEK_SET); + info("Input: " + input + "(" + input.toSource() + ")"); + info("Output: " + output + "(" + output.toSource() + ")"); + result = OS.Unix.File.lseek(input, 0, OS.Constants.libc.SEEK_SET); + info("Result of lseek: " + result); + isnot(result, -1, "test_read_write: input seek succeeded " + ctypes.errno); + result = OS.Unix.File.lseek(output, 0, OS.Constants.libc.SEEK_SET); + isnot(result, -1, "test_read_write: output seek succeeded " + ctypes.errno); + + let array2 = new (ctypes.ArrayType(ctypes.char, 4096))(); + let bytes2 = -1; + let pos = 0; + while (true) { + bytes = OS.Unix.File.read(input, array, 4096); + isnot(bytes, -1, "test_read_write: input read succeeded"); + bytes2 = OS.Unix.File.read(output, array2, 4096); + isnot(bytes, -1, "test_read_write: output read succeeded"); + is(bytes > 0, bytes2 > 0, "Both files contain data or neither does "+bytes+", "+bytes2); + if (bytes == 0) { + break; + } + if (bytes != bytes2) { + // This would be surprising, but theoretically possible with a + // remote file system, I believe. + bytes = Math.min(bytes, bytes2); + pos += bytes; + result = OS.Unix.File.lseek(input, pos, OS.Constants.libc.SEEK_SET); + isnot(result, -1, "test_read_write: input seek succeeded"); + result = OS.Unix.File.lseek(output, pos, OS.Constants.libc.SEEK_SET); + isnot(result, -1, "test_read_write: output seek succeeded"); + } else { + pos += bytes; + } + for (let i = 0; i < bytes; ++i) { + if (array[i] != array2[i]) { + ok(false, "Files do not match at position " + i + + " ("+array[i] + "/"+array2[i] + ")"); + } + } + } + info("test_read_write test complete"); + result = OS.Unix.File.close(input); + isnot(result, -1, "test_read_write: input close succeeded"); + result = OS.Unix.File.close(output); + isnot(result, -1, "test_read_write: output close succeeded"); + result = OS.Unix.File.unlink(output_name); + isnot(result, -1, "test_read_write: input remove succeeded"); + info("test_read_write cleanup complete"); +} + +function test_passing_undefined() +{ + info("Testing that an exception gets thrown when an FFI function is passed undefined"); + let exceptionRaised = false; + + try { + let file = OS.Unix.File.open(undefined, OS.Constants.libc.O_RDWR + | OS.Constants.libc.O_CREAT + | OS.Constants.libc.O_TRUNC, + OS.Constants.libc.S_IRWXU); + } catch(e if e instanceof TypeError && e.message.indexOf("open") > -1) { + exceptionRaised = true; + } + + ok(exceptionRaised, "test_passing_undefined: exception gets thrown") +} + diff --git a/toolkit/components/osfile/tests/mochi/worker_test_osfile_win.js b/toolkit/components/osfile/tests/mochi/worker_test_osfile_win.js new file mode 100644 index 000000000..f41fdecfe --- /dev/null +++ b/toolkit/components/osfile/tests/mochi/worker_test_osfile_win.js @@ -0,0 +1,211 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +importScripts('worker_test_osfile_shared.js'); + +self.onmessage = function(msg) { + self.onmessage = function(msg) { + log("ignored message "+JSON.stringify(msg.data)); + }; + + test_init(); + test_GetCurrentDirectory(); + test_OpenClose(); + test_CreateFile(); + test_ReadWrite(); + test_passing_undefined(); + finish(); +}; + +function test_init() { + info("Starting test_init"); + importScripts("resource://gre/modules/osfile.jsm"); +} + +function test_OpenClose() { + info("Starting test_OpenClose"); + is(typeof OS.Win.File.CreateFile, "function", "OS.Win.File.CreateFile is a function"); + is(OS.Win.File.CloseHandle(OS.Constants.Win.INVALID_HANDLE_VALUE), true, "CloseHandle returns true given the invalid handle"); + is(OS.Win.File.FindClose(OS.Constants.Win.INVALID_HANDLE_VALUE), true, "FindClose returns true given the invalid handle"); + isnot(OS.Constants.Win.GENERIC_READ, undefined, "GENERIC_READ exists"); + isnot(OS.Constants.Win.FILE_SHARE_READ, undefined, "FILE_SHARE_READ exists"); + isnot(OS.Constants.Win.FILE_ATTRIBUTE_NORMAL, undefined, "FILE_ATTRIBUTE_NORMAL exists"); + let file = OS.Win.File.CreateFile( + "chrome\\toolkit\\components\\osfile\\tests\\mochi\\worker_test_osfile_win.js", + OS.Constants.Win.GENERIC_READ, + 0, + null, + OS.Constants.Win.OPEN_EXISTING, + 0, + null); + info("test_OpenClose: Passed open"); + isnot(file, OS.Constants.Win.INVALID_HANDLE_VALUE, "test_OpenClose: file opened"); + let result = OS.Win.File.CloseHandle(file); + isnot(result, 0, "test_OpenClose: close succeeded"); + + file = OS.Win.File.CreateFile( + "\\I do not exist", + OS.Constants.Win.GENERIC_READ, + OS.Constants.Win.FILE_SHARE_READ, + null, + OS.Constants.Win.OPEN_EXISTING, + OS.Constants.Win.FILE_ATTRIBUTE_NORMAL, + null); + is(file, OS.Constants.Win.INVALID_HANDLE_VALUE, "test_OpenClose: cannot open non-existing file"); + is(ctypes.winLastError, OS.Constants.Win.ERROR_FILE_NOT_FOUND, "test_OpenClose: error is ERROR_FILE_NOT_FOUND"); +} + +function test_CreateFile() +{ + info("Starting test_CreateFile"); + let file = OS.Win.File.CreateFile( + "test.tmp", + OS.Constants.Win.GENERIC_READ | OS.Constants.Win.GENERIC_WRITE, + OS.Constants.Win.FILE_SHARE_READ | OS.Constants.FILE_SHARE_WRITE, + null, + OS.Constants.Win.CREATE_ALWAYS, + OS.Constants.Win.FILE_ATTRIBUTE_NORMAL, + null); + isnot(file, OS.Constants.Win.INVALID_HANDLE_VALUE, "test_CreateFile: opening succeeded"); + let result = OS.Win.File.CloseHandle(file); + isnot(result, 0, "test_CreateFile: close succeeded"); +} + +function test_GetCurrentDirectory() +{ + let array = new (ctypes.ArrayType(ctypes.char16_t, 4096))(); + let result = OS.Win.File.GetCurrentDirectory(4096, array); + ok(result < array.length, "test_GetCurrentDirectory: length sufficient"); + ok(result > 0, "test_GetCurrentDirectory: length != 0"); +} + +function test_ReadWrite() +{ + info("Starting test_ReadWrite"); + let output_name = "osfile_copy.tmp"; + // Copy file + let input = OS.Win.File.CreateFile( + "chrome\\toolkit\\components\\osfile\\tests\\mochi\\worker_test_osfile_win.js", + OS.Constants.Win.GENERIC_READ, + 0, + null, + OS.Constants.Win.OPEN_EXISTING, + 0, + null); + isnot(input, OS.Constants.Win.INVALID_HANDLE_VALUE, "test_ReadWrite: input file opened"); + let output = OS.Win.File.CreateFile( + "osfile_copy.tmp", + OS.Constants.Win.GENERIC_READ | OS.Constants.Win.GENERIC_WRITE, + 0, + null, + OS.Constants.Win.CREATE_ALWAYS, + OS.Constants.Win.FILE_ATTRIBUTE_NORMAL, + null); + isnot(output, OS.Constants.Win.INVALID_HANDLE_VALUE, "test_ReadWrite: output file opened"); + let array = new (ctypes.ArrayType(ctypes.char, 4096))(); + let bytes_read = new ctypes.uint32_t(0); + let bytes_read_ptr = bytes_read.address(); + log("We have a pointer for bytes read: "+bytes_read_ptr); + let bytes_written = new ctypes.uint32_t(0); + let bytes_written_ptr = bytes_written.address(); + log("We have a pointer for bytes written: "+bytes_written_ptr); + log("test_ReadWrite: buffer and pointers ready"); + let result; + while (true) { + log("test_ReadWrite: reading"); + result = OS.Win.File.ReadFile(input, array, 4096, bytes_read_ptr, null); + isnot (result, 0, "test_ReadWrite: read success"); + let write_from = 0; + let bytes_left = bytes_read; + log("test_ReadWrite: read chunk complete " + bytes_left.value); + if (bytes_left.value == 0) { + break; + } + while (bytes_left.value > 0) { + log("test_ReadWrite: writing "+bytes_left.value); + let ptr = array.addressOfElement(write_from); + // Note: |WriteFile| launches an exception in case of error + result = OS.Win.File.WriteFile(output, array, bytes_left, bytes_written_ptr, null); + isnot (result, 0, "test_ReadWrite: write success"); + write_from += bytes_written; + bytes_left -= bytes_written; + } + } + info("test_ReadWrite: copy complete"); + + // Compare files + result = OS.Win.File.SetFilePointer(input, 0, null, OS.Constants.Win.FILE_BEGIN); + isnot (result, OS.Constants.Win.INVALID_SET_FILE_POINTER, "test_ReadWrite: input reset"); + + result = OS.Win.File.SetFilePointer(output, 0, null, OS.Constants.Win.FILE_BEGIN); + isnot (result, OS.Constants.Win.INVALID_SET_FILE_POINTER, "test_ReadWrite: output reset"); + + let array2 = new (ctypes.ArrayType(ctypes.char, 4096))(); + let bytes_read2 = new ctypes.uint32_t(0); + let bytes_read2_ptr = bytes_read2.address(); + let pos = 0; + while (true) { + result = OS.Win.File.ReadFile(input, array, 4096, bytes_read_ptr, null); + isnot(result, 0, "test_ReadWrite: input read succeeded"); + + result = OS.Win.File.ReadFile(output, array2, 4096, bytes_read2_ptr, null); + isnot(result, 0, "test_ReadWrite: output read succeeded"); + + is(bytes_read.value > 0, bytes_read2.value > 0, + "Both files contain data or neither does " + bytes_read.value + ", " + bytes_read2.value); + if (bytes_read.value == 0) { + break; + } + let bytes; + if (bytes_read.value != bytes_read2.value) { + // This would be surprising, but theoretically possible with a + // remote file system, I believe. + bytes = Math.min(bytes_read.value, bytes_read2.value); + pos += bytes; + result = OS.Win.File.SetFilePointer(input, pos, null, OS.Constants.Win.FILE_BEGIN); + isnot(result, 0, "test_ReadWrite: input seek succeeded"); + + result = OS.Win.File.SetFilePointer(output, pos, null, OS.Constants.Win.FILE_BEGIN); + isnot(result, 0, "test_ReadWrite: output seek succeeded"); + + } else { + bytes = bytes_read.value; + pos += bytes; + } + for (let i = 0; i < bytes; ++i) { + if (array[i] != array2[i]) { + ok(false, "Files do not match at position " + i + + " ("+array[i] + "/"+array2[i] + ")"); + } + } + } + info("test_ReadWrite test complete"); + result = OS.Win.File.CloseHandle(input); + isnot(result, 0, "test_ReadWrite: inpout close succeeded"); + result = OS.Win.File.CloseHandle(output); + isnot(result, 0, "test_ReadWrite: outpout close succeeded"); + result = OS.Win.File.DeleteFile(output_name); + isnot(result, 0, "test_ReadWrite: output remove succeeded"); + info("test_ReadWrite cleanup complete"); +} + +function test_passing_undefined() +{ + info("Testing that an exception gets thrown when an FFI function is passed undefined"); + let exceptionRaised = false; + + try { + let file = OS.Win.File.CreateFile( + undefined, + OS.Constants.Win.GENERIC_READ, + 0, + null, + OS.Constants.Win.OPEN_EXISTING, + 0, + null); + } catch(e if e instanceof TypeError && e.message.indexOf("CreateFile") > -1) { + exceptionRaised = true; + } + + ok(exceptionRaised, "test_passing_undefined: exception gets thrown") +} diff --git a/toolkit/components/osfile/tests/xpcshell/.eslintrc.js b/toolkit/components/osfile/tests/xpcshell/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/toolkit/components/osfile/tests/xpcshell/head.js b/toolkit/components/osfile/tests/xpcshell/head.js new file mode 100644 index 000000000..eef29962a --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/head.js @@ -0,0 +1,99 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var {utils: Cu, interfaces: Ci} = Components; + +var {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); + +// Bug 1014484 can only be reproduced by loading OS.File first from the +// CommonJS loader, so we do not want OS.File to be loaded eagerly for +// all the tests in this directory. +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", + "resource://gre/modules/NetUtil.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); + +var {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); +var {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); + +Services.prefs.setBoolPref("toolkit.osfile.log", true); + +/** + * As add_task, but execute the test both with native operations and + * without. + */ +function add_test_pair(generator) { + add_task(function*() { + do_print("Executing test " + generator.name + " with native operations"); + Services.prefs.setBoolPref("toolkit.osfile.native", true); + return Task.spawn(generator); + }); + add_task(function*() { + do_print("Executing test " + generator.name + " without native operations"); + Services.prefs.setBoolPref("toolkit.osfile.native", false); + return Task.spawn(generator); + }); +} + +/** + * Fetch asynchronously the contents of a file using xpcom. + * + * Used for comparing xpcom-based results to os.file-based results. + * + * @param {string} path The _absolute_ path to the file. + * @return {promise} + * @resolves {string} The contents of the file. + */ +function reference_fetch_file(path, test) { + do_print("Fetching file " + path); + let deferred = Promise.defer(); + let file = new FileUtils.File(path); + NetUtil.asyncFetch({ + uri: NetUtil.newURI(file), + loadUsingSystemPrincipal: true + }, function(stream, status) { + if (!Components.isSuccessCode(status)) { + deferred.reject(status); + return; + } + let result, reject; + try { + result = NetUtil.readInputStreamToString(stream, stream.available()); + } catch (x) { + reject = x; + } + stream.close(); + if (reject) { + deferred.reject(reject); + } else { + deferred.resolve(result); + } + }); + + return deferred.promise; +}; + +/** + * Compare asynchronously the contents two files using xpcom. + * + * Used for comparing xpcom-based results to os.file-based results. + * + * @param {string} a The _absolute_ path to the first file. + * @param {string} b The _absolute_ path to the second file. + * + * @resolves {null} + */ +function reference_compare_files(a, b, test) { + return Task.spawn(function*() { + do_print("Comparing files " + a + " and " + b); + let a_contents = yield reference_fetch_file(a, test); + let b_contents = yield reference_fetch_file(b, test); + do_check_eq(a_contents, b_contents); + }); +}; diff --git a/toolkit/components/osfile/tests/xpcshell/test_available_free_space.js b/toolkit/components/osfile/tests/xpcshell/test_available_free_space.js new file mode 100644 index 000000000..08e67763b --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_available_free_space.js @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +do_register_cleanup(function() { + Services.prefs.setBoolPref("toolkit.osfile.log", false); +}); + +function run_test() { + Services.prefs.setBoolPref("toolkit.osfile.log", true); + + run_next_test(); +} + +/** + * Test OS.File.getAvailableFreeSpace + */ +add_task(function() { + // Set up profile. We will use profile path to query for available free + // space. + do_get_profile(); + + let dir = OS.Constants.Path.profileDir; + + // Sanity checking for the test + do_check_true((yield OS.File.exists(dir))); + + // Query for available bytes for user + let availableBytes = yield OS.File.getAvailableFreeSpace(dir); + + do_check_true(!!availableBytes); + do_check_true(availableBytes > 0); +}); diff --git a/toolkit/components/osfile/tests/xpcshell/test_compression.js b/toolkit/components/osfile/tests/xpcshell/test_compression.js new file mode 100644 index 000000000..b40235615 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_compression.js @@ -0,0 +1,98 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); + +function run_test() { + do_test_pending(); + run_next_test(); +} + +add_task(function test_compress_lz4() { + let path = OS.Path.join(OS.Constants.Path.tmpDir, "compression.lz"); + let length = 1024; + let array = new Uint8Array(length); + for (let i = 0; i < array.byteLength; ++i) { + array[i] = i; + } + let arrayAsString = Array.prototype.join.call(array); + + do_print("Writing data with lz4 compression"); + let bytes = yield OS.File.writeAtomic(path, array, { compression: "lz4" }); + do_print("Compressed " + length + " bytes into " + bytes); + + do_print("Reading back with lz4 decompression"); + let decompressed = yield OS.File.read(path, { compression: "lz4" }); + do_print("Decompressed into " + decompressed.byteLength + " bytes"); + do_check_eq(arrayAsString, Array.prototype.join.call(decompressed)); +}); + +add_task(function test_uncompressed() { + do_print("Writing data without compression"); + let path = OS.Path.join(OS.Constants.Path.tmpDir, "no_compression.tmp"); + let array = new Uint8Array(1024); + for (let i = 0; i < array.byteLength; ++i) { + array[i] = i; + } + let bytes = yield OS.File.writeAtomic(path, array); // No compression + + let exn; + // Force decompression, reading should fail + try { + yield OS.File.read(path, { compression: "lz4" }); + } catch (ex) { + exn = ex; + } + do_check_true(!!exn); + // Check the exception message (and that it contains the file name) + do_check_true(exn.message.indexOf(`Invalid header (no magic number) - Data: ${ path }`) != -1); +}); + +add_task(function test_no_header() { + let path = OS.Path.join(OS.Constants.Path.tmpDir, "no_header.tmp"); + let array = new Uint8Array(8).fill(0,0); // Small array with no header + + do_print("Writing data with no header"); + + let bytes = yield OS.File.writeAtomic(path, array); // No compression + let exn; + // Force decompression, reading should fail + try { + yield OS.File.read(path, { compression: "lz4" }); + } catch (ex) { + exn = ex; + } + do_check_true(!!exn); + // Check the exception message (and that it contains the file name) + do_check_true(exn.message.indexOf(`Buffer is too short (no header) - Data: ${ path }`) != -1); +}); + +add_task(function test_invalid_content() { + let path = OS.Path.join(OS.Constants.Path.tmpDir, "invalid_content.tmp"); + let arr1 = new Uint8Array([109, 111, 122, 76, 122, 52, 48, 0]); + let arr2 = new Uint8Array(248).fill(1,0); + + let array = new Uint8Array(arr1.length + arr2.length); + array.set(arr1); + array.set(arr2, arr1.length); + + do_print("Writing invalid data (with a valid header and only ones after that)"); + + let bytes = yield OS.File.writeAtomic(path, array); // No compression + let exn; + // Force decompression, reading should fail + try { + yield OS.File.read(path, { compression: "lz4" }); + } catch (ex) { + exn = ex; + } + do_check_true(!!exn); + // Check the exception message (and that it contains the file name) + do_check_true(exn.message.indexOf(`Invalid content: Decompression stopped at 0 - Data: ${ path }`) != -1); +}); + +add_task(function() { + do_test_finished(); +});
\ No newline at end of file diff --git a/toolkit/components/osfile/tests/xpcshell/test_constants.js b/toolkit/components/osfile/tests/xpcshell/test_constants.js new file mode 100644 index 000000000..e92f33ab8 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_constants.js @@ -0,0 +1,31 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm", this); + +function run_test() { + run_next_test(); +} + +// Test that OS.Constants is defined correctly. +add_task(function* check_definition() { + do_check_true(OS.Constants!=null); + do_check_true(!!OS.Constants.Win || !!OS.Constants.libc); + do_check_true(OS.Constants.Path!=null); + do_check_true(OS.Constants.Sys!=null); + //check system name + if (OS.Constants.Sys.Name == "Gonk") { + // Services.appinfo.OS doesn't know the difference between Gonk and Android + do_check_eq(Services.appinfo.OS, "Android"); + } else { + do_check_eq(Services.appinfo.OS, OS.Constants.Sys.Name); + } + + //check if using DEBUG build + if (Components.classes["@mozilla.org/xpcom/debug;1"].getService(Components.interfaces.nsIDebug2).isDebugBuild == true) { + do_check_true(OS.Constants.Sys.DEBUG); + } else { + do_check_true(typeof(OS.Constants.Sys.DEBUG) == 'undefined'); + } +}); diff --git a/toolkit/components/osfile/tests/xpcshell/test_creationDate.js b/toolkit/components/osfile/tests/xpcshell/test_creationDate.js new file mode 100644 index 000000000..9c4fa1dfc --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_creationDate.js @@ -0,0 +1,31 @@ +"use strict"; + +function run_test() { + do_test_pending(); + run_next_test(); +} + +/** + * Test to ensure that deprecation warning is issued on use + * of creationDate. + */ +add_task(function test_deprecatedCreationDate () { + let deferred = Promise.defer(); + let consoleListener = { + observe: function (aMessage) { + if(aMessage.message.indexOf("Field 'creationDate' is deprecated.") > -1) { + do_print("Deprecation message printed"); + do_check_true(true); + Services.console.unregisterListener(consoleListener); + deferred.resolve(); + } + } + }; + let currentDir = yield OS.File.getCurrentDirectory(); + let path = OS.Path.join(currentDir, "test_creationDate.js"); + + Services.console.registerListener(consoleListener); + (yield OS.File.stat(path)).creationDate; +}); + +add_task(do_test_finished); diff --git a/toolkit/components/osfile/tests/xpcshell/test_duration.js b/toolkit/components/osfile/tests/xpcshell/test_duration.js new file mode 100644 index 000000000..305c03da8 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_duration.js @@ -0,0 +1,91 @@ +var {OS} = Components.utils.import("resource://gre/modules/osfile.jsm", {}); +var {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {}); + +/** + * Test optional duration reporting that can be used for telemetry. + */ +add_task(function* duration() { + Services.prefs.setBoolPref("toolkit.osfile.log", true); + // Options structure passed to a OS.File copy method. + let copyOptions = { + // This field should be overridden with the actual duration + // measurement. + outExecutionDuration: null + }; + let currentDir = yield OS.File.getCurrentDirectory(); + let pathSource = OS.Path.join(currentDir, "test_duration.js"); + let copyFile = pathSource + ".bak"; + function testOptions(options, name) { + do_print("Checking outExecutionDuration for operation: " + name); + do_print(name + ": Gathered method duration time: " + + options.outExecutionDuration + "ms"); + // Making sure that duration was updated. + do_check_eq(typeof options.outExecutionDuration, "number"); + do_check_true(options.outExecutionDuration >= 0); + }; + // Testing duration of OS.File.copy. + yield OS.File.copy(pathSource, copyFile, copyOptions); + testOptions(copyOptions, "OS.File.copy"); + yield OS.File.remove(copyFile); + + // Trying an operation where options are cloned. + let pathDest = OS.Path.join(OS.Constants.Path.tmpDir, + "osfile async test read writeAtomic.tmp"); + let tmpPath = pathDest + ".tmp"; + let readOptions = { + outExecutionDuration: null + }; + let contents = yield OS.File.read(pathSource, undefined, readOptions); + testOptions(readOptions, "OS.File.read"); + // Options structure passed to a OS.File writeAtomic method. + let writeAtomicOptions = { + // This field should be first initialized with the actual + // duration measurement then progressively incremented. + outExecutionDuration: null, + tmpPath: tmpPath + }; + yield OS.File.writeAtomic(pathDest, contents, writeAtomicOptions); + testOptions(writeAtomicOptions, "OS.File.writeAtomic"); + yield OS.File.remove(pathDest); + + do_print("Ensuring that we can use outExecutionDuration to accumulate durations"); + + let ARBITRARY_BASE_DURATION = 5; + copyOptions = { + // This field should now be incremented with the actual duration + // measurement. + outExecutionDuration: ARBITRARY_BASE_DURATION + }; + let backupDuration = ARBITRARY_BASE_DURATION; + // Testing duration of OS.File.copy. + yield OS.File.copy(pathSource, copyFile, copyOptions); + + do_check_true(copyOptions.outExecutionDuration >= backupDuration); + + backupDuration = copyOptions.outExecutionDuration; + yield OS.File.remove(copyFile, copyOptions); + do_check_true(copyOptions.outExecutionDuration >= backupDuration); + + // Trying an operation where options are cloned. + // Options structure passed to a OS.File writeAtomic method. + writeAtomicOptions = { + // This field should be overridden with the actual duration + // measurement. + outExecutionDuration: copyOptions.outExecutionDuration, + tmpPath: tmpPath + }; + backupDuration = writeAtomicOptions.outExecutionDuration; + + yield OS.File.writeAtomic(pathDest, contents, writeAtomicOptions); + do_check_true(copyOptions.outExecutionDuration >= backupDuration); + OS.File.remove(pathDest); + + // Testing an operation that doesn't take arguments at all + let file = yield OS.File.open(pathSource); + yield file.stat(); + yield file.close(); +}); + +function run_test() { + run_next_test(); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_exception.js b/toolkit/components/osfile/tests/xpcshell/test_exception.js new file mode 100644 index 000000000..1282adb3e --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_exception.js @@ -0,0 +1,89 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that functions throw the appropriate exceptions. + */ + +"use strict"; + +var EXISTING_FILE = do_get_file("xpcshell.ini").path; + + +// Tests on |open| + +add_test_pair(function test_typeerror() { + let exn; + try { + let fd = yield OS.File.open("/tmp", {no_such_key: 1}); + do_print("Fd: " + fd); + } catch (ex) { + exn = ex; + } + do_print("Exception: " + exn); + do_check_true(exn.constructor.name == "TypeError"); +}); + +// Tests on |read| + +add_test_pair(function* test_bad_encoding() { + do_print("Testing with a wrong encoding"); + try { + yield OS.File.read(EXISTING_FILE, { encoding: "baby-speak-encoded" }); + do_throw("Should have thrown with an ex.becauseInvalidArgument"); + } catch (ex if ex.becauseInvalidArgument) { + do_print("Wrong encoding caused the correct exception"); + } + + try { + yield OS.File.read(EXISTING_FILE, { encoding: 4 }); + do_throw("Should have thrown a TypeError"); + } catch (ex if ex.constructor.name == "TypeError") { + // Note that TypeError doesn't carry across compartments + do_print("Non-string encoding caused the correct exception"); + } + }); + +add_test_pair(function* test_bad_compression() { + do_print("Testing with a non-existing compression"); + try { + yield OS.File.read(EXISTING_FILE, { compression: "mmmh-crunchy" }); + do_throw("Should have thrown with an ex.becauseInvalidArgument"); + } catch (ex if ex.becauseInvalidArgument) { + do_print("Wrong encoding caused the correct exception"); + } + + do_print("Testing with a bad type for option compression"); + try { + yield OS.File.read(EXISTING_FILE, { compression: 5 }); + do_throw("Should have thrown a TypeError"); + } catch (ex if ex.constructor.name == "TypeError") { + // Note that TypeError doesn't carry across compartments + do_print("Non-string encoding caused the correct exception"); + } +}); + +add_test_pair(function* test_bad_bytes() { + do_print("Testing with a bad type for option bytes"); + try { + yield OS.File.read(EXISTING_FILE, { bytes: "five" }); + do_throw("Should have thrown a TypeError"); + } catch (ex if ex.constructor.name == "TypeError") { + // Note that TypeError doesn't carry across compartments + do_print("Non-number bytes caused the correct exception"); + } +}); + +add_test_pair(function* read_non_existent() { + do_print("Testing with a non-existent file"); + try { + yield OS.File.read("I/do/not/exist"); + do_throw("Should have thrown with an ex.becauseNoSuchFile"); + } catch (ex if ex.becauseNoSuchFile) { + do_print("Correct exceptions"); + } +}); + +function run_test() { + run_next_test(); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_file_URL_conversion.js b/toolkit/components/osfile/tests/xpcshell/test_file_URL_conversion.js new file mode 100644 index 000000000..3ec42065b --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_file_URL_conversion.js @@ -0,0 +1,114 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + Components.utils.import("resource://gre/modules/Services.jsm"); + Components.utils.import("resource://gre/modules/osfile.jsm"); + Components.utils.import("resource://gre/modules/FileUtils.jsm"); + + let isWindows = ("@mozilla.org/windows-registry-key;1" in Components.classes); + + // Test cases for filePathToURI + let paths = isWindows ? [ + 'C:\\', + 'C:\\test', + 'C:\\test\\', + 'C:\\test%2f', + 'C:\\test\\test\\test', + 'C:\\test;+%', + 'C:\\test?action=index\\', + 'C:\\test\ test', + '\\\\C:\\a\\b\\c', + '\\\\Server\\a\\b\\c', + + // note that per http://support.microsoft.com/kb/177506 (under more info), + // the following characters are allowed on Windows: + 'C:\\char^', + 'C:\\char&', + 'C:\\char\'', + 'C:\\char@', + 'C:\\char{', + 'C:\\char}', + 'C:\\char[', + 'C:\\char]', + 'C:\\char,', + 'C:\\char$', + 'C:\\char=', + 'C:\\char!', + 'C:\\char-', + 'C:\\char#', + 'C:\\char(', + 'C:\\char)', + 'C:\\char%', + 'C:\\char.', + 'C:\\char+', + 'C:\\char~', + 'C:\\char_' + ] : [ + '/', + '/test', + '/test/', + '/test%2f', + '/test/test/test', + '/test;+%', + '/test?action=index/', + '/test\ test', + '/punctuation/;,/?:@&=+$-_.!~*\'()[]"#', + '/CasePreserving' + ]; + + // some additional URIs to test, beyond those generated from paths + let uris = isWindows ? [ + 'file:///C:/test/', + 'file://localhost/C:/test', + 'file:///c:/test/test.txt', + //'file:///C:/foo%2f', // trailing, encoded slash + 'file:///C:/%3f%3F', + 'file:///C:/%3b%3B', + 'file:///C:/%3c%3C', // not one of the special-cased ? or ; + 'file:///C:/%78', // 'x', not usually uri encoded + 'file:///C:/test#frag', // a fragment identifier + 'file:///C:/test?action=index' // an actual query component + ] : [ + 'file:///test/', + 'file://localhost/test', + 'file:///test/test.txt', + 'file:///foo%2f', // trailing, encoded slash + 'file:///%3f%3F', + 'file:///%3b%3B', + 'file:///%3c%3C', // not one of the special-cased ? or ; + 'file:///%78', // 'x', not usually uri encoded + 'file:///test#frag', // a fragment identifier + 'file:///test?action=index' // an actual query component + ]; + + for (let path of paths) { + // convert that to a uri using FileUtils and Services, which toFileURI is trying to model + let file = FileUtils.File(path); + let uri = Services.io.newFileURI(file).spec; + do_check_eq(uri, OS.Path.toFileURI(path)); + + // keep the resulting URI to try the reverse, except for "C:\" for which the + // behavior of nsIFileURL and OS.File is inconsistent + if (path != "C:\\") { + uris.push(uri); + } + } + + for (let uri of uris) { + // convert URIs to paths with nsIFileURI, which fromFileURI is trying to model + let path = Services.io.newURI(uri, null, null).QueryInterface(Components.interfaces.nsIFileURL).file.path; + do_check_eq(path, OS.Path.fromFileURI(uri)); + } + + // check that non-file URLs aren't allowed + let thrown = false; + try { + OS.Path.fromFileURI('http://test.com') + } catch (e) { + do_check_eq(e.message, "fromFileURI expects a file URI"); + thrown = true; + } + do_check_true(thrown); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_loader.js b/toolkit/components/osfile/tests/xpcshell/test_loader.js new file mode 100644 index 000000000..dcfa819be --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_loader.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Test that OS.File can be loaded using the CommonJS loader. + */ + +var { Loader } = Components.utils.import('resource://gre/modules/commonjs/toolkit/loader.js', {}); + +function run_test() { + run_next_test(); +} + + +add_task(function*() { + let dataDir = Services.io.newFileURI(do_get_file("test_loader/", true)).spec + "/"; + let loader = Loader.Loader({ + paths: {'': dataDir } + }); + + let require = Loader.Require(loader, Loader.Module('module_test_loader', 'foo')); + do_print("Require is ready"); + try { + require('module_test_loader'); + } catch (error) { + dump('Bootstrap error: ' + + (error.message ? error.message : String(error)) + '\n' + + (error.stack || error.fileName + ': ' + error.lineNumber) + '\n'); + + throw error; + } + + do_print("Require has worked"); +}); + diff --git a/toolkit/components/osfile/tests/xpcshell/test_loader/module_test_loader.js b/toolkit/components/osfile/tests/xpcshell/test_loader/module_test_loader.js new file mode 100644 index 000000000..18356d6ad --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_loader/module_test_loader.js @@ -0,0 +1,9 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Load OS.File from a module loaded with the CommonJS/addon-sdk loader + +var {Cu} = require("chrome"); +Cu.import('resource://gre/modules/osfile.jsm'); diff --git a/toolkit/components/osfile/tests/xpcshell/test_logging.js b/toolkit/components/osfile/tests/xpcshell/test_logging.js new file mode 100644 index 000000000..133909e0b --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_logging.js @@ -0,0 +1,74 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +/** + * Tests logging by passing OS.Shared.LOG both an object with its own + * toString method, and one with the default. + */ +function run_test() { + do_test_pending(); + let messageCount = 0; + + do_print("Test starting"); + + // Create a console listener. + let consoleListener = { + observe: function (aMessage) { + //Ignore unexpected messages. + if (!(aMessage instanceof Components.interfaces.nsIConsoleMessage)) { + return; + } + // This is required, as printing to the |Services.console| + // while in the observe function causes an exception. + do_execute_soon(function() { + do_print("Observing message " + aMessage.message); + if (aMessage.message.indexOf("TEST OS") < 0) { + return; + } + + ++messageCount; + if(messageCount == 1) { + do_check_eq(aMessage.message, "TEST OS {\"name\":\"test\"}\n"); + } + if(messageCount == 2) { + do_check_eq(aMessage.message, "TEST OS name is test\n"); + toggleConsoleListener(false); + do_test_finished(); + } + }); + } + }; + + // Set/Unset the console listener. + function toggleConsoleListener (pref) { + do_print("Setting console listener: " + pref); + Services.prefs.setBoolPref("toolkit.osfile.log", pref); + Services.prefs.setBoolPref("toolkit.osfile.log.redirect", pref); + Services.console[pref ? "registerListener" : "unregisterListener"]( + consoleListener); + } + + toggleConsoleListener(true); + + let objectDefault = {name: "test"}; + let CustomToString = function() { + this.name = "test"; + }; + CustomToString.prototype.toString = function() { + return "name is " + this.name; + }; + let objectCustom = new CustomToString(); + + do_print(OS.Shared.LOG.toSource()); + + do_print("Logging 1"); + OS.Shared.LOG(objectDefault); + + do_print("Logging 2"); + OS.Shared.LOG(objectCustom); + // Once both messages are observed OS.Shared.DEBUG, and OS.Shared.TEST + // are reset to false. +} + diff --git a/toolkit/components/osfile/tests/xpcshell/test_makeDir.js b/toolkit/components/osfile/tests/xpcshell/test_makeDir.js new file mode 100644 index 000000000..5b9740a7f --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_makeDir.js @@ -0,0 +1,142 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +var Path = OS.Path; +var profileDir; + +do_register_cleanup(function() { + Services.prefs.setBoolPref("toolkit.osfile.log", false); +}); + +function run_test() { + run_next_test(); +} + +/** + * Test OS.File.makeDir + */ + +add_task(function init() { + // Set up profile. We create the directory in the profile, because the profile + // is removed after every test run. + do_get_profile(); + profileDir = OS.Constants.Path.profileDir; + Services.prefs.setBoolPref("toolkit.osfile.log", true); +}); + +/** + * Basic use + */ + +add_task(function* test_basic() { + let dir = Path.join(profileDir, "directory"); + + // Sanity checking for the test + do_check_false((yield OS.File.exists(dir))); + + // Make a directory + yield OS.File.makeDir(dir); + + //check if the directory exists + yield OS.File.stat(dir); + + // Make a directory that already exists, this should succeed + yield OS.File.makeDir(dir); + + // Make a directory with ignoreExisting + yield OS.File.makeDir(dir, {ignoreExisting: true}); + + // Make a directory with ignoreExisting false + let exception = null; + try { + yield OS.File.makeDir(dir, {ignoreExisting: false}); + } catch (ex) { + exception = ex; + } + + do_check_true(!!exception); + do_check_true(exception instanceof OS.File.Error); + do_check_true(exception.becauseExists); +}); + +// Make a root directory that already exists +add_task(function* test_root() { + if (OS.Constants.Win) { + yield OS.File.makeDir("C:"); + yield OS.File.makeDir("C:\\"); + } else { + yield OS.File.makeDir("/"); + } +}); + +/** + * Creating subdirectories + */ +add_task(function test_option_from() { + let dir = Path.join(profileDir, "a", "b", "c"); + + // Sanity checking for the test + do_check_false((yield OS.File.exists(dir))); + + // Make a directory + yield OS.File.makeDir(dir, {from: profileDir}); + + //check if the directory exists + yield OS.File.stat(dir); + + // Make a directory that already exists, this should succeed + yield OS.File.makeDir(dir); + + // Make a directory with ignoreExisting + yield OS.File.makeDir(dir, {ignoreExisting: true}); + + // Make a directory with ignoreExisting false + let exception = null; + try { + yield OS.File.makeDir(dir, {ignoreExisting: false}); + } catch (ex) { + exception = ex; + } + + do_check_true(!!exception); + do_check_true(exception instanceof OS.File.Error); + do_check_true(exception.becauseExists); + + // Make a directory without |from| and fail + let dir2 = Path.join(profileDir, "g", "h", "i"); + exception = null; + try { + yield OS.File.makeDir(dir2); + } catch (ex) { + exception = ex; + } + + do_check_true(!!exception); + do_check_true(exception instanceof OS.File.Error); + do_check_true(exception.becauseNoSuchFile); + + // Test edge cases on paths + + let dir3 = Path.join(profileDir, "d", "", "e", "f"); + do_check_false((yield OS.File.exists(dir3))); + yield OS.File.makeDir(dir3, {from: profileDir}); + do_check_true((yield OS.File.exists(dir3))); + + let dir4; + if (OS.Constants.Win) { + // Test that we can create a directory recursively even + // if we have too many "\\". + dir4 = profileDir + "\\\\g"; + } else { + dir4 = profileDir + "////g"; + } + do_check_false((yield OS.File.exists(dir4))); + yield OS.File.makeDir(dir4, {from: profileDir}); + do_check_true((yield OS.File.exists(dir4))); +}); diff --git a/toolkit/components/osfile/tests/xpcshell/test_open.js b/toolkit/components/osfile/tests/xpcshell/test_open.js new file mode 100644 index 000000000..78772ad09 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_open.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); + +function run_test() { + run_next_test(); +} + +/** + * Test OS.File.open for reading: + * - with an existing file (should succeed); + * - with a non-existing file (should fail); + * - with inconsistent arguments (should fail). + */ +add_task(function() { + // Attempt to open a file that does not exist, ensure that it yields the + // appropriate error. + try { + let fd = yield OS.File.open(OS.Path.join(".", "This file does not exist")); + do_check_true(false, "File opening 1 succeeded (it should fail)"); + } catch (err if err instanceof OS.File.Error && err.becauseNoSuchFile) { + do_print("File opening 1 failed " + err); + } + + // Attempt to open a file with the wrong args, so that it fails before + // serialization, ensure that it yields the appropriate error. + do_print("Attempting to open a file with wrong arguments"); + try { + let fd = yield OS.File.open(1, 2, 3); + do_check_true(false, "File opening 2 succeeded (it should fail)" + fd); + } catch (err) { + do_print("File opening 2 failed " + err); + do_check_false(err instanceof OS.File.Error, + "File opening 2 returned something that is not a file error"); + do_check_true(err.constructor.name == "TypeError", + "File opening 2 returned a TypeError"); + } + + // Attempt to open a file correctly + do_print("Attempting to open a file correctly"); + let openedFile = yield OS.File.open(OS.Path.join(do_get_cwd().path, "test_open.js")); + do_print("File opened correctly"); + + do_print("Attempting to close a file correctly"); + yield openedFile.close(); + + do_print("Attempting to close a file again"); + yield openedFile.close(); +}); + +/** + * Test the error thrown by OS.File.open when attempting to open a directory + * that does not exist. + */ +add_task(function test_error_attributes () { + + let dir = OS.Path.join(do_get_profile().path, "test_osfileErrorAttrs"); + let fpath = OS.Path.join(dir, "test_error_attributes.txt"); + + try { + yield OS.File.open(fpath, {truncate: true}, {}); + do_check_true(false, "Opening path suceeded (it should fail) " + fpath); + } catch (err) { + do_check_true(err instanceof OS.File.Error); + do_check_true(err.becauseNoSuchFile); + } +}); diff --git a/toolkit/components/osfile/tests/xpcshell/test_osfile_async.js b/toolkit/components/osfile/tests/xpcshell/test_osfile_async.js new file mode 100644 index 000000000..0f86b2ea8 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_async.js @@ -0,0 +1,16 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); + +/** + * A trivial test ensuring that we can call osfile from xpcshell. + * (see bug 808161) + */ + +function run_test() { + do_test_pending(); + OS.File.getCurrentDirectory().then( + do_test_finished, + do_test_finished + ); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_osfile_async_append.js b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_append.js new file mode 100644 index 000000000..0aef2c58a --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_append.js @@ -0,0 +1,122 @@ +"use strict"; + +do_print("starting tests"); + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); + +/** + * A test to check that the |append| mode flag is correctly implemented. + * (see bug 925865) + */ + +function setup_mode(mode) { + // Complete mode. + let realMode = { + read: true, + write: true + }; + for (let k in mode) { + realMode[k] = mode[k]; + } + return realMode; +} + +// Test append mode. +function test_append(mode) { + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_async_append.tmp"); + + // Clear any left-over files from previous runs. + try { + yield OS.File.remove(path); + } catch (ex if ex.becauseNoSuchFile) { + // ignore + } + + try { + mode = setup_mode(mode); + mode.append = true; + if (mode.trunc) { + // Pre-fill file with some data to see if |trunc| actually works. + yield OS.File.writeAtomic(path, new Uint8Array(500)); + } + let file = yield OS.File.open(path, mode); + try { + yield file.write(new Uint8Array(1000)); + yield file.setPosition(0, OS.File.POS_START); + yield file.read(100); + // Should be at offset 100, length 1000 now. + yield file.write(new Uint8Array(100)); + // Should be at offset 1100, length 1100 now. + let stat = yield file.stat(); + do_check_eq(1100, stat.size); + } finally { + yield file.close(); + } + } catch(ex) { + try { + yield OS.File.remove(path); + } catch (ex if ex.becauseNoSuchFile) { + // ignore. + } + } +} + +// Test no-append mode. +function test_no_append(mode) { + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_async_noappend.tmp"); + + // Clear any left-over files from previous runs. + try { + yield OS.File.remove(path); + } catch (ex if ex.becauseNoSuchFile) { + // ignore + } + + try { + mode = setup_mode(mode); + mode.append = false; + if (mode.trunc) { + // Pre-fill file with some data to see if |trunc| actually works. + yield OS.File.writeAtomic(path, new Uint8Array(500)); + } + let file = yield OS.File.open(path, mode); + try { + yield file.write(new Uint8Array(1000)); + yield file.setPosition(0, OS.File.POS_START); + yield file.read(100); + // Should be at offset 100, length 1000 now. + yield file.write(new Uint8Array(100)); + // Should be at offset 200, length 1000 now. + let stat = yield file.stat(); + do_check_eq(1000, stat.size); + } finally { + yield file.close(); + } + } finally { + try { + yield OS.File.remove(path); + } catch (ex if ex.becauseNoSuchFile) { + // ignore. + } + } +} + +var test_flags = [ + {}, + {create:true}, + {trunc:true} +]; +function run_test() { + do_test_pending(); + + for (let t of test_flags) { + add_task(test_append.bind(null, t)); + add_task(test_no_append.bind(null, t)); + } + add_task(do_test_finished); + + run_next_test(); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_osfile_async_bytes.js b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_bytes.js new file mode 100644 index 000000000..68fa9152c --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_bytes.js @@ -0,0 +1,39 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); + +function run_test() { + do_test_pending(); + run_next_test(); +} + +/** + * Test to ensure that {bytes:} in options to |write| is correctly + * preserved. + */ +add_task(function* test_bytes() { + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_async_bytes.tmp"); + let file = yield OS.File.open(path, {trunc: true, read: true, write: true}); + try { + try { + // 1. Test write, by supplying {bytes:} options smaller than the actual + // buffer. + yield file.write(new Uint8Array(2048), {bytes: 1024}); + do_check_eq((yield file.stat()).size, 1024); + + // 2. Test that passing nullish values for |options| still works. + yield file.setPosition(0, OS.File.POS_END); + yield file.write(new Uint8Array(1024), null); + yield file.write(new Uint8Array(1024), undefined); + do_check_eq((yield file.stat()).size, 3072); + } finally { + yield file.close(); + } + } finally { + yield OS.File.remove(path); + } +}); + +add_task(do_test_finished); diff --git a/toolkit/components/osfile/tests/xpcshell/test_osfile_async_copy.js b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_copy.js new file mode 100644 index 000000000..9c52c8a80 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_copy.js @@ -0,0 +1,113 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/FileUtils.jsm"); +Components.utils.import("resource://gre/modules/NetUtil.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/Promise.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); + +function run_test() { + do_test_pending(); + run_next_test(); +} + +/** + * A file that we know exists and that can be used for reading. + */ +var EXISTING_FILE = "test_osfile_async_copy.js"; + +/** + * Fetch asynchronously the contents of a file using xpcom. + * + * Used for comparing xpcom-based results to os.file-based results. + * + * @param {string} path The _absolute_ path to the file. + * @return {promise} + * @resolves {string} The contents of the file. + */ +var reference_fetch_file = function reference_fetch_file(path) { + let promise = Promise.defer(); + let file = new FileUtils.File(path); + NetUtil.asyncFetch({ + uri: NetUtil.newURI(file), + loadUsingSystemPrincipal: true + }, function(stream, status) { + if (!Components.isSuccessCode(status)) { + promise.reject(status); + return; + } + let result, reject; + try { + result = NetUtil.readInputStreamToString(stream, stream.available()); + } catch (x) { + reject = x; + } + stream.close(); + if (reject) { + promise.reject(reject); + } else { + promise.resolve(result); + } + }); + + return promise.promise; +}; + +/** + * Compare asynchronously the contents two files using xpcom. + * + * Used for comparing xpcom-based results to os.file-based results. + * + * @param {string} a The _absolute_ path to the first file. + * @param {string} b The _absolute_ path to the second file. + * + * @resolves {null} + */ +var reference_compare_files = function reference_compare_files(a, b) { + let a_contents = yield reference_fetch_file(a); + let b_contents = yield reference_fetch_file(b); + // Not using do_check_eq to avoid dumping the whole file to the log. + // It is OK to === compare here, as both variables contain a string. + do_check_true(a_contents === b_contents); +}; + +/** + * Test to ensure that OS.File.copy works. + */ +function test_copymove(options = {}) { + let source = OS.Path.join((yield OS.File.getCurrentDirectory()), + EXISTING_FILE); + let dest = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_async_copy_dest.tmp"); + let dest2 = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_async_copy_dest2.tmp"); + try { + // 1. Test copy. + yield OS.File.copy(source, dest, options); + yield reference_compare_files(source, dest); + // 2. Test subsequent move. + yield OS.File.move(dest, dest2); + yield reference_compare_files(source, dest2); + // 3. Check that the moved file was really moved. + do_check_eq((yield OS.File.exists(dest)), false); + } finally { + try { + yield OS.File.remove(dest); + } catch (ex if ex.becauseNoSuchFile) { + // ignore + } + try { + yield OS.File.remove(dest2); + } catch (ex if ex.becauseNoSuchFile) { + // ignore + } + } +} + +// Regular copy test. +add_task(test_copymove); +// Userland copy test. +add_task(test_copymove.bind(null, {unixUserland: true})); + +add_task(do_test_finished); diff --git a/toolkit/components/osfile/tests/xpcshell/test_osfile_async_flush.js b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_flush.js new file mode 100644 index 000000000..9ed087f4e --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_flush.js @@ -0,0 +1,30 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); + +function run_test() { + do_test_pending(); + run_next_test(); +} + +/** + * Test to ensure that |File.prototype.flush| is available in the async API. + */ + +add_task(function test_flush() { + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_async_flush.tmp"); + let file = yield OS.File.open(path, {trunc: true, write: true}); + try { + try { + yield file.flush(); + } finally { + yield file.close(); + } + } finally { + yield OS.File.remove(path); + } +}); + +add_task(do_test_finished); diff --git a/toolkit/components/osfile/tests/xpcshell/test_osfile_async_largefiles.js b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_largefiles.js new file mode 100644 index 000000000..a9ac776b0 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_largefiles.js @@ -0,0 +1,153 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Components.utils.import("resource://gre/modules/ctypes.jsm"); +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); + +/** + * A test to check that .getPosition/.setPosition work with large files. + * (see bug 952997) + */ + +// Test setPosition/getPosition. +function test_setPosition(forward, current, backward) { + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_async_largefiles.tmp"); + + // Clear any left-over files from previous runs. + try { + yield OS.File.remove(path); + } catch (ex if ex.becauseNoSuchFile) { + // ignore + } + + try { + let file = yield OS.File.open(path, {write:true, append:false}); + try { + let pos = 0; + + // 1. seek forward from start + do_print("Moving forward: " + forward); + yield file.setPosition(forward, OS.File.POS_START); + pos += forward; + do_check_eq((yield file.getPosition()), pos); + + // 2. seek forward from current position + do_print("Moving current: " + current); + yield file.setPosition(current, OS.File.POS_CURRENT); + pos += current; + do_check_eq((yield file.getPosition()), pos); + + // 3. seek backward from current position + do_print("Moving current backward: " + backward); + yield file.setPosition(-backward, OS.File.POS_CURRENT); + pos -= backward; + do_check_eq((yield file.getPosition()), pos); + + } finally { + yield file.setPosition(0, OS.File.POS_START); + yield file.close(); + } + } catch(ex) { + try { + yield OS.File.remove(path); + } catch (ex if ex.becauseNoSuchFile) { + // ignore. + } + do_throw(ex); + } +} + +// Test setPosition/getPosition expected failures. +function test_setPosition_failures() { + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_async_largefiles.tmp"); + + // Clear any left-over files from previous runs. + try { + yield OS.File.remove(path); + } catch (ex if ex.becauseNoSuchFile) { + // ignore + } + + try { + let file = yield OS.File.open(path, {write:true, append:false}); + try { + let pos = 0; + + // 1. Use an invalid position value + try { + yield file.setPosition(0.5, OS.File.POS_START); + do_throw("Shouldn't have succeeded"); + } catch (ex) { + do_check_true(ex.toString().includes("can't pass")); + } + // Since setPosition should have bailed, it shouldn't have moved the + // file pointer at all. + do_check_eq((yield file.getPosition()), 0); + + // 2. Use an invalid position value + try { + yield file.setPosition(0xffffffff + 0.5, OS.File.POS_START); + do_throw("Shouldn't have succeeded"); + } catch (ex) { + do_check_true(ex.toString().includes("can't pass")); + } + // Since setPosition should have bailed, it shouldn't have moved the + // file pointer at all. + do_check_eq((yield file.getPosition()), 0); + + // 3. Use a position that cannot be represented as a double + try { + // Not all numbers after 9007199254740992 can be represented as a + // double. E.g. in js 9007199254740992 + 1 == 9007199254740992 + yield file.setPosition(9007199254740992, OS.File.POS_START); + yield file.setPosition(1, OS.File.POS_CURRENT); + do_throw("Shouldn't have succeeded"); + } catch (ex) { + do_print(ex.toString()); + do_check_true(!!ex); + } + + } finally { + yield file.setPosition(0, OS.File.POS_START); + yield file.close(); + try { + yield OS.File.remove(path); + } catch (ex if ex.becauseNoSuchFile) { + // ignore. + } + } + } catch(ex) { + do_throw(ex); + } +} + +function run_test() { + // First verify stuff works for small values. + add_task(test_setPosition.bind(null, 0, 100, 50)); + add_task(test_setPosition.bind(null, 1000, 100, 50)); + add_task(test_setPosition.bind(null, 1000, -100, -50)); + + if (OS.Constants.Win || ctypes.off_t.size >= 8) { + // Now verify stuff still works for large values. + // 1. Multiple small seeks, which add up to > MAXINT32 + add_task(test_setPosition.bind(null, 0x7fffffff, 0x7fffffff, 0)); + // 2. Plain large seek, that should end up at 0 again. + // 0xffffffff also happens to be the INVALID_SET_FILE_POINTER value on + // Windows, so this also tests the error handling + add_task(test_setPosition.bind(null, 0, 0xffffffff, 0xffffffff)); + // 3. Multiple large seeks that should end up > MAXINT32. + add_task(test_setPosition.bind(null, 0xffffffff, 0xffffffff, 0xffffffff)); + // 5. Multiple large seeks with negative offsets. + add_task(test_setPosition.bind(null, 0xffffffff, -0x7fffffff, 0x7fffffff)); + + // 6. Check failures + add_task(test_setPosition_failures); + } + + run_next_test(); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_osfile_async_setDates.js b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_setDates.js new file mode 100644 index 000000000..17d3afa7c --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_setDates.js @@ -0,0 +1,211 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); + +/** + * A test to ensure that OS.File.setDates and OS.File.prototype.setDates are + * working correctly. + * (see bug 924916) + */ + +function run_test() { + run_next_test(); +} + +// Non-prototypical tests, operating on path names. +add_task(function* test_nonproto() { + // First, create a file we can mess with. + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_async_setDates_nonproto.tmp"); + yield OS.File.writeAtomic(path, new Uint8Array(1)); + + try { + // 1. Try to set some well known dates. + // We choose multiples of 2000ms, because the time stamp resolution of + // the underlying OS might not support something more precise. + const accDate = 2000; + const modDate = 4000; + { + yield OS.File.setDates(path, accDate, modDate); + let stat = yield OS.File.stat(path); + do_check_eq(accDate, stat.lastAccessDate.getTime()); + do_check_eq(modDate, stat.lastModificationDate.getTime()); + } + + // 2.1 Try to omit modificationDate (which should then default to + // |Date.now()|, expect for resolution differences). + { + yield OS.File.setDates(path, accDate); + let stat = yield OS.File.stat(path); + do_check_eq(accDate, stat.lastAccessDate.getTime()); + do_check_neq(modDate, stat.lastModificationDate.getTime()); + } + + // 2.2 Try to omit accessDate as well (which should then default to + // |Date.now()|, expect for resolution differences). + { + yield OS.File.setDates(path); + let stat = yield OS.File.stat(path); + do_check_neq(accDate, stat.lastAccessDate.getTime()); + do_check_neq(modDate, stat.lastModificationDate.getTime()); + } + + // 3. Repeat 1., but with Date objects this time + { + yield OS.File.setDates(path, new Date(accDate), new Date(modDate)); + let stat = yield OS.File.stat(path); + do_check_eq(accDate, stat.lastAccessDate.getTime()); + do_check_eq(modDate, stat.lastModificationDate.getTime()); + } + + // 4. Check that invalid params will cause an exception/rejection. + { + for (let p of ["invalid", new Uint8Array(1), NaN]) { + try { + yield OS.File.setDates(path, p, modDate); + do_throw("Invalid access date should have thrown for: " + p); + } catch (ex) { + let stat = yield OS.File.stat(path); + do_check_eq(accDate, stat.lastAccessDate.getTime()); + do_check_eq(modDate, stat.lastModificationDate.getTime()); + } + try { + yield OS.File.setDates(path, accDate, p); + do_throw("Invalid modification date should have thrown for: " + p); + } catch (ex) { + let stat = yield OS.File.stat(path); + do_check_eq(accDate, stat.lastAccessDate.getTime()); + do_check_eq(modDate, stat.lastModificationDate.getTime()); + } + try { + yield OS.File.setDates(path, p, p); + do_throw("Invalid dates should have thrown for: " + p); + } catch (ex) { + let stat = yield OS.File.stat(path); + do_check_eq(accDate, stat.lastAccessDate.getTime()); + do_check_eq(modDate, stat.lastModificationDate.getTime()); + } + } + } + } finally { + // Remove the temp file again + yield OS.File.remove(path); + } +}); + +// Prototypical tests, operating on |File| handles. +add_task(function* test_proto() { + if (OS.Constants.Sys.Name == "Android" || OS.Constants.Sys.Name == "Gonk") { + do_print("File.prototype.setDates is not implemented for Android/B2G"); + do_check_eq(OS.File.prototype.setDates, undefined); + return; + } + + // First, create a file we can mess with. + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_async_setDates_proto.tmp"); + yield OS.File.writeAtomic(path, new Uint8Array(1)); + + try { + let fd = yield OS.File.open(path, {write: true}); + + try { + // 1. Try to set some well known dates. + // We choose multiples of 2000ms, because the time stamp resolution of + // the underlying OS might not support something more precise. + const accDate = 2000; + const modDate = 4000; + { + yield fd.setDates(accDate, modDate); + let stat = yield fd.stat(); + do_check_eq(accDate, stat.lastAccessDate.getTime()); + do_check_eq(modDate, stat.lastModificationDate.getTime()); + } + + // 2.1 Try to omit modificationDate (which should then default to + // |Date.now()|, expect for resolution differences). + { + yield fd.setDates(accDate); + let stat = yield fd.stat(); + do_check_eq(accDate, stat.lastAccessDate.getTime()); + do_check_neq(modDate, stat.lastModificationDate.getTime()); + } + + // 2.2 Try to omit accessDate as well (which should then default to + // |Date.now()|, expect for resolution differences). + { + yield fd.setDates(); + let stat = yield fd.stat(); + do_check_neq(accDate, stat.lastAccessDate.getTime()); + do_check_neq(modDate, stat.lastModificationDate.getTime()); + } + + // 3. Repeat 1., but with Date objects this time + { + yield fd.setDates(new Date(accDate), new Date(modDate)); + let stat = yield fd.stat(); + do_check_eq(accDate, stat.lastAccessDate.getTime()); + do_check_eq(modDate, stat.lastModificationDate.getTime()); + } + + // 4. Check that invalid params will cause an exception/rejection. + { + for (let p of ["invalid", new Uint8Array(1), NaN]) { + try { + yield fd.setDates(p, modDate); + do_throw("Invalid access date should have thrown for: " + p); + } catch (ex) { + let stat = yield fd.stat(); + do_check_eq(accDate, stat.lastAccessDate.getTime()); + do_check_eq(modDate, stat.lastModificationDate.getTime()); + } + try { + yield fd.setDates(accDate, p); + do_throw("Invalid modification date should have thrown for: " + p); + } catch (ex) { + let stat = yield fd.stat(); + do_check_eq(accDate, stat.lastAccessDate.getTime()); + do_check_eq(modDate, stat.lastModificationDate.getTime()); + } + try { + yield fd.setDates(p, p); + do_throw("Invalid dates should have thrown for: " + p); + } catch (ex) { + let stat = yield fd.stat(); + do_check_eq(accDate, stat.lastAccessDate.getTime()); + do_check_eq(modDate, stat.lastModificationDate.getTime()); + } + } + } + } finally { + yield fd.close(); + } + } finally { + // Remove the temp file again + yield OS.File.remove(path); + } +}); + +// Tests setting dates on directories. +add_task(function* test_dirs() { + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_async_setDates_dir"); + yield OS.File.makeDir(path); + + try { + // 1. Try to set some well known dates. + // We choose multiples of 2000ms, because the time stamp resolution of + // the underlying OS might not support something more precise. + const accDate = 2000; + const modDate = 4000; + { + yield OS.File.setDates(path, accDate, modDate); + let stat = yield OS.File.stat(path); + do_check_eq(accDate, stat.lastAccessDate.getTime()); + do_check_eq(modDate, stat.lastModificationDate.getTime()); + } + } finally { + yield OS.File.removeEmptyDir(path); + } +}); diff --git a/toolkit/components/osfile/tests/xpcshell/test_osfile_async_setPermissions.js b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_setPermissions.js new file mode 100644 index 000000000..ab8bf7dd9 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_setPermissions.js @@ -0,0 +1,103 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * A test to ensure that OS.File.setPermissions and + * OS.File.prototype.setPermissions are all working correctly. + * (see bug 1001849) + * These functions are currently Unix-specific. The manifest skips + * the test on Windows. + */ + +/** + * Helper function for test logging: prints a POSIX file permission mode as an + * octal number, with a leading '0' per C (not JS) convention. When the + * numeric value is 0o777 or lower, it is padded on the left with zeroes to + * four digits wide. + * Sample outputs: 0022, 0644, 04755. + */ +function format_mode(mode) { + if (mode <= 0o777) { + return ("0000" + mode.toString(8)).slice(-4); + } else { + return "0" + mode.toString(8); + } +} + +const _umask = OS.Constants.Sys.umask; +do_print("umask: " + format_mode(_umask)); + +/** + * Compute the mode that a file should have after applying the umask, + * whatever it happens to be. + */ +function apply_umask(mode) { + return mode & ~_umask; +} + +// Sequence of setPermission parameters and expected file mode. The first test +// checks the permissions when the file is first created. +var testSequence = [ + [null, apply_umask(0o600)], + [{ unixMode: 0o4777 }, apply_umask(0o4777)], + [{ unixMode: 0o4777, unixHonorUmask: false }, 0o4777], + [{ unixMode: 0o4777, unixHonorUmask: true }, apply_umask(0o4777)], + [undefined, apply_umask(0o600)], + [{ unixMode: 0o666 }, apply_umask(0o666)], + [{ unixMode: 0o600 }, apply_umask(0o600)], + [{ unixMode: 0 }, 0], + [{}, apply_umask(0o600)], +]; + +// Test application to paths. +add_task(function* test_path_setPermissions() { + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_async_setPermissions_path.tmp"); + yield OS.File.writeAtomic(path, new Uint8Array(1)); + + try { + for (let [options, expectedMode] of testSequence) { + if (options !== null) { + do_print("Setting permissions to " + JSON.stringify(options)); + yield OS.File.setPermissions(path, options); + } + + let stat = yield OS.File.stat(path); + do_check_eq(format_mode(stat.unixMode), format_mode(expectedMode)); + } + } finally { + yield OS.File.remove(path); + } +}); + +// Test application to open files. +add_task(function* test_file_setPermissions() { + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_async_setPermissions_file.tmp"); + yield OS.File.writeAtomic(path, new Uint8Array(1)); + + try { + let fd = yield OS.File.open(path, { write: true }); + try { + for (let [options, expectedMode] of testSequence) { + if (options !== null) { + do_print("Setting permissions to " + JSON.stringify(options)); + yield fd.setPermissions(options); + } + + let stat = yield fd.stat(); + do_check_eq(format_mode(stat.unixMode), format_mode(expectedMode)); + } + } finally { + yield fd.close(); + } + } finally { + yield OS.File.remove(path); + } +}); + +function run_test() { + run_next_test(); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_osfile_closed.js b/toolkit/components/osfile/tests/xpcshell/test_osfile_closed.js new file mode 100644 index 000000000..5740f7f1a --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_closed.js @@ -0,0 +1,48 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); + +function run_test() { + do_test_pending(); + run_next_test(); +} + +add_task(function test_closed() { + OS.Shared.DEBUG = true; + let currentDir = yield OS.File.getCurrentDirectory(); + do_print("Open a file, ensure that we can call stat()"); + let path = OS.Path.join(currentDir, "test_osfile_closed.js"); + let file = yield OS.File.open(path); + yield file.stat(); + do_check_true(true); + + yield file.close(); + + do_print("Ensure that we cannot stat() on closed file"); + let exn; + try { + yield file.stat(); + } catch (ex) { + exn = ex; + } + do_print("Ensure that this raises the correct error"); + do_check_true(!!exn); + do_check_true(exn instanceof OS.File.Error); + do_check_true(exn.becauseClosed); + + do_print("Ensure that we cannot read() on closed file"); + exn = null; + try { + yield file.read(); + } catch (ex) { + exn = ex; + } + do_print("Ensure that this raises the correct error"); + do_check_true(!!exn); + do_check_true(exn instanceof OS.File.Error); + do_check_true(exn.becauseClosed); + +}); + +add_task(do_test_finished); diff --git a/toolkit/components/osfile/tests/xpcshell/test_osfile_error.js b/toolkit/components/osfile/tests/xpcshell/test_osfile_error.js new file mode 100644 index 000000000..a1c319eca --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_error.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var {OS: {File, Path, Constants}} = Components.utils.import("resource://gre/modules/osfile.jsm", {}); +Components.utils.import("resource://gre/modules/Task.jsm"); + +function run_test() { + run_next_test(); +} + +add_task(function* testFileError_with_writeAtomic() { + let DEFAULT_CONTENTS = "default contents" + Math.random(); + let path = Path.join(Constants.Path.tmpDir, + "testFileError.tmp"); + yield File.remove(path); + yield File.writeAtomic(path, DEFAULT_CONTENTS); + let exception; + try { + yield File.writeAtomic(path, DEFAULT_CONTENTS, { noOverwrite: true }); + } catch (ex) { + exception = ex; + } + do_check_true(exception instanceof File.Error); + do_check_true(exception.path == path); +}); + +add_task(function* testFileError_with_makeDir() { + let path = Path.join(Constants.Path.tmpDir, + "directory"); + yield File.removeDir(path); + yield File.makeDir(path); + let exception; + try { + yield File.makeDir(path, { ignoreExisting: false }); + } catch (ex) { + exception = ex; + } + do_check_true(exception instanceof File.Error); + do_check_true(exception.path == path); +}); + +add_task(function* testFileError_with_move() { + let DEFAULT_CONTENTS = "default contents" + Math.random(); + let sourcePath = Path.join(Constants.Path.tmpDir, + "src.tmp"); + let destPath = Path.join(Constants.Path.tmpDir, + "dest.tmp"); + yield File.remove(sourcePath); + yield File.remove(destPath); + yield File.writeAtomic(sourcePath, DEFAULT_CONTENTS); + yield File.writeAtomic(destPath, DEFAULT_CONTENTS); + let exception; + try { + yield File.move(sourcePath, destPath, { noOverwrite: true }); + } catch (ex) { + exception = ex; + } + do_print(exception); + do_check_true(exception instanceof File.Error); + do_check_true(exception.path == sourcePath); +}); diff --git a/toolkit/components/osfile/tests/xpcshell/test_osfile_kill.js b/toolkit/components/osfile/tests/xpcshell/test_osfile_kill.js new file mode 100644 index 000000000..e32c37224 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_kill.js @@ -0,0 +1,100 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); + +// We want the actual global to get at the internals since Scheduler is not +// exported. +var AsyncFrontGlobal = Components.utils.import( + "resource://gre/modules/osfile/osfile_async_front.jsm", + null); +var Scheduler = AsyncFrontGlobal.Scheduler; + +/** + * Verify that Scheduler.kill() interacts with other OS.File requests correctly, + * and that no requests are lost. This is relevant because on B2G we + * auto-kill the worker periodically, making it very possible for valid requests + * to be interleaved with the automatic kill(). + * + * This test is being created with the fix for Bug 1125989 where `kill` queue + * management was found to be buggy. It is a glass-box test that explicitly + * re-creates the observed failure situation; it is not guaranteed to prevent + * all future regressions. The following is a detailed explanation of the test + * for your benefit if this test ever breaks or you are wondering what was the + * point of all this. You might want to skim the code below first. + * + * OS.File maintains a `queue` of operations to be performed. This queue is + * nominally implemented as a chain of promises. Every time a new job is + * OS.File.push()ed, it effectively becomes the new `queue` promise. (An + * extra promise is interposed with a rejection handler to avoid the rejection + * cascading, but that does not matter for our purposes.) + * + * The flaw in `kill` was that it would wait for the `queue` to complete before + * replacing `queue`. As a result, another OS.File operation could use `push` + * (by way of OS.File.post()) to also use .then() on the same `queue` promise. + * Accordingly, assuming that promise was not yet resolved (due to a pending + * OS.File request), when it was resolved, both the task scheduled in `kill` + * and in `post` would be triggered. Both of those tasks would run until + * encountering a call to worker.post(). + * + * Re-creating this race is not entirely trivial because of the large number of + * promises used by the code causing control flow to repeatedly be deferred. In + * a slightly simpler world we could run the follwing in the same turn of the + * event loop and trigger the problem. + * - any OS.File request + * - Scheduler.kill() + * - any OS.File request + * + * However, we need the Scheduler.kill task to reach the point where it is + * waiting on the same `queue` that another task has been scheduled against. + * Since the `kill` task yields on the `killQueue` promise prior to yielding + * on `queue`, however, some turns of the event loop are required. Happily, + * for us, as discussed above, the problem triggers when we have two promises + * scheduled on the `queue`, so we can just wait to schedule the second OS.File + * request on the queue. (Note that because of the additional then() added to + * eat rejections, there is an important difference between the value of + * `queue` and the value returned by the first OS.File request.) + */ +add_task(function* test_kill_race() { + // Ensure the worker has been created and that SET_DEBUG has taken effect. + // We have chosen OS.File.exists for our tests because it does not trigger + // a rejection and we absolutely do not care what the operation is other + // than it does not invoke a native fast-path. + yield OS.File.exists('foo.foo'); + + do_print('issuing first request'); + let firstRequest = OS.File.exists('foo.bar'); + let secondRequest; + let secondResolved = false; + + // As noted in our big block comment, we want to wait to schedule the + // second request so that it races `kill`'s call to `worker.post`. Having + // ourselves wait on the same promise, `queue`, and registering ourselves + // before we issue the kill request means we will get run before the `kill` + // task resumes and allow us to precisely create the desired race. + Scheduler.queue.then(function() { + do_print('issuing second request'); + secondRequest = OS.File.exists('foo.baz'); + secondRequest.then(function() { + secondResolved = true; + }); + }); + + do_print('issuing kill request'); + let killRequest = Scheduler.kill({ reset: true, shutdown: false }); + + // Wait on the killRequest so that we can schedule a new OS.File request + // after it completes... + yield killRequest; + // ...because our ordering guarantee ensures that there is at most one + // worker (and this usage here should not be vulnerable even with the + // bug present), so when this completes the secondRequest has either been + // resolved or lost. + yield OS.File.exists('foo.goz'); + + ok(secondResolved, + 'The second request was resolved so we avoided the bug. Victory!'); +}); + +function run_test() { + run_next_test(); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_osfile_win_async_setPermissions.js b/toolkit/components/osfile/tests/xpcshell/test_osfile_win_async_setPermissions.js new file mode 100644 index 000000000..990d722f5 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_win_async_setPermissions.js @@ -0,0 +1,114 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * A test to ensure that OS.File.setPermissions and + * OS.File.prototype.setPermissions are all working correctly. + * (see bug 1022816) + * The manifest tests on Windows. + */ + +// Sequence of setPermission parameters. +var testSequence = [ + [ { winAttributes: { readOnly: true, system: true, hidden: true } }, + { readOnly: true, system: true, hidden: true } ], + [ { winAttributes: { readOnly: false } }, + { readOnly: false, system: true, hidden: true } ], + [ { winAttributes: { system: false } }, + { readOnly: false, system: false, hidden: true } ], + [ { winAttributes: { hidden: false } }, + { readOnly: false, system: false, hidden: false } ], + [ { winAttributes: {readOnly: true, system: false, hidden: false} }, + { readOnly: true, system: false, hidden: false } ], + [ { winAttributes: {readOnly: false, system: true, hidden: false} }, + { readOnly: false, system: true, hidden: false } ], + [ { winAttributes: {readOnly: false, system: false, hidden: true} }, + { readOnly: false, system: false, hidden: true } ], +]; + +// Test application to paths. +add_task(function* test_path_setPermissions() { + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_win_async_setPermissions_path.tmp"); + yield OS.File.writeAtomic(path, new Uint8Array(1)); + + try { + for (let [options, attributesExpected] of testSequence) { + if (options !== null) { + do_print("Setting permissions to " + JSON.stringify(options)); + yield OS.File.setPermissions(path, options); + } + + let stat = yield OS.File.stat(path); + do_print("Got stat winAttributes: " + JSON.stringify(stat.winAttributes)); + + do_check_eq(stat.winAttributes.readOnly, attributesExpected.readOnly); + do_check_eq(stat.winAttributes.system, attributesExpected.system); + do_check_eq(stat.winAttributes.hidden, attributesExpected.hidden); + + } + } finally { + yield OS.File.remove(path); + } +}); + +// Test application to open files. +add_task(function* test_file_setPermissions() { + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_win_async_setPermissions_file.tmp"); + yield OS.File.writeAtomic(path, new Uint8Array(1)); + + try { + let fd = yield OS.File.open(path, { write: true }); + try { + for (let [options, attributesExpected] of testSequence) { + if (options !== null) { + do_print("Setting permissions to " + JSON.stringify(options)); + yield fd.setPermissions(options); + } + + let stat = yield fd.stat(); + do_print("Got stat winAttributes: " + JSON.stringify(stat.winAttributes)); + do_check_eq(stat.winAttributes.readOnly, attributesExpected.readOnly); + do_check_eq(stat.winAttributes.system, attributesExpected.system); + do_check_eq(stat.winAttributes.hidden, attributesExpected.hidden); + } + } finally { + yield fd.close(); + } + } finally { + yield OS.File.remove(path); + } +}); + +// Test application to Check setPermissions on a non-existant file path. +add_task(function* test_non_existant_file_path_setPermissions() { + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_win_async_setPermissions_path.tmp"); + Assert.rejects(OS.File.setPermissions(path, {winAttributes: {readOnly: true}}), + /The system cannot find the file specified/, + "setPermissions failed as expected on a non-existant file path"); +}); + +// Test application to Check setPermissions on a invalid file handle. +add_task(function* test_closed_file_handle_setPermissions() { + let path = OS.Path.join(OS.Constants.Path.tmpDir, + "test_osfile_win_async_setPermissions_path.tmp"); + yield OS.File.writeAtomic(path, new Uint8Array(1)); + + try { + let fd = yield OS.File.open(path, { write: true }); + yield fd.close(); + Assert.rejects(fd.setPermissions(path, {winAttributes: {readOnly: true}}), + /The handle is invalid/, + "setPermissions failed as expected on a invalid file handle"); + } finally { + yield OS.File.remove(path); + } +}); + +function run_test() { + run_next_test(); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_osfile_writeAtomic_backupTo_option.js b/toolkit/components/osfile/tests/xpcshell/test_osfile_writeAtomic_backupTo_option.js new file mode 100644 index 000000000..adf345b0c --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_writeAtomic_backupTo_option.js @@ -0,0 +1,143 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var {OS: {File, Path, Constants}} = Components.utils.import("resource://gre/modules/osfile.jsm", {}); +Components.utils.import("resource://gre/modules/Task.jsm"); + +/** + * Remove all temporary files and back up files, including + * test_backupTo_option_with_tmpPath.tmp + * test_backupTo_option_with_tmpPath.tmp.backup + * test_backupTo_option_without_tmpPath.tmp + * test_backupTo_option_without_tmpPath.tmp.backup + * test_non_backupTo_option.tmp + * test_non_backupTo_option.tmp.backup + * test_backupTo_option_without_destination_file.tmp + * test_backupTo_option_without_destination_file.tmp.backup + * test_backupTo_option_with_backup_file.tmp + * test_backupTo_option_with_backup_file.tmp.backup + */ +function clearFiles() { + return Task.spawn(function () { + let files = ["test_backupTo_option_with_tmpPath.tmp", + "test_backupTo_option_without_tmpPath.tmp", + "test_non_backupTo_option.tmp", + "test_backupTo_option_without_destination_file.tmp", + "test_backupTo_option_with_backup_file.tmp"]; + for (let file of files) { + let path = Path.join(Constants.Path.tmpDir, file); + yield File.remove(path); + yield File.remove(path + ".backup"); + } + }); +} + +function run_test() { + run_next_test(); +} + +add_task(function* init() { + yield clearFiles(); +}); + +/** + * test when + * |backupTo| specified + * |tmpPath| specified + * destination file exists + * @result destination file will be backed up + */ +add_task(function* test_backupTo_option_with_tmpPath() { + let DEFAULT_CONTENTS = "default contents" + Math.random(); + let WRITE_CONTENTS = "abc" + Math.random(); + let path = Path.join(Constants.Path.tmpDir, + "test_backupTo_option_with_tmpPath.tmp"); + yield File.writeAtomic(path, DEFAULT_CONTENTS); + yield File.writeAtomic(path, WRITE_CONTENTS, { tmpPath: path + ".tmp", + backupTo: path + ".backup" }); + do_check_true((yield File.exists(path + ".backup"))); + let contents = yield File.read(path + ".backup"); + do_check_eq(DEFAULT_CONTENTS, (new TextDecoder()).decode(contents)); +}); + +/** + * test when + * |backupTo| specified + * |tmpPath| not specified + * destination file exists + * @result destination file will be backed up + */ +add_task(function* test_backupTo_option_without_tmpPath() { + let DEFAULT_CONTENTS = "default contents" + Math.random(); + let WRITE_CONTENTS = "abc" + Math.random(); + let path = Path.join(Constants.Path.tmpDir, + "test_backupTo_option_without_tmpPath.tmp"); + yield File.writeAtomic(path, DEFAULT_CONTENTS); + yield File.writeAtomic(path, WRITE_CONTENTS, { backupTo: path + ".backup" }); + do_check_true((yield File.exists(path + ".backup"))); + let contents = yield File.read(path + ".backup"); + do_check_eq(DEFAULT_CONTENTS, (new TextDecoder()).decode(contents)); +}); + +/** + * test when + * |backupTo| not specified + * |tmpPath| not specified + * destination file exists + * @result destination file will not be backed up + */ +add_task(function* test_non_backupTo_option() { + let DEFAULT_CONTENTS = "default contents" + Math.random(); + let WRITE_CONTENTS = "abc" + Math.random(); + let path = Path.join(Constants.Path.tmpDir, + "test_non_backupTo_option.tmp"); + yield File.writeAtomic(path, DEFAULT_CONTENTS); + yield File.writeAtomic(path, WRITE_CONTENTS); + do_check_false((yield File.exists(path + ".backup"))); +}); + +/** + * test when + * |backupTo| specified + * |tmpPath| not specified + * destination file not exists + * @result no back up file exists + */ +add_task(function* test_backupTo_option_without_destination_file() { + let DEFAULT_CONTENTS = "default contents" + Math.random(); + let WRITE_CONTENTS = "abc" + Math.random(); + let path = Path.join(Constants.Path.tmpDir, + "test_backupTo_option_without_destination_file.tmp"); + yield File.remove(path); + yield File.writeAtomic(path, WRITE_CONTENTS, { backupTo: path + ".backup" }); + do_check_false((yield File.exists(path + ".backup"))); +}); + +/** + * test when + * |backupTo| specified + * |tmpPath| not specified + * backup file exists + * destination file exists + * @result destination file will be backed up + */ +add_task(function* test_backupTo_option_with_backup_file() { + let DEFAULT_CONTENTS = "default contents" + Math.random(); + let WRITE_CONTENTS = "abc" + Math.random(); + let path = Path.join(Constants.Path.tmpDir, + "test_backupTo_option_with_backup_file.tmp"); + yield File.writeAtomic(path, DEFAULT_CONTENTS); + + yield File.writeAtomic(path + ".backup", new Uint8Array(1000)); + + yield File.writeAtomic(path, WRITE_CONTENTS, { backupTo: path + ".backup" }); + do_check_true((yield File.exists(path + ".backup"))); + let contents = yield File.read(path + ".backup"); + do_check_eq(DEFAULT_CONTENTS, (new TextDecoder()).decode(contents)); +}); + +add_task(function* cleanup() { + yield clearFiles(); +}); diff --git a/toolkit/components/osfile/tests/xpcshell/test_osfile_writeAtomic_zerobytes.js b/toolkit/components/osfile/tests/xpcshell/test_osfile_writeAtomic_zerobytes.js new file mode 100644 index 000000000..a32a690e6 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_writeAtomic_zerobytes.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; +var SHARED_PATH; + +add_task(function* init() { + do_get_profile(); + SHARED_PATH = OS.Path.join(OS.Constants.Path.profileDir, "test_osfile_write_zerobytes.tmp"); +}); + +add_test_pair(function* test_osfile_writeAtomic_zerobytes() { + let encoder = new TextEncoder(); + let string1 = ""; + let outbin = encoder.encode(string1); + yield OS.File.writeAtomic(SHARED_PATH, outbin); + + let decoder = new TextDecoder(); + let bin = yield OS.File.read(SHARED_PATH); + let string2 = decoder.decode(bin); + // Checking if writeAtomic supports writing encoded zero-byte strings + Assert.equal(string2, string1, "Read the expected (empty) string."); +}); + +function run_test() { + run_next_test(); +}
\ No newline at end of file diff --git a/toolkit/components/osfile/tests/xpcshell/test_path.js b/toolkit/components/osfile/tests/xpcshell/test_path.js new file mode 100644 index 000000000..76a507ee3 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_path.js @@ -0,0 +1,159 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +Components.utils.import("resource://gre/modules/Services.jsm", this); +Services.prefs.setBoolPref("toolkit.osfile.test.syslib_necessary", false); + // We don't need libc/kernel32.dll for this test + +var ImportWin = {}; +var ImportUnix = {}; +Components.utils.import("resource://gre/modules/osfile/ospath_win.jsm", ImportWin); +Components.utils.import("resource://gre/modules/osfile/ospath_unix.jsm", ImportUnix); + +var Win = ImportWin; +var Unix = ImportUnix; + +function do_check_fail(f) +{ + try { + let result = f(); + do_print("Failed do_check_fail: " + result); + do_check_true(false); + } catch (ex) { + do_check_true(true); + } +}; + +function run_test() +{ + do_print("Testing Windows paths"); + + do_print("Backslash-separated, no drive"); + do_check_eq(Win.basename("a\\b"), "b"); + do_check_eq(Win.basename("a\\b\\"), ""); + do_check_eq(Win.basename("abc"), "abc"); + do_check_eq(Win.dirname("a\\b"), "a"); + do_check_eq(Win.dirname("a\\b\\"), "a\\b"); + do_check_eq(Win.dirname("a\\\\\\\\b"), "a"); + do_check_eq(Win.dirname("abc"), "."); + do_check_eq(Win.normalize("\\a\\b\\c"), "\\a\\b\\c"); + do_check_eq(Win.normalize("\\a\\b\\\\\\\\c"), "\\a\\b\\c"); + do_check_eq(Win.normalize("\\a\\b\\c\\\\\\"), "\\a\\b\\c"); + do_check_eq(Win.normalize("\\a\\b\\c\\..\\..\\..\\d\\e\\f"), "\\d\\e\\f"); + do_check_eq(Win.normalize("a\\b\\c\\..\\..\\..\\d\\e\\f"), "d\\e\\f"); + do_check_fail(() => Win.normalize("\\a\\b\\c\\..\\..\\..\\..\\d\\e\\f")); + + do_check_eq(Win.join("\\tmp", "foo", "bar"), "\\tmp\\foo\\bar", "join \\tmp,foo,bar"); + do_check_eq(Win.join("\\tmp", "\\foo", "bar"), "\\foo\\bar", "join \\tmp,\\foo,bar"); + do_check_eq(Win.winGetDrive("\\tmp"), null); + do_check_eq(Win.winGetDrive("\\tmp\\a\\b\\c\\d\\e"), null); + do_check_eq(Win.winGetDrive("\\"), null); + + + do_print("Backslash-separated, with a drive"); + do_check_eq(Win.basename("c:a\\b"), "b"); + do_check_eq(Win.basename("c:a\\b\\"), ""); + do_check_eq(Win.basename("c:abc"), "abc"); + do_check_eq(Win.dirname("c:a\\b"), "c:a"); + do_check_eq(Win.dirname("c:a\\b\\"), "c:a\\b"); + do_check_eq(Win.dirname("c:a\\\\\\\\b"), "c:a"); + do_check_eq(Win.dirname("c:abc"), "c:"); + let options = { + winNoDrive: true + }; + do_check_eq(Win.dirname("c:a\\b", options), "a"); + do_check_eq(Win.dirname("c:a\\b\\", options), "a\\b"); + do_check_eq(Win.dirname("c:a\\\\\\\\b", options), "a"); + do_check_eq(Win.dirname("c:abc", options), "."); + do_check_eq(Win.join("c:", "abc"), "c:\\abc", "join c:,abc"); + + do_check_eq(Win.normalize("c:"), "c:\\"); + do_check_eq(Win.normalize("c:\\"), "c:\\"); + do_check_eq(Win.normalize("c:\\a\\b\\c"), "c:\\a\\b\\c"); + do_check_eq(Win.normalize("c:\\a\\b\\\\\\\\c"), "c:\\a\\b\\c"); + do_check_eq(Win.normalize("c:\\\\\\\\a\\b\\c"), "c:\\a\\b\\c"); + do_check_eq(Win.normalize("c:\\a\\b\\c\\\\\\"), "c:\\a\\b\\c"); + do_check_eq(Win.normalize("c:\\a\\b\\c\\..\\..\\..\\d\\e\\f"), "c:\\d\\e\\f"); + do_check_eq(Win.normalize("c:a\\b\\c\\..\\..\\..\\d\\e\\f"), "c:\\d\\e\\f"); + do_check_fail(() => Win.normalize("c:\\a\\b\\c\\..\\..\\..\\..\\d\\e\\f")); + + do_check_eq(Win.join("c:\\", "foo"), "c:\\foo", "join c:\,foo"); + do_check_eq(Win.join("c:\\tmp", "foo", "bar"), "c:\\tmp\\foo\\bar", "join c:\\tmp,foo,bar"); + do_check_eq(Win.join("c:\\tmp", "\\foo", "bar"), "c:\\foo\\bar", "join c:\\tmp,\\foo,bar"); + do_check_eq(Win.join("c:\\tmp", "c:\\foo", "bar"), "c:\\foo\\bar", "join c:\\tmp,c:\\foo,bar"); + do_check_eq(Win.join("c:\\tmp", "c:foo", "bar"), "c:\\foo\\bar", "join c:\\tmp,c:foo,bar"); + do_check_eq(Win.winGetDrive("c:"), "c:"); + do_check_eq(Win.winGetDrive("c:\\"), "c:"); + do_check_eq(Win.winGetDrive("c:abc"), "c:"); + do_check_eq(Win.winGetDrive("c:abc\\d\\e\\f\\g"), "c:"); + do_check_eq(Win.winGetDrive("c:\\abc"), "c:"); + do_check_eq(Win.winGetDrive("c:\\abc\\d\\e\\f\\g"), "c:"); + + do_print("Forwardslash-separated, no drive"); + do_check_eq(Win.normalize("/a/b/c"), "\\a\\b\\c"); + do_check_eq(Win.normalize("/a/b////c"), "\\a\\b\\c"); + do_check_eq(Win.normalize("/a/b/c///"), "\\a\\b\\c"); + do_check_eq(Win.normalize("/a/b/c/../../../d/e/f"), "\\d\\e\\f"); + do_check_eq(Win.normalize("a/b/c/../../../d/e/f"), "d\\e\\f"); + + do_print("Forwardslash-separated, with a drive"); + do_check_eq(Win.normalize("c:/"), "c:\\"); + do_check_eq(Win.normalize("c:/a/b/c"), "c:\\a\\b\\c"); + do_check_eq(Win.normalize("c:/a/b////c"), "c:\\a\\b\\c"); + do_check_eq(Win.normalize("c:////a/b/c"), "c:\\a\\b\\c"); + do_check_eq(Win.normalize("c:/a/b/c///"), "c:\\a\\b\\c"); + do_check_eq(Win.normalize("c:/a/b/c/../../../d/e/f"), "c:\\d\\e\\f"); + do_check_eq(Win.normalize("c:a/b/c/../../../d/e/f"), "c:\\d\\e\\f"); + + do_print("Backslash-separated, UNC-style"); + do_check_eq(Win.basename("\\\\a\\b"), "b"); + do_check_eq(Win.basename("\\\\a\\b\\"), ""); + do_check_eq(Win.basename("\\\\abc"), ""); + do_check_eq(Win.dirname("\\\\a\\b"), "\\\\a"); + do_check_eq(Win.dirname("\\\\a\\b\\"), "\\\\a\\b"); + do_check_eq(Win.dirname("\\\\a\\\\\\\\b"), "\\\\a"); + do_check_eq(Win.dirname("\\\\abc"), "\\\\abc"); + do_check_eq(Win.normalize("\\\\a\\b\\c"), "\\\\a\\b\\c"); + do_check_eq(Win.normalize("\\\\a\\b\\\\\\\\c"), "\\\\a\\b\\c"); + do_check_eq(Win.normalize("\\\\a\\b\\c\\\\\\"), "\\\\a\\b\\c"); + do_check_eq(Win.normalize("\\\\a\\b\\c\\..\\..\\d\\e\\f"), "\\\\a\\d\\e\\f"); + do_check_fail(() => Win.normalize("\\\\a\\b\\c\\..\\..\\..\\d\\e\\f")); + + do_check_eq(Win.join("\\\\a\\tmp", "foo", "bar"), "\\\\a\\tmp\\foo\\bar"); + do_check_eq(Win.join("\\\\a\\tmp", "\\foo", "bar"), "\\\\a\\foo\\bar"); + do_check_eq(Win.join("\\\\a\\tmp", "\\\\foo\\", "bar"), "\\\\foo\\bar"); + do_check_eq(Win.winGetDrive("\\\\"), null); + do_check_eq(Win.winGetDrive("\\\\c"), "\\\\c"); + do_check_eq(Win.winGetDrive("\\\\c\\abc"), "\\\\c"); + + do_print("Testing unix paths"); + do_check_eq(Unix.basename("a/b"), "b"); + do_check_eq(Unix.basename("a/b/"), ""); + do_check_eq(Unix.basename("abc"), "abc"); + do_check_eq(Unix.dirname("a/b"), "a"); + do_check_eq(Unix.dirname("a/b/"), "a/b"); + do_check_eq(Unix.dirname("a////b"), "a"); + do_check_eq(Unix.dirname("abc"), "."); + do_check_eq(Unix.normalize("/a/b/c"), "/a/b/c"); + do_check_eq(Unix.normalize("/a/b////c"), "/a/b/c"); + do_check_eq(Unix.normalize("////a/b/c"), "/a/b/c"); + do_check_eq(Unix.normalize("/a/b/c///"), "/a/b/c"); + do_check_eq(Unix.normalize("/a/b/c/../../../d/e/f"), "/d/e/f"); + do_check_eq(Unix.normalize("a/b/c/../../../d/e/f"), "d/e/f"); + do_check_fail(() => Unix.normalize("/a/b/c/../../../../d/e/f")); + + do_check_eq(Unix.join("/tmp", "foo", "bar"), "/tmp/foo/bar", "join /tmp,foo,bar"); + do_check_eq(Unix.join("/tmp", "/foo", "bar"), "/foo/bar", "join /tmp,/foo,bar"); + + do_print("Testing the presence of ospath.jsm"); + let Scope = {}; + try { + Components.utils.import("resource://gre/modules/osfile/ospath.jsm", Scope); + } catch (ex) { + // Can't load ospath + } + do_check_true(!!Scope.basename); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_path_constants.js b/toolkit/components/osfile/tests/xpcshell/test_path_constants.js new file mode 100644 index 000000000..c0057c750 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_path_constants.js @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +Cu.import("resource://gre/modules/ctypes.jsm", this); +Cu.import("resource://testing-common/AppData.jsm", this); + + +function run_test() { + run_next_test(); +} + +function compare_paths(ospath, key) { + let file; + try { + file = Services.dirsvc.get(key, Components.interfaces.nsIFile); + } catch(ex) {} + + if (file) { + do_check_true(!!ospath); + do_check_eq(ospath, file.path); + } else { + do_print("WARNING: " + key + " is not defined. Test may not be testing anything!"); + do_check_false(!!ospath); + } +} + +// Some path constants aren't set up until the profile is available. This +// test verifies that behavior. +add_task(function* test_before_after_profile() { + do_check_null(OS.Constants.Path.profileDir); + do_check_null(OS.Constants.Path.localProfileDir); + do_check_null(OS.Constants.Path.userApplicationDataDir); + + do_get_profile(); + do_check_true(!!OS.Constants.Path.profileDir); + do_check_true(!!OS.Constants.Path.localProfileDir); + + // UAppData is still null because the xpcshell profile doesn't set it up. + // This test is mostly here to fail in case behavior of do_get_profile() ever + // changes. We want to know if our assumptions no longer hold! + do_check_null(OS.Constants.Path.userApplicationDataDir); + + yield makeFakeAppDir(); + do_check_true(!!OS.Constants.Path.userApplicationDataDir); + + // FUTURE: verify AppData too (bug 964291). +}); + +// Test simple paths +add_task(function* test_simple_paths() { + do_check_true(!!OS.Constants.Path.tmpDir); + compare_paths(OS.Constants.Path.tmpDir, "TmpD"); + +}); + +// Test presence of paths that only exist on Desktop platforms +add_task(function* test_desktop_paths() { + if (OS.Constants.Sys.Name == "Android" || OS.Constants.Sys.Name == "Gonk") { + return; + } + do_check_true(!!OS.Constants.Path.desktopDir); + do_check_true(!!OS.Constants.Path.homeDir); + + compare_paths(OS.Constants.Path.homeDir, "Home"); + compare_paths(OS.Constants.Path.desktopDir, "Desk"); + compare_paths(OS.Constants.Path.userApplicationDataDir, "UAppData"); + + compare_paths(OS.Constants.Path.winAppDataDir, "AppData"); + compare_paths(OS.Constants.Path.winStartMenuProgsDir, "Progs"); + + compare_paths(OS.Constants.Path.macUserLibDir, "ULibDir"); + compare_paths(OS.Constants.Path.macLocalApplicationsDir, "LocApp"); + compare_paths(OS.Constants.Path.macTrashDir, "Trsh"); +}); + +// Open libxul +add_task(function* test_libxul() { + ctypes.open(OS.Constants.Path.libxul); + do_print("Linked to libxul"); +}); diff --git a/toolkit/components/osfile/tests/xpcshell/test_queue.js b/toolkit/components/osfile/tests/xpcshell/test_queue.js new file mode 100644 index 000000000..c9d23eabc --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_queue.js @@ -0,0 +1,38 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); + +function run_test() { + run_next_test(); +} + +// Check if Scheduler.queue returned by OS.File.queue is resolved initially. +add_task(function* check_init() { + yield OS.File.queue; + do_print("Function resolved"); +}); + +// Check if Scheduler.queue returned by OS.File.queue is resolved +// after an operation is successful. +add_task(function* check_success() { + do_print("Attempting to open a file correctly"); + let openedFile = yield OS.File.open(OS.Path.join(do_get_cwd().path, "test_queue.js")); + do_print("File opened correctly"); + yield OS.File.queue; + do_print("Function resolved"); +}); + +// Check if Scheduler.queue returned by OS.File.queue is resolved +// after an operation fails. +add_task(function* check_failure() { + let exception; + try { + do_print("Attempting to open a non existing file"); + yield OS.File.open(OS.Path.join(".", "Bigfoot")); + } catch (err) { + exception = err; + yield OS.File.queue; + } + do_check_true(exception!=null); + do_print("Function resolved"); +});
\ No newline at end of file diff --git a/toolkit/components/osfile/tests/xpcshell/test_read_write.js b/toolkit/components/osfile/tests/xpcshell/test_read_write.js new file mode 100644 index 000000000..00235ed8c --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_read_write.js @@ -0,0 +1,103 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var {utils: Cu} = Components; + +var SHARED_PATH; + +var EXISTING_FILE = do_get_file("xpcshell.ini").path; + +add_task(function* init() { + do_get_profile(); + SHARED_PATH = OS.Path.join(OS.Constants.Path.profileDir, "test_osfile_read.tmp"); +}); + + +// Check that OS.File.read() is executed after the previous operation +add_test_pair(function* ordering() { + let string1 = "Initial state " + Math.random(); + let string2 = "After writing " + Math.random(); + yield OS.File.writeAtomic(SHARED_PATH, string1); + OS.File.writeAtomic(SHARED_PATH, string2); + let string3 = yield OS.File.read(SHARED_PATH, { encoding: "utf-8" }); + do_check_eq(string3, string2); +}); + +add_test_pair(function* read_write_all() { + let DEST_PATH = SHARED_PATH + Math.random(); + let TMP_PATH = DEST_PATH + ".tmp"; + + let test_with_options = function(options, suffix) { + return Task.spawn(function*() { + do_print("Running test read_write_all with options " + JSON.stringify(options)); + let TEST = "read_write_all " + suffix; + + let optionsBackup = JSON.parse(JSON.stringify(options)); + + // Check that read + writeAtomic performs a correct copy + let currentDir = yield OS.File.getCurrentDirectory(); + let pathSource = OS.Path.join(currentDir, EXISTING_FILE); + let contents = yield OS.File.read(pathSource); + do_check_true(!!contents); // Content is not empty + let bytesRead = contents.byteLength; + + let bytesWritten = yield OS.File.writeAtomic(DEST_PATH, contents, options); + do_check_eq(bytesRead, bytesWritten); // Correct number of bytes written + + // Check that options are not altered + do_check_eq(JSON.stringify(options), JSON.stringify(optionsBackup)); + yield reference_compare_files(pathSource, DEST_PATH, TEST); + + // Check that temporary file was removed or never created exist + do_check_false(new FileUtils.File(TMP_PATH).exists()); + + // Check that writeAtomic fails if noOverwrite is true and the destination + // file already exists! + contents = new Uint8Array(300); + let view = new Uint8Array(contents.buffer, 10, 200); + try { + let opt = JSON.parse(JSON.stringify(options)); + opt.noOverwrite = true; + yield OS.File.writeAtomic(DEST_PATH, view, opt); + do_throw("With noOverwrite, writeAtomic should have refused to overwrite file (" + suffix + ")"); + } catch (err if err instanceof OS.File.Error && err.becauseExists) { + do_print("With noOverwrite, writeAtomic correctly failed (" + suffix + ")"); + } + yield reference_compare_files(pathSource, DEST_PATH, TEST); + + // Check that temporary file was removed or never created + do_check_false(new FileUtils.File(TMP_PATH).exists()); + + // Now write a subset + let START = 10; + let LENGTH = 100; + contents = new Uint8Array(300); + for (var i = 0; i < contents.byteLength; i++) + contents[i] = i % 256; + view = new Uint8Array(contents.buffer, START, LENGTH); + bytesWritten = yield OS.File.writeAtomic(DEST_PATH, view, options); + do_check_eq(bytesWritten, LENGTH); + + let array2 = yield OS.File.read(DEST_PATH); + do_check_eq(LENGTH, array2.length); + for (var i = 0; i < LENGTH; i++) + do_check_eq(array2[i], (i + START) % 256); + + // Cleanup. + yield OS.File.remove(DEST_PATH); + yield OS.File.remove(TMP_PATH); + }); + }; + + yield test_with_options({tmpPath: TMP_PATH}, "Renaming, not flushing"); + yield test_with_options({tmpPath: TMP_PATH, flush: true}, "Renaming, flushing"); + yield test_with_options({}, "Not renaming, not flushing"); + yield test_with_options({flush: true}, "Not renaming, flushing"); +}); + + +function run_test() { + run_next_test(); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_remove.js b/toolkit/components/osfile/tests/xpcshell/test_remove.js new file mode 100644 index 000000000..c8dc33054 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_remove.js @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +do_register_cleanup(function() { + Services.prefs.setBoolPref("toolkit.osfile.log", false); +}); + +function run_test() { + Services.prefs.setBoolPref("toolkit.osfile.log", true); + run_next_test(); +} + +add_task(function* test_ignoreAbsent() { + let absent_file_name = "test_osfile_front_absent.tmp"; + + // Removing absent files should throw if "ignoreAbsent" is true. + yield Assert.rejects(OS.File.remove(absent_file_name, {ignoreAbsent: false}), + "OS.File.remove throws if there is no such file."); + + // Removing absent files should not throw if "ignoreAbsent" is true or not + // defined. + let exception = null; + try { + yield OS.File.remove(absent_file_name, {ignoreAbsent: true}); + yield OS.File.remove(absent_file_name); + } catch (ex) { + exception = ex; + } + Assert.ok(!exception, "OS.File.remove should not throw when not requested."); +}); + +add_task(function* test_ignoreAbsent_directory_missing() { + let absent_file_name = OS.Path.join("absent_parent", "test.tmp"); + + // Removing absent files should throw if "ignoreAbsent" is true. + yield Assert.rejects(OS.File.remove(absent_file_name, {ignoreAbsent: false}), + "OS.File.remove throws if there is no such file."); + + // Removing files from absent directories should not throw if "ignoreAbsent" + // is true or not defined. + let exception = null; + try { + yield OS.File.remove(absent_file_name, {ignoreAbsent: true}); + yield OS.File.remove(absent_file_name); + } catch (ex) { + exception = ex; + } + Assert.ok(!exception, "OS.File.remove should not throw when not requested."); +}); diff --git a/toolkit/components/osfile/tests/xpcshell/test_removeDir.js b/toolkit/components/osfile/tests/xpcshell/test_removeDir.js new file mode 100644 index 000000000..41ad0eb8c --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_removeDir.js @@ -0,0 +1,177 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +do_register_cleanup(function() { + Services.prefs.setBoolPref("toolkit.osfile.log", false); +}); + +function run_test() { + Services.prefs.setBoolPref("toolkit.osfile.log", true); + + run_next_test(); +} + +add_task(function() { + // Set up profile. We create the directory in the profile, because the profile + // is removed after every test run. + do_get_profile(); + + let file = OS.Path.join(OS.Constants.Path.profileDir, "file"); + let dir = OS.Path.join(OS.Constants.Path.profileDir, "directory"); + let file1 = OS.Path.join(dir, "file1"); + let file2 = OS.Path.join(dir, "file2"); + let subDir = OS.Path.join(dir, "subdir"); + let fileInSubDir = OS.Path.join(subDir, "file"); + + // Sanity checking for the test + do_check_false((yield OS.File.exists(dir))); + + // Remove non-existent directory + let exception = null; + try { + yield OS.File.removeDir(dir, {ignoreAbsent: false}); + } catch (ex) { + exception = ex; + } + + do_check_true(!!exception); + do_check_true(exception instanceof OS.File.Error); + + // Remove non-existent directory with ignoreAbsent + yield OS.File.removeDir(dir, {ignoreAbsent: true}); + yield OS.File.removeDir(dir); + + // Remove file with ignoreAbsent: false + yield OS.File.writeAtomic(file, "content", { tmpPath: file + ".tmp" }); + exception = null; + try { + yield OS.File.removeDir(file, {ignoreAbsent: false}); + } catch (ex) { + exception = ex; + } + + do_check_true(!!exception); + do_check_true(exception instanceof OS.File.Error); + + // Remove empty directory + yield OS.File.makeDir(dir); + yield OS.File.removeDir(dir); + do_check_false((yield OS.File.exists(dir))); + + // Remove directory that contains one file + yield OS.File.makeDir(dir); + yield OS.File.writeAtomic(file1, "content", { tmpPath: file1 + ".tmp" }); + yield OS.File.removeDir(dir); + do_check_false((yield OS.File.exists(dir))); + + // Remove directory that contains multiple files + yield OS.File.makeDir(dir); + yield OS.File.writeAtomic(file1, "content", { tmpPath: file1 + ".tmp" }); + yield OS.File.writeAtomic(file2, "content", { tmpPath: file2 + ".tmp" }); + yield OS.File.removeDir(dir); + do_check_false((yield OS.File.exists(dir))); + + // Remove directory that contains a file and a directory + yield OS.File.makeDir(dir); + yield OS.File.writeAtomic(file1, "content", { tmpPath: file1 + ".tmp" }); + yield OS.File.makeDir(subDir); + yield OS.File.writeAtomic(fileInSubDir, "content", { tmpPath: fileInSubDir + ".tmp" }); + yield OS.File.removeDir(dir); + do_check_false((yield OS.File.exists(dir))); +}); + +add_task(function* test_unix_symlink() { + // Windows does not implement OS.File.unixSymLink() + if (OS.Constants.Win) { + return; + } + + // Android / B2G file systems typically don't support symlinks. + if (OS.Constants.Sys.Name == "Android") { + return; + } + + let file = OS.Path.join(OS.Constants.Path.profileDir, "file"); + let dir = OS.Path.join(OS.Constants.Path.profileDir, "directory"); + let file1 = OS.Path.join(dir, "file1"); + + // This test will create the following directory structure: + // <profileDir>/file (regular file) + // <profileDir>/file.link => file (symlink) + // <profileDir>/directory (directory) + // <profileDir>/linkdir => directory (directory) + // <profileDir>/directory/file1 (regular file) + // <profileDir>/directory3 (directory) + // <profileDir>/directory3/file3 (directory) + // <profileDir>/directory/link2 => ../directory3 (regular file) + + // Sanity checking for the test + do_check_false((yield OS.File.exists(dir))); + + yield OS.File.writeAtomic(file, "content", { tmpPath: file + ".tmp" }); + do_check_true((yield OS.File.exists(file))); + let info = yield OS.File.stat(file, {unixNoFollowingLinks: true}); + do_check_false(info.isDir); + do_check_false(info.isSymLink); + + yield OS.File.unixSymLink(file, file + ".link"); + do_check_true((yield OS.File.exists(file + ".link"))); + info = yield OS.File.stat(file + ".link", {unixNoFollowingLinks: true}); + do_check_false(info.isDir); + do_check_true(info.isSymLink); + info = yield OS.File.stat(file + ".link"); + do_check_false(info.isDir); + do_check_false(info.isSymLink); + yield OS.File.remove(file + ".link"); + do_check_false((yield OS.File.exists(file + ".link"))); + + yield OS.File.makeDir(dir); + do_check_true((yield OS.File.exists(dir))); + info = yield OS.File.stat(dir, {unixNoFollowingLinks: true}); + do_check_true(info.isDir); + do_check_false(info.isSymLink); + + let link = OS.Path.join(OS.Constants.Path.profileDir, "linkdir"); + + yield OS.File.unixSymLink(dir, link); + do_check_true((yield OS.File.exists(link))); + info = yield OS.File.stat(link, {unixNoFollowingLinks: true}); + do_check_false(info.isDir); + do_check_true(info.isSymLink); + info = yield OS.File.stat(link); + do_check_true(info.isDir); + do_check_false(info.isSymLink); + + let dir3 = OS.Path.join(OS.Constants.Path.profileDir, "directory3"); + let file3 = OS.Path.join(dir3, "file3"); + let link2 = OS.Path.join(dir, "link2"); + + yield OS.File.writeAtomic(file1, "content", { tmpPath: file1 + ".tmp" }); + do_check_true((yield OS.File.exists(file1))); + yield OS.File.makeDir(dir3); + do_check_true((yield OS.File.exists(dir3))); + yield OS.File.writeAtomic(file3, "content", { tmpPath: file3 + ".tmp" }); + do_check_true((yield OS.File.exists(file3))); + yield OS.File.unixSymLink("../directory3", link2); + do_check_true((yield OS.File.exists(link2))); + + yield OS.File.removeDir(link); + do_check_false((yield OS.File.exists(link))); + do_check_true((yield OS.File.exists(file1))); + yield OS.File.removeDir(dir); + do_check_false((yield OS.File.exists(dir))); + do_check_true((yield OS.File.exists(file3))); + yield OS.File.removeDir(dir3); + do_check_false((yield OS.File.exists(dir3))); + + // This task will be executed only on Unix-like systems. + // Please do not add tests independent to operating systems here + // or implement symlink() on Windows. +}); diff --git a/toolkit/components/osfile/tests/xpcshell/test_removeEmptyDir.js b/toolkit/components/osfile/tests/xpcshell/test_removeEmptyDir.js new file mode 100644 index 000000000..95f8d5cd1 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_removeEmptyDir.js @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +do_register_cleanup(function() { + Services.prefs.setBoolPref("toolkit.osfile.log", false); +}); + +function run_test() { + Services.prefs.setBoolPref("toolkit.osfile.log", true); + + run_next_test(); +} + +/** + * Test OS.File.removeEmptyDir + */ +add_task(function() { + // Set up profile. We create the directory in the profile, because the profile + // is removed after every test run. + do_get_profile(); + + let dir = OS.Path.join(OS.Constants.Path.profileDir, "directory"); + + // Sanity checking for the test + do_check_false((yield OS.File.exists(dir))); + + // Remove non-existent directory + yield OS.File.removeEmptyDir(dir); + + // Remove non-existent directory with ignoreAbsent + yield OS.File.removeEmptyDir(dir, {ignoreAbsent: true}); + + // Remove non-existent directory with ignoreAbsent false + let exception = null; + try { + yield OS.File.removeEmptyDir(dir, {ignoreAbsent: false}); + } catch (ex) { + exception = ex; + } + + do_check_true(!!exception); + do_check_true(exception instanceof OS.File.Error); + do_check_true(exception.becauseNoSuchFile); + + // Remove empty directory + yield OS.File.makeDir(dir); + yield OS.File.removeEmptyDir(dir); + do_check_false((yield OS.File.exists(dir))); +}); diff --git a/toolkit/components/osfile/tests/xpcshell/test_reset.js b/toolkit/components/osfile/tests/xpcshell/test_reset.js new file mode 100644 index 000000000..f1e1b14d1 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_reset.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +var Path = OS.Constants.Path; + +add_task(function* init() { + do_get_profile(); +}); + +add_task(function* reset_before_launching() { + do_print("Reset without launching OS.File, it shouldn't break"); + yield OS.File.resetWorker(); +}); + +add_task(function* transparent_reset() { + for (let i = 1; i < 3; ++i) { + do_print("Do stome stuff before and after " + i + " reset(s), " + + "it shouldn't break"); + let CONTENT = "some content " + i; + let path = OS.Path.join(Path.profileDir, "tmp"); + yield OS.File.writeAtomic(path, CONTENT); + for (let j = 0; j < i; ++j) { + yield OS.File.resetWorker(); + } + let data = yield OS.File.read(path); + let string = (new TextDecoder()).decode(data); + do_check_eq(string, CONTENT); + } +}); + +add_task(function* file_open_cannot_reset() { + let TEST_FILE = OS.Path.join(Path.profileDir, "tmp-" + Math.random()); + do_print("Leaking file descriptor " + TEST_FILE + ", we shouldn't be able to reset"); + let openedFile = yield OS.File.open(TEST_FILE, { create: true} ); + let thrown = false; + try { + yield OS.File.resetWorker(); + } catch (ex if ex.message.indexOf(OS.Path.basename(TEST_FILE)) != -1 ) { + thrown = true; + } + do_check_true(thrown); + + do_print("Closing the file, we should now be able to reset"); + yield openedFile.close(); + yield OS.File.resetWorker(); +}); + +add_task(function* dir_open_cannot_reset() { + let TEST_DIR = yield OS.File.getCurrentDirectory(); + do_print("Leaking directory " + TEST_DIR + ", we shouldn't be able to reset"); + let iterator = new OS.File.DirectoryIterator(TEST_DIR); + let thrown = false; + try { + yield OS.File.resetWorker(); + } catch (ex if ex.message.indexOf(OS.Path.basename(TEST_DIR)) != -1 ) { + thrown = true; + } + do_check_true(thrown); + + do_print("Closing the directory, we should now be able to reset"); + yield iterator.close(); + yield OS.File.resetWorker(); +}); + +add_task(function* race_against_itself() { + do_print("Attempt to get resetWorker() to race against itself"); + // Arbitrary operation, just to wake up the worker + try { + yield OS.File.read("/foo"); + } catch (ex) { + } + + let all = []; + for (let i = 0; i < 100; ++i) { + all.push(OS.File.resetWorker()); + } + + yield Promise.all(all); +}); + + +add_task(function* finish_with_a_reset() { + do_print("Reset without waiting for the result"); + // Arbitrary operation, just to wake up the worker + try { + yield OS.File.read("/foo"); + } catch (ex) { + } + // Now reset + /*don't yield*/ OS.File.resetWorker(); +}); + +function run_test() { + run_next_test(); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_shutdown.js b/toolkit/components/osfile/tests/xpcshell/test_shutdown.js new file mode 100644 index 000000000..667965d9e --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_shutdown.js @@ -0,0 +1,98 @@ +Components.utils.import("resource://gre/modules/Services.jsm", this); +Components.utils.import("resource://gre/modules/Promise.jsm", this); +Components.utils.import("resource://gre/modules/Task.jsm", this); +Components.utils.import("resource://gre/modules/osfile.jsm", this); + +add_task(function init() { + do_get_profile(); +}); + +/** + * Test logging of file descriptors leaks. + */ +add_task(function system_shutdown() { + + // Test that unclosed files cause warnings + // Test that unclosed directories cause warnings + // Test that closed files do not cause warnings + // Test that closed directories do not cause warnings + function testLeaksOf(resource, topic) { + return Task.spawn(function() { + let deferred = Promise.defer(); + + // Register observer + Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true); + Services.prefs.setBoolPref("toolkit.osfile.log", true); + Services.prefs.setBoolPref("toolkit.osfile.log.redirect", true); + Services.prefs.setCharPref("toolkit.osfile.test.shutdown.observer", topic); + + let observer = function(aMessage) { + try { + do_print("Got message: " + aMessage); + if (!(aMessage instanceof Components.interfaces.nsIConsoleMessage)) { + return; + } + let message = aMessage.message; + do_print("Got message: " + message); + if (message.indexOf("TEST OS Controller WARNING") < 0) { + return; + } + do_print("Got message: " + message + ", looking for resource " + resource); + if (message.indexOf(resource) < 0) { + return; + } + do_print("Resource: " + resource + " found"); + do_execute_soon(deferred.resolve); + } catch (ex) { + do_execute_soon(function() { + deferred.reject(ex); + }); + } + }; + Services.console.registerListener(observer); + Services.obs.notifyObservers(null, topic, null); + do_timeout(1000, function() { + do_print("Timeout while waiting for resource: " + resource); + deferred.reject("timeout"); + }); + + let resolved = false; + try { + yield deferred.promise; + resolved = true; + } catch (ex if ex == "timeout") { + resolved = false; + } + Services.console.unregisterListener(observer); + Services.prefs.clearUserPref("toolkit.osfile.log"); + Services.prefs.clearUserPref("toolkit.osfile.log.redirect"); + Services.prefs.clearUserPref("toolkit.osfile.test.shutdown.observer"); + Services.prefs.clearUserPref("toolkit.async_shutdown.testing", true); + + throw new Task.Result(resolved); + }); + } + + let TEST_DIR = OS.Path.join((yield OS.File.getCurrentDirectory()), ".."); + do_print("Testing for leaks of directory iterator " + TEST_DIR); + let iterator = new OS.File.DirectoryIterator(TEST_DIR); + do_print("At this stage, we leak the directory"); + do_check_true((yield testLeaksOf(TEST_DIR, "test.shutdown.dir.leak"))); + yield iterator.close(); + do_print("At this stage, we don't leak the directory anymore"); + do_check_false((yield testLeaksOf(TEST_DIR, "test.shutdown.dir.noleak"))); + + let TEST_FILE = OS.Path.join(OS.Constants.Path.profileDir, "test"); + do_print("Testing for leaks of file descriptor: " + TEST_FILE); + let openedFile = yield OS.File.open(TEST_FILE, { create: true} ); + do_print("At this stage, we leak the file"); + do_check_true((yield testLeaksOf(TEST_FILE, "test.shutdown.file.leak"))); + yield openedFile.close(); + do_print("At this stage, we don't leak the file anymore"); + do_check_false((yield testLeaksOf(TEST_FILE, "test.shutdown.file.leak.2"))); +}); + + +function run_test() { + run_next_test(); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_telemetry.js b/toolkit/components/osfile/tests/xpcshell/test_telemetry.js new file mode 100644 index 000000000..dc5104443 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_telemetry.js @@ -0,0 +1,63 @@ +"use strict"; + +var {OS: {File, Path, Constants}} = Components.utils.import("resource://gre/modules/osfile.jsm", {}); +var {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {}); + +// Ensure that we have a profile but that the OS.File worker is not launched +add_task(function* init() { + do_get_profile(); + yield File.resetWorker(); +}); + +function getCount(histogram) { + if (histogram == null) { + return 0; + } + + let total = 0; + for (let i of histogram.counts) { + total += i; + } + return total; +} + +// Ensure that launching the OS.File worker adds data to the relevant +// histograms +add_task(function* test_startup() { + let LAUNCH = "OSFILE_WORKER_LAUNCH_MS"; + let READY = "OSFILE_WORKER_READY_MS"; + + let before = Services.telemetry.histogramSnapshots; + + // Launch the OS.File worker + yield File.getCurrentDirectory(); + + let after = Services.telemetry.histogramSnapshots; + + + do_print("Ensuring that we have recorded measures for histograms"); + do_check_eq(getCount(after[LAUNCH]), getCount(before[LAUNCH]) + 1); + do_check_eq(getCount(after[READY]), getCount(before[READY]) + 1); + + do_print("Ensuring that launh <= ready"); + do_check_true(after[LAUNCH].sum <= after[READY].sum); +}); + +// Ensure that calling writeAtomic adds data to the relevant histograms +add_task(function* test_writeAtomic() { + let LABEL = "OSFILE_WRITEATOMIC_JANK_MS"; + + let before = Services.telemetry.histogramSnapshots; + + // Perform a write. + let path = Path.join(Constants.Path.profileDir, "test_osfile_telemetry.tmp"); + yield File.writeAtomic(path, LABEL, { tmpPath: path + ".tmp" } ); + + let after = Services.telemetry.histogramSnapshots; + + do_check_eq(getCount(after[LABEL]), getCount(before[LABEL]) + 1); +}); + +function run_test() { + run_next_test(); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_unique.js b/toolkit/components/osfile/tests/xpcshell/test_unique.js new file mode 100644 index 000000000..8aa81b803 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_unique.js @@ -0,0 +1,88 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/osfile.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); + +function run_test() { + do_get_profile(); + run_next_test(); +} + +function testFiles(filename) { + return Task.spawn(function() { + const MAX_TRIES = 10; + let profileDir = OS.Constants.Path.profileDir; + let path = OS.Path.join(profileDir, filename); + + // Ensure that openUnique() uses the file name if there is no file with that name already. + let openedFile = yield OS.File.openUnique(path); + do_print("\nCreate new file: " + openedFile.path); + yield openedFile.file.close(); + let exists = yield OS.File.exists(openedFile.path); + do_check_true(exists); + do_check_eq(path, openedFile.path); + let fileInfo = yield OS.File.stat(openedFile.path); + do_check_true(fileInfo.size == 0); + + // Ensure that openUnique() creates a new file name using a HEX number, as the original name is already taken. + openedFile = yield OS.File.openUnique(path); + do_print("\nCreate unique HEX file: " + openedFile.path); + yield openedFile.file.close(); + exists = yield OS.File.exists(openedFile.path); + do_check_true(exists); + fileInfo = yield OS.File.stat(openedFile.path); + do_check_true(fileInfo.size == 0); + + // Ensure that openUnique() generates different file names each time, using the HEX number algorithm + let filenames = new Set(); + for (let i=0; i < MAX_TRIES; i++) { + openedFile = yield OS.File.openUnique(path); + yield openedFile.file.close(); + filenames.add(openedFile.path); + } + + do_check_eq(filenames.size, MAX_TRIES); + + // Ensure that openUnique() creates a new human readable file name using, as the original name is already taken. + openedFile = yield OS.File.openUnique(path, {humanReadable : true}); + do_print("\nCreate unique Human Readable file: " + openedFile.path); + yield openedFile.file.close(); + exists = yield OS.File.exists(openedFile.path); + do_check_true(exists); + fileInfo = yield OS.File.stat(openedFile.path); + do_check_true(fileInfo.size == 0); + + // Ensure that openUnique() generates different human readable file names each time + filenames = new Set(); + for (let i=0; i < MAX_TRIES; i++) { + openedFile = yield OS.File.openUnique(path, {humanReadable : true}); + yield openedFile.file.close(); + filenames.add(openedFile.path); + } + + do_check_eq(filenames.size, MAX_TRIES); + + let exn; + try { + for (let i=0; i < 100; i++) { + openedFile = yield OS.File.openUnique(path, {humanReadable : true}); + yield openedFile.file.close(); + } + } catch (ex) { + exn = ex; + } + + do_print("Ensure that this raises the correct error"); + do_check_true(!!exn); + do_check_true(exn instanceof OS.File.Error); + do_check_true(exn.becauseExists); + }); +} + +add_task(function test_unique() { + OS.Shared.DEBUG = true; + // Tests files with extension + yield testFiles("dummy_unique_file.txt"); + // Tests files with no extension + yield testFiles("dummy_unique_file_no_ext"); +}); diff --git a/toolkit/components/osfile/tests/xpcshell/xpcshell.ini b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini new file mode 100644 index 000000000..58b106d3d --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini @@ -0,0 +1,51 @@ +[DEFAULT] +head = head.js +tail = + +support-files = + test_loader/module_test_loader.js + +[test_available_free_space.js] +[test_compression.js] +[test_constants.js] +[test_creationDate.js] +[test_duration.js] +[test_exception.js] +[test_file_URL_conversion.js] +[test_loader.js] +[test_logging.js] +[test_makeDir.js] +[test_open.js] +[test_osfile_async.js] +[test_osfile_async_append.js] +[test_osfile_async_bytes.js] +[test_osfile_async_copy.js] +[test_osfile_async_flush.js] +[test_osfile_async_largefiles.js] +[test_osfile_async_setDates.js] +# Unimplemented on Windows (bug 1022816). +# Spurious failure on Android test farm due to non-POSIX behavior of +# filesystem backing /mnt/sdcard (not worth trying to fix). +[test_osfile_async_setPermissions.js] +skip-if = os == "win" || os == "android" +[test_osfile_closed.js] +[test_osfile_error.js] +[test_osfile_kill.js] +# Windows test +[test_osfile_win_async_setPermissions.js] +skip-if = os != "win" +[test_osfile_writeAtomic_backupTo_option.js] +[test_osfile_writeAtomic_zerobytes.js] +[test_path.js] +[test_path_constants.js] +[test_queue.js] +[test_read_write.js] +requesttimeoutfactor = 4 +[test_remove.js] +[test_removeDir.js] +requesttimeoutfactor = 4 +[test_removeEmptyDir.js] +[test_reset.js] +[test_shutdown.js] +[test_telemetry.js] +[test_unique.js] |