tor-browser

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

FileTestUtils.sys.mjs (5097B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /**
      6 * Provides testing functions dealing with local files and their contents.
      7 */
      8 
      9 import { DownloadPaths } from "resource://gre/modules/DownloadPaths.sys.mjs";
     10 import { FileUtils } from "resource://gre/modules/FileUtils.sys.mjs";
     11 
     12 import { Assert } from "resource://testing-common/Assert.sys.mjs";
     13 
     14 let gFileCounter = 1;
     15 let gPathsToRemove = [];
     16 
     17 export var FileTestUtils = {
     18  /**
     19   * Returns a reference to a temporary file that is guaranteed not to exist and
     20   * to have never been created before. If a file or a directory with this name
     21   * is created by the test, it will be deleted when all tests terminate.
     22   *
     23   * @param suggestedName [optional]
     24   *        Any extension on this template file name will be preserved. If this
     25   *        is unspecified, the returned file name will have the generic ".dat"
     26   *        extension, which may indicate either a binary or a text data file.
     27   *
     28   * @return nsIFile pointing to a non-existent file in a temporary directory.
     29   *
     30   * Note: It is not enough to delete the file if it exists, or to delete the
     31   *       file after calling nsIFile.createUnique, because on Windows the
     32   *       delete operation in the file system may still be pending, preventing
     33   *       a new file with the same name to be created.
     34   */
     35  getTempFile(suggestedName = "test.dat") {
     36    // Prepend a serial number to the extension in the suggested leaf name.
     37    let [base, ext] = DownloadPaths.splitBaseNameAndExtension(suggestedName);
     38    let leafName = base + "-" + gFileCounter + ext;
     39    gFileCounter++;
     40 
     41    // Get a file reference under the temporary directory for this test file.
     42    let file = this._globalTemporaryDirectory.clone();
     43    file.append(leafName);
     44    Assert.ok(!file.exists(), "Sanity check the temporary file doesn't exist.");
     45 
     46    // Since directory iteration on Windows may not see files that have just
     47    // been created, keep track of the known file names to be removed.
     48    gPathsToRemove.push(file.path);
     49    return file;
     50  },
     51 
     52  /**
     53   * Attemps to remove the given file or directory recursively, in a way that
     54   * works even on Windows, where race conditions may occur in the file system
     55   * when creating and removing files at the pace of the test suites.
     56   *
     57   * The function may fail silently if access is denied. This means that it
     58   * should only be used to clean up temporary files, rather than for cases
     59   * where the removal is part of a test and must be guaranteed.
     60   *
     61   * @param path
     62   *        String representing the path to remove.
     63   */
     64  async tolerantRemove(path) {
     65    try {
     66      await IOUtils.remove(path, { recursive: true });
     67    } catch (ex) {
     68      // On Windows, we may get an access denied error instead of a no such file
     69      // error if the file existed before, and was recently deleted. There is no
     70      // way to distinguish this from an access list issue because checking for
     71      // the file existence would also result in the same error.
     72      if (
     73        !DOMException.isInstance(ex) ||
     74        ex.name !== "NotFoundError" ||
     75        ex.name !== "NotAllowedError"
     76      ) {
     77        throw ex;
     78      }
     79    }
     80  },
     81 };
     82 
     83 /**
     84 * Returns a reference to a global temporary directory that will be deleted
     85 * when all tests terminate.
     86 */
     87 ChromeUtils.defineLazyGetter(
     88  FileTestUtils,
     89  "_globalTemporaryDirectory",
     90  function () {
     91    // While previous test runs should have deleted their temporary directories,
     92    // on Windows they might still be pending deletion on the physical file
     93    // system. This makes a simple nsIFile.createUnique call unreliable, and we
     94    // have to use a random number to make a collision unlikely.
     95    let randomNumber = Math.floor(Math.random() * 1000000);
     96    let dir = new FileUtils.File(
     97      PathUtils.join(PathUtils.tempDir, `testdir-${randomNumber}`)
     98    );
     99    dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
    100 
    101    // We need to run this *after* the profile-before-change phase because
    102    // otherwise we can race other shutdown blockers who have created files in
    103    // our temporary directory. This can cause our shutdown blocker to fail due
    104    // to, e.g., JSONFile attempting to flush its contents to disk while we are
    105    // trying to delete the file.
    106 
    107    IOUtils.sendTelemetry.addBlocker("Removing test files", async () => {
    108      // Remove the files we know about first.
    109      for (let path of gPathsToRemove) {
    110        await FileTestUtils.tolerantRemove(path);
    111      }
    112 
    113      if (!(await IOUtils.exists(dir.path))) {
    114        return;
    115      }
    116 
    117      // Detect any extra files, like the ".part" files of downloads.
    118      for (const child of await IOUtils.getChildren(dir.path)) {
    119        await FileTestUtils.tolerantRemove(child);
    120      }
    121      // This will fail if any test leaves inaccessible files behind.
    122      await IOUtils.remove(dir.path, { recursive: false });
    123    });
    124    return dir;
    125  }
    126 );