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