Wed, 31 Dec 2014 06:55:46 +0100
Added tag TORBROWSER_REPLICA for changeset 6474c204b198
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim: set ts=2 et sw=2 tw=80: */ |
michael@0 | 3 | /* Any copyright is dedicated to the Public Domain. |
michael@0 | 4 | * http://creativecommons.org/publicdomain/zero/1.0/ */ |
michael@0 | 5 | |
michael@0 | 6 | /** |
michael@0 | 7 | * This file tests components that implement nsIBackgroundFileSaver. |
michael@0 | 8 | */ |
michael@0 | 9 | |
michael@0 | 10 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 11 | //// Globals |
michael@0 | 12 | |
michael@0 | 13 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 14 | |
michael@0 | 15 | XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", |
michael@0 | 16 | "resource://gre/modules/FileUtils.jsm"); |
michael@0 | 17 | XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", |
michael@0 | 18 | "resource://gre/modules/NetUtil.jsm"); |
michael@0 | 19 | XPCOMUtils.defineLazyModuleGetter(this, "Promise", |
michael@0 | 20 | "resource://gre/modules/Promise.jsm"); |
michael@0 | 21 | XPCOMUtils.defineLazyModuleGetter(this, "Task", |
michael@0 | 22 | "resource://gre/modules/Task.jsm"); |
michael@0 | 23 | |
michael@0 | 24 | const BackgroundFileSaverOutputStream = Components.Constructor( |
michael@0 | 25 | "@mozilla.org/network/background-file-saver;1?mode=outputstream", |
michael@0 | 26 | "nsIBackgroundFileSaver"); |
michael@0 | 27 | |
michael@0 | 28 | const BackgroundFileSaverStreamListener = Components.Constructor( |
michael@0 | 29 | "@mozilla.org/network/background-file-saver;1?mode=streamlistener", |
michael@0 | 30 | "nsIBackgroundFileSaver"); |
michael@0 | 31 | |
michael@0 | 32 | const StringInputStream = Components.Constructor( |
michael@0 | 33 | "@mozilla.org/io/string-input-stream;1", |
michael@0 | 34 | "nsIStringInputStream", |
michael@0 | 35 | "setData"); |
michael@0 | 36 | |
michael@0 | 37 | const REQUEST_SUSPEND_AT = 1024 * 1024 * 4; |
michael@0 | 38 | const TEST_DATA_SHORT = "This test string is written to the file."; |
michael@0 | 39 | const TEST_FILE_NAME_1 = "test-backgroundfilesaver-1.txt"; |
michael@0 | 40 | const TEST_FILE_NAME_2 = "test-backgroundfilesaver-2.txt"; |
michael@0 | 41 | const TEST_FILE_NAME_3 = "test-backgroundfilesaver-3.txt"; |
michael@0 | 42 | |
michael@0 | 43 | // A map of test data length to the expected SHA-256 hashes |
michael@0 | 44 | const EXPECTED_HASHES = { |
michael@0 | 45 | // No data |
michael@0 | 46 | 0 : "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", |
michael@0 | 47 | // TEST_DATA_SHORT |
michael@0 | 48 | 40 : "f37176b690e8744ee990a206c086cba54d1502aa2456c3b0c84ef6345d72a192", |
michael@0 | 49 | // TEST_DATA_SHORT + TEST_DATA_SHORT |
michael@0 | 50 | 80 : "780c0e91f50bb7ec922cc11e16859e6d5df283c0d9470f61772e3d79f41eeb58", |
michael@0 | 51 | // TEST_DATA_LONG |
michael@0 | 52 | 8388608 : "e3611a47714c42bdf326acfb2eb6ed9fa4cca65cb7d7be55217770a5bf5e7ff0", |
michael@0 | 53 | // TEST_DATA_LONG + TEST_DATA_LONG |
michael@0 | 54 | 16777216 : "03a0db69a30140f307587ee746a539247c181bafd85b85c8516a3533c7d9ea1d" |
michael@0 | 55 | }; |
michael@0 | 56 | |
michael@0 | 57 | const gTextDecoder = new TextDecoder(); |
michael@0 | 58 | |
michael@0 | 59 | // Generate a long string of data in a moderately fast way. |
michael@0 | 60 | const TEST_256_CHARS = new Array(257).join("-"); |
michael@0 | 61 | const DESIRED_LENGTH = REQUEST_SUSPEND_AT * 2; |
michael@0 | 62 | const TEST_DATA_LONG = new Array(1 + DESIRED_LENGTH / 256).join(TEST_256_CHARS); |
michael@0 | 63 | do_check_eq(TEST_DATA_LONG.length, DESIRED_LENGTH); |
michael@0 | 64 | |
michael@0 | 65 | /** |
michael@0 | 66 | * Returns a reference to a temporary file. If the file is then created, it |
michael@0 | 67 | * will be removed when tests in this file finish. |
michael@0 | 68 | */ |
michael@0 | 69 | function getTempFile(aLeafName) { |
michael@0 | 70 | let file = FileUtils.getFile("TmpD", [aLeafName]); |
michael@0 | 71 | do_register_cleanup(function GTF_cleanup() { |
michael@0 | 72 | if (file.exists()) { |
michael@0 | 73 | file.remove(false); |
michael@0 | 74 | } |
michael@0 | 75 | }); |
michael@0 | 76 | return file; |
michael@0 | 77 | } |
michael@0 | 78 | |
michael@0 | 79 | /** |
michael@0 | 80 | * Helper function for converting a binary blob to its hex equivalent. |
michael@0 | 81 | * |
michael@0 | 82 | * @param str |
michael@0 | 83 | * String possibly containing non-printable chars. |
michael@0 | 84 | * @return A hex-encoded string. |
michael@0 | 85 | */ |
michael@0 | 86 | function toHex(str) { |
michael@0 | 87 | var hex = ''; |
michael@0 | 88 | for (var i = 0; i < str.length; i++) { |
michael@0 | 89 | hex += ('0' + str.charCodeAt(i).toString(16)).slice(-2); |
michael@0 | 90 | } |
michael@0 | 91 | return hex; |
michael@0 | 92 | } |
michael@0 | 93 | |
michael@0 | 94 | /** |
michael@0 | 95 | * Ensures that the given file contents are equal to the given string. |
michael@0 | 96 | * |
michael@0 | 97 | * @param aFile |
michael@0 | 98 | * nsIFile whose contents should be verified. |
michael@0 | 99 | * @param aExpectedContents |
michael@0 | 100 | * String containing the octets that are expected in the file. |
michael@0 | 101 | * |
michael@0 | 102 | * @return {Promise} |
michael@0 | 103 | * @resolves When the operation completes. |
michael@0 | 104 | * @rejects Never. |
michael@0 | 105 | */ |
michael@0 | 106 | function promiseVerifyContents(aFile, aExpectedContents) { |
michael@0 | 107 | let deferred = Promise.defer(); |
michael@0 | 108 | NetUtil.asyncFetch(aFile, function(aInputStream, aStatus) { |
michael@0 | 109 | do_check_true(Components.isSuccessCode(aStatus)); |
michael@0 | 110 | let contents = NetUtil.readInputStreamToString(aInputStream, |
michael@0 | 111 | aInputStream.available()); |
michael@0 | 112 | if (contents.length <= TEST_DATA_SHORT.length * 2) { |
michael@0 | 113 | do_check_eq(contents, aExpectedContents); |
michael@0 | 114 | } else { |
michael@0 | 115 | // Do not print the entire content string to the test log. |
michael@0 | 116 | do_check_eq(contents.length, aExpectedContents.length); |
michael@0 | 117 | do_check_true(contents == aExpectedContents); |
michael@0 | 118 | } |
michael@0 | 119 | deferred.resolve(); |
michael@0 | 120 | }); |
michael@0 | 121 | return deferred.promise; |
michael@0 | 122 | } |
michael@0 | 123 | |
michael@0 | 124 | /** |
michael@0 | 125 | * Waits for the given saver object to complete. |
michael@0 | 126 | * |
michael@0 | 127 | * @param aSaver |
michael@0 | 128 | * The saver, with the output stream or a stream listener implementation. |
michael@0 | 129 | * @param aOnTargetChangeFn |
michael@0 | 130 | * Optional callback invoked with the target file name when it changes. |
michael@0 | 131 | * |
michael@0 | 132 | * @return {Promise} |
michael@0 | 133 | * @resolves When onSaveComplete is called with a success code. |
michael@0 | 134 | * @rejects With an exception, if onSaveComplete is called with a failure code. |
michael@0 | 135 | */ |
michael@0 | 136 | function promiseSaverComplete(aSaver, aOnTargetChangeFn) { |
michael@0 | 137 | let deferred = Promise.defer(); |
michael@0 | 138 | aSaver.observer = { |
michael@0 | 139 | onTargetChange: function BFSO_onSaveComplete(aSaver, aTarget) |
michael@0 | 140 | { |
michael@0 | 141 | if (aOnTargetChangeFn) { |
michael@0 | 142 | aOnTargetChangeFn(aTarget); |
michael@0 | 143 | } |
michael@0 | 144 | }, |
michael@0 | 145 | onSaveComplete: function BFSO_onSaveComplete(aSaver, aStatus) |
michael@0 | 146 | { |
michael@0 | 147 | if (Components.isSuccessCode(aStatus)) { |
michael@0 | 148 | deferred.resolve(); |
michael@0 | 149 | } else { |
michael@0 | 150 | deferred.reject(new Components.Exception("Saver failed.", aStatus)); |
michael@0 | 151 | } |
michael@0 | 152 | }, |
michael@0 | 153 | }; |
michael@0 | 154 | return deferred.promise; |
michael@0 | 155 | } |
michael@0 | 156 | |
michael@0 | 157 | /** |
michael@0 | 158 | * Feeds a string to a BackgroundFileSaverOutputStream. |
michael@0 | 159 | * |
michael@0 | 160 | * @param aSourceString |
michael@0 | 161 | * The source data to copy. |
michael@0 | 162 | * @param aSaverOutputStream |
michael@0 | 163 | * The BackgroundFileSaverOutputStream to feed. |
michael@0 | 164 | * @param aCloseWhenDone |
michael@0 | 165 | * If true, the output stream will be closed when the copy finishes. |
michael@0 | 166 | * |
michael@0 | 167 | * @return {Promise} |
michael@0 | 168 | * @resolves When the copy completes with a success code. |
michael@0 | 169 | * @rejects With an exception, if the copy fails. |
michael@0 | 170 | */ |
michael@0 | 171 | function promiseCopyToSaver(aSourceString, aSaverOutputStream, aCloseWhenDone) { |
michael@0 | 172 | let deferred = Promise.defer(); |
michael@0 | 173 | let inputStream = new StringInputStream(aSourceString, aSourceString.length); |
michael@0 | 174 | let copier = Cc["@mozilla.org/network/async-stream-copier;1"] |
michael@0 | 175 | .createInstance(Ci.nsIAsyncStreamCopier); |
michael@0 | 176 | copier.init(inputStream, aSaverOutputStream, null, false, true, 0x8000, true, |
michael@0 | 177 | aCloseWhenDone); |
michael@0 | 178 | copier.asyncCopy({ |
michael@0 | 179 | onStartRequest: function () { }, |
michael@0 | 180 | onStopRequest: function (aRequest, aContext, aStatusCode) |
michael@0 | 181 | { |
michael@0 | 182 | if (Components.isSuccessCode(aStatusCode)) { |
michael@0 | 183 | deferred.resolve(); |
michael@0 | 184 | } else { |
michael@0 | 185 | deferred.reject(new Components.Exception(aResult)); |
michael@0 | 186 | } |
michael@0 | 187 | }, |
michael@0 | 188 | }, null); |
michael@0 | 189 | return deferred.promise; |
michael@0 | 190 | } |
michael@0 | 191 | |
michael@0 | 192 | /** |
michael@0 | 193 | * Feeds a string to a BackgroundFileSaverStreamListener. |
michael@0 | 194 | * |
michael@0 | 195 | * @param aSourceString |
michael@0 | 196 | * The source data to copy. |
michael@0 | 197 | * @param aSaverStreamListener |
michael@0 | 198 | * The BackgroundFileSaverStreamListener to feed. |
michael@0 | 199 | * @param aCloseWhenDone |
michael@0 | 200 | * If true, the output stream will be closed when the copy finishes. |
michael@0 | 201 | * |
michael@0 | 202 | * @return {Promise} |
michael@0 | 203 | * @resolves When the operation completes with a success code. |
michael@0 | 204 | * @rejects With an exception, if the operation fails. |
michael@0 | 205 | */ |
michael@0 | 206 | function promisePumpToSaver(aSourceString, aSaverStreamListener, |
michael@0 | 207 | aCloseWhenDone) { |
michael@0 | 208 | let deferred = Promise.defer(); |
michael@0 | 209 | aSaverStreamListener.QueryInterface(Ci.nsIStreamListener); |
michael@0 | 210 | let inputStream = new StringInputStream(aSourceString, aSourceString.length); |
michael@0 | 211 | let pump = Cc["@mozilla.org/network/input-stream-pump;1"] |
michael@0 | 212 | .createInstance(Ci.nsIInputStreamPump); |
michael@0 | 213 | pump.init(inputStream, -1, -1, 0, 0, true); |
michael@0 | 214 | pump.asyncRead({ |
michael@0 | 215 | onStartRequest: function PPTS_onStartRequest(aRequest, aContext) |
michael@0 | 216 | { |
michael@0 | 217 | aSaverStreamListener.onStartRequest(aRequest, aContext); |
michael@0 | 218 | }, |
michael@0 | 219 | onStopRequest: function PPTS_onStopRequest(aRequest, aContext, aStatusCode) |
michael@0 | 220 | { |
michael@0 | 221 | aSaverStreamListener.onStopRequest(aRequest, aContext, aStatusCode); |
michael@0 | 222 | if (Components.isSuccessCode(aStatusCode)) { |
michael@0 | 223 | deferred.resolve(); |
michael@0 | 224 | } else { |
michael@0 | 225 | deferred.reject(new Components.Exception(aResult)); |
michael@0 | 226 | } |
michael@0 | 227 | }, |
michael@0 | 228 | onDataAvailable: function PPTS_onDataAvailable(aRequest, aContext, |
michael@0 | 229 | aInputStream, aOffset, |
michael@0 | 230 | aCount) |
michael@0 | 231 | { |
michael@0 | 232 | aSaverStreamListener.onDataAvailable(aRequest, aContext, aInputStream, |
michael@0 | 233 | aOffset, aCount); |
michael@0 | 234 | }, |
michael@0 | 235 | }, null); |
michael@0 | 236 | return deferred.promise; |
michael@0 | 237 | } |
michael@0 | 238 | |
michael@0 | 239 | let gStillRunning = true; |
michael@0 | 240 | |
michael@0 | 241 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 242 | //// Tests |
michael@0 | 243 | |
michael@0 | 244 | function run_test() |
michael@0 | 245 | { |
michael@0 | 246 | run_next_test(); |
michael@0 | 247 | } |
michael@0 | 248 | |
michael@0 | 249 | add_task(function test_setup() |
michael@0 | 250 | { |
michael@0 | 251 | // Wait 10 minutes, that is half of the external xpcshell timeout. |
michael@0 | 252 | do_timeout(10 * 60 * 1000, function() { |
michael@0 | 253 | if (gStillRunning) { |
michael@0 | 254 | do_throw("Test timed out."); |
michael@0 | 255 | } |
michael@0 | 256 | }) |
michael@0 | 257 | }); |
michael@0 | 258 | |
michael@0 | 259 | add_task(function test_normal() |
michael@0 | 260 | { |
michael@0 | 261 | // This test demonstrates the most basic use case. |
michael@0 | 262 | let destFile = getTempFile(TEST_FILE_NAME_1); |
michael@0 | 263 | |
michael@0 | 264 | // Create the object implementing the output stream. |
michael@0 | 265 | let saver = new BackgroundFileSaverOutputStream(); |
michael@0 | 266 | |
michael@0 | 267 | // Set up callbacks for completion and target file name change. |
michael@0 | 268 | let receivedOnTargetChange = false; |
michael@0 | 269 | function onTargetChange(aTarget) { |
michael@0 | 270 | do_check_true(destFile.equals(aTarget)); |
michael@0 | 271 | receivedOnTargetChange = true; |
michael@0 | 272 | } |
michael@0 | 273 | let completionPromise = promiseSaverComplete(saver, onTargetChange); |
michael@0 | 274 | |
michael@0 | 275 | // Set the target file. |
michael@0 | 276 | saver.setTarget(destFile, false); |
michael@0 | 277 | |
michael@0 | 278 | // Write some data and close the output stream. |
michael@0 | 279 | yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); |
michael@0 | 280 | |
michael@0 | 281 | // Indicate that we are ready to finish, and wait for a successful callback. |
michael@0 | 282 | saver.finish(Cr.NS_OK); |
michael@0 | 283 | yield completionPromise; |
michael@0 | 284 | |
michael@0 | 285 | // Only after we receive the completion notification, we can also be sure that |
michael@0 | 286 | // we've received the target file name change notification before it. |
michael@0 | 287 | do_check_true(receivedOnTargetChange); |
michael@0 | 288 | |
michael@0 | 289 | // Clean up. |
michael@0 | 290 | destFile.remove(false); |
michael@0 | 291 | }); |
michael@0 | 292 | |
michael@0 | 293 | add_task(function test_combinations() |
michael@0 | 294 | { |
michael@0 | 295 | let initialFile = getTempFile(TEST_FILE_NAME_1); |
michael@0 | 296 | let renamedFile = getTempFile(TEST_FILE_NAME_2); |
michael@0 | 297 | |
michael@0 | 298 | // Keep track of the current file. |
michael@0 | 299 | let currentFile = null; |
michael@0 | 300 | function onTargetChange(aTarget) { |
michael@0 | 301 | currentFile = null; |
michael@0 | 302 | do_print("Target file changed to: " + aTarget.leafName); |
michael@0 | 303 | currentFile = aTarget; |
michael@0 | 304 | } |
michael@0 | 305 | |
michael@0 | 306 | // Tests various combinations of events and behaviors for both the stream |
michael@0 | 307 | // listener and the output stream implementations. |
michael@0 | 308 | for (let testFlags = 0; testFlags < 32; testFlags++) { |
michael@0 | 309 | let keepPartialOnFailure = !!(testFlags & 1); |
michael@0 | 310 | let renameAtSomePoint = !!(testFlags & 2); |
michael@0 | 311 | let cancelAtSomePoint = !!(testFlags & 4); |
michael@0 | 312 | let useStreamListener = !!(testFlags & 8); |
michael@0 | 313 | let useLongData = !!(testFlags & 16); |
michael@0 | 314 | |
michael@0 | 315 | let startTime = Date.now(); |
michael@0 | 316 | do_print("Starting keepPartialOnFailure = " + keepPartialOnFailure + |
michael@0 | 317 | ", renameAtSomePoint = " + renameAtSomePoint + |
michael@0 | 318 | ", cancelAtSomePoint = " + cancelAtSomePoint + |
michael@0 | 319 | ", useStreamListener = " + useStreamListener + |
michael@0 | 320 | ", useLongData = " + useLongData); |
michael@0 | 321 | |
michael@0 | 322 | // Create the object and register the observers. |
michael@0 | 323 | currentFile = null; |
michael@0 | 324 | let saver = useStreamListener |
michael@0 | 325 | ? new BackgroundFileSaverStreamListener() |
michael@0 | 326 | : new BackgroundFileSaverOutputStream(); |
michael@0 | 327 | saver.enableSha256(); |
michael@0 | 328 | let completionPromise = promiseSaverComplete(saver, onTargetChange); |
michael@0 | 329 | |
michael@0 | 330 | // Start feeding the first chunk of data to the saver. In case we are using |
michael@0 | 331 | // the stream listener, we only write one chunk. |
michael@0 | 332 | let testData = useLongData ? TEST_DATA_LONG : TEST_DATA_SHORT; |
michael@0 | 333 | let feedPromise = useStreamListener |
michael@0 | 334 | ? promisePumpToSaver(testData + testData, saver) |
michael@0 | 335 | : promiseCopyToSaver(testData, saver, false); |
michael@0 | 336 | |
michael@0 | 337 | // Set a target output file. |
michael@0 | 338 | saver.setTarget(initialFile, keepPartialOnFailure); |
michael@0 | 339 | |
michael@0 | 340 | // Wait for the first chunk of data to be copied. |
michael@0 | 341 | yield feedPromise; |
michael@0 | 342 | |
michael@0 | 343 | if (renameAtSomePoint) { |
michael@0 | 344 | saver.setTarget(renamedFile, keepPartialOnFailure); |
michael@0 | 345 | } |
michael@0 | 346 | |
michael@0 | 347 | if (cancelAtSomePoint) { |
michael@0 | 348 | saver.finish(Cr.NS_ERROR_FAILURE); |
michael@0 | 349 | } |
michael@0 | 350 | |
michael@0 | 351 | // Feed the second chunk of data to the saver. |
michael@0 | 352 | if (!useStreamListener) { |
michael@0 | 353 | yield promiseCopyToSaver(testData, saver, true); |
michael@0 | 354 | } |
michael@0 | 355 | |
michael@0 | 356 | // Wait for completion, and ensure we succeeded or failed as expected. |
michael@0 | 357 | if (!cancelAtSomePoint) { |
michael@0 | 358 | saver.finish(Cr.NS_OK); |
michael@0 | 359 | } |
michael@0 | 360 | try { |
michael@0 | 361 | yield completionPromise; |
michael@0 | 362 | if (cancelAtSomePoint) { |
michael@0 | 363 | do_throw("Failure expected."); |
michael@0 | 364 | } |
michael@0 | 365 | } catch (ex if cancelAtSomePoint && ex.result == Cr.NS_ERROR_FAILURE) { } |
michael@0 | 366 | |
michael@0 | 367 | if (!cancelAtSomePoint) { |
michael@0 | 368 | // In this case, the file must exist. |
michael@0 | 369 | do_check_true(currentFile.exists()); |
michael@0 | 370 | let expectedContents = testData + testData; |
michael@0 | 371 | yield promiseVerifyContents(currentFile, expectedContents); |
michael@0 | 372 | do_check_eq(EXPECTED_HASHES[expectedContents.length], |
michael@0 | 373 | toHex(saver.sha256Hash)); |
michael@0 | 374 | currentFile.remove(false); |
michael@0 | 375 | |
michael@0 | 376 | // If the target was really renamed, the old file should not exist. |
michael@0 | 377 | if (renamedFile.equals(currentFile)) { |
michael@0 | 378 | do_check_false(initialFile.exists()); |
michael@0 | 379 | } |
michael@0 | 380 | } else if (!keepPartialOnFailure) { |
michael@0 | 381 | // In this case, the file must not exist. |
michael@0 | 382 | do_check_false(initialFile.exists()); |
michael@0 | 383 | do_check_false(renamedFile.exists()); |
michael@0 | 384 | } else { |
michael@0 | 385 | // In this case, the file may or may not exist, because canceling can |
michael@0 | 386 | // interrupt the asynchronous operation at any point, even before the file |
michael@0 | 387 | // has been created for the first time. |
michael@0 | 388 | if (initialFile.exists()) { |
michael@0 | 389 | initialFile.remove(false); |
michael@0 | 390 | } |
michael@0 | 391 | if (renamedFile.exists()) { |
michael@0 | 392 | renamedFile.remove(false); |
michael@0 | 393 | } |
michael@0 | 394 | } |
michael@0 | 395 | |
michael@0 | 396 | do_print("Test case completed in " + (Date.now() - startTime) + " ms."); |
michael@0 | 397 | } |
michael@0 | 398 | }); |
michael@0 | 399 | |
michael@0 | 400 | add_task(function test_setTarget_after_close_stream() |
michael@0 | 401 | { |
michael@0 | 402 | // This test checks the case where we close the output stream before we call |
michael@0 | 403 | // the setTarget method. All the data should be buffered and written anyway. |
michael@0 | 404 | let destFile = getTempFile(TEST_FILE_NAME_1); |
michael@0 | 405 | |
michael@0 | 406 | // Test the case where the file does not already exists first, then the case |
michael@0 | 407 | // where the file already exists. |
michael@0 | 408 | for (let i = 0; i < 2; i++) { |
michael@0 | 409 | let saver = new BackgroundFileSaverOutputStream(); |
michael@0 | 410 | saver.enableSha256(); |
michael@0 | 411 | let completionPromise = promiseSaverComplete(saver); |
michael@0 | 412 | |
michael@0 | 413 | // Copy some data to the output stream of the file saver. This data must |
michael@0 | 414 | // be shorter than the internal component's pipe buffer for the test to |
michael@0 | 415 | // succeed, because otherwise the test would block waiting for the write to |
michael@0 | 416 | // complete. |
michael@0 | 417 | yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); |
michael@0 | 418 | |
michael@0 | 419 | // Set the target file and wait for the output to finish. |
michael@0 | 420 | saver.setTarget(destFile, false); |
michael@0 | 421 | saver.finish(Cr.NS_OK); |
michael@0 | 422 | yield completionPromise; |
michael@0 | 423 | |
michael@0 | 424 | // Verify results. |
michael@0 | 425 | yield promiseVerifyContents(destFile, TEST_DATA_SHORT); |
michael@0 | 426 | do_check_eq(EXPECTED_HASHES[TEST_DATA_SHORT.length], |
michael@0 | 427 | toHex(saver.sha256Hash)); |
michael@0 | 428 | } |
michael@0 | 429 | |
michael@0 | 430 | // Clean up. |
michael@0 | 431 | destFile.remove(false); |
michael@0 | 432 | }); |
michael@0 | 433 | |
michael@0 | 434 | add_task(function test_setTarget_fast() |
michael@0 | 435 | { |
michael@0 | 436 | // This test checks a fast rename of the target file. |
michael@0 | 437 | let destFile1 = getTempFile(TEST_FILE_NAME_1); |
michael@0 | 438 | let destFile2 = getTempFile(TEST_FILE_NAME_2); |
michael@0 | 439 | let saver = new BackgroundFileSaverOutputStream(); |
michael@0 | 440 | let completionPromise = promiseSaverComplete(saver); |
michael@0 | 441 | |
michael@0 | 442 | // Set the initial name after the stream is closed, then rename immediately. |
michael@0 | 443 | yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); |
michael@0 | 444 | saver.setTarget(destFile1, false); |
michael@0 | 445 | saver.setTarget(destFile2, false); |
michael@0 | 446 | |
michael@0 | 447 | // Wait for all the operations to complete. |
michael@0 | 448 | saver.finish(Cr.NS_OK); |
michael@0 | 449 | yield completionPromise; |
michael@0 | 450 | |
michael@0 | 451 | // Verify results and clean up. |
michael@0 | 452 | do_check_false(destFile1.exists()); |
michael@0 | 453 | yield promiseVerifyContents(destFile2, TEST_DATA_SHORT); |
michael@0 | 454 | destFile2.remove(false); |
michael@0 | 455 | }); |
michael@0 | 456 | |
michael@0 | 457 | add_task(function test_setTarget_multiple() |
michael@0 | 458 | { |
michael@0 | 459 | // This test checks multiple renames of the target file. |
michael@0 | 460 | let destFile = getTempFile(TEST_FILE_NAME_1); |
michael@0 | 461 | let saver = new BackgroundFileSaverOutputStream(); |
michael@0 | 462 | let completionPromise = promiseSaverComplete(saver); |
michael@0 | 463 | |
michael@0 | 464 | // Rename both before and after the stream is closed. |
michael@0 | 465 | saver.setTarget(getTempFile(TEST_FILE_NAME_2), false); |
michael@0 | 466 | saver.setTarget(getTempFile(TEST_FILE_NAME_3), false); |
michael@0 | 467 | yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); |
michael@0 | 468 | saver.setTarget(getTempFile(TEST_FILE_NAME_2), false); |
michael@0 | 469 | saver.setTarget(destFile, false); |
michael@0 | 470 | |
michael@0 | 471 | // Wait for all the operations to complete. |
michael@0 | 472 | saver.finish(Cr.NS_OK); |
michael@0 | 473 | yield completionPromise; |
michael@0 | 474 | |
michael@0 | 475 | // Verify results and clean up. |
michael@0 | 476 | do_check_false(getTempFile(TEST_FILE_NAME_2).exists()); |
michael@0 | 477 | do_check_false(getTempFile(TEST_FILE_NAME_3).exists()); |
michael@0 | 478 | yield promiseVerifyContents(destFile, TEST_DATA_SHORT); |
michael@0 | 479 | destFile.remove(false); |
michael@0 | 480 | }); |
michael@0 | 481 | |
michael@0 | 482 | add_task(function test_enableAppend() |
michael@0 | 483 | { |
michael@0 | 484 | // This test checks append mode with hashing disabled. |
michael@0 | 485 | let destFile = getTempFile(TEST_FILE_NAME_1); |
michael@0 | 486 | |
michael@0 | 487 | // Test the case where the file does not already exists first, then the case |
michael@0 | 488 | // where the file already exists. |
michael@0 | 489 | for (let i = 0; i < 2; i++) { |
michael@0 | 490 | let saver = new BackgroundFileSaverOutputStream(); |
michael@0 | 491 | saver.enableAppend(); |
michael@0 | 492 | let completionPromise = promiseSaverComplete(saver); |
michael@0 | 493 | |
michael@0 | 494 | saver.setTarget(destFile, false); |
michael@0 | 495 | yield promiseCopyToSaver(TEST_DATA_LONG, saver, true); |
michael@0 | 496 | |
michael@0 | 497 | saver.finish(Cr.NS_OK); |
michael@0 | 498 | yield completionPromise; |
michael@0 | 499 | |
michael@0 | 500 | // Verify results. |
michael@0 | 501 | let expectedContents = (i == 0 ? TEST_DATA_LONG |
michael@0 | 502 | : TEST_DATA_LONG + TEST_DATA_LONG); |
michael@0 | 503 | yield promiseVerifyContents(destFile, expectedContents); |
michael@0 | 504 | } |
michael@0 | 505 | |
michael@0 | 506 | // Clean up. |
michael@0 | 507 | destFile.remove(false); |
michael@0 | 508 | }); |
michael@0 | 509 | |
michael@0 | 510 | add_task(function test_enableAppend_setTarget_fast() |
michael@0 | 511 | { |
michael@0 | 512 | // This test checks a fast rename of the target file in append mode. |
michael@0 | 513 | let destFile1 = getTempFile(TEST_FILE_NAME_1); |
michael@0 | 514 | let destFile2 = getTempFile(TEST_FILE_NAME_2); |
michael@0 | 515 | |
michael@0 | 516 | // Test the case where the file does not already exists first, then the case |
michael@0 | 517 | // where the file already exists. |
michael@0 | 518 | for (let i = 0; i < 2; i++) { |
michael@0 | 519 | let saver = new BackgroundFileSaverOutputStream(); |
michael@0 | 520 | saver.enableAppend(); |
michael@0 | 521 | let completionPromise = promiseSaverComplete(saver); |
michael@0 | 522 | |
michael@0 | 523 | yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); |
michael@0 | 524 | |
michael@0 | 525 | // The first time, we start appending to the first file and rename to the |
michael@0 | 526 | // second file. The second time, we start appending to the second file, |
michael@0 | 527 | // that was created the first time, and rename back to the first file. |
michael@0 | 528 | let firstFile = (i == 0) ? destFile1 : destFile2; |
michael@0 | 529 | let secondFile = (i == 0) ? destFile2 : destFile1; |
michael@0 | 530 | saver.setTarget(firstFile, false); |
michael@0 | 531 | saver.setTarget(secondFile, false); |
michael@0 | 532 | |
michael@0 | 533 | saver.finish(Cr.NS_OK); |
michael@0 | 534 | yield completionPromise; |
michael@0 | 535 | |
michael@0 | 536 | // Verify results. |
michael@0 | 537 | do_check_false(firstFile.exists()); |
michael@0 | 538 | let expectedContents = (i == 0 ? TEST_DATA_SHORT |
michael@0 | 539 | : TEST_DATA_SHORT + TEST_DATA_SHORT); |
michael@0 | 540 | yield promiseVerifyContents(secondFile, expectedContents); |
michael@0 | 541 | } |
michael@0 | 542 | |
michael@0 | 543 | // Clean up. |
michael@0 | 544 | destFile1.remove(false); |
michael@0 | 545 | }); |
michael@0 | 546 | |
michael@0 | 547 | add_task(function test_enableAppend_hash() |
michael@0 | 548 | { |
michael@0 | 549 | // This test checks append mode, also verifying that the computed hash |
michael@0 | 550 | // includes the contents of the existing data. |
michael@0 | 551 | let destFile = getTempFile(TEST_FILE_NAME_1); |
michael@0 | 552 | |
michael@0 | 553 | // Test the case where the file does not already exists first, then the case |
michael@0 | 554 | // where the file already exists. |
michael@0 | 555 | for (let i = 0; i < 2; i++) { |
michael@0 | 556 | let saver = new BackgroundFileSaverOutputStream(); |
michael@0 | 557 | saver.enableAppend(); |
michael@0 | 558 | saver.enableSha256(); |
michael@0 | 559 | let completionPromise = promiseSaverComplete(saver); |
michael@0 | 560 | |
michael@0 | 561 | saver.setTarget(destFile, false); |
michael@0 | 562 | yield promiseCopyToSaver(TEST_DATA_LONG, saver, true); |
michael@0 | 563 | |
michael@0 | 564 | saver.finish(Cr.NS_OK); |
michael@0 | 565 | yield completionPromise; |
michael@0 | 566 | |
michael@0 | 567 | // Verify results. |
michael@0 | 568 | let expectedContents = (i == 0 ? TEST_DATA_LONG |
michael@0 | 569 | : TEST_DATA_LONG + TEST_DATA_LONG); |
michael@0 | 570 | yield promiseVerifyContents(destFile, expectedContents); |
michael@0 | 571 | do_check_eq(EXPECTED_HASHES[expectedContents.length], |
michael@0 | 572 | toHex(saver.sha256Hash)); |
michael@0 | 573 | } |
michael@0 | 574 | |
michael@0 | 575 | // Clean up. |
michael@0 | 576 | destFile.remove(false); |
michael@0 | 577 | }); |
michael@0 | 578 | |
michael@0 | 579 | add_task(function test_finish_only() |
michael@0 | 580 | { |
michael@0 | 581 | // This test checks creating the object and doing nothing. |
michael@0 | 582 | let destFile = getTempFile(TEST_FILE_NAME_1); |
michael@0 | 583 | let saver = new BackgroundFileSaverOutputStream(); |
michael@0 | 584 | function onTargetChange(aTarget) { |
michael@0 | 585 | do_throw("Should not receive the onTargetChange notification."); |
michael@0 | 586 | } |
michael@0 | 587 | let completionPromise = promiseSaverComplete(saver, onTargetChange); |
michael@0 | 588 | saver.finish(Cr.NS_OK); |
michael@0 | 589 | yield completionPromise; |
michael@0 | 590 | }); |
michael@0 | 591 | |
michael@0 | 592 | add_task(function test_empty() |
michael@0 | 593 | { |
michael@0 | 594 | // This test checks we still create an empty file when no data is fed. |
michael@0 | 595 | let destFile = getTempFile(TEST_FILE_NAME_1); |
michael@0 | 596 | |
michael@0 | 597 | let saver = new BackgroundFileSaverOutputStream(); |
michael@0 | 598 | let completionPromise = promiseSaverComplete(saver); |
michael@0 | 599 | |
michael@0 | 600 | saver.setTarget(destFile, false); |
michael@0 | 601 | yield promiseCopyToSaver("", saver, true); |
michael@0 | 602 | |
michael@0 | 603 | saver.finish(Cr.NS_OK); |
michael@0 | 604 | yield completionPromise; |
michael@0 | 605 | |
michael@0 | 606 | // Verify results. |
michael@0 | 607 | do_check_true(destFile.exists()); |
michael@0 | 608 | do_check_eq(destFile.fileSize, 0); |
michael@0 | 609 | |
michael@0 | 610 | // Clean up. |
michael@0 | 611 | destFile.remove(false); |
michael@0 | 612 | }); |
michael@0 | 613 | |
michael@0 | 614 | add_task(function test_empty_hash() |
michael@0 | 615 | { |
michael@0 | 616 | // This test checks the hash of an empty file, both in normal and append mode. |
michael@0 | 617 | let destFile = getTempFile(TEST_FILE_NAME_1); |
michael@0 | 618 | |
michael@0 | 619 | // Test normal mode first, then append mode. |
michael@0 | 620 | for (let i = 0; i < 2; i++) { |
michael@0 | 621 | let saver = new BackgroundFileSaverOutputStream(); |
michael@0 | 622 | if (i == 1) { |
michael@0 | 623 | saver.enableAppend(); |
michael@0 | 624 | } |
michael@0 | 625 | saver.enableSha256(); |
michael@0 | 626 | let completionPromise = promiseSaverComplete(saver); |
michael@0 | 627 | |
michael@0 | 628 | saver.setTarget(destFile, false); |
michael@0 | 629 | yield promiseCopyToSaver("", saver, true); |
michael@0 | 630 | |
michael@0 | 631 | saver.finish(Cr.NS_OK); |
michael@0 | 632 | yield completionPromise; |
michael@0 | 633 | |
michael@0 | 634 | // Verify results. |
michael@0 | 635 | do_check_eq(destFile.fileSize, 0); |
michael@0 | 636 | do_check_eq(EXPECTED_HASHES[0], toHex(saver.sha256Hash)); |
michael@0 | 637 | } |
michael@0 | 638 | |
michael@0 | 639 | // Clean up. |
michael@0 | 640 | destFile.remove(false); |
michael@0 | 641 | }); |
michael@0 | 642 | |
michael@0 | 643 | add_task(function test_invalid_hash() |
michael@0 | 644 | { |
michael@0 | 645 | let saver = new BackgroundFileSaverStreamListener(); |
michael@0 | 646 | let completionPromise = promiseSaverComplete(saver); |
michael@0 | 647 | // We shouldn't be able to get the hash if hashing hasn't been enabled |
michael@0 | 648 | try { |
michael@0 | 649 | let hash = saver.sha256Hash; |
michael@0 | 650 | do_throw("Shouldn't be able to get hash if hashing not enabled"); |
michael@0 | 651 | } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } |
michael@0 | 652 | // Enable hashing, but don't feed any data to saver |
michael@0 | 653 | saver.enableSha256(); |
michael@0 | 654 | let destFile = getTempFile(TEST_FILE_NAME_1); |
michael@0 | 655 | saver.setTarget(destFile, false); |
michael@0 | 656 | // We don't wait on promiseSaverComplete, so the hash getter can run before |
michael@0 | 657 | // or after onSaveComplete is called. However, the expected behavior is the |
michael@0 | 658 | // same in both cases since the hash is only valid when the save completes |
michael@0 | 659 | // successfully. |
michael@0 | 660 | saver.finish(Cr.NS_ERROR_FAILURE); |
michael@0 | 661 | try { |
michael@0 | 662 | let hash = saver.sha256Hash; |
michael@0 | 663 | do_throw("Shouldn't be able to get hash if save did not succeed"); |
michael@0 | 664 | } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } |
michael@0 | 665 | // Wait for completion so that the worker thread finishes dealing with the |
michael@0 | 666 | // target file. We expect it to fail. |
michael@0 | 667 | try { |
michael@0 | 668 | yield completionPromise; |
michael@0 | 669 | do_throw("completionPromise should throw"); |
michael@0 | 670 | } catch (ex if ex.result == Cr.NS_ERROR_FAILURE) { } |
michael@0 | 671 | }); |
michael@0 | 672 | |
michael@0 | 673 | add_task(function test_signature() |
michael@0 | 674 | { |
michael@0 | 675 | // Check that we get a signature if the saver is finished. |
michael@0 | 676 | let destFile = getTempFile(TEST_FILE_NAME_1); |
michael@0 | 677 | |
michael@0 | 678 | let saver = new BackgroundFileSaverOutputStream(); |
michael@0 | 679 | let completionPromise = promiseSaverComplete(saver); |
michael@0 | 680 | |
michael@0 | 681 | try { |
michael@0 | 682 | let signatureInfo = saver.signatureInfo; |
michael@0 | 683 | do_throw("Can't get signature if saver is not complete"); |
michael@0 | 684 | } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } |
michael@0 | 685 | |
michael@0 | 686 | saver.enableSignatureInfo(); |
michael@0 | 687 | saver.setTarget(destFile, false); |
michael@0 | 688 | yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); |
michael@0 | 689 | |
michael@0 | 690 | saver.finish(Cr.NS_OK); |
michael@0 | 691 | yield completionPromise; |
michael@0 | 692 | yield promiseVerifyContents(destFile, TEST_DATA_SHORT); |
michael@0 | 693 | |
michael@0 | 694 | // signatureInfo is an empty nsIArray |
michael@0 | 695 | do_check_eq(0, saver.signatureInfo.length); |
michael@0 | 696 | |
michael@0 | 697 | // Clean up. |
michael@0 | 698 | destFile.remove(false); |
michael@0 | 699 | }); |
michael@0 | 700 | |
michael@0 | 701 | add_task(function test_signature_not_enabled() |
michael@0 | 702 | { |
michael@0 | 703 | // Check that we get a signature if the saver is finished on Windows. |
michael@0 | 704 | let destFile = getTempFile(TEST_FILE_NAME_1); |
michael@0 | 705 | |
michael@0 | 706 | let saver = new BackgroundFileSaverOutputStream(); |
michael@0 | 707 | let completionPromise = promiseSaverComplete(saver); |
michael@0 | 708 | saver.setTarget(destFile, false); |
michael@0 | 709 | yield promiseCopyToSaver(TEST_DATA_SHORT, saver, true); |
michael@0 | 710 | |
michael@0 | 711 | saver.finish(Cr.NS_OK); |
michael@0 | 712 | yield completionPromise; |
michael@0 | 713 | try { |
michael@0 | 714 | let signatureInfo = saver.signatureInfo; |
michael@0 | 715 | do_throw("Can't get signature if not enabled"); |
michael@0 | 716 | } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } |
michael@0 | 717 | |
michael@0 | 718 | // Clean up. |
michael@0 | 719 | destFile.remove(false); |
michael@0 | 720 | }); |
michael@0 | 721 | |
michael@0 | 722 | add_task(function test_teardown() |
michael@0 | 723 | { |
michael@0 | 724 | gStillRunning = false; |
michael@0 | 725 | }); |