diff options
Diffstat (limited to 'toolkit/components/osfile/tests/mochi/main_test_osfile_async.js')
-rw-r--r-- | toolkit/components/osfile/tests/mochi/main_test_osfile_async.js | 443 |
1 files changed, 443 insertions, 0 deletions
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); + }); +}); + + |