• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.junit.rules;
2 
3 import static org.junit.Assert.fail;
4 
5 import java.io.File;
6 import java.io.IOException;
7 import java.lang.reflect.Array;
8 import java.lang.reflect.InvocationTargetException;
9 import java.lang.reflect.Method;
10 
11 import org.junit.Rule;
12 
13 /**
14  * The TemporaryFolder Rule allows creation of files and folders that should
15  * be deleted when the test method finishes (whether it passes or
16  * fails).
17  * By default no exception will be thrown in case the deletion fails.
18  *
19  * <p>Example of usage:
20  * <pre>
21  * public static class HasTempFolder {
22  *  &#064;Rule
23  *  public TemporaryFolder folder= new TemporaryFolder();
24  *
25  *  &#064;Test
26  *  public void testUsingTempFolder() throws IOException {
27  *      File createdFile= folder.newFile(&quot;myfile.txt&quot;);
28  *      File createdFolder= folder.newFolder(&quot;subfolder&quot;);
29  *      // ...
30  *     }
31  * }
32  * </pre>
33  *
34  * <p>TemporaryFolder rule supports assured deletion mode, which
35  * will fail the test in case deletion fails with {@link AssertionError}.
36  *
37  * <p>Creating TemporaryFolder with assured deletion:
38  * <pre>
39  *  &#064;Rule
40  *  public TemporaryFolder folder= TemporaryFolder.builder().assureDeletion().build();
41  * </pre>
42  *
43  * @since 4.7
44  */
45 public class TemporaryFolder extends ExternalResource {
46     private final File parentFolder;
47     private final boolean assureDeletion;
48     private File folder;
49 
50     private static final int TEMP_DIR_ATTEMPTS = 10000;
51     private static final String TMP_PREFIX = "junit";
52 
53     /**
54      * Create a temporary folder which uses system default temporary-file
55      * directory to create temporary resources.
56      */
TemporaryFolder()57     public TemporaryFolder() {
58         this((File) null);
59     }
60 
61     /**
62      * Create a temporary folder which uses the specified directory to create
63      * temporary resources.
64      *
65      * @param parentFolder folder where temporary resources will be created.
66      * If {@code null} then system default temporary-file directory is used.
67      */
TemporaryFolder(File parentFolder)68     public TemporaryFolder(File parentFolder) {
69         this.parentFolder = parentFolder;
70         this.assureDeletion = false;
71     }
72 
73     /**
74      * Create a {@link TemporaryFolder} initialized with
75      * values from a builder.
76      */
TemporaryFolder(Builder builder)77     protected TemporaryFolder(Builder builder) {
78         this.parentFolder = builder.parentFolder;
79         this.assureDeletion = builder.assureDeletion;
80     }
81 
82     /**
83      * Returns a new builder for building an instance of {@link TemporaryFolder}.
84      *
85      * @since 4.13
86      */
builder()87     public static Builder builder() {
88         return new Builder();
89     }
90 
91     /**
92      * Builds an instance of {@link TemporaryFolder}.
93      *
94      * @since 4.13
95      */
96     public static class Builder {
97         private File parentFolder;
98         private boolean assureDeletion;
99 
Builder()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          */
parentFolder(File parentFolder)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          */
assureDeletion()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          */
build()129         public TemporaryFolder build() {
130             return new TemporaryFolder(this);
131         }
132     }
133 
134     @Override
before()135     protected void before() throws Throwable {
136         create();
137     }
138 
139     @Override
after()140     protected void after() {
141         delete();
142     }
143 
144     // testing purposes only
145 
146     /**
147      * for testing purposes only. Do not use.
148      */
create()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      */
newFile(String fileName)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      */
newFile()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      */
newFolder(String path)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      */
newFolder(String... paths)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      */
newFolder()231     public File newFolder() throws IOException {
232         return createTemporaryFolderIn(getRoot());
233     }
234 
createTemporaryFolderIn(File parentFolder)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 
createTemporaryFolderWithNioApi(File parentFolder)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 
createTemporaryFolderWithFileApi(File parentFolder)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      */
getRoot()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      */
delete()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      */
tryDelete()327     private boolean tryDelete() {
328         if (folder == null) {
329             return true;
330         }
331 
332         return recursiveDelete(folder);
333     }
334 
recursiveDelete(File file)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 }
352