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     *  &#064;Rule
020     *  public TemporaryFolder folder= new TemporaryFolder();
021     *
022     *  &#064;Test
023     *  public void testUsingTempFolder() throws IOException {
024     *      File createdFile= folder.newFile(&quot;myfile.txt&quot;);
025     *      File createdFolder= folder.newFolder(&quot;subfolder&quot;);
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     *  &#064;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    }