tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_backgroundfilesaver.js (23710B)


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