Skip to content

Commit

Permalink
Feature/temp dir cleanup (#2729)
Browse files Browse the repository at this point in the history
This PR adds a cleanup mode (`ALWAYS` (default), `ON_SUCCESS`, or
`NEVER`) parameter to `@TempDir` that allows to configured when the
temp directory should be deleted. The default can be configured by
setting a configuration parameter.

Resolves #2159.

Co-authored-by: Marc Philipp <mail@marcphilipp.de>
  • Loading branch information
Harry Glasgow and marcphilipp committed Nov 29, 2021
1 parent a7bc80f commit 3e06d8f
Show file tree
Hide file tree
Showing 16 changed files with 724 additions and 17 deletions.
Expand Up @@ -63,4 +63,6 @@ on GitHub.

==== New Features and Improvements

* ❓
* `@TempDir` now includes a cleanup mode attribute for preventing a temporary directory
from being deleted after a test. The default cleanup mode can be configured via a
configuration parameter.
15 changes: 15 additions & 0 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Expand Up @@ -2349,3 +2349,18 @@ method uses a separate directory.
----
include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_field_injection]
----

The `@TempDir` annotation has an optional `cleanup` attribute that can be set to either
`NEVER`, `ON_SUCCESS`, or `ALWAYS`. If the cleanup mode is set to `NEVER`, temporary
directories are not deleted after a test completes. If it is set to `ON_SUCCESS`,
temporary directories are deleted only after a test completed successfully.

The default cleanup mode is `ALWAYS`. You can use the
`junit.jupiter.temp.dir.cleanup.mode.default`
<<running-tests-config-params, configuration parameter>> to override this default.

[source,java,indent=0]
.A test class with a temporary directory that doesn't get cleaned up
----
include::{testDir}/example/TempDirCleanupModeDemo.java[tags=user_guide]
----
28 changes: 28 additions & 0 deletions documentation/src/test/java/example/TempDirCleanupModeDemo.java
@@ -0,0 +1,28 @@
/*
* Copyright 2015-2021 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package example;

import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS;

import java.nio.file.Path;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

// tag::user_guide[]
class TempDirCleanupModeDemo {

@Test
void fileTest(@TempDir(cleanup = ON_SUCCESS) Path tempDir) {
// perform test
}
}
// end::user_guide[]
@@ -0,0 +1,50 @@
/*
* Copyright 2015-2021 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.jupiter.api.io;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

import org.apiguardian.api.API;

/**
* Enumeration of cleanup modes for a {@code TempDir}.
*
* <p>When a test with a temporary directory completes, it might be useful in
* some cases to be able to view the contents of the directory resulting from
* the test. {@code CleanupMode} allows control of how a {@code TempDir}
* is cleaned up.
*
* @since 5.4
* @see TempDir
*/
@API(status = EXPERIMENTAL, since = "5.4")
public enum CleanupMode {

/**
* Defer to the configured cleanup mode.
*/
DEFAULT,

/**
* Always clean up a temporary directory after the test has completed.
*/
ALWAYS,

/**
* Don't clean up a temporary directory after the test has completed.
*/
NEVER,

/**
* Only clean up a temporary directory if the test completed successfully.
*/
ON_SUCCESS
}
Expand Up @@ -69,18 +69,35 @@
*
* <h3>Deletion</h3>
*
* <p>When the end of the scope of a temporary directory is reached, i.e. when
* the test method or class has finished execution, JUnit will attempt to
* recursively delete all files and directories in the temporary directory
* <p>By default, when the end of the scope of a temporary directory is reached,
* i.e. when the test method or class has finished execution, JUnit will attempt
* to recursively delete all files and directories in the temporary directory
* and, finally, the temporary directory itself. In case deletion of a file or
* directory fails, an {@link IOException} will be thrown that will cause the
* test or test class to fail.
*
* <p>The {@code @TempDir} annotation has a {@link CleanupMode} parameter that
* allows overriding the default behavior. If the cleanup mode is set to
* {@link CleanupMode#NEVER}, then the temporary directory will not be deleted
* after the test completes. If the cleanup mode is set to
* {@link CleanupMode#ON_SUCCESS}, then the temporary directory will only be
* deleted if the test completes successfully. The default behavior can be
* altered by setting the {@value #DEFAULT_CLEANUP_MODE_PROPERTY_NAME}
* configuration parameter.
*
* @since 5.4
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = EXPERIMENTAL, since = "5.4")
public @interface TempDir {

String DEFAULT_CLEANUP_MODE_PROPERTY_NAME = "junit.jupiter.cleanup.mode.default";

/**
* How the temporary directory gets cleaned up after the test completes.
*/
CleanupMode cleanup() default CleanupMode.DEFAULT;

}
Expand Up @@ -11,6 +11,7 @@
package org.junit.jupiter.engine.config;

import static org.apiguardian.api.API.Status.INTERNAL;
import static org.junit.jupiter.api.io.TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME;

import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -23,6 +24,7 @@
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.io.CleanupMode;
import org.junit.jupiter.api.parallel.ExecutionMode;

/**
Expand Down Expand Up @@ -107,4 +109,10 @@ public Optional<ClassOrderer> getDefaultTestClassOrderer() {
key -> delegate.getDefaultTestClassOrderer());
}

@Override
public CleanupMode getDefaultTempDirCleanupMode() {
return (CleanupMode) cache.computeIfAbsent(DEFAULT_CLEANUP_MODE_PROPERTY_NAME,
key -> delegate.getDefaultTempDirCleanupMode());
}

}
Expand Up @@ -11,6 +11,8 @@
package org.junit.jupiter.engine.config;

import static org.apiguardian.api.API.Status.INTERNAL;
import static org.junit.jupiter.api.io.CleanupMode.ALWAYS;
import static org.junit.jupiter.api.io.TempDir.DEFAULT_CLEANUP_MODE_PROPERTY_NAME;

import java.util.Optional;
import java.util.function.Function;
Expand All @@ -22,6 +24,7 @@
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.io.CleanupMode;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.commons.util.ClassNamePatternFilterUtils;
import org.junit.platform.commons.util.Preconditions;
Expand Down Expand Up @@ -50,6 +53,9 @@ public class DefaultJupiterConfiguration implements JupiterConfiguration {
private static final InstantiatingConfigurationParameterConverter<ClassOrderer> classOrdererConverter = //
new InstantiatingConfigurationParameterConverter<>(ClassOrderer.class, "class orderer");

private static final EnumConfigurationParameterConverter<CleanupMode> cleanupModeConverter = //
new EnumConfigurationParameterConverter<>(CleanupMode.class, "cleanup mode");

private final ConfigurationParameters configurationParameters;

public DefaultJupiterConfiguration(ConfigurationParameters configurationParameters) {
Expand Down Expand Up @@ -117,4 +123,9 @@ public Optional<ClassOrderer> getDefaultTestClassOrderer() {
return classOrdererConverter.get(configurationParameters, DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME);
}

@Override
public CleanupMode getDefaultTempDirCleanupMode() {
return cleanupModeConverter.get(configurationParameters, DEFAULT_CLEANUP_MODE_PROPERTY_NAME, ALWAYS);
}

}
Expand Up @@ -22,6 +22,7 @@
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.io.CleanupMode;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.commons.util.ClassNamePatternFilterUtils;

Expand All @@ -42,7 +43,6 @@ public interface JupiterConfiguration {
String DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME = "junit.jupiter.testmethod.order.default";
String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = "junit.jupiter.testclass.order.default";
String TEMP_DIR_SCOPE_PROPERTY_NAME = "junit.jupiter.tempdir.scope";

String DEFAULT_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.default";
String DEFAULT_TESTABLE_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.testable.method.default";
String DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.test.method.default";
Expand Down Expand Up @@ -77,4 +77,6 @@ public interface JupiterConfiguration {

Optional<ClassOrderer> getDefaultTestClassOrderer();

CleanupMode getDefaultTempDirCleanupMode();

}
Expand Up @@ -47,9 +47,8 @@ public class MutableExtensionRegistry implements ExtensionRegistry, ExtensionReg

private static final Logger logger = LoggerFactory.getLogger(MutableExtensionRegistry.class);

private static final List<Extension> DEFAULT_EXTENSIONS = Collections.unmodifiableList(Arrays.asList(//
private static final List<Extension> DEFAULT_STATELESS_EXTENSIONS = Collections.unmodifiableList(Arrays.asList(//
new DisabledCondition(), //
new TempDirectory(), //
new TimeoutExtension(), //
new RepeatedTestExtension(), //
new TestInfoParameterResolver(), //
Expand All @@ -71,7 +70,9 @@ public class MutableExtensionRegistry implements ExtensionRegistry, ExtensionReg
public static MutableExtensionRegistry createRegistryWithDefaultExtensions(JupiterConfiguration configuration) {
MutableExtensionRegistry extensionRegistry = new MutableExtensionRegistry(null);

DEFAULT_EXTENSIONS.forEach(extensionRegistry::registerDefaultExtension);
DEFAULT_STATELESS_EXTENSIONS.forEach(extensionRegistry::registerDefaultExtension);

extensionRegistry.registerDefaultExtension(new TempDirectory(configuration));

if (configuration.isExtensionAutoDetectionEnabled()) {
registerAutoDetectedExtensions(extensionRegistry);
Expand Down

0 comments on commit 3e06d8f

Please sign in to comment.