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