Bug 1411979 - Share the getTempFile function in xpcshell and browser tests. r=mak
☠☠ backed out by bdb364ba3e05 ☠ ☠
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Mon, 30 Oct 2017 16:53:22 +0000
changeset 439898 68e85782bbcab3c06e729551643bdc602cf8de71
parent 439897 90b950f6e65bf7abab85c01afc9370792e1895e2
child 439899 ed385db8511241a5178506ef9a015fd1df098b15
push id8114
push userjlorenzo@mozilla.com
push dateThu, 02 Nov 2017 16:33:21 +0000
treeherdermozilla-beta@73e0d89a540f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1411979
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1411979 - Share the getTempFile function in xpcshell and browser tests. r=mak MozReview-Commit-ID: 5hshgOrFqws
browser/extensions/formautofill/test/unit/head.js
netwerk/test/unit/test_backgroundfilesaver.js
netwerk/test/unit/test_signature_extraction.js
testing/modules/FileTestUtils.jsm
testing/modules/TestUtils.jsm
testing/modules/moz.build
toolkit/components/jsdownloads/test/browser/head.js
toolkit/components/jsdownloads/test/unit/head.js
toolkit/components/jsdownloads/test/unit/test_DownloadPaths.js
toolkit/components/passwordmgr/test/unit/head.js
toolkit/components/reputationservice/test/unit/test_app_rep_windows.js
toolkit/modules/tests/xpcshell/test_JSONFile.js
uriloader/exthandler/tests/mochitest/browser_download_privatebrowsing.js
--- a/browser/extensions/formautofill/test/unit/head.js
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -10,16 +10,17 @@
 
 var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/ObjectUtils.jsm");
 Cu.import("resource://gre/modules/FormLikeFactory.jsm");
+Cu.import("resource://testing-common/FileTestUtils.jsm");
 Cu.import("resource://testing-common/MockDocument.jsm");
 Cu.import("resource://testing-common/TestUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
                                   "resource://gre/modules/DownloadPaths.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 
@@ -46,53 +47,22 @@ let bootstrapURI = Services.io.newFileUR
 if (!extensionDir.exists()) {
   extensionDir = extensionDir.parent;
   extensionDir.append(EXTENSION_ID + ".xpi");
   let jarURI = Services.io.newFileURI(extensionDir);
   bootstrapURI = "jar:" + jarURI.spec + "!/bootstrap.js";
 }
 Components.manager.addBootstrappedManifestLocation(extensionDir);
 
-// While the previous test file should have deleted all the temporary files it
-// used, on Windows these might still be pending deletion on the physical file
-// system.  Thus, start from a new base number every time, to make a collision
-// with a file that is still pending deletion highly unlikely.
-let gFileCounter = Math.floor(Math.random() * 1000000);
-
 /**
- * Returns a reference to a temporary file, that is guaranteed not to exist, and
- * to have never been created before.
- *
- * @param {string} leafName
- *        Suggested leaf name for the file to be created.
- *
- * @returns {nsIFile} pointing to a non-existent file in a temporary directory.
- *
- * @note It is not enough to delete the file if it exists, or to delete the file
- *       after calling nsIFile.createUnique, because on Windows the delete
- *       operation in the file system may still be pending, preventing a new
- *       file with the same name to be created.
+ * Returns a reference to a temporary file that is guaranteed not to exist and
+ * is cleaned up later. See FileTestUtils.getTempFile for details.
  */
 function getTempFile(leafName) {
-  // Prepend a serial number to the extension in the suggested leaf name.
-  let [base, ext] = DownloadPaths.splitBaseNameAndExtension(leafName);
-  let finalLeafName = base + "-" + gFileCounter + ext;
-  gFileCounter++;
-
-  // Get a file reference under the temporary directory for this test file.
-  let file = FileUtils.getFile("TmpD", [finalLeafName]);
-  do_check_false(file.exists());
-
-  do_register_cleanup(function() {
-    if (file.exists()) {
-      file.remove(false);
-    }
-  });
-
-  return file;
+  return FileTestUtils.getTempFile(leafName);
 }
 
 async function initProfileStorage(fileName, records, collectionName = "addresses") {
   let {ProfileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
   let path = getTempFile(fileName).path;
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
--- a/netwerk/test/unit/test_backgroundfilesaver.js
+++ b/netwerk/test/unit/test_backgroundfilesaver.js
@@ -13,16 +13,18 @@
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileTestUtils",
+                                  "resource://testing-common/FileTestUtils.jsm");
 
 const BackgroundFileSaverOutputStream = Components.Constructor(
       "@mozilla.org/network/background-file-saver;1?mode=outputstream",
       "nsIBackgroundFileSaver");
 
 const BackgroundFileSaverStreamListener = Components.Constructor(
       "@mozilla.org/network/background-file-saver;1?mode=streamlistener",
       "nsIBackgroundFileSaver");
@@ -56,27 +58,21 @@ const gTextDecoder = new TextDecoder();
 
 // Generate a long string of data in a moderately fast way.
 const TEST_256_CHARS = new Array(257).join("-");
 const DESIRED_LENGTH = REQUEST_SUSPEND_AT * 2;
 const TEST_DATA_LONG = new Array(1 + DESIRED_LENGTH / 256).join(TEST_256_CHARS);
 do_check_eq(TEST_DATA_LONG.length, DESIRED_LENGTH);
 
 /**
- * Returns a reference to a temporary file.  If the file is then created, it
- * will be removed when tests in this file finish.
+ * Returns a reference to a temporary file that is guaranteed not to exist and
+ * is cleaned up later. See FileTestUtils.getTempFile for details.
  */
-function getTempFile(aLeafName) {
-  let file = FileUtils.getFile("TmpD", [aLeafName]);
-  do_register_cleanup(function GTF_cleanup() {
-    if (file.exists()) {
-      file.remove(false);
-    }
-  });
-  return file;
+function getTempFile(leafName) {
+  return FileTestUtils.getTempFile(leafName);
 }
 
 /**
  * Helper function for converting a binary blob to its hex equivalent.
  *
  * @param str
  *        String possibly containing non-printable chars.
  * @return A hex-encoded string.
--- a/netwerk/test/unit/test_signature_extraction.js
+++ b/netwerk/test/unit/test_signature_extraction.js
@@ -12,40 +12,36 @@
 //// Globals
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileTestUtils",
+                                  "resource://testing-common/FileTestUtils.jsm");
 
 const BackgroundFileSaverOutputStream = Components.Constructor(
       "@mozilla.org/network/background-file-saver;1?mode=outputstream",
       "nsIBackgroundFileSaver");
 
 const StringInputStream = Components.Constructor(
       "@mozilla.org/io/string-input-stream;1",
       "nsIStringInputStream",
       "setData");
 
 const TEST_FILE_NAME_1 = "test-backgroundfilesaver-1.txt";
 
 /**
- * Returns a reference to a temporary file.  If the file is then created, it
- * will be removed when tests in this file finish.
+ * Returns a reference to a temporary file that is guaranteed not to exist and
+ * is cleaned up later. See FileTestUtils.getTempFile for details.
  */
-function getTempFile(aLeafName) {
-  let file = FileUtils.getFile("TmpD", [aLeafName]);
-  do_register_cleanup(function GTF_cleanup() {
-    if (file.exists()) {
-      file.remove(false);
-    }
-  });
-  return file;
+function getTempFile(leafName) {
+  return FileTestUtils.getTempFile(leafName);
 }
 
 /**
  * Waits for the given saver object to complete.
  *
  * @param aSaver
  *        The saver, with the output stream or a stream listener implementation.
  * @param aOnTargetChangeFn
new file mode 100644
--- /dev/null
+++ b/testing/modules/FileTestUtils.jsm
@@ -0,0 +1,74 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Provides testing functions dealing with local files and their contents.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+  "FileTestUtils",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
+Cu.import("resource://gre/modules/DownloadPaths.jsm", this);
+Cu.import("resource://gre/modules/FileUtils.jsm", this);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://testing-common/Assert.jsm", this);
+
+let gFileCounter = 1;
+
+this.FileTestUtils = {
+  /**
+   * Returns a reference to a temporary file that is guaranteed not to exist and
+   * to have never been created before. If a file or a directory with this name
+   * is created by the test, it will be deleted when all tests terminate.
+   *
+   * @param suggestedName [optional]
+   *        Any extension on this template file name will be preserved. If this
+   *        is unspecified, the returned file name will have the generic ".dat"
+   *        extension, which may indicate either a binary or a text data file.
+   *
+   * @return nsIFile pointing to a non-existent file in a temporary directory.
+   *
+   * @note It is not enough to delete the file if it exists, or to delete the
+   *       file after calling nsIFile.createUnique, because on Windows the
+   *       delete operation in the file system may still be pending, preventing
+   *       a new file with the same name to be created.
+   */
+  getTempFile(suggestedName = "test.dat") {
+    // Prepend a serial number to the extension in the suggested leaf name.
+    let [base, ext] = DownloadPaths.splitBaseNameAndExtension(suggestedName);
+    let leafName = base + "-" + gFileCounter + ext;
+    gFileCounter++;
+
+    // Get a file reference under the temporary directory for this test file.
+    let file = this._globalTemporaryDirectory.clone();
+    file.append(leafName);
+    Assert.ok(!file.exists(), "Sanity check the temporary file doesn't exist.");
+    return file;
+  },
+};
+
+/**
+ * Returns a reference to a global temporary directory that will be deleted
+ * when all tests terminate.
+ */
+XPCOMUtils.defineLazyGetter(FileTestUtils, "_globalTemporaryDirectory", () => {
+  // While previous test runs should have deleted their temporary directories,
+  // on Windows they might still be pending deletion on the physical file
+  // system. This makes a simple nsIFile.createUnique call unreliable, and we
+  // have to use a random number to make a collision unlikely.
+  let randomNumber = Math.floor(Math.random() * 1000000);
+  let dir = FileUtils.getFile("TmpD", ["testdir-" + randomNumber]);
+  dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+  AsyncShutdown.profileBeforeChange.addBlocker("Removing test files", () => {
+    // Note that this may fail if any test leaves inaccessible files behind.
+    dir.remove(true);
+  });
+  return dir;
+});
--- a/testing/modules/TestUtils.jsm
+++ b/testing/modules/TestUtils.jsm
@@ -1,19 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/*
- * This module implements a number of utilities useful for testing.
+/**
+ * Contains a limited number of testing functions that are commonly used in a
+ * wide variety of situations, for example waiting for an event loop tick or an
+ * observer notification.
  *
- * Additions to this module should be generic and useful to testing multiple
- * features. Utilities only useful to a sepcific testing framework should live
- * in a module specific to that framework, such as BrowserTestUtils.jsm in the
- * case of mochitest-browser-chrome.
+ * More complex functions are likely to belong to a separate test-only module.
+ * Examples include Assert.jsm for generic assertions, FileTestUtils.jsm to work
+ * with local files and their contents, and BrowserTestUtils.jsm to work with
+ * browser windows and tabs.
+ *
+ * Individual components also offer testing functions to other components, for
+ * example LoginTestUtils.jsm.
  */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [
   "TestUtils",
 ];
 
--- a/testing/modules/moz.build
+++ b/testing/modules/moz.build
@@ -8,16 +8,17 @@ XPCSHELL_TESTS_MANIFESTS += ['tests/xpcs
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 
 TESTING_JS_MODULES += [
     'ajv-4.1.1.js',
     'AppData.jsm',
     'AppInfo.jsm',
     'Assert.jsm',
     'CoverageUtils.jsm',
+    'FileTestUtils.jsm',
     'MockRegistrar.jsm',
     'sinon-2.3.2.js',
     'StructuredLog.jsm',
     'TestUtils.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     TESTING_JS_MODULES += [
--- a/toolkit/components/jsdownloads/test/browser/head.js
+++ b/toolkit/components/jsdownloads/test/browser/head.js
@@ -25,58 +25,29 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
                                   "resource://testing-common/httpd.js");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileTestUtils",
+                                  "resource://testing-common/FileTestUtils.jsm");
 
 const TEST_TARGET_FILE_NAME_PDF = "test-download.pdf";
 
 // Support functions
 
-// While the previous test file should have deleted all the temporary files it
-// used, on Windows these might still be pending deletion on the physical file
-// system.  Thus, start from a new base number every time, to make a collision
-// with a file that is still pending deletion highly unlikely.
-var gFileCounter = Math.floor(Math.random() * 1000000);
-
 /**
- * Returns a reference to a temporary file, that is guaranteed not to exist, and
- * to have never been created before.
- *
- * @param aLeafName
- *        Suggested leaf name for the file to be created.
- *
- * @return nsIFile pointing to a non-existent file in a temporary directory.
- *
- * @note It is not enough to delete the file if it exists, or to delete the file
- *       after calling nsIFile.createUnique, because on Windows the delete
- *       operation in the file system may still be pending, preventing a new
- *       file with the same name to be created.
+ * Returns a reference to a temporary file that is guaranteed not to exist and
+ * is cleaned up later. See FileTestUtils.getTempFile for details.
  */
-function getTempFile(aLeafName) {
-  // Prepend a serial number to the extension in the suggested leaf name.
-  let [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
-  let leafName = base + "-" + gFileCounter + ext;
-  gFileCounter++;
-
-  // Get a file reference under the temporary directory for this test file.
-  let file = FileUtils.getFile("TmpD", [leafName]);
-  ok(!file.exists(), "Temp file does not exist");
-
-  registerCleanupFunction(function() {
-    if (file.exists()) {
-      file.remove(false);
-    }
-  });
-
-  return file;
+function getTempFile(leafName) {
+  return FileTestUtils.getTempFile(leafName);
 }
 
 function promiseBrowserLoaded(browser) {
   return new Promise(resolve => {
     browser.addEventListener("load", function onLoad(event) {
       if (event.target == browser.contentDocument) {
         browser.removeEventListener("load", onLoad, true);
         resolve();
--- a/toolkit/components/jsdownloads/test/unit/head.js
+++ b/toolkit/components/jsdownloads/test/unit/head.js
@@ -32,16 +32,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileTestUtils",
+                                  "resource://testing-common/FileTestUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MockRegistrar",
                                   "resource://testing-common/MockRegistrar.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gExternalHelperAppService",
            "@mozilla.org/uriloader/external-helper-app-service;1",
            Ci.nsIExternalHelperAppService);
 
 /* global DownloadIntegration */
@@ -96,64 +98,22 @@ var gHttpServer;
  * Given a file name, returns a string containing an URI that points to the file
  * on the currently running instance of the test HTTP server.
  */
 function httpUrl(aFileName) {
   return "http://localhost:" + gHttpServer.identity.primaryPort + "/" +
          aFileName;
 }
 
-// While the previous test file should have deleted all the temporary files it
-// used, on Windows these might still be pending deletion on the physical file
-// system.  Thus, start from a new base number every time, to make a collision
-// with a file that is still pending deletion highly unlikely.
-var gFileCounter = Math.floor(Math.random() * 1000000);
-
 /**
- * Returns a reference to a temporary file, that is guaranteed not to exist, and
- * to have never been created before.
- *
- * @param aLeafName
- *        Suggested leaf name for the file to be created.
- *
- * @return nsIFile pointing to a non-existent file in a temporary directory.
- *
- * @note It is not enough to delete the file if it exists, or to delete the file
- *       after calling nsIFile.createUnique, because on Windows the delete
- *       operation in the file system may still be pending, preventing a new
- *       file with the same name to be created.
+ * Returns a reference to a temporary file that is guaranteed not to exist and
+ * is cleaned up later. See FileTestUtils.getTempFile for details.
  */
-function getTempFile(aLeafName) {
-  // Prepend a serial number to the extension in the suggested leaf name.
-  let [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
-  let leafName = base + "-" + gFileCounter + ext;
-  gFileCounter++;
-
-  // Get a file reference under the temporary directory for this test file.
-  let file = FileUtils.getFile("TmpD", [leafName]);
-  do_check_false(file.exists());
-
-  do_register_cleanup(function() {
-    try {
-      file.remove(false);
-    } catch (e) {
-      if (!(e instanceof Components.Exception &&
-            (e.result == Cr.NS_ERROR_FILE_ACCESS_DENIED ||
-             e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST ||
-             e.result == Cr.NS_ERROR_FILE_NOT_FOUND))) {
-        throw e;
-      }
-      // On Windows, we may get an access denied error if the file existed before,
-      // and was recently deleted.
-      // Don't bother checking file.exists() as that may also cause an access
-      // denied error.
-    }
-  });
-
-  return file;
+function getTempFile(leafName) {
+  return FileTestUtils.getTempFile(leafName);
 }
 
 /**
  * Waits for pending events to be processed.
  *
  * @return {Promise}
  * @resolves When pending events have been processed.
  * @rejects Never.
--- a/toolkit/components/jsdownloads/test/unit/test_DownloadPaths.js
+++ b/toolkit/components/jsdownloads/test/unit/test_DownloadPaths.js
@@ -2,31 +2,16 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests for the "DownloadPaths.jsm" JavaScript module.
  */
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
 
-/**
- * Provides a temporary save directory.
- *
- * @returns nsIFile pointing to the new or existing directory.
- */
-function createTemporarySaveDirectory() {
-  var saveDir = Cc["@mozilla.org/file/directory_service;1"].
-                getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
-  saveDir.append("testsavedir");
-  if (!saveDir.exists()) {
-    saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
-  }
-  return saveDir;
-}
-
 function testSanitize(leafName, expectedLeafName) {
   do_check_eq(DownloadPaths.sanitize(leafName), expectedLeafName);
 }
 
 function testSplitBaseNameAndExtension(aLeafName, [aBase, aExt]) {
   var [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
   do_check_eq(base, aBase);
   do_check_eq(ext, aExt);
@@ -126,44 +111,41 @@ add_task(async function test_splitBaseNa
   testSplitBaseNameAndExtension(" . ", [" ", ". "]);
   testSplitBaseNameAndExtension(" .. ", [" .", ". "]);
   testSplitBaseNameAndExtension(" .ext", [" ", ".ext"]);
   testSplitBaseNameAndExtension(" .ext. ", [" .ext", ". "]);
   testSplitBaseNameAndExtension(" .ext.gz ", [" .ext", ".gz "]);
 });
 
 add_task(async function test_createNiceUniqueFile() {
-  var destDir = createTemporarySaveDirectory();
-  try {
-    // Single extension.
-    var tempFile = destDir.clone();
-    tempFile.append("test.txt");
-    testCreateNiceUniqueFile(tempFile, "test.txt");
-    testCreateNiceUniqueFile(tempFile, "test(1).txt");
-    testCreateNiceUniqueFile(tempFile, "test(2).txt");
+  var destDir = FileTestUtils.getTempFile("destdir");
+  destDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
 
-    // Double extension.
-    tempFile.leafName = "test.tar.gz";
-    testCreateNiceUniqueFile(tempFile, "test.tar.gz");
-    testCreateNiceUniqueFile(tempFile, "test(1).tar.gz");
-    testCreateNiceUniqueFile(tempFile, "test(2).tar.gz");
+  // Single extension.
+  var tempFile = destDir.clone();
+  tempFile.append("test.txt");
+  testCreateNiceUniqueFile(tempFile, "test.txt");
+  testCreateNiceUniqueFile(tempFile, "test(1).txt");
+  testCreateNiceUniqueFile(tempFile, "test(2).txt");
+
+  // Double extension.
+  tempFile.leafName = "test.tar.gz";
+  testCreateNiceUniqueFile(tempFile, "test.tar.gz");
+  testCreateNiceUniqueFile(tempFile, "test(1).tar.gz");
+  testCreateNiceUniqueFile(tempFile, "test(2).tar.gz");
 
-    // Test automatic shortening of long file names. We don't know exactly how
-    // many characters are removed, because it depends on the name of the folder
-    // where the file is located.
-    tempFile.leafName = new Array(256).join("T") + ".txt";
-    var newFile = DownloadPaths.createNiceUniqueFile(tempFile);
-    do_check_true(newFile.leafName.length < tempFile.leafName.length);
-    do_check_eq(newFile.leafName.slice(-4), ".txt");
+  // Test automatic shortening of long file names. We don't know exactly how
+  // many characters are removed, because it depends on the name of the folder
+  // where the file is located.
+  tempFile.leafName = new Array(256).join("T") + ".txt";
+  var newFile = DownloadPaths.createNiceUniqueFile(tempFile);
+  do_check_true(newFile.leafName.length < tempFile.leafName.length);
+  do_check_eq(newFile.leafName.slice(-4), ".txt");
 
-    // Creating a valid file name from an invalid one is not always possible.
-    tempFile.append("file-under-long-directory.txt");
-    try {
-      DownloadPaths.createNiceUniqueFile(tempFile);
-      do_throw("Exception expected with a long parent directory name.");
-    } catch (e) {
-      // An exception is expected, but we don't know which one exactly.
-    }
-  } finally {
-    // Clean up the temporary directory.
-    destDir.remove(true);
+  // Creating a valid file name from an invalid one is not always possible.
+  tempFile.append("file-under-long-directory.txt");
+  try {
+    DownloadPaths.createNiceUniqueFile(tempFile);
+    do_throw("Exception expected with a long parent directory name.");
+  } catch (e) {
+    // An exception is expected, but we don't know which one exactly.
   }
 });
--- a/toolkit/components/passwordmgr/test/unit/head.js
+++ b/toolkit/components/passwordmgr/test/unit/head.js
@@ -7,16 +7,17 @@
 // Globals
 
 let { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/LoginRecipes.jsm");
 Cu.import("resource://gre/modules/LoginHelper.jsm");
+Cu.import("resource://testing-common/FileTestUtils.jsm");
 Cu.import("resource://testing-common/LoginTestUtils.jsm");
 Cu.import("resource://testing-common/MockDocument.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
                                   "resource://gre/modules/DownloadPaths.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
@@ -35,57 +36,22 @@ const newPropertyBag = LoginHelper.newPr
 function run_test()
 {
   do_get_profile();
   run_next_test();
 }
 
 // Global helpers
 
-// Some of these functions are already implemented in other parts of the source
-// tree, see bug 946708 about sharing more code.
-
-// While the previous test file should have deleted all the temporary files it
-// used, on Windows these might still be pending deletion on the physical file
-// system.  Thus, start from a new base number every time, to make a collision
-// with a file that is still pending deletion highly unlikely.
-let gFileCounter = Math.floor(Math.random() * 1000000);
-
 /**
- * Returns a reference to a temporary file, that is guaranteed not to exist, and
- * to have never been created before.
- *
- * @param aLeafName
- *        Suggested leaf name for the file to be created.
- *
- * @return nsIFile pointing to a non-existent file in a temporary directory.
- *
- * @note It is not enough to delete the file if it exists, or to delete the file
- *       after calling nsIFile.createUnique, because on Windows the delete
- *       operation in the file system may still be pending, preventing a new
- *       file with the same name to be created.
+ * Returns a reference to a temporary file that is guaranteed not to exist and
+ * is cleaned up later. See FileTestUtils.getTempFile for details.
  */
-function getTempFile(aLeafName)
-{
-  // Prepend a serial number to the extension in the suggested leaf name.
-  let [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
-  let leafName = base + "-" + gFileCounter + ext;
-  gFileCounter++;
-
-  // Get a file reference under the temporary directory for this test file.
-  let file = FileUtils.getFile("TmpD", [leafName]);
-  do_check_false(file.exists());
-
-  do_register_cleanup(function() {
-    if (file.exists()) {
-      file.remove(false);
-    }
-  });
-
-  return file;
+function getTempFile(leafName) {
+  return FileTestUtils.getTempFile(leafName);
 }
 
 const RecipeHelpers = {
   initNewParent() {
     return (new LoginRecipesParent({ defaults: null })).initializationPromise;
   },
 };
 
--- a/toolkit/components/reputationservice/test/unit/test_app_rep_windows.js
+++ b/toolkit/components/reputationservice/test/unit/test_app_rep_windows.js
@@ -11,16 +11,18 @@
 // Globals
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileTestUtils",
+                                  "resource://testing-common/FileTestUtils.jsm");
 
 const BackgroundFileSaverOutputStream = Components.Constructor(
       "@mozilla.org/network/background-file-saver;1?mode=outputstream",
       "nsIBackgroundFileSaver");
 
 const StringInputStream = Components.Constructor(
       "@mozilla.org/io/string-input-stream;1",
       "nsIStringInputStream",
@@ -32,30 +34,16 @@ const gAppRep = Cc["@mozilla.org/reputat
                   getService(Ci.nsIApplicationReputationService);
 var gStillRunning = true;
 var gTables = {};
 var gHttpServer = null;
 
 const appRepURLPref = "browser.safebrowsing.downloads.remote.url";
 const remoteEnabledPref = "browser.safebrowsing.downloads.remote.enabled";
 
-/**
- * Returns a reference to a temporary file.  If the file is then created, it
- * will be removed when tests in this file finish.
- */
-function getTempFile(aLeafName) {
-  let file = FileUtils.getFile("TmpD", [aLeafName]);
-  do_register_cleanup(function GTF_cleanup() {
-    if (file.exists()) {
-      file.remove(false);
-    }
-  });
-  return file;
-}
-
 function readFileToString(aFilename) {
   let f = do_get_file(aFilename);
   let stream = Cc["@mozilla.org/network/file-input-stream;1"]
                  .createInstance(Ci.nsIFileInputStream);
   stream.init(f, -1, 0, 0);
   let buf = NetUtil.readInputStreamToString(stream, stream.available());
   return buf;
 }
@@ -313,17 +301,17 @@ add_task(async function() {
 add_task(async function test_signature_whitelists() {
   // We should never get to the remote server.
   Services.prefs.setBoolPref(remoteEnabledPref,
                              true);
   Services.prefs.setCharPref(appRepURLPref,
                              "http://localhost:4444/throw");
 
   // Use BackgroundFileSaver to extract the signature on Windows.
-  let destFile = getTempFile(TEST_FILE_NAME_1);
+  let destFile = FileTestUtils.getTempFile(TEST_FILE_NAME_1);
 
   let data = readFileToString("data/signed_win.exe");
   let saver = new BackgroundFileSaverOutputStream();
   let completionPromise = promiseSaverComplete(saver);
   saver.enableSignatureInfo();
   saver.setTarget(destFile, false);
   await promiseCopyToSaver(data, saver, true);
 
--- a/toolkit/modules/tests/xpcshell/test_JSONFile.js
+++ b/toolkit/modules/tests/xpcshell/test_JSONFile.js
@@ -10,50 +10,25 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
                                   "resource://gre/modules/DownloadPaths.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
                                   "resource://gre/modules/JSONFile.jsm");
-
-let gFileCounter = Math.floor(Math.random() * 1000000);
+XPCOMUtils.defineLazyModuleGetter(this, "FileTestUtils",
+                                  "resource://testing-common/FileTestUtils.jsm");
 
 /**
- * Returns a reference to a temporary file, that is guaranteed not to exist, and
- * to have never been created before.
- *
- * @param aLeafName
- *        Suggested leaf name for the file to be created.
- *
- * @return nsIFile pointing to a non-existent file in a temporary directory.
- *
- * @note It is not enough to delete the file if it exists, or to delete the file
- *       after calling nsIFile.createUnique, because on Windows the delete
- *       operation in the file system may still be pending, preventing a new
- *       file with the same name to be created.
+ * Returns a reference to a temporary file that is guaranteed not to exist and
+ * is cleaned up later. See FileTestUtils.getTempFile for details.
  */
-function getTempFile(aLeafName) {
-  // Prepend a serial number to the extension in the suggested leaf name.
-  let [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
-  let leafName = base + "-" + gFileCounter + ext;
-  gFileCounter++;
-
-  // Get a file reference under the temporary directory for this test file.
-  let file = FileUtils.getFile("TmpD", [leafName]);
-  do_check_false(file.exists());
-
-  do_register_cleanup(function() {
-    if (file.exists()) {
-      file.remove(false);
-    }
-  });
-
-  return file;
+function getTempFile(leafName) {
+  return FileTestUtils.getTempFile(leafName);
 }
 
 const TEST_STORE_FILE_NAME = "test-store.json";
 
 const TEST_DATA = {
   number: 123,
   string: "test",
   object: {
--- a/uriloader/exthandler/tests/mochitest/browser_download_privatebrowsing.js
+++ b/uriloader/exthandler/tests/mochitest/browser_download_privatebrowsing.js
@@ -5,78 +5,29 @@
  * Tests that downloads started from a private window by clicking on a link end
  * up in the global list of private downloads (see bug 1367581).
  */
 
 "use strict";
 
 Cu.import("resource://gre/modules/Downloads.jsm", this);
 Cu.import("resource://gre/modules/DownloadPaths.jsm", this);
+Cu.import("resource://testing-common/FileTestUtils.jsm", this);
 Cu.import("resource://testing-common/MockRegistrar.jsm", this);
 
-const TEST_TARGET_FILE_NAME = "test-download.txt";
-
-// While the previous test file should have deleted all the temporary files it
-// used, on Windows these might still be pending deletion on the physical file
-// system.  Thus, start from a new base number every time, to make a collision
-// with a file that is still pending deletion highly unlikely.
-let gFileCounter = Math.floor(Math.random() * 1000000);
-
-/**
- * Returns a reference to a temporary file, that is guaranteed not to exist, and
- * to have never been created before.
- *
- * @param aLeafName
- *        Suggested leaf name for the file to be created.
- *
- * @return nsIFile pointing to a non-existent file in a temporary directory.
- *
- * @note It is not enough to delete the file if it exists, or to delete the file
- *       after calling nsIFile.createUnique, because on Windows the delete
- *       operation in the file system may still be pending, preventing a new
- *       file with the same name to be created.
- */
-function getTempFile(aLeafName) {
-  // Prepend a serial number to the extension in the suggested leaf name.
-  let [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
-  let leafName = base + "-" + gFileCounter + ext;
-  gFileCounter++;
-
-  // Get a file reference under the temporary directory for this test file.
-  let file = FileUtils.getFile("TmpD", [leafName]);
-  Assert.ok(!file.exists());
-
-  registerCleanupFunction(() => {
-    try {
-      file.remove(false);
-    } catch (ex) {
-      // On Windows, we may get an access denied error if the file existed
-      // before, and was recently deleted.
-      if (!(ex instanceof Components.Exception &&
-            (ex.result == Cr.NS_ERROR_FILE_ACCESS_DENIED ||
-             ex.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST ||
-             ex.result == Cr.NS_ERROR_FILE_NOT_FOUND))) {
-        throw ex;
-      }
-    }
-  });
-
-  return file;
-}
-
 add_task(async function test_setup() {
   // Save downloads to disk without showing the dialog.
   let cid = MockRegistrar.register("@mozilla.org/helperapplauncherdialog;1", {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]),
     show(launcher) {
       launcher.saveToDisk(null, false);
     },
     promptForSaveToFileAsync(launcher) {
       // The dialog should create the empty placeholder file.
-      let file = getTempFile(TEST_TARGET_FILE_NAME);
+      let file = FileTestUtils.getTempFile();
       file.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
       launcher.saveDestinationAvailable(file);
     },
   });
   registerCleanupFunction(() => {
     MockRegistrar.unregister(cid);
   });
 });