diff --git a/src/main/java/org/junit/rules/TemporaryFolder.java b/src/main/java/org/junit/rules/TemporaryFolder.java index 14ebf6b017bbc..bb13d8f367f58 100644 --- a/src/main/java/org/junit/rules/TemporaryFolder.java +++ b/src/main/java/org/junit/rules/TemporaryFolder.java @@ -29,15 +29,88 @@ public class TemporaryFolder extends ExternalResource { private final File parentFolder; private File folder; - + private boolean assureDeletion; + public TemporaryFolder() { - this(null); + this((File) null); } 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.getParentFolder(); + this.assureDeletion = builder.isDeletionAssured(); + } + + /** + * Returns a new builder for building an instance of {@link TemporaryFolder} + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); } + /** + * Builds an instance of {@link TemporaryFolder} + */ + public static class Builder { + + private File parentFolder; + private boolean assureDeletion; + + /** + * + * @param parentFolder + * @return + */ + 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; + } + + /** + * Returns whether deletion of resources is assured or not + */ + public boolean isDeletionAssured() { + return this.assureDeletion; + } + + /** + * + * @return + */ + public File getParentFolder() { + return this.parentFolder; + } + + /** + * 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 +221,60 @@ 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() { + boolean result = true; + if (folder != null) { + result = recursiveDelete(folder); + } + return result; + } + + /** + * 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 void assureDeletion() { + this.assureDeletion = true; + } + + /** + * Returns whether deletion of resources is assured or not. Failure to + * fulfill the assurance results in failure of tests with an {@link IllegalStateException} + */ + public 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..927ef76493a59 --- /dev/null +++ b/src/test/java/org/junit/tests/experimental/rules/TemporaryFolderRuleAssuredDeletionTest.java @@ -0,0 +1,89 @@ +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 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 HasTempFolderWithAssuredDeletion { + @Rule public TemporaryFolder folder = new TemporaryFolder() { + protected void before() throws Throwable { + + }; + + protected boolean tryDelete() { + return false; + }; + + public boolean isDeletionAssured() { + return true; + }; + }; + + @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 TemporaryFolder() { + protected void before() throws Throwable { + + }; + + protected boolean tryDelete() { + return false; + }; + }; + + @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 TemporaryFolder() { + protected boolean tryDelete() { + return false; + }; + }; + + @Test + public void test1() { + folder.assureDeletion(); + } + + @Test + public void test2() { + + } + } + + @Test + public void testStrictVerificationFailureForLazyAssurance() { + PrintableResult result = testResult(HasTempFolderWithLazyAssuredDeletion.class); + assertThat(result, failureCountIs(1)); + } +}