diff --git a/src/main/java/org/junit/rules/TemporaryFolder.java b/src/main/java/org/junit/rules/TemporaryFolder.java index 14ebf6b017bbc..180c3f4301a1f 100644 --- a/src/main/java/org/junit/rules/TemporaryFolder.java +++ b/src/main/java/org/junit/rules/TemporaryFolder.java @@ -29,15 +29,86 @@ public class TemporaryFolder extends ExternalResource { private final File parentFolder; private File folder; - + private boolean assureDeletion; + + /** + * 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}. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builds an instance of {@link TemporaryFolder}. + */ + public static class Builder { + + private File parentFolder; + private boolean assureDeletion; + + /** + * 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 IllegalStateException}. + * + * @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 protected void before() throws Throwable { create(); @@ -148,21 +219,61 @@ public File getRoot() { /** * 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 {@link IllegalStateException} if unable to clean up resources + * and deletion of resources is assured. + * + * @throws IllegalStateException if unable to clean up resources and + * deletion of resources is assured. */ public void delete() { - if (folder != null) { - recursiveDelete(folder); + if (!tryDelete()) { + if (isDeletionAssured()) { + throw new IllegalStateException("Unable to clean up temporary folder " + folder); + } } } - private void recursiveDelete(File file) { + /** + * Tries to delete all files and folders under the temporary folder and + * returns whether deletion was successful or not. + * + * @return true if all resources are deleted successfully, false otherwise. + */ + protected boolean tryDelete() { + if (folder == null) { + return true; + } + + return recursiveDelete(folder); + } + + /** + * Setting this flag assures that no resources are left undeleted. Failure + * to fulfill the assurance results in failure of tests with an + * {@link IllegalStateException}. + */ + public final void assureDeletion() { + this.assureDeletion = true; + } + + /** + * Returns whether deletion of resources is assured or not. + */ + public final boolean isDeletionAssured() { + return assureDeletion; + } + + private boolean recursiveDelete(File file) { + boolean result = true; File[] files = file.listFiles(); if (files != null) { for (File each : files) { - recursiveDelete(each); + result = result && recursiveDelete(each); } } - file.delete(); + return result && file.delete(); } } diff --git a/src/main/java/org/junit/rules/Timeout.java b/src/main/java/org/junit/rules/Timeout.java index 45a5bc5320503..6cab3c3dfe9b7 100644 --- a/src/main/java/org/junit/rules/Timeout.java +++ b/src/main/java/org/junit/rules/Timeout.java @@ -84,7 +84,7 @@ public Timeout(long timeout, TimeUnit timeUnit) { } /** - * Create a {@code Timeout} instance initialized with values form + * Create a {@code Timeout} instance initialized with values from * a builder. * * @since 4.12 diff --git a/src/test/java/org/junit/tests/AllTests.java b/src/test/java/org/junit/tests/AllTests.java index 00c259b773204..40d32d24c834c 100644 --- a/src/test/java/org/junit/tests/AllTests.java +++ b/src/test/java/org/junit/tests/AllTests.java @@ -52,6 +52,7 @@ import org.junit.tests.experimental.rules.RuleChainTest; import org.junit.tests.experimental.rules.RuleMemberValidatorTest; import org.junit.tests.experimental.rules.TempFolderRuleTest; +import org.junit.tests.experimental.rules.TemporaryFolderRuleAssuredDeletionTest; import org.junit.tests.experimental.rules.TemporaryFolderUsageTest; import org.junit.tests.experimental.rules.TestRuleTest; import org.junit.tests.experimental.rules.TimeoutRuleTest; @@ -212,7 +213,8 @@ ParameterizedNamesTest.class, PublicClassValidatorTest.class, DisableOnDebugTest.class, - ThrowableCauseMatcherTest.class + ThrowableCauseMatcherTest.class, + TemporaryFolderRuleAssuredDeletionTest.class }) public class AllTests { public static Test suite() { diff --git a/src/test/java/org/junit/tests/experimental/rules/TemporaryFolderRuleAssuredDeletionTest.java b/src/test/java/org/junit/tests/experimental/rules/TemporaryFolderRuleAssuredDeletionTest.java new file mode 100644 index 0000000000000..75bcb62232592 --- /dev/null +++ b/src/test/java/org/junit/tests/experimental/rules/TemporaryFolderRuleAssuredDeletionTest.java @@ -0,0 +1,94 @@ +package org.junit.tests.experimental.rules; + +import static org.junit.Assert.assertThat; +import static org.junit.experimental.results.PrintableResult.testResult; +import static org.junit.experimental.results.ResultMatchers.failureCountIs; +import static org.junit.experimental.results.ResultMatchers.isSuccessful; + +import java.io.IOException; + +import org.hamcrest.CoreMatchers; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.results.PrintableResult; +import org.junit.rules.TemporaryFolder; + +public class TemporaryFolderRuleAssuredDeletionTest { + + public static class TemporaryFolderStub extends TemporaryFolder { + public TemporaryFolderStub(BuilderStub builder) { + super(builder); + } + + /* + * Don't need to create as we are overriding deletion + */ + public void create() throws IOException { + + } + + /* + * Simulates failure to clean-up temporary folder + */ + protected boolean tryDelete() { + return false; + } + } + + public static class BuilderStub extends TemporaryFolder.Builder { + public TemporaryFolder build() { + return new TemporaryFolderStub(this); + } + } + + public static class HasTempFolderWithAssuredDeletion { + @Rule public TemporaryFolder folder = new BuilderStub().assureDeletion().build(); + + @Test + public void test() { + // no-op + } + } + + @Test + public void testStrictVerificationFailure() { + PrintableResult result = testResult(HasTempFolderWithAssuredDeletion.class); + assertThat(result, failureCountIs(1)); + assertThat(result.toString(), CoreMatchers.containsString("Unable to clean up temporary folder")); + } + + public static class HasTempFolderWithoutAssuredDeletion { + @Rule public TemporaryFolder folder = new BuilderStub().build(); + + @Test + public void test() { + // no-op + } + } + + @Test + public void testStrictVerificationSuccess() { + PrintableResult result = testResult(HasTempFolderWithoutAssuredDeletion.class); + assertThat(result, isSuccessful()); + } + + public static class HasTempFolderWithLazyAssuredDeletion { + @Rule public TemporaryFolder folder = new BuilderStub().build(); + + @Test + public void test1() { + folder.assureDeletion(); + } + + @Test + public void test2() { + + } + } + + @Test + public void testStrictVerificationFailureForLazyAssurance() { + PrintableResult result = testResult(HasTempFolderWithLazyAssuredDeletion.class); + assertThat(result, failureCountIs(1)); + } +}