Bug 961665 - Tests for OS.File.read. r=froydnj

This commit is contained in:
David Rajchenbach-Teller 2014-03-13 09:52:07 -04:00
parent 1b4b73d192
commit bf44435306
9 changed files with 260 additions and 114 deletions

View File

@ -155,7 +155,6 @@ let test = maketest("Main", function main(test) {
yield test_debug();
yield test_info_features_detect();
yield test_read_write();
yield test_read_write_all();
yield test_position();
yield test_iter();
yield test_exists();
@ -303,92 +302,6 @@ let test_read_write = maketest("read_write", function read_write(test) {
});
});
/**
* Test OS.File.writeAtomic
*/
let test_read_write_all = maketest("read_write_all", function read_write_all(test) {
return Task.spawn(function() {
let pathDest = OS.Path.join(OS.Constants.Path.tmpDir,
"osfile async test read writeAtomic.tmp");
let tmpPath = pathDest + ".tmp";
let test_with_options = function(options, suffix) {
return Task.spawn(function() {
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);
test.ok(contents, "Obtained contents");
let bytesWritten = yield OS.File.writeAtomic(pathDest, contents, options);
test.is(contents.byteLength, bytesWritten, "Wrote the correct number of bytes (" + suffix + ")");
// Check that options are not altered
test.is(Object.keys(options).length, Object.keys(optionsBackup).length,
"The number of options was not changed");
for (let k in options) {
test.is(options[k], optionsBackup[k], "Option was not changed (" + suffix + ")");
}
yield reference_compare_files(pathSource, pathDest, test);
// Check that temporary file was removed or doesn't exist
test.info("Compare complete");
test.ok(!(new FileUtils.File(tmpPath).exists()), "No temporary file at the end of the run (" + suffix + ")");
// Check that writeAtomic fails if noOverwrite is true and the destination
// file already exists!
let view = new Uint8Array(contents.buffer, 10, 200);
try {
let opt = JSON.parse(JSON.stringify(options));
opt.noOverwrite = true;
yield OS.File.writeAtomic(pathDest, view, opt);
test.fail("With noOverwrite, writeAtomic should have refused to overwrite file (" + suffix + ")");
} catch (err) {
test.info("With noOverwrite, writeAtomic correctly failed (" + suffix + ")");
test.ok(err instanceof OS.File.Error, "writeAtomic correctly failed with a file error (" + suffix + ")");
test.ok(err.becauseExists, "writeAtomic file error confirmed that the file already exists (" + suffix + ")");
}
yield reference_compare_files(pathSource, pathDest, test);
test.ok(!(new FileUtils.File(tmpPath).exists()), "Temporary file was removed");
// Now write a subset
let START = 10;
let LENGTH = 100;
view = new Uint8Array(contents.buffer, START, LENGTH);
bytesWritten = yield OS.File.writeAtomic(pathDest, view, options);
test.is(bytesWritten, LENGTH, "Partial write wrote the correct number of bytes (" + suffix + ")");
let array2 = yield OS.File.read(pathDest);
let view1 = new Uint8Array(contents.buffer, START, LENGTH);
test.is(view1.length, array2.length, "Re-read partial write with the correct number of bytes (" + suffix + ")");
let decoder = new TextDecoder();
test.is(decoder.decode(view1), decoder.decode(array2), "Comparing re-read of partial write (" + suffix + ")");
// Write strings, default encoding
let ARBITRARY_STRING = "aeiouyâêîôûçß•";
yield OS.File.writeAtomic(pathDest, ARBITRARY_STRING, options);
let array = yield OS.File.read(pathDest);
let IN_STRING = decoder.decode(array);
test.is(ARBITRARY_STRING, IN_STRING, "String write + read with default encoding works (" + suffix + ")");
let opt16 = JSON.parse(JSON.stringify(options));
opt16.encoding = "utf-16";
yield OS.File.writeAtomic(pathDest, ARBITRARY_STRING, opt16);
array = yield OS.File.read(pathDest);
IN_STRING = (new TextDecoder("utf-16")).decode(array);
test.is(ARBITRARY_STRING, IN_STRING, "String write + read with utf-16 encoding works (" + suffix + ")");
// Cleanup.
OS.File.remove(pathDest);
});
};
yield test_with_options({tmpPath: tmpPath}, "Renaming, not flushing");
yield test_with_options({tmpPath: tmpPath, flush: true}, "Renaming, flushing");
yield test_with_options({}, "Not renaming, not flushing");
yield test_with_options({flush: true}, "Not renaming, flushing");
});
});
/**
* Test file.{getPosition, setPosition}

View File

@ -0,0 +1,86 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let {utils: Cu, interfaces: Ci} = Components;
let {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
let {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
let {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
let {NetUtil} = Cu.import("resource://gre/modules/NetUtil.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(file,
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);
});
};

View File

@ -1,13 +1,5 @@
"use strict";
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
function run_test() {
do_test_pending();
run_next_test();

View File

@ -1,16 +1,18 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that functions throw the appropriate exceptions.
*/
"use strict";
Components.utils.import("resource://gre/modules/osfile.jsm");
let EXISTING_FILE = do_get_file("xpcshell.ini").path;
function run_test() {
do_test_pending();
run_next_test();
}
add_task(function test_typeerror() {
// Tests on |open|
add_test_pair(function test_typeerror() {
let exn;
try {
let fd = yield OS.File.open("/tmp", {no_such_key: 1});
@ -22,6 +24,66 @@ add_task(function test_typeerror() {
do_check_true(exn.constructor.name == "TypeError");
});
add_task(function() {
do_test_finished();
// 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();
}

View File

@ -6,7 +6,6 @@
Components.utils.import("resource://gre/modules/osfile.jsm");
function run_test() {
do_test_pending();
run_next_test();
}
@ -69,7 +68,3 @@ add_task(function test_error_attributes () {
do_check_true(err.becauseNoSuchFile);
}
});
add_task(function() {
do_test_finished();
});

View File

@ -4,10 +4,6 @@
"use strict";
const Cu = Components.utils;
Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/ctypes.jsm", this);
Cu.import("resource://testing-common/AppData.jsm", this);

View File

@ -0,0 +1,100 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let {utils: Cu} = Components;
let SHARED_PATH;
let 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 bytesWritten = yield OS.File.writeAtomic(DEST_PATH, contents, options);
do_check_eq(contents.byteLength, 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!
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;
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);
let view1 = new Uint8Array(contents.buffer, START, LENGTH);
do_check_eq(view1.length, array2.length);
let decoder = new TextDecoder();
do_check_eq(decoder.decode(view1), decoder.decode(array2));
// 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();
}

View File

@ -1,5 +1,5 @@
[DEFAULT]
head =
head = head.js
tail =
[test_available_free_space.js]
@ -25,6 +25,7 @@ tail =
[test_open.js]
[test_telemetry.js]
[test_duration.js]
[test_read_write.js]
[test_compression.js]
[test_osfile_writeAtomic_backupTo_option.js]
[test_osfile_error.js]

View File

@ -40,6 +40,7 @@ function setup_osfile_crash_noerror() {
Services.prefs.setBoolPref("toolkit.osfile.debug.failshutdown", true);
Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 1);
Services.prefs.setBoolPref("toolkit.osfile.native", false);
OS.File.getCurrentDirectory();
Services.obs.notifyObservers(null, "profile-before-change", null);
@ -68,6 +69,7 @@ function setup_osfile_crash_exn() {
Services.prefs.setBoolPref("toolkit.osfile.debug.failshutdown", true);
Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 1);
Services.prefs.setBoolPref("toolkit.osfile.native", false);
OS.File.read("I do not exist");
Services.obs.notifyObservers(null, "profile-before-change", null);
@ -80,7 +82,6 @@ function after_osfile_crash_exn(mdump, extra) {
let state = info.conditions[0].state;
do_print("Keys: " + Object.keys(state).join(", "));
do_check_eq(info.phase, "profile-before-change");
do_check_true(state.launched);
do_check_false(state.shutdown);
do_check_true(state.worker);
do_check_true(!!state.latestSent);