michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: /** michael@0: * This file tests components that implement nsIBackgroundFileSaver. michael@0: */ michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Globals michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", michael@0: "resource://gre/modules/FileUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", michael@0: "resource://gre/modules/NetUtil.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Promise", michael@0: "resource://gre/modules/Promise.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Task", michael@0: "resource://gre/modules/Task.jsm"); michael@0: michael@0: const BackgroundFileSaverOutputStream = Components.Constructor( michael@0: "@mozilla.org/network/background-file-saver;1?mode=outputstream", michael@0: "nsIBackgroundFileSaver"); michael@0: michael@0: const BackgroundFileSaverStreamListener = Components.Constructor( michael@0: "@mozilla.org/network/background-file-saver;1?mode=streamlistener", michael@0: "nsIBackgroundFileSaver"); michael@0: michael@0: const StringInputStream = Components.Constructor( michael@0: "@mozilla.org/io/string-input-stream;1", michael@0: "nsIStringInputStream", michael@0: "setData"); michael@0: michael@0: const REQUEST_SUSPEND_AT = 1024 * 1024 * 4; michael@0: const TEST_DATA_SHORT = "This test string is written to the file."; michael@0: const TEST_FILE_NAME_1 = "test-backgroundfilesaver-1.txt"; michael@0: const TEST_FILE_NAME_2 = "test-backgroundfilesaver-2.txt"; michael@0: const TEST_FILE_NAME_3 = "test-backgroundfilesaver-3.txt"; michael@0: michael@0: // A map of test data length to the expected SHA-256 hashes michael@0: const EXPECTED_HASHES = { michael@0: // No data michael@0: 0 : "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", michael@0: // TEST_DATA_SHORT michael@0: 40 : "f37176b690e8744ee990a206c086cba54d1502aa2456c3b0c84ef6345d72a192", michael@0: // TEST_DATA_SHORT + TEST_DATA_SHORT michael@0: 80 : "780c0e91f50bb7ec922cc11e16859e6d5df283c0d9470f61772e3d79f41eeb58", michael@0: // TEST_DATA_LONG michael@0: 8388608 : "e3611a47714c42bdf326acfb2eb6ed9fa4cca65cb7d7be55217770a5bf5e7ff0", michael@0: // TEST_DATA_LONG + TEST_DATA_LONG michael@0: 16777216 : "03a0db69a30140f307587ee746a539247c181bafd85b85c8516a3533c7d9ea1d" michael@0: }; michael@0: michael@0: const gTextDecoder = new TextDecoder(); michael@0: michael@0: // Generate a long string of data in a moderately fast way. michael@0: const TEST_256_CHARS = new Array(257).join("-"); michael@0: const DESIRED_LENGTH = REQUEST_SUSPEND_AT * 2; michael@0: const TEST_DATA_LONG = new Array(1 + DESIRED_LENGTH / 256).join(TEST_256_CHARS); michael@0: do_check_eq(TEST_DATA_LONG.length, DESIRED_LENGTH); michael@0: michael@0: /** michael@0: * Returns a reference to a temporary file. If the file is then created, it michael@0: * will be removed when tests in this file finish. michael@0: */ michael@0: function getTempFile(aLeafName) { michael@0: let file = FileUtils.getFile("TmpD", [aLeafName]); michael@0: do_register_cleanup(function GTF_cleanup() { michael@0: if (file.exists()) { michael@0: file.remove(false); michael@0: } michael@0: }); michael@0: return file; michael@0: } michael@0: michael@0: /** michael@0: * Helper function for converting a binary blob to its hex equivalent. michael@0: * michael@0: * @param str michael@0: * String possibly containing non-printable chars. michael@0: * @return A hex-encoded string. michael@0: */ michael@0: function toHex(str) { michael@0: var hex = ''; michael@0: for (var i = 0; i < str.length; i++) { michael@0: hex += ('0' + str.charCodeAt(i).toString(16)).slice(-2); michael@0: } michael@0: return hex; michael@0: } michael@0: michael@0: /** michael@0: * Ensures that the given file contents are equal to the given string. michael@0: * michael@0: * @param aFile michael@0: * nsIFile whose contents should be verified. michael@0: * @param aExpectedContents michael@0: * String containing the octets that are expected in the file. michael@0: * michael@0: * @return {Promise} michael@0: * @resolves When the operation completes. michael@0: * @rejects Never. michael@0: */ michael@0: function promiseVerifyContents(aFile, aExpectedContents) { michael@0: let deferred = Promise.defer(); michael@0: NetUtil.asyncFetch(aFile, function(aInputStream, aStatus) { michael@0: do_check_true(Components.isSuccessCode(aStatus)); michael@0: let contents = NetUtil.readInputStreamToString(aInputStream, michael@0: aInputStream.available()); michael@0: if (contents.length <= TEST_DATA_SHORT.length * 2) { michael@0: do_check_eq(contents, aExpectedContents); michael@0: } else { michael@0: // Do not print the entire content string to the test log. michael@0: do_check_eq(contents.length, aExpectedContents.length); michael@0: do_check_true(contents == aExpectedContents); michael@0: } michael@0: deferred.resolve(); michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: /** michael@0: * Waits for the given saver object to complete. michael@0: * michael@0: * @param aSaver michael@0: * The saver, with the output stream or a stream listener implementation. michael@0: * @param aOnTargetChangeFn michael@0: * Optional callback invoked with the target file name when it changes. michael@0: * michael@0: * @return {Promise} michael@0: * @resolves When onSaveComplete is called with a success code. michael@0: * @rejects With an exception, if onSaveComplete is called with a failure code. michael@0: */ michael@0: function promiseSaverComplete(aSaver, aOnTargetChangeFn) { michael@0: let deferred = Promise.defer(); michael@0: aSaver.observer = { michael@0: onTargetChange: function BFSO_onSaveComplete(aSaver, aTarget) michael@0: { michael@0: if (aOnTargetChangeFn) { michael@0: aOnTargetChangeFn(aTarget); michael@0: } michael@0: }, michael@0: onSaveComplete: function BFSO_onSaveComplete(aSaver, aStatus) michael@0: { michael@0: if (Components.isSuccessCode(aStatus)) { michael@0: deferred.resolve(); michael@0: } else { michael@0: deferred.reject(new Components.Exception("Saver failed.", aStatus)); michael@0: } michael@0: }, michael@0: }; michael@0: return deferred.promise; michael@0: } michael@0: michael@0: /** michael@0: * Feeds a string to a BackgroundFileSaverOutputStream. michael@0: * michael@0: * @param aSourceString michael@0: * The source data to copy. michael@0: * @param aSaverOutputStream michael@0: * The BackgroundFileSaverOutputStream to feed. michael@0: * @param aCloseWhenDone michael@0: * If true, the output stream will be closed when the copy finishes. michael@0: * michael@0: * @return {Promise} michael@0: * @resolves When the copy completes with a success code. michael@0: * @rejects With an exception, if the copy fails. michael@0: */ michael@0: function promiseCopyToSaver(aSourceString, aSaverOutputStream, aCloseWhenDone) { michael@0: let deferred = Promise.defer(); michael@0: let inputStream = new StringInputStream(aSourceString, aSourceString.length); michael@0: let copier = Cc["@mozilla.org/network/async-stream-copier;1"] michael@0: .createInstance(Ci.nsIAsyncStreamCopier); michael@0: copier.init(inputStream, aSaverOutputStream, null, false, true, 0x8000, true, michael@0: aCloseWhenDone); michael@0: copier.asyncCopy({ michael@0: onStartRequest: function () { }, michael@0: onStopRequest: function (aRequest, aContext, aStatusCode) michael@0: { michael@0: if (Components.isSuccessCode(aStatusCode)) { michael@0: deferred.resolve(); michael@0: } else { michael@0: deferred.reject(new Components.Exception(aResult)); michael@0: } michael@0: }, michael@0: }, null); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: /** michael@0: * Feeds a string to a BackgroundFileSaverStreamListener. michael@0: * michael@0: * @param aSourceString michael@0: * The source data to copy. michael@0: * @param aSaverStreamListener michael@0: * The BackgroundFileSaverStreamListener to feed. michael@0: * @param aCloseWhenDone michael@0: * If true, the output stream will be closed when the copy finishes. michael@0: * michael@0: * @return {Promise} michael@0: * @resolves When the operation completes with a success code. michael@0: * @rejects With an exception, if the operation fails. michael@0: */ michael@0: function promisePumpToSaver(aSourceString, aSaverStreamListener, michael@0: aCloseWhenDone) { michael@0: let deferred = Promise.defer(); michael@0: aSaverStreamListener.QueryInterface(Ci.nsIStreamListener); michael@0: let inputStream = new StringInputStream(aSourceString, aSourceString.length); michael@0: let pump = Cc["@mozilla.org/network/input-stream-pump;1"] michael@0: .createInstance(Ci.nsIInputStreamPump); michael@0: pump.init(inputStream, -1, -1, 0, 0, true); michael@0: pump.asyncRead({ michael@0: onStartRequest: function PPTS_onStartRequest(aRequest, aContext) michael@0: { michael@0: aSaverStreamListener.onStartRequest(aRequest, aContext); michael@0: }, michael@0: onStopRequest: function PPTS_onStopRequest(aRequest, aContext, aStatusCode) michael@0: { michael@0: aSaverStreamListener.onStopRequest(aRequest, aContext, aStatusCode); michael@0: if (Components.isSuccessCode(aStatusCode)) { michael@0: deferred.resolve(); michael@0: } else { michael@0: deferred.reject(new Components.Exception(aResult)); michael@0: } michael@0: }, michael@0: onDataAvailable: function PPTS_onDataAvailable(aRequest, aContext, michael@0: aInputStream, aOffset, michael@0: aCount) michael@0: { michael@0: aSaverStreamListener.onDataAvailable(aRequest, aContext, aInputStream, michael@0: aOffset, aCount); michael@0: }, michael@0: }, null); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: let gStillRunning = true; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Tests michael@0: michael@0: function run_test() michael@0: { michael@0: run_next_test(); michael@0: } michael@0: michael@0: add_task(function test_setup() michael@0: { michael@0: // Wait 10 minutes, that is half of the external xpcshell timeout. michael@0: do_timeout(10 * 60 * 1000, function() { michael@0: if (gStillRunning) { michael@0: do_throw("Test timed out."); michael@0: } michael@0: }) michael@0: }); michael@0: michael@0: add_task(function test_normal() michael@0: { michael@0: // This test demonstrates the most basic use case. michael@0: let destFile = getTempFile(TEST_FILE_NAME_1); michael@0: michael@0: // Create the object implementing the output stream. michael@0: let saver = new BackgroundFileSaverOutputStream(); michael@0: michael@0: // Set up callbacks for completion and target file name change. michael@0: let receivedOnTargetChange = false; michael@0: function onTargetChange(aTarget) { michael@0: do_check_true(destFile.equals(aTarget)); michael@0: receivedOnTargetChange = true; michael@0: } michael@0: let completionPromise = promiseSaverComplete(saver, onTargetChange); michael@0: michael@0: // Set the target file. michael@0: saver.setTarget(destFile, false); michael@0: michael@0: // Write some data and close the output stream. michael@0: yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); michael@0: michael@0: // Indicate that we are ready to finish, and wait for a successful callback. michael@0: saver.finish(Cr.NS_OK); michael@0: yield completionPromise; michael@0: michael@0: // Only after we receive the completion notification, we can also be sure that michael@0: // we've received the target file name change notification before it. michael@0: do_check_true(receivedOnTargetChange); michael@0: michael@0: // Clean up. michael@0: destFile.remove(false); michael@0: }); michael@0: michael@0: add_task(function test_combinations() michael@0: { michael@0: let initialFile = getTempFile(TEST_FILE_NAME_1); michael@0: let renamedFile = getTempFile(TEST_FILE_NAME_2); michael@0: michael@0: // Keep track of the current file. michael@0: let currentFile = null; michael@0: function onTargetChange(aTarget) { michael@0: currentFile = null; michael@0: do_print("Target file changed to: " + aTarget.leafName); michael@0: currentFile = aTarget; michael@0: } michael@0: michael@0: // Tests various combinations of events and behaviors for both the stream michael@0: // listener and the output stream implementations. michael@0: for (let testFlags = 0; testFlags < 32; testFlags++) { michael@0: let keepPartialOnFailure = !!(testFlags & 1); michael@0: let renameAtSomePoint = !!(testFlags & 2); michael@0: let cancelAtSomePoint = !!(testFlags & 4); michael@0: let useStreamListener = !!(testFlags & 8); michael@0: let useLongData = !!(testFlags & 16); michael@0: michael@0: let startTime = Date.now(); michael@0: do_print("Starting keepPartialOnFailure = " + keepPartialOnFailure + michael@0: ", renameAtSomePoint = " + renameAtSomePoint + michael@0: ", cancelAtSomePoint = " + cancelAtSomePoint + michael@0: ", useStreamListener = " + useStreamListener + michael@0: ", useLongData = " + useLongData); michael@0: michael@0: // Create the object and register the observers. michael@0: currentFile = null; michael@0: let saver = useStreamListener michael@0: ? new BackgroundFileSaverStreamListener() michael@0: : new BackgroundFileSaverOutputStream(); michael@0: saver.enableSha256(); michael@0: let completionPromise = promiseSaverComplete(saver, onTargetChange); michael@0: michael@0: // Start feeding the first chunk of data to the saver. In case we are using michael@0: // the stream listener, we only write one chunk. michael@0: let testData = useLongData ? TEST_DATA_LONG : TEST_DATA_SHORT; michael@0: let feedPromise = useStreamListener michael@0: ? promisePumpToSaver(testData + testData, saver) michael@0: : promiseCopyToSaver(testData, saver, false); michael@0: michael@0: // Set a target output file. michael@0: saver.setTarget(initialFile, keepPartialOnFailure); michael@0: michael@0: // Wait for the first chunk of data to be copied. michael@0: yield feedPromise; michael@0: michael@0: if (renameAtSomePoint) { michael@0: saver.setTarget(renamedFile, keepPartialOnFailure); michael@0: } michael@0: michael@0: if (cancelAtSomePoint) { michael@0: saver.finish(Cr.NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: // Feed the second chunk of data to the saver. michael@0: if (!useStreamListener) { michael@0: yield promiseCopyToSaver(testData, saver, true); michael@0: } michael@0: michael@0: // Wait for completion, and ensure we succeeded or failed as expected. michael@0: if (!cancelAtSomePoint) { michael@0: saver.finish(Cr.NS_OK); michael@0: } michael@0: try { michael@0: yield completionPromise; michael@0: if (cancelAtSomePoint) { michael@0: do_throw("Failure expected."); michael@0: } michael@0: } catch (ex if cancelAtSomePoint && ex.result == Cr.NS_ERROR_FAILURE) { } michael@0: michael@0: if (!cancelAtSomePoint) { michael@0: // In this case, the file must exist. michael@0: do_check_true(currentFile.exists()); michael@0: let expectedContents = testData + testData; michael@0: yield promiseVerifyContents(currentFile, expectedContents); michael@0: do_check_eq(EXPECTED_HASHES[expectedContents.length], michael@0: toHex(saver.sha256Hash)); michael@0: currentFile.remove(false); michael@0: michael@0: // If the target was really renamed, the old file should not exist. michael@0: if (renamedFile.equals(currentFile)) { michael@0: do_check_false(initialFile.exists()); michael@0: } michael@0: } else if (!keepPartialOnFailure) { michael@0: // In this case, the file must not exist. michael@0: do_check_false(initialFile.exists()); michael@0: do_check_false(renamedFile.exists()); michael@0: } else { michael@0: // In this case, the file may or may not exist, because canceling can michael@0: // interrupt the asynchronous operation at any point, even before the file michael@0: // has been created for the first time. michael@0: if (initialFile.exists()) { michael@0: initialFile.remove(false); michael@0: } michael@0: if (renamedFile.exists()) { michael@0: renamedFile.remove(false); michael@0: } michael@0: } michael@0: michael@0: do_print("Test case completed in " + (Date.now() - startTime) + " ms."); michael@0: } michael@0: }); michael@0: michael@0: add_task(function test_setTarget_after_close_stream() michael@0: { michael@0: // This test checks the case where we close the output stream before we call michael@0: // the setTarget method. All the data should be buffered and written anyway. michael@0: let destFile = getTempFile(TEST_FILE_NAME_1); michael@0: michael@0: // Test the case where the file does not already exists first, then the case michael@0: // where the file already exists. michael@0: for (let i = 0; i < 2; i++) { michael@0: let saver = new BackgroundFileSaverOutputStream(); michael@0: saver.enableSha256(); michael@0: let completionPromise = promiseSaverComplete(saver); michael@0: michael@0: // Copy some data to the output stream of the file saver. This data must michael@0: // be shorter than the internal component's pipe buffer for the test to michael@0: // succeed, because otherwise the test would block waiting for the write to michael@0: // complete. michael@0: yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); michael@0: michael@0: // Set the target file and wait for the output to finish. michael@0: saver.setTarget(destFile, false); michael@0: saver.finish(Cr.NS_OK); michael@0: yield completionPromise; michael@0: michael@0: // Verify results. michael@0: yield promiseVerifyContents(destFile, TEST_DATA_SHORT); michael@0: do_check_eq(EXPECTED_HASHES[TEST_DATA_SHORT.length], michael@0: toHex(saver.sha256Hash)); michael@0: } michael@0: michael@0: // Clean up. michael@0: destFile.remove(false); michael@0: }); michael@0: michael@0: add_task(function test_setTarget_fast() michael@0: { michael@0: // This test checks a fast rename of the target file. michael@0: let destFile1 = getTempFile(TEST_FILE_NAME_1); michael@0: let destFile2 = getTempFile(TEST_FILE_NAME_2); michael@0: let saver = new BackgroundFileSaverOutputStream(); michael@0: let completionPromise = promiseSaverComplete(saver); michael@0: michael@0: // Set the initial name after the stream is closed, then rename immediately. michael@0: yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); michael@0: saver.setTarget(destFile1, false); michael@0: saver.setTarget(destFile2, false); michael@0: michael@0: // Wait for all the operations to complete. michael@0: saver.finish(Cr.NS_OK); michael@0: yield completionPromise; michael@0: michael@0: // Verify results and clean up. michael@0: do_check_false(destFile1.exists()); michael@0: yield promiseVerifyContents(destFile2, TEST_DATA_SHORT); michael@0: destFile2.remove(false); michael@0: }); michael@0: michael@0: add_task(function test_setTarget_multiple() michael@0: { michael@0: // This test checks multiple renames of the target file. michael@0: let destFile = getTempFile(TEST_FILE_NAME_1); michael@0: let saver = new BackgroundFileSaverOutputStream(); michael@0: let completionPromise = promiseSaverComplete(saver); michael@0: michael@0: // Rename both before and after the stream is closed. michael@0: saver.setTarget(getTempFile(TEST_FILE_NAME_2), false); michael@0: saver.setTarget(getTempFile(TEST_FILE_NAME_3), false); michael@0: yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); michael@0: saver.setTarget(getTempFile(TEST_FILE_NAME_2), false); michael@0: saver.setTarget(destFile, false); michael@0: michael@0: // Wait for all the operations to complete. michael@0: saver.finish(Cr.NS_OK); michael@0: yield completionPromise; michael@0: michael@0: // Verify results and clean up. michael@0: do_check_false(getTempFile(TEST_FILE_NAME_2).exists()); michael@0: do_check_false(getTempFile(TEST_FILE_NAME_3).exists()); michael@0: yield promiseVerifyContents(destFile, TEST_DATA_SHORT); michael@0: destFile.remove(false); michael@0: }); michael@0: michael@0: add_task(function test_enableAppend() michael@0: { michael@0: // This test checks append mode with hashing disabled. michael@0: let destFile = getTempFile(TEST_FILE_NAME_1); michael@0: michael@0: // Test the case where the file does not already exists first, then the case michael@0: // where the file already exists. michael@0: for (let i = 0; i < 2; i++) { michael@0: let saver = new BackgroundFileSaverOutputStream(); michael@0: saver.enableAppend(); michael@0: let completionPromise = promiseSaverComplete(saver); michael@0: michael@0: saver.setTarget(destFile, false); michael@0: yield promiseCopyToSaver(TEST_DATA_LONG, saver, true); michael@0: michael@0: saver.finish(Cr.NS_OK); michael@0: yield completionPromise; michael@0: michael@0: // Verify results. michael@0: let expectedContents = (i == 0 ? TEST_DATA_LONG michael@0: : TEST_DATA_LONG + TEST_DATA_LONG); michael@0: yield promiseVerifyContents(destFile, expectedContents); michael@0: } michael@0: michael@0: // Clean up. michael@0: destFile.remove(false); michael@0: }); michael@0: michael@0: add_task(function test_enableAppend_setTarget_fast() michael@0: { michael@0: // This test checks a fast rename of the target file in append mode. michael@0: let destFile1 = getTempFile(TEST_FILE_NAME_1); michael@0: let destFile2 = getTempFile(TEST_FILE_NAME_2); michael@0: michael@0: // Test the case where the file does not already exists first, then the case michael@0: // where the file already exists. michael@0: for (let i = 0; i < 2; i++) { michael@0: let saver = new BackgroundFileSaverOutputStream(); michael@0: saver.enableAppend(); michael@0: let completionPromise = promiseSaverComplete(saver); michael@0: michael@0: yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); michael@0: michael@0: // The first time, we start appending to the first file and rename to the michael@0: // second file. The second time, we start appending to the second file, michael@0: // that was created the first time, and rename back to the first file. michael@0: let firstFile = (i == 0) ? destFile1 : destFile2; michael@0: let secondFile = (i == 0) ? destFile2 : destFile1; michael@0: saver.setTarget(firstFile, false); michael@0: saver.setTarget(secondFile, false); michael@0: michael@0: saver.finish(Cr.NS_OK); michael@0: yield completionPromise; michael@0: michael@0: // Verify results. michael@0: do_check_false(firstFile.exists()); michael@0: let expectedContents = (i == 0 ? TEST_DATA_SHORT michael@0: : TEST_DATA_SHORT + TEST_DATA_SHORT); michael@0: yield promiseVerifyContents(secondFile, expectedContents); michael@0: } michael@0: michael@0: // Clean up. michael@0: destFile1.remove(false); michael@0: }); michael@0: michael@0: add_task(function test_enableAppend_hash() michael@0: { michael@0: // This test checks append mode, also verifying that the computed hash michael@0: // includes the contents of the existing data. michael@0: let destFile = getTempFile(TEST_FILE_NAME_1); michael@0: michael@0: // Test the case where the file does not already exists first, then the case michael@0: // where the file already exists. michael@0: for (let i = 0; i < 2; i++) { michael@0: let saver = new BackgroundFileSaverOutputStream(); michael@0: saver.enableAppend(); michael@0: saver.enableSha256(); michael@0: let completionPromise = promiseSaverComplete(saver); michael@0: michael@0: saver.setTarget(destFile, false); michael@0: yield promiseCopyToSaver(TEST_DATA_LONG, saver, true); michael@0: michael@0: saver.finish(Cr.NS_OK); michael@0: yield completionPromise; michael@0: michael@0: // Verify results. michael@0: let expectedContents = (i == 0 ? TEST_DATA_LONG michael@0: : TEST_DATA_LONG + TEST_DATA_LONG); michael@0: yield promiseVerifyContents(destFile, expectedContents); michael@0: do_check_eq(EXPECTED_HASHES[expectedContents.length], michael@0: toHex(saver.sha256Hash)); michael@0: } michael@0: michael@0: // Clean up. michael@0: destFile.remove(false); michael@0: }); michael@0: michael@0: add_task(function test_finish_only() michael@0: { michael@0: // This test checks creating the object and doing nothing. michael@0: let destFile = getTempFile(TEST_FILE_NAME_1); michael@0: let saver = new BackgroundFileSaverOutputStream(); michael@0: function onTargetChange(aTarget) { michael@0: do_throw("Should not receive the onTargetChange notification."); michael@0: } michael@0: let completionPromise = promiseSaverComplete(saver, onTargetChange); michael@0: saver.finish(Cr.NS_OK); michael@0: yield completionPromise; michael@0: }); michael@0: michael@0: add_task(function test_empty() michael@0: { michael@0: // This test checks we still create an empty file when no data is fed. michael@0: let destFile = getTempFile(TEST_FILE_NAME_1); michael@0: michael@0: let saver = new BackgroundFileSaverOutputStream(); michael@0: let completionPromise = promiseSaverComplete(saver); michael@0: michael@0: saver.setTarget(destFile, false); michael@0: yield promiseCopyToSaver("", saver, true); michael@0: michael@0: saver.finish(Cr.NS_OK); michael@0: yield completionPromise; michael@0: michael@0: // Verify results. michael@0: do_check_true(destFile.exists()); michael@0: do_check_eq(destFile.fileSize, 0); michael@0: michael@0: // Clean up. michael@0: destFile.remove(false); michael@0: }); michael@0: michael@0: add_task(function test_empty_hash() michael@0: { michael@0: // This test checks the hash of an empty file, both in normal and append mode. michael@0: let destFile = getTempFile(TEST_FILE_NAME_1); michael@0: michael@0: // Test normal mode first, then append mode. michael@0: for (let i = 0; i < 2; i++) { michael@0: let saver = new BackgroundFileSaverOutputStream(); michael@0: if (i == 1) { michael@0: saver.enableAppend(); michael@0: } michael@0: saver.enableSha256(); michael@0: let completionPromise = promiseSaverComplete(saver); michael@0: michael@0: saver.setTarget(destFile, false); michael@0: yield promiseCopyToSaver("", saver, true); michael@0: michael@0: saver.finish(Cr.NS_OK); michael@0: yield completionPromise; michael@0: michael@0: // Verify results. michael@0: do_check_eq(destFile.fileSize, 0); michael@0: do_check_eq(EXPECTED_HASHES[0], toHex(saver.sha256Hash)); michael@0: } michael@0: michael@0: // Clean up. michael@0: destFile.remove(false); michael@0: }); michael@0: michael@0: add_task(function test_invalid_hash() michael@0: { michael@0: let saver = new BackgroundFileSaverStreamListener(); michael@0: let completionPromise = promiseSaverComplete(saver); michael@0: // We shouldn't be able to get the hash if hashing hasn't been enabled michael@0: try { michael@0: let hash = saver.sha256Hash; michael@0: do_throw("Shouldn't be able to get hash if hashing not enabled"); michael@0: } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } michael@0: // Enable hashing, but don't feed any data to saver michael@0: saver.enableSha256(); michael@0: let destFile = getTempFile(TEST_FILE_NAME_1); michael@0: saver.setTarget(destFile, false); michael@0: // We don't wait on promiseSaverComplete, so the hash getter can run before michael@0: // or after onSaveComplete is called. However, the expected behavior is the michael@0: // same in both cases since the hash is only valid when the save completes michael@0: // successfully. michael@0: saver.finish(Cr.NS_ERROR_FAILURE); michael@0: try { michael@0: let hash = saver.sha256Hash; michael@0: do_throw("Shouldn't be able to get hash if save did not succeed"); michael@0: } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } michael@0: // Wait for completion so that the worker thread finishes dealing with the michael@0: // target file. We expect it to fail. michael@0: try { michael@0: yield completionPromise; michael@0: do_throw("completionPromise should throw"); michael@0: } catch (ex if ex.result == Cr.NS_ERROR_FAILURE) { } michael@0: }); michael@0: michael@0: add_task(function test_signature() michael@0: { michael@0: // Check that we get a signature if the saver is finished. michael@0: let destFile = getTempFile(TEST_FILE_NAME_1); michael@0: michael@0: let saver = new BackgroundFileSaverOutputStream(); michael@0: let completionPromise = promiseSaverComplete(saver); michael@0: michael@0: try { michael@0: let signatureInfo = saver.signatureInfo; michael@0: do_throw("Can't get signature if saver is not complete"); michael@0: } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } michael@0: michael@0: saver.enableSignatureInfo(); michael@0: saver.setTarget(destFile, false); michael@0: yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); michael@0: michael@0: saver.finish(Cr.NS_OK); michael@0: yield completionPromise; michael@0: yield promiseVerifyContents(destFile, TEST_DATA_SHORT); michael@0: michael@0: // signatureInfo is an empty nsIArray michael@0: do_check_eq(0, saver.signatureInfo.length); michael@0: michael@0: // Clean up. michael@0: destFile.remove(false); michael@0: }); michael@0: michael@0: add_task(function test_signature_not_enabled() michael@0: { michael@0: // Check that we get a signature if the saver is finished on Windows. michael@0: let destFile = getTempFile(TEST_FILE_NAME_1); michael@0: michael@0: let saver = new BackgroundFileSaverOutputStream(); michael@0: let completionPromise = promiseSaverComplete(saver); michael@0: saver.setTarget(destFile, false); michael@0: yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); michael@0: michael@0: saver.finish(Cr.NS_OK); michael@0: yield completionPromise; michael@0: try { michael@0: let signatureInfo = saver.signatureInfo; michael@0: do_throw("Can't get signature if not enabled"); michael@0: } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } michael@0: michael@0: // Clean up. michael@0: destFile.remove(false); michael@0: }); michael@0: michael@0: add_task(function test_teardown() michael@0: { michael@0: gStillRunning = false; michael@0: });