Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issues/3367 introduce autoclose #3369

Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ in the `junit-jupiter-api` module.
| `@ExtendWith` | Used to <<extensions-registration-declarative,register extensions declaratively>>. Such annotations are _inherited_.
| `@RegisterExtension` | Used to <<extensions-registration-programmatic,register extensions programmatically>> via fields. Such fields are _inherited_ unless they are _shadowed_.
| `@TempDir` | Used to supply a <<writing-tests-built-in-extensions-TempDirectory,temporary directory>> via field injection or parameter injection in a lifecycle method or test method; located in the `org.junit.jupiter.api.io` package.
| @AutoClose | Indicates that a field in a JUnit 5 test class represents a resource that should be automatically closed after test execution. The field must implement the java.io.Closeable interface. The class must be annotated with `@ExtendWith(AutoCloseExtension.class)`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should work out of the box like @TempDir without specifying @ExtendWith(AutoCloseExtension.class) explicitly.
As @AutoClose is an opt-in feature it should not interfere with other extension if this extension isn't used but registered.

|===

WARNING: Some annotations may currently be _experimental_. Consult the table in
Expand Down
1 change: 0 additions & 1 deletion junit-jupiter-api/junit-jupiter-api.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ dependencies {
osgiVerification(projects.junitJupiterEngine)
osgiVerification(projects.junitPlatformLauncher)
}

tasks {
jar {
bundle {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2015-2023 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;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;

/**
* The {@code AutoClose} annotation is used to automatically close resources used in JUnit 5 tests.
*
* <p>
* This annotation should be applied to fields within JUnit 5 test classes. It indicates that the annotated
* resource should be automatically closed after the test execution. The annotation targets
* {@link java.lang.annotation.ElementType#FIELD} elements, allowing it to be applied to instance variables.
* </p>
*
* <p>
* By default, the {@code AutoClose} annotation expects the annotated resource to provide a {@code close()} method
* that will be invoked for closing the resource. However, developers can customize the closing behavior by providing
* a different method name through the {@code value} attribute. For example, setting {@code value = "destroy"} will
* look for a method named {@code destroy()} to close the resource.
* </p>
*
* <p>
* The {@code AutoClose} annotation is retained at runtime, allowing it to be accessed and processed during test execution.
* </p>
*
* @see java.lang.annotation.Retention
* @see java.lang.annotation.Target
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@API(status = API.Status.EXPERIMENTAL, since = "5.9")
public @interface AutoClose {
/**
* Specifies the name of the method to invoke for closing the resource.
* The default value is "close".
*
* @return the method name for closing the resource
*/
String value() default "close";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • If it is allowed to specify a different method name then the annotated resource does not necessarily have to implement java.io.Closeable.
  • value() isn't evaluated and so a different method other than close() isn't invoked.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2015-2023 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;

import java.io.Closeable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.apiguardian.api.API;

/**
* The {@code AutoCloseUtils} class provides utility methods for automatically closing resources used in JUnit 5 tests.
*
* <p>
* The class includes a static method {@code closeResources} that accepts a test instance as a parameter and closes
* all the fields annotated with {@link org.junit.jupiter.api.AutoClose}. This allows for automatic resource cleanup
* after test execution.
* </p>
*
* <p>
* The {@code closeResources} method utilizes reflection to find the fields annotated with {@link org.junit.jupiter.api.AutoClose}
* within the given test instance. It collects all the closeable fields and invokes the {@code close} method on each one.
* </p>
*
* <p>
* To be eligible for automatic closing, the fields must implement the {@link java.io.Closeable} interface.
* </p>
*
* <p>
* The {@code AutoCloseUtils} class is not intended to be instantiated, as all its methods are static.
* </p>
*
* @see org.junit.jupiter.api.AutoClose
* @see java.io.Closeable
* @see java.lang.reflect.Field
*/
@API(status = API.Status.EXPERIMENTAL, since = "5.9")
public class AutoCloseUtils {
private AutoCloseUtils() {
// Private constructor to prevent instantiation
}

/**
* Closes all the resources annotated with {@link org.junit.jupiter.api.AutoClose} within the given test instance.
*
* @param testInstance the test instance containing the resources to be closed
*/
public static void closeResources(Object testInstance) {
List<Closeable> closeables = findCloseableFields(testInstance);
for (Closeable closeable : closeables) {
close(closeable);
}
}

private static List<Closeable> findCloseableFields(Object testInstance) {
List<Closeable> closeables = new ArrayList<>();
Field[] fields = testInstance.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(AutoClose.class)) {
field.setAccessible(true);
try {
Object fieldValue = field.get(testInstance);
if (fieldValue instanceof Closeable) {
closeables.add((Closeable) fieldValue);
}
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return closeables;
}

private static void close(Closeable closeable) {
try {
closeable.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2015-2023 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.extension;

import org.apiguardian.api.API;
import org.junit.jupiter.api.AutoCloseUtils;

/**
* The {@code AutoCloseExtension} class is a JUnit 5 extension that automatically closes resources used in tests.
*
* <p>
* This extension implements the {@link org.junit.jupiter.api.extension.AfterEachCallback} interface,
* allowing it to perform resource cleanup after each test execution. It invokes the
* {@link AutoCloseUtils#closeResources(Object)} method to close the resources annotated with
* {@link org.junit.jupiter.api.AutoClose}.
* </p>
*
* <p>
* To use this extension, annotate your test class or test method with {@link org.junit.jupiter.api.extension.ExtendWith}
* and provide an instance of {@code AutoCloseExtension}.
* </p>
*
* @see org.junit.jupiter.api.extension.AfterEachCallback
* @see org.junit.jupiter.api.extension.Extension
* @see org.junit.jupiter.api.extension.ExtensionContext
* @see org.junit.jupiter.api.AutoClose
* @see AutoCloseUtils#closeResources(Object)
*/
@API(status = API.Status.EXPERIMENTAL, since = "5.9")
public class AutoCloseExtension implements AfterEachCallback {

/**
* Creates a new instance of AutoCloseExtension.
*/
public AutoCloseExtension() {

}

/**
* Invoked after each test execution to close the annotated resources within the test instance.
*
* @param context the extension context for the current test execution
* @throws Exception if an exception occurs during resource cleanup
*/
@Override
public void afterEach(ExtensionContext context) throws Exception {
Object testInstance = context.getRequiredTestInstance();
try {
AutoCloseUtils.closeResources(testInstance);
}
catch (Exception e) {
e.printStackTrace();
throw e;
}
}
}