netwerk/test/unit/test_backgroundfilesaver.js

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

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 });

mercurial