diff options
Diffstat (limited to 'src/main/java/org/junit/rules/TemporaryFolder.java')
-rw-r--r-- | src/main/java/org/junit/rules/TemporaryFolder.java | 277 |
1 files changed, 229 insertions, 48 deletions
diff --git a/src/main/java/org/junit/rules/TemporaryFolder.java b/src/main/java/org/junit/rules/TemporaryFolder.java index dc75c93..a726c66 100644 --- a/src/main/java/org/junit/rules/TemporaryFolder.java +++ b/src/main/java/org/junit/rules/TemporaryFolder.java @@ -1,15 +1,20 @@ package org.junit.rules; +import static org.junit.Assert.fail; + import java.io.File; import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import org.junit.Rule; /** * The TemporaryFolder Rule allows creation of files and folders that should * be deleted when the test method finishes (whether it passes or - * fails). Whether the deletion is successful or not is not checked by this rule. - * No exception will be thrown in case the deletion fails. + * fails). + * By default no exception will be thrown in case the deletion fails. * * <p>Example of usage: * <pre> @@ -26,18 +31,104 @@ import org.junit.Rule; * } * </pre> * + * <p>TemporaryFolder rule supports assured deletion mode, which + * will fail the test in case deletion fails with {@link AssertionError}. + * + * <p>Creating TemporaryFolder with assured deletion: + * <pre> + * @Rule + * public TemporaryFolder folder= TemporaryFolder.builder().assureDeletion().build(); + * </pre> + * * @since 4.7 */ public class TemporaryFolder extends ExternalResource { private final File parentFolder; + private final boolean assureDeletion; private File folder; + private static final int TEMP_DIR_ATTEMPTS = 10000; + private static final String TMP_PREFIX = "junit"; + + /** + * Create a temporary folder which uses system default temporary-file + * directory to create temporary resources. + */ public TemporaryFolder() { - this(null); + this((File) null); } + /** + * Create a temporary folder which uses the specified directory to create + * temporary resources. + * + * @param parentFolder folder where temporary resources will be created. + * If {@code null} then system default temporary-file directory is used. + */ public TemporaryFolder(File parentFolder) { this.parentFolder = parentFolder; + this.assureDeletion = false; + } + + /** + * Create a {@link TemporaryFolder} initialized with + * values from a builder. + */ + protected TemporaryFolder(Builder builder) { + this.parentFolder = builder.parentFolder; + this.assureDeletion = builder.assureDeletion; + } + + /** + * Returns a new builder for building an instance of {@link TemporaryFolder}. + * + * @since 4.13 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builds an instance of {@link TemporaryFolder}. + * + * @since 4.13 + */ + public static class Builder { + private File parentFolder; + private boolean assureDeletion; + + protected Builder() {} + + /** + * Specifies which folder to use for creating temporary resources. + * If {@code null} then system default temporary-file directory is + * used. + * + * @return this + */ + public Builder parentFolder(File parentFolder) { + this.parentFolder = parentFolder; + return this; + } + + /** + * Setting this flag assures that no resources are left undeleted. Failure + * to fulfill the assurance results in failure of tests with an + * {@link AssertionError}. + * + * @return this + */ + public Builder assureDeletion() { + this.assureDeletion = true; + return this; + } + + /** + * Builds a {@link TemporaryFolder} instance using the values in this builder. + */ + public TemporaryFolder build() { + return new TemporaryFolder(this); + } } @Override @@ -75,52 +166,63 @@ public class TemporaryFolder extends ExternalResource { * Returns a new fresh file with a random name under the temporary folder. */ public File newFile() throws IOException { - return File.createTempFile("junit", null, getRoot()); + return File.createTempFile(TMP_PREFIX, null, getRoot()); } /** - * Returns a new fresh folder with the given name under the temporary + * Returns a new fresh folder with the given path under the temporary * folder. */ - public File newFolder(String folder) throws IOException { - return newFolder(new String[]{folder}); + public File newFolder(String path) throws IOException { + return newFolder(new String[]{path}); } /** - * Returns a new fresh folder with the given name(s) under the temporary - * folder. + * Returns a new fresh folder with the given paths under the temporary + * folder. For example, if you pass in the strings {@code "parent"} and {@code "child"} + * then a directory named {@code "parent"} will be created under the temporary folder + * and a directory named {@code "child"} will be created under the newly-created + * {@code "parent"} directory. */ - public File newFolder(String... folderNames) throws IOException { - File file = getRoot(); - for (int i = 0; i < folderNames.length; i++) { - String folderName = folderNames[i]; - validateFolderName(folderName); - file = new File(file, folderName); - if (!file.mkdir() && isLastElementInArray(i, folderNames)) { - throw new IOException( - "a folder with the name \'" + folderName + "\' already exists"); - } + public File newFolder(String... paths) throws IOException { + if (paths.length == 0) { + throw new IllegalArgumentException("must pass at least one path"); } - return file; - } - - /** - * Validates if multiple path components were used while creating a folder. - * - * @param folderName - * Name of the folder being created - */ - private void validateFolderName(String folderName) throws IOException { - File tempFile = new File(folderName); - if (tempFile.getParent() != null) { - String errorMsg = "Folder name cannot consist of multiple path components separated by a file separator." - + " Please use newFolder('MyParentFolder','MyFolder') to create hierarchies of folders"; - throw new IOException(errorMsg); + + /* + * Before checking if the paths are absolute paths, check if create() was ever called, + * and if it wasn't, throw IllegalStateException. + */ + File root = getRoot(); + for (String path : paths) { + if (new File(path).isAbsolute()) { + throw new IOException("folder path \'" + path + "\' is not a relative path"); + } } - } - private boolean isLastElementInArray(int index, String[] array) { - return index == array.length - 1; + File relativePath = null; + File file = root; + boolean lastMkdirsCallSuccessful = true; + for (String path : paths) { + relativePath = new File(relativePath, path); + file = new File(root, relativePath.getPath()); + + lastMkdirsCallSuccessful = file.mkdirs(); + if (!lastMkdirsCallSuccessful && !file.isDirectory()) { + if (file.exists()) { + throw new IOException( + "a file with the path \'" + relativePath.getPath() + "\' exists"); + } else { + throw new IOException( + "could not create a folder with the path \'" + relativePath.getPath() + "\'"); + } + } + } + if (!lastMkdirsCallSuccessful) { + throw new IOException( + "a folder with the path \'" + relativePath.getPath() + "\' already exists"); + } + return file; } /** @@ -130,11 +232,63 @@ public class TemporaryFolder extends ExternalResource { return createTemporaryFolderIn(getRoot()); } - private File createTemporaryFolderIn(File parentFolder) throws IOException { - File createdFolder = File.createTempFile("junit", "", parentFolder); - createdFolder.delete(); - createdFolder.mkdir(); - return createdFolder; + private static File createTemporaryFolderIn(File parentFolder) throws IOException { + try { + return createTemporaryFolderWithNioApi(parentFolder); + } catch (ClassNotFoundException ignore) { + // Fallback for Java 5 and 6 + return createTemporaryFolderWithFileApi(parentFolder); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof IOException) { + throw (IOException) cause; + } + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + IOException exception = new IOException("Failed to create temporary folder in " + parentFolder); + exception.initCause(cause); + throw exception; + } catch (Exception e) { + throw new RuntimeException("Failed to create temporary folder in " + parentFolder, e); + } + } + + private static File createTemporaryFolderWithNioApi(File parentFolder) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Class<?> filesClass = Class.forName("java.nio.file.Files"); + Object fileAttributeArray = Array.newInstance(Class.forName("java.nio.file.attribute.FileAttribute"), 0); + Class<?> pathClass = Class.forName("java.nio.file.Path"); + Object tempDir; + if (parentFolder != null) { + Method createTempDirectoryMethod = filesClass.getDeclaredMethod("createTempDirectory", pathClass, String.class, fileAttributeArray.getClass()); + Object parentPath = File.class.getDeclaredMethod("toPath").invoke(parentFolder); + tempDir = createTempDirectoryMethod.invoke(null, parentPath, TMP_PREFIX, fileAttributeArray); + } else { + Method createTempDirectoryMethod = filesClass.getDeclaredMethod("createTempDirectory", String.class, fileAttributeArray.getClass()); + tempDir = createTempDirectoryMethod.invoke(null, TMP_PREFIX, fileAttributeArray); + } + return (File) pathClass.getDeclaredMethod("toFile").invoke(tempDir); + } + + private static File createTemporaryFolderWithFileApi(File parentFolder) throws IOException { + File createdFolder = null; + for (int i = 0; i < TEMP_DIR_ATTEMPTS; ++i) { + // Use createTempFile to get a suitable folder name. + String suffix = ".tmp"; + File tmpFile = File.createTempFile(TMP_PREFIX, suffix, parentFolder); + String tmpName = tmpFile.toString(); + // Discard .tmp suffix of tmpName. + String folderName = tmpName.substring(0, tmpName.length() - suffix.length()); + createdFolder = new File(folderName); + if (createdFolder.mkdir()) { + tmpFile.delete(); + return createdFolder; + } + tmpFile.delete(); + } + throw new IOException("Unable to create temporary directory in: " + + parentFolder.toString() + ". Tried " + TEMP_DIR_ATTEMPTS + " times. " + + "Last attempted to create: " + createdFolder.toString()); } /** @@ -150,21 +304,48 @@ public class TemporaryFolder extends ExternalResource { /** * Delete all files and folders under the temporary folder. Usually not - * called directly, since it is automatically applied by the {@link Rule} + * called directly, since it is automatically applied by the {@link Rule}. + * + * @throws AssertionError if unable to clean up resources + * and deletion of resources is assured. */ public void delete() { - if (folder != null) { - recursiveDelete(folder); + if (!tryDelete()) { + if (assureDeletion) { + fail("Unable to clean up temporary folder " + folder); + } + } + } + + /** + * Tries to delete all files and folders under the temporary folder and + * returns whether deletion was successful or not. + * + * @return {@code true} if all resources are deleted successfully, + * {@code false} otherwise. + */ + private boolean tryDelete() { + if (folder == null) { + return true; } + + return recursiveDelete(folder); } - private void recursiveDelete(File file) { + private boolean recursiveDelete(File file) { + // Try deleting file before assuming file is a directory + // to prevent following symbolic links. + if (file.delete()) { + return true; + } File[] files = file.listFiles(); if (files != null) { for (File each : files) { - recursiveDelete(each); + if (!recursiveDelete(each)) { + return false; + } } } - file.delete(); + return file.delete(); } } |