001 package org.junit.rules; 002 003 import static org.junit.Assert.fail; 004 005 import java.io.File; 006 import java.io.IOException; 007 008 import org.junit.Rule; 009 010 /** 011 * The TemporaryFolder Rule allows creation of files and folders that should 012 * be deleted when the test method finishes (whether it passes or 013 * fails). 014 * By default no exception will be thrown in case the deletion fails. 015 * 016 * <p>Example of usage: 017 * <pre> 018 * public static class HasTempFolder { 019 * @Rule 020 * public TemporaryFolder folder= new TemporaryFolder(); 021 * 022 * @Test 023 * public void testUsingTempFolder() throws IOException { 024 * File createdFile= folder.newFile("myfile.txt"); 025 * File createdFolder= folder.newFolder("subfolder"); 026 * // ... 027 * } 028 * } 029 * </pre> 030 * 031 * <p>TemporaryFolder rule supports assured deletion mode, which 032 * will fail the test in case deletion fails with {@link AssertionError}. 033 * 034 * <p>Creating TemporaryFolder with assured deletion: 035 * <pre> 036 * @Rule 037 * public TemporaryFolder folder= TemporaryFolder.builder().assureDeletion().build(); 038 * </pre> 039 * 040 * @since 4.7 041 */ 042 public class TemporaryFolder extends ExternalResource { 043 private final File parentFolder; 044 private final boolean assureDeletion; 045 private File folder; 046 047 private static final int TEMP_DIR_ATTEMPTS = 10000; 048 private static final String TMP_PREFIX = "junit"; 049 050 /** 051 * Create a temporary folder which uses system default temporary-file 052 * directory to create temporary resources. 053 */ 054 public TemporaryFolder() { 055 this((File) null); 056 } 057 058 /** 059 * Create a temporary folder which uses the specified directory to create 060 * temporary resources. 061 * 062 * @param parentFolder folder where temporary resources will be created. 063 * If {@code null} then system default temporary-file directory is used. 064 */ 065 public TemporaryFolder(File parentFolder) { 066 this.parentFolder = parentFolder; 067 this.assureDeletion = false; 068 } 069 070 /** 071 * Create a {@link TemporaryFolder} initialized with 072 * values from a builder. 073 */ 074 protected TemporaryFolder(Builder builder) { 075 this.parentFolder = builder.parentFolder; 076 this.assureDeletion = builder.assureDeletion; 077 } 078 079 /** 080 * Returns a new builder for building an instance of {@link TemporaryFolder}. 081 * 082 * @since 4.13 083 */ 084 public static Builder builder() { 085 return new Builder(); 086 } 087 088 /** 089 * Builds an instance of {@link TemporaryFolder}. 090 * 091 * @since 4.13 092 */ 093 public static class Builder { 094 private File parentFolder; 095 private boolean assureDeletion; 096 097 protected Builder() {} 098 099 /** 100 * Specifies which folder to use for creating temporary resources. 101 * If {@code null} then system default temporary-file directory is 102 * used. 103 * 104 * @return this 105 */ 106 public Builder parentFolder(File parentFolder) { 107 this.parentFolder = parentFolder; 108 return this; 109 } 110 111 /** 112 * Setting this flag assures that no resources are left undeleted. Failure 113 * to fulfill the assurance results in failure of tests with an 114 * {@link AssertionError}. 115 * 116 * @return this 117 */ 118 public Builder assureDeletion() { 119 this.assureDeletion = true; 120 return this; 121 } 122 123 /** 124 * Builds a {@link TemporaryFolder} instance using the values in this builder. 125 */ 126 public TemporaryFolder build() { 127 return new TemporaryFolder(this); 128 } 129 } 130 131 @Override 132 protected void before() throws Throwable { 133 create(); 134 } 135 136 @Override 137 protected void after() { 138 delete(); 139 } 140 141 // testing purposes only 142 143 /** 144 * for testing purposes only. Do not use. 145 */ 146 public void create() throws IOException { 147 folder = createTemporaryFolderIn(parentFolder); 148 } 149 150 /** 151 * Returns a new fresh file with the given name under the temporary folder. 152 */ 153 public File newFile(String fileName) throws IOException { 154 File file = new File(getRoot(), fileName); 155 if (!file.createNewFile()) { 156 throw new IOException( 157 "a file with the name \'" + fileName + "\' already exists in the test folder"); 158 } 159 return file; 160 } 161 162 /** 163 * Returns a new fresh file with a random name under the temporary folder. 164 */ 165 public File newFile() throws IOException { 166 return File.createTempFile(TMP_PREFIX, null, getRoot()); 167 } 168 169 /** 170 * Returns a new fresh folder with the given path under the temporary 171 * folder. 172 */ 173 public File newFolder(String path) throws IOException { 174 return newFolder(new String[]{path}); 175 } 176 177 /** 178 * Returns a new fresh folder with the given paths under the temporary 179 * folder. For example, if you pass in the strings {@code "parent"} and {@code "child"} 180 * then a directory named {@code "parent"} will be created under the temporary folder 181 * and a directory named {@code "child"} will be created under the newly-created 182 * {@code "parent"} directory. 183 */ 184 public File newFolder(String... paths) throws IOException { 185 if (paths.length == 0) { 186 throw new IllegalArgumentException("must pass at least one path"); 187 } 188 189 /* 190 * Before checking if the paths are absolute paths, check if create() was ever called, 191 * and if it wasn't, throw IllegalStateException. 192 */ 193 File root = getRoot(); 194 for (String path : paths) { 195 if (new File(path).isAbsolute()) { 196 throw new IOException("folder path \'" + path + "\' is not a relative path"); 197 } 198 } 199 200 File relativePath = null; 201 File file = root; 202 boolean lastMkdirsCallSuccessful = true; 203 for (String path : paths) { 204 relativePath = new File(relativePath, path); 205 file = new File(root, relativePath.getPath()); 206 207 lastMkdirsCallSuccessful = file.mkdirs(); 208 if (!lastMkdirsCallSuccessful && !file.isDirectory()) { 209 if (file.exists()) { 210 throw new IOException( 211 "a file with the path \'" + relativePath.getPath() + "\' exists"); 212 } else { 213 throw new IOException( 214 "could not create a folder with the path \'" + relativePath.getPath() + "\'"); 215 } 216 } 217 } 218 if (!lastMkdirsCallSuccessful) { 219 throw new IOException( 220 "a folder with the path \'" + relativePath.getPath() + "\' already exists"); 221 } 222 return file; 223 } 224 225 /** 226 * Returns a new fresh folder with a random name under the temporary folder. 227 */ 228 public File newFolder() throws IOException { 229 return createTemporaryFolderIn(getRoot()); 230 } 231 232 private File createTemporaryFolderIn(File parentFolder) throws IOException { 233 File createdFolder = null; 234 for (int i = 0; i < TEMP_DIR_ATTEMPTS; ++i) { 235 // Use createTempFile to get a suitable folder name. 236 String suffix = ".tmp"; 237 File tmpFile = File.createTempFile(TMP_PREFIX, suffix, parentFolder); 238 String tmpName = tmpFile.toString(); 239 // Discard .tmp suffix of tmpName. 240 String folderName = tmpName.substring(0, tmpName.length() - suffix.length()); 241 createdFolder = new File(folderName); 242 if (createdFolder.mkdir()) { 243 tmpFile.delete(); 244 return createdFolder; 245 } 246 tmpFile.delete(); 247 } 248 throw new IOException("Unable to create temporary directory in: " 249 + parentFolder.toString() + ". Tried " + TEMP_DIR_ATTEMPTS + " times. " 250 + "Last attempted to create: " + createdFolder.toString()); 251 } 252 253 /** 254 * @return the location of this temporary folder. 255 */ 256 public File getRoot() { 257 if (folder == null) { 258 throw new IllegalStateException( 259 "the temporary folder has not yet been created"); 260 } 261 return folder; 262 } 263 264 /** 265 * Delete all files and folders under the temporary folder. Usually not 266 * called directly, since it is automatically applied by the {@link Rule}. 267 * 268 * @throws AssertionError if unable to clean up resources 269 * and deletion of resources is assured. 270 */ 271 public void delete() { 272 if (!tryDelete()) { 273 if (assureDeletion) { 274 fail("Unable to clean up temporary folder " + folder); 275 } 276 } 277 } 278 279 /** 280 * Tries to delete all files and folders under the temporary folder and 281 * returns whether deletion was successful or not. 282 * 283 * @return {@code true} if all resources are deleted successfully, 284 * {@code false} otherwise. 285 */ 286 private boolean tryDelete() { 287 if (folder == null) { 288 return true; 289 } 290 291 return recursiveDelete(folder); 292 } 293 294 private boolean recursiveDelete(File file) { 295 // Try deleting file before assuming file is a directory 296 // to prevent following symbolic links. 297 if (file.delete()) { 298 return true; 299 } 300 File[] files = file.listFiles(); 301 if (files != null) { 302 for (File each : files) { 303 if (!recursiveDelete(each)) { 304 return false; 305 } 306 } 307 } 308 return file.delete(); 309 } 310 }