1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,530 @@ 1.4 +"use strict"; 1.5 + 1.6 +Components.utils.import("resource://gre/modules/osfile.jsm"); 1.7 +Components.utils.import("resource://gre/modules/Promise.jsm"); 1.8 +Components.utils.import("resource://gre/modules/Task.jsm"); 1.9 +Components.utils.import("resource://gre/modules/AsyncShutdown.jsm"); 1.10 + 1.11 +// The following are used to compare against a well-tested reference 1.12 +// implementation of file I/O. 1.13 +Components.utils.import("resource://gre/modules/NetUtil.jsm"); 1.14 +Components.utils.import("resource://gre/modules/FileUtils.jsm"); 1.15 +Components.utils.import("resource://gre/modules/Services.jsm"); 1.16 + 1.17 +let myok = ok; 1.18 +let myis = is; 1.19 +let myinfo = info; 1.20 +let myisnot = isnot; 1.21 + 1.22 +let isPromise = function ispromise(value) { 1.23 + return value != null && typeof value == "object" && "then" in value; 1.24 +}; 1.25 + 1.26 +let maketest = function(prefix, test) { 1.27 + let utils = { 1.28 + ok: function ok(t, m) { 1.29 + myok(t, prefix + ": " + m); 1.30 + }, 1.31 + is: function is(l, r, m) { 1.32 + myis(l, r, prefix + ": " + m); 1.33 + }, 1.34 + isnot: function isnot(l, r, m) { 1.35 + myisnot(l, r, prefix + ": " + m); 1.36 + }, 1.37 + info: function info(m) { 1.38 + myinfo(prefix + ": " + m); 1.39 + }, 1.40 + fail: function fail(m) { 1.41 + utils.ok(false, m); 1.42 + }, 1.43 + okpromise: function okpromise(t, m) { 1.44 + return t.then( 1.45 + function onSuccess() { 1.46 + util.ok(true, m); 1.47 + }, 1.48 + function onFailure() { 1.49 + util.ok(false, m); 1.50 + } 1.51 + ); 1.52 + } 1.53 + }; 1.54 + return function runtest() { 1.55 + utils.info("Entering"); 1.56 + try { 1.57 + let result = test.call(this, utils); 1.58 + if (!isPromise(result)) { 1.59 + throw new TypeError("The test did not return a promise"); 1.60 + } 1.61 + utils.info("This was a promise"); 1.62 + // The test returns a promise 1.63 + result = result.then(function test_complete() { 1.64 + utils.info("Complete"); 1.65 + }, function catch_uncaught_errors(err) { 1.66 + utils.fail("Uncaught error " + err); 1.67 + if (err && typeof err == "object" && "message" in err) { 1.68 + utils.fail("(" + err.message + ")"); 1.69 + } 1.70 + if (err && typeof err == "object" && "stack" in err) { 1.71 + utils.fail("at " + err.stack); 1.72 + } 1.73 + }); 1.74 + return result; 1.75 + } catch (x) { 1.76 + utils.fail("Error " + x + " at " + x.stack); 1.77 + return null; 1.78 + } 1.79 + }; 1.80 +}; 1.81 + 1.82 +/** 1.83 + * Fetch asynchronously the contents of a file using xpcom. 1.84 + * 1.85 + * Used for comparing xpcom-based results to os.file-based results. 1.86 + * 1.87 + * @param {string} path The _absolute_ path to the file. 1.88 + * @return {promise} 1.89 + * @resolves {string} The contents of the file. 1.90 + */ 1.91 +let reference_fetch_file = function reference_fetch_file(path, test) { 1.92 + test.info("Fetching file " + path); 1.93 + let promise = Promise.defer(); 1.94 + let file = new FileUtils.File(path); 1.95 + NetUtil.asyncFetch(file, 1.96 + function(stream, status) { 1.97 + if (!Components.isSuccessCode(status)) { 1.98 + promise.reject(status); 1.99 + return; 1.100 + } 1.101 + let result, reject; 1.102 + try { 1.103 + result = NetUtil.readInputStreamToString(stream, stream.available()); 1.104 + } catch (x) { 1.105 + reject = x; 1.106 + } 1.107 + stream.close(); 1.108 + if (reject) { 1.109 + promise.reject(reject); 1.110 + } else { 1.111 + promise.resolve(result); 1.112 + } 1.113 + }); 1.114 + return promise.promise; 1.115 +}; 1.116 + 1.117 +/** 1.118 + * Compare asynchronously the contents two files using xpcom. 1.119 + * 1.120 + * Used for comparing xpcom-based results to os.file-based results. 1.121 + * 1.122 + * @param {string} a The _absolute_ path to the first file. 1.123 + * @param {string} b The _absolute_ path to the second file. 1.124 + * 1.125 + * @resolves {null} 1.126 + */ 1.127 +let reference_compare_files = function reference_compare_files(a, b, test) { 1.128 + test.info("Comparing files " + a + " and " + b); 1.129 + let a_contents = yield reference_fetch_file(a, test); 1.130 + let b_contents = yield reference_fetch_file(b, test); 1.131 + is(a_contents, b_contents, "Contents of files " + a + " and " + b + " match"); 1.132 +}; 1.133 + 1.134 +let reference_dir_contents = function reference_dir_contents(path) { 1.135 + let result = []; 1.136 + let entries = new FileUtils.File(path).directoryEntries; 1.137 + while (entries.hasMoreElements()) { 1.138 + let entry = entries.getNext().QueryInterface(Components.interfaces.nsILocalFile); 1.139 + result.push(entry.path); 1.140 + } 1.141 + return result; 1.142 +}; 1.143 + 1.144 +// Set/Unset OS.Shared.DEBUG, OS.Shared.TEST and a console listener. 1.145 +function toggleDebugTest (pref, consoleListener) { 1.146 + Services.prefs.setBoolPref("toolkit.osfile.log", pref); 1.147 + Services.prefs.setBoolPref("toolkit.osfile.log.redirect", pref); 1.148 + Services.console[pref ? "registerListener" : "unregisterListener"]( 1.149 + consoleListener); 1.150 +} 1.151 + 1.152 +let test = maketest("Main", function main(test) { 1.153 + return Task.spawn(function() { 1.154 + SimpleTest.waitForExplicitFinish(); 1.155 + yield test_constants(); 1.156 + yield test_path(); 1.157 + yield test_stat(); 1.158 + yield test_debug(); 1.159 + yield test_info_features_detect(); 1.160 + yield test_read_write(); 1.161 + yield test_position(); 1.162 + yield test_iter(); 1.163 + yield test_exists(); 1.164 + yield test_debug_test(); 1.165 + info("Test is over"); 1.166 + SimpleTest.finish(); 1.167 + }); 1.168 +}); 1.169 + 1.170 +/** 1.171 + * A file that we know exists and that can be used for reading. 1.172 + */ 1.173 +let EXISTING_FILE = OS.Path.join("chrome", "toolkit", "components", 1.174 + "osfile", "tests", "mochi", "main_test_osfile_async.js"); 1.175 + 1.176 +/** 1.177 + * Test that OS.Constants is defined correctly. 1.178 + */ 1.179 +let test_constants = maketest("constants", function constants(test) { 1.180 + return Task.spawn(function() { 1.181 + test.isnot(OS.Constants, null, "OS.Constants exists"); 1.182 + test.ok(OS.Constants.Win || OS.Constants.libc, "OS.Constants.Win exists or OS.Constants.Unix exists"); 1.183 + test.isnot(OS.Constants.Path, null, "OS.Constants.Path exists"); 1.184 + test.isnot(OS.Constants.Sys, null, "OS.Constants.Sys exists"); 1.185 + }); 1.186 +}); 1.187 + 1.188 +/** 1.189 + * Test that OS.Constants.Path paths are consistent. 1.190 + */ 1.191 +let test_path = maketest("path", function path(test) { 1.192 + return Task.spawn(function() { 1.193 + test.ok(OS.Path, "OS.Path exists"); 1.194 + test.ok(OS.Constants.Path, "OS.Constants.Path exists"); 1.195 + test.is(OS.Constants.Path.tmpDir, Services.dirsvc.get("TmpD", Components.interfaces.nsIFile).path, "OS.Constants.Path.tmpDir is correct"); 1.196 + test.is(OS.Constants.Path.profileDir, Services.dirsvc.get("ProfD", Components.interfaces.nsIFile).path, "OS.Constants.Path.profileDir is correct"); 1.197 + test.is(OS.Constants.Path.localProfileDir, Services.dirsvc.get("ProfLD", Components.interfaces.nsIFile).path, "OS.Constants.Path.localProfileDir is correct"); 1.198 + }); 1.199 +}); 1.200 + 1.201 +/** 1.202 + * Test OS.File.stat and OS.File.prototype.stat 1.203 + */ 1.204 +let test_stat = maketest("stat", function stat(test) { 1.205 + return Task.spawn(function() { 1.206 + // Open a file and stat it 1.207 + let file = yield OS.File.open(EXISTING_FILE); 1.208 + let stat1; 1.209 + 1.210 + try { 1.211 + test.info("Stating file"); 1.212 + stat1 = yield file.stat(); 1.213 + test.ok(true, "stat has worked " + stat1); 1.214 + test.ok(stat1, "stat is not empty"); 1.215 + } finally { 1.216 + yield file.close(); 1.217 + } 1.218 + 1.219 + // Stat the same file without opening it 1.220 + test.info("Stating a file without opening it"); 1.221 + let stat2 = yield OS.File.stat(EXISTING_FILE); 1.222 + test.ok(true, "stat 2 has worked " + stat2); 1.223 + test.ok(stat2, "stat 2 is not empty"); 1.224 + for (let key in stat2) { 1.225 + test.is("" + stat1[key], "" + stat2[key], "Stat field " + key + "is the same"); 1.226 + } 1.227 + }); 1.228 +}); 1.229 + 1.230 +/** 1.231 + * Test feature detection using OS.File.Info.prototype on main thread 1.232 + */ 1.233 +let test_info_features_detect = maketest("features_detect", function features_detect(test) { 1.234 + return Task.spawn(function() { 1.235 + if (OS.Constants.Win) { 1.236 + // see if winBirthDate is defined 1.237 + if ("winBirthDate" in OS.File.Info.prototype) { 1.238 + test.ok(true, "winBirthDate is defined"); 1.239 + } else { 1.240 + test.fail("winBirthDate not defined though we are under Windows"); 1.241 + } 1.242 + } else if (OS.Constants.libc) { 1.243 + // see if unixGroup is defined 1.244 + if ("unixGroup" in OS.File.Info.prototype) { 1.245 + test.ok(true, "unixGroup is defined"); 1.246 + } else { 1.247 + test.fail("unixGroup is not defined though we are under Unix"); 1.248 + } 1.249 + } 1.250 + }); 1.251 +}); 1.252 + 1.253 +/** 1.254 + * Test OS.File.prototype.{read, readTo, write} 1.255 + */ 1.256 +let test_read_write = maketest("read_write", function read_write(test) { 1.257 + return Task.spawn(function() { 1.258 + // Test readTo/write 1.259 + let currentDir = yield OS.File.getCurrentDirectory(); 1.260 + let pathSource = OS.Path.join(currentDir, EXISTING_FILE); 1.261 + let pathDest = OS.Path.join(OS.Constants.Path.tmpDir, 1.262 + "osfile async test.tmp"); 1.263 + 1.264 + let fileSource = yield OS.File.open(pathSource); 1.265 + test.info("Input file opened"); 1.266 + let fileDest = yield OS.File.open(pathDest, 1.267 + { truncate: true, read: true, write: true}); 1.268 + test.info("Output file opened"); 1.269 + 1.270 + let stat = yield fileSource.stat(); 1.271 + test.info("Input stat worked"); 1.272 + let size = stat.size; 1.273 + let array = new Uint8Array(size); 1.274 + 1.275 + try { 1.276 + test.info("Now calling readTo"); 1.277 + let readLength = yield fileSource.readTo(array); 1.278 + test.info("ReadTo worked"); 1.279 + test.is(readLength, size, "ReadTo got all bytes"); 1.280 + let writeLength = yield fileDest.write(array); 1.281 + test.info("Write worked"); 1.282 + test.is(writeLength, size, "Write wrote all bytes"); 1.283 + 1.284 + // Test read 1.285 + yield fileSource.setPosition(0); 1.286 + let readAllResult = yield fileSource.read(); 1.287 + test.info("ReadAll worked"); 1.288 + test.is(readAllResult.length, size, "ReadAll read all bytes"); 1.289 + test.is(Array.prototype.join.call(readAllResult), 1.290 + Array.prototype.join.call(array), 1.291 + "ReadAll result is correct"); 1.292 + } finally { 1.293 + // Close stuff 1.294 + yield fileSource.close(); 1.295 + yield fileDest.close(); 1.296 + test.info("Files are closed"); 1.297 + } 1.298 + 1.299 + stat = yield OS.File.stat(pathDest); 1.300 + test.is(stat.size, size, "Both files have the same size"); 1.301 + yield reference_compare_files(pathSource, pathDest, test); 1.302 + 1.303 + // Cleanup. 1.304 + OS.File.remove(pathDest); 1.305 + }); 1.306 +}); 1.307 + 1.308 + 1.309 +/** 1.310 + * Test file.{getPosition, setPosition} 1.311 + */ 1.312 +let test_position = maketest("position", function position(test) { 1.313 + return Task.spawn(function() { 1.314 + let file = yield OS.File.open(EXISTING_FILE); 1.315 + 1.316 + try { 1.317 + let stat = yield file.stat(); 1.318 + test.info("Obtained file length"); 1.319 + 1.320 + let view = new Uint8Array(stat.size); 1.321 + yield file.readTo(view); 1.322 + test.info("First batch of content read"); 1.323 + 1.324 + let CHUNK_SIZE = 178;// An arbitrary number of bytes to read from the file 1.325 + let pos = yield file.getPosition(); 1.326 + test.info("Obtained position"); 1.327 + test.is(pos, view.byteLength, "getPosition returned the end of the file"); 1.328 + pos = yield file.setPosition(-CHUNK_SIZE, OS.File.POS_END); 1.329 + test.info("Changed position"); 1.330 + test.is(pos, view.byteLength - CHUNK_SIZE, "setPosition returned the correct position"); 1.331 + 1.332 + let view2 = new Uint8Array(CHUNK_SIZE); 1.333 + yield file.readTo(view2); 1.334 + test.info("Read the end of the file"); 1.335 + for (let i = 0; i < CHUNK_SIZE; ++i) { 1.336 + if (view2[i] != view[i + view.byteLength - CHUNK_SIZE]) { 1.337 + test.is(view2[i], view[i], "setPosition put us in the right position"); 1.338 + } 1.339 + } 1.340 + } finally { 1.341 + yield file.close(); 1.342 + } 1.343 + }); 1.344 +}); 1.345 + 1.346 +/** 1.347 + * Test OS.File.prototype.{DirectoryIterator} 1.348 + */ 1.349 +let test_iter = maketest("iter", function iter(test) { 1.350 + return Task.spawn(function() { 1.351 + let currentDir = yield OS.File.getCurrentDirectory(); 1.352 + 1.353 + // Trivial walks through the directory 1.354 + test.info("Preparing iteration"); 1.355 + let iterator = new OS.File.DirectoryIterator(currentDir); 1.356 + let temporary_file_name = OS.Path.join(currentDir, "empty-temporary-file.tmp"); 1.357 + try { 1.358 + yield OS.File.remove(temporary_file_name); 1.359 + } catch (err) { 1.360 + // Ignore errors removing file 1.361 + } 1.362 + let allFiles1 = yield iterator.nextBatch(); 1.363 + test.info("Obtained all files through nextBatch"); 1.364 + test.isnot(allFiles1.length, 0, "There is at least one file"); 1.365 + test.isnot(allFiles1[0].path, null, "Files have a path"); 1.366 + 1.367 + // Ensure that we have the same entries with |reference_dir_contents| 1.368 + let referenceEntries = new Set(); 1.369 + for (let entry of reference_dir_contents(currentDir)) { 1.370 + referenceEntries.add(entry); 1.371 + } 1.372 + test.is(referenceEntries.size, allFiles1.length, "All the entries in the directory have been listed"); 1.373 + for (let entry of allFiles1) { 1.374 + test.ok(referenceEntries.has(entry.path), "File " + entry.path + " effectively exists"); 1.375 + // Ensure that we have correct isDir and isSymLink 1.376 + // Current directory is {objdir}/_tests/testing/mochitest/, assume it has some dirs and symlinks. 1.377 + var f = new FileUtils.File(entry.path); 1.378 + test.is(entry.isDir, f.isDirectory(), "Get file " + entry.path + " isDir correctly"); 1.379 + test.is(entry.isSymLink, f.isSymlink(), "Get file " + entry.path + " isSymLink correctly"); 1.380 + } 1.381 + 1.382 + yield iterator.close(); 1.383 + test.info("Closed iterator"); 1.384 + 1.385 + test.info("Double closing DirectoryIterator"); 1.386 + iterator = new OS.File.DirectoryIterator(currentDir); 1.387 + yield iterator.close(); 1.388 + yield iterator.close(); //double closing |DirectoryIterator| 1.389 + test.ok(true, "|DirectoryIterator| was closed twice successfully"); 1.390 + 1.391 + let allFiles2 = []; 1.392 + let i = 0; 1.393 + iterator = new OS.File.DirectoryIterator(currentDir); 1.394 + yield iterator.forEach(function(entry, index) { 1.395 + test.is(i++, index, "Getting the correct index"); 1.396 + allFiles2.push(entry); 1.397 + }); 1.398 + test.info("Obtained all files through forEach"); 1.399 + is(allFiles1.length, allFiles2.length, "Both runs returned the same number of files"); 1.400 + for (let i = 0; i < allFiles1.length; ++i) { 1.401 + if (allFiles1[i].path != allFiles2[i].path) { 1.402 + test.is(allFiles1[i].path, allFiles2[i].path, "Both runs return the same files"); 1.403 + break; 1.404 + } 1.405 + } 1.406 + 1.407 + // Testing batch iteration + whether an iteration can be stopped early 1.408 + let BATCH_LENGTH = 10; 1.409 + test.info("Getting some files through nextBatch"); 1.410 + yield iterator.close(); 1.411 + 1.412 + iterator = new OS.File.DirectoryIterator(currentDir); 1.413 + let someFiles1 = yield iterator.nextBatch(BATCH_LENGTH); 1.414 + let someFiles2 = yield iterator.nextBatch(BATCH_LENGTH); 1.415 + yield iterator.close(); 1.416 + 1.417 + iterator = new OS.File.DirectoryIterator(currentDir); 1.418 + yield iterator.forEach(function cb(entry, index, iterator) { 1.419 + if (index < BATCH_LENGTH) { 1.420 + test.is(entry.path, someFiles1[index].path, "Both runs return the same files (part 1)"); 1.421 + } else if (index < 2*BATCH_LENGTH) { 1.422 + test.is(entry.path, someFiles2[index - BATCH_LENGTH].path, "Both runs return the same files (part 2)"); 1.423 + } else if (index == 2 * BATCH_LENGTH) { 1.424 + test.info("Attempting to stop asynchronous forEach"); 1.425 + return iterator.close(); 1.426 + } else { 1.427 + test.fail("Can we stop an asynchronous forEach? " + index); 1.428 + } 1.429 + return null; 1.430 + }); 1.431 + yield iterator.close(); 1.432 + 1.433 + // Ensuring that we find new files if they appear 1.434 + let file = yield OS.File.open(temporary_file_name, { write: true } ); 1.435 + file.close(); 1.436 + iterator = new OS.File.DirectoryIterator(currentDir); 1.437 + try { 1.438 + let files = yield iterator.nextBatch(); 1.439 + is(files.length, allFiles1.length + 1, "The directory iterator has noticed the new file"); 1.440 + let exists = yield iterator.exists(); 1.441 + test.ok(exists, "After nextBatch, iterator detects that the directory exists"); 1.442 + } finally { 1.443 + yield iterator.close(); 1.444 + } 1.445 + 1.446 + // Ensuring that opening a non-existing directory fails consistently 1.447 + // once iteration starts. 1.448 + try { 1.449 + iterator = null; 1.450 + iterator = new OS.File.DirectoryIterator("/I do not exist"); 1.451 + let exists = yield iterator.exists(); 1.452 + test.ok(!exists, "Before any iteration, iterator detects that the directory doesn't exist"); 1.453 + let exn = null; 1.454 + try { 1.455 + yield iterator.next(); 1.456 + } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) { 1.457 + exn = ex; 1.458 + let exists = yield iterator.exists(); 1.459 + test.ok(!exists, "After one iteration, iterator detects that the directory doesn't exist"); 1.460 + } 1.461 + test.ok(exn, "Iterating through a directory that does not exist has failed with becauseNoSuchFile"); 1.462 + } finally { 1.463 + if (iterator) { 1.464 + iterator.close(); 1.465 + } 1.466 + } 1.467 + test.ok(!!iterator, "The directory iterator for a non-existing directory was correctly created"); 1.468 + }); 1.469 +}); 1.470 + 1.471 +/** 1.472 + * Test OS.File.prototype.{exists} 1.473 + */ 1.474 +let test_exists = maketest("exists", function exists(test) { 1.475 + return Task.spawn(function() { 1.476 + let fileExists = yield OS.File.exists(EXISTING_FILE); 1.477 + test.ok(fileExists, "file exists"); 1.478 + fileExists = yield OS.File.exists(EXISTING_FILE + ".tmp"); 1.479 + test.ok(!fileExists, "file does not exists"); 1.480 + }); 1.481 +}); 1.482 + 1.483 +/** 1.484 + * Test changes to OS.Shared.DEBUG flag. 1.485 + */ 1.486 +let test_debug = maketest("debug", function debug(test) { 1.487 + return Task.spawn(function() { 1.488 + function testSetDebugPref (pref) { 1.489 + try { 1.490 + Services.prefs.setBoolPref("toolkit.osfile.log", pref); 1.491 + } catch (x) { 1.492 + test.fail("Setting OS.Shared.DEBUG to " + pref + 1.493 + " should not cause error."); 1.494 + } finally { 1.495 + test.is(OS.Shared.DEBUG, pref, "OS.Shared.DEBUG is set correctly."); 1.496 + } 1.497 + } 1.498 + testSetDebugPref(true); 1.499 + let workerDEBUG = yield OS.File.GET_DEBUG(); 1.500 + test.is(workerDEBUG, true, "Worker's DEBUG is set."); 1.501 + testSetDebugPref(false); 1.502 + workerDEBUG = yield OS.File.GET_DEBUG(); 1.503 + test.is(workerDEBUG, false, "Worker's DEBUG is unset."); 1.504 + }); 1.505 +}); 1.506 + 1.507 +/** 1.508 + * Test logging in the main thread with set OS.Shared.DEBUG and 1.509 + * OS.Shared.TEST flags. 1.510 + */ 1.511 +let test_debug_test = maketest("debug_test", function debug_test(test) { 1.512 + return Task.spawn(function () { 1.513 + // Create a console listener. 1.514 + let consoleListener = { 1.515 + observe: function (aMessage) { 1.516 + // Ignore unexpected messages. 1.517 + if (!(aMessage instanceof Components.interfaces.nsIConsoleMessage)) { 1.518 + return; 1.519 + } 1.520 + if (aMessage.message.indexOf("TEST OS") < 0) { 1.521 + return; 1.522 + } 1.523 + test.ok(true, "DEBUG TEST messages are logged correctly."); 1.524 + } 1.525 + }; 1.526 + toggleDebugTest(true, consoleListener); 1.527 + // Execution of OS.File.exist method will trigger OS.File.LOG several times. 1.528 + let fileExists = yield OS.File.exists(EXISTING_FILE); 1.529 + toggleDebugTest(false, consoleListener); 1.530 + }); 1.531 +}); 1.532 + 1.533 +