Skip to content

Commit

Permalink
Revise @⁠AutoClose implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
sbrannen committed Jan 2, 2024
1 parent 0ea9f2b commit 70d9fe8
Show file tree
Hide file tree
Showing 14 changed files with 568 additions and 269 deletions.
3 changes: 2 additions & 1 deletion documentation/src/docs/asciidoc/link-attributes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ endif::[]
:api-package: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/package-summary.html[org.junit.jupiter.api]
:Assertions: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html[org.junit.jupiter.api.Assertions]
:Assumptions: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Assumptions.html[org.junit.jupiter.api.Assumptions]
:AutoClose: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/AutoClose.html[@AutoClose]
:ClassOrderer_ClassName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.ClassName.html[ClassOrderer.ClassName]
:ClassOrderer_DisplayName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.DisplayName.html[ClassOrderer.DisplayName]
:ClassOrderer_OrderAnnotation: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.OrderAnnotation.html[ClassOrderer.OrderAnnotation]
Expand Down Expand Up @@ -153,7 +154,7 @@ endif::[]
// Jupiter Engine
:junit-jupiter-engine: {javadoc-root}/org.junit.jupiter.engine/org/junit/jupiter/engine/package-summary.html[junit-jupiter-engine]
// Jupiter Extension Implementations
:AutoCloseExtension: {current-branch}/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AutoCloseExtension.java[AutoCloseExtension]
:AutoCloseExtension: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/AutoCloseExtension.java[AutoCloseExtension]
:DisabledCondition: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java[DisabledCondition]
:RepetitionExtension: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionExtension.java[RepetitionExtension]
:TempDirectory: {current-branch}/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java[TempDirectory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ repository on GitHub.

==== New Features and Improvements

* The new `@AutoClose` annotation can be applied to fields within tests to automatically
close the annotated resource after test execution. See
* New `@AutoClose` annotation that can be applied to fields within tests to automatically
close the annotated resource after test execution. See the
<<../user-guide/index.adoc#writing-tests-built-in-extensions-AutoClose, User Guide>> for
details.

Expand Down
72 changes: 48 additions & 24 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ in the `junit-jupiter-api` module.
| `@Nested` | Denotes that the annotated class is a non-static <<writing-tests-nested,nested test class>>. On Java 8 through Java 15, `@BeforeAll` and `@AfterAll` methods cannot be used directly in a `@Nested` test class unless the "per-class" <<writing-tests-test-instance-lifecycle, test instance lifecycle>> is used. Beginning with Java 16, `@BeforeAll` and `@AfterAll` methods can be declared as `static` in a `@Nested` test class with either test instance lifecycle mode. Such annotations are not _inherited_.
| `@Tag` | Used to declare <<writing-tests-tagging-and-filtering,tags for filtering tests>>, either at the class or method level; analogous to test groups in TestNG or Categories in JUnit 4. Such annotations are _inherited_ at the class level but not at the method level.
| `@Disabled` | Used to <<writing-tests-disabling,disable>> a test class or test method; analogous to JUnit 4's `@Ignore`. Such annotations are not _inherited_.
| `@AutoClose` | Denotes that the annotated field represents a resource that will be <<writing-tests-built-in-extensions-AutoClose,automatically closed>> after test execution.
| `@Timeout` | Used to fail a test, test factory, test template, or lifecycle method if its execution exceeds a given duration. Such annotations are _inherited_.
| `@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.
| `@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` | Denotes that the annotated field represents a resource that should be automatically closed after test execution.
|===

WARNING: Some annotations may currently be _experimental_. Consult the table in
Expand Down Expand Up @@ -2573,12 +2573,12 @@ include::{testDir}/example/SharedResourcesDemo.java[tags=user_guide]
=== Built-in Extensions

While the JUnit team encourages reusable extensions to be packaged and maintained in
separate libraries, the JUnit Jupiter API artifact includes a few user-facing extension
implementations that are considered so generally useful that users shouldn't have to add
another dependency.
separate libraries, JUnit Jupiter includes a few user-facing extension implementations
that are considered so generally useful that users shouldn't have to add another
dependency.

[[writing-tests-built-in-extensions-TempDirectory]]
==== The TempDirectory Extension
==== The @TempDir Extension

The built-in `{TempDirectory}` extension is used to create and clean up a temporary
directory for an individual test or all tests in a test class. It is registered by
Expand Down Expand Up @@ -2712,24 +2712,48 @@ parameter, if present
3. Otherwise, `org.junit.jupiter.api.io.TempDirFactory$Standard` will be used.

[[writing-tests-built-in-extensions-AutoClose]]
==== The AutoClose Extension

The built-in `{AutoCloseExtension}` is used to automatically close resources used in
tests. Therefore, the `@AutoClose` annotation is applied to fields within the
test class to indicate that the annotated resource should be automatically closed after
the test execution.

By default, the `@AutoClose` annotation expects the annotated resource to provide
a `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
`value` attribute. For example, setting `value = "shutdown"` will look
for a method named `shutdown()` to close the resource.

For example, the following test declares a database connection field annotated with
`@AutoClose` that is automatically closed afterward.

[source,java,indent=0]
.A test class using @AutoClose annotation to close used resource
==== The @AutoClose Extension

The built-in `{AutoCloseExtension}` automatically closes resources associated with fields.
It is registered by default. To use it, annotate a field in a test class with
`{AutoClose}`.

`@AutoClose` fields may be either `static` or non-static. If the value of an `@AutoClose`
field is `null` when it is evaluated the field will be ignored, but a warning message will
be logged to inform you.

By default, `@AutoClose` expects the value of the annotated field to implement a `close()`
method that will be invoked to close the resource. However, developers can customize the
name of the close method via the `value` attribute. For example, `@AutoClose("shutdown")`
instructs JUnit to look for a `shutdown()` method to close the resource.

`@AutoClose` fields are inherited from superclasses as long as they are not hidden.
Furthermore, `@AutoClose` fields from subclasses will be closed before `@AutoClose` fields
in superclasses.

When multiple `@AutoClose` fields exist within a given test class, the order in which the
resources are closed depends on an algorithm that is deterministic but intentionally
nonobvious. This ensures that subsequent runs of a test suite close resources in the same
order, thereby allowing for repeatable builds.

The extension that closes `@AutoClose` fields implements the `AfterAllCallback` and
`TestInstancePreDestroyCallback` extension APIs. Consequently, a `static` `@AutoClose`
field will be closed after all tests in the current test class have completed, effectively
after `@AfterAll` methods have executed for the test class. A non-static `@AutoClose`
field will be closed before the current test class instance is destroyed. Specifically, if
the test class is configured with `@TestInstance(Lifecycle.PER_METHOD)` semantics, a
non-static `@AutoClose` field will be closed after the execution of each test method, test
factory method, or test template method. However, if the test class is configured with
`@TestInstance(Lifecycle.PER_CLASS)` semantics, a non-static `@AutoClose` field will not
be closed until the current test class instance is no longer needed, which means after
`@AfterAll` methods and after all `static` `@AutoClose` fields have been closed.

The following example demonstrates how to annotate an instance field with `@AutoClose` so
that the resource is automatically closed after test execution. Note that `WebClient`
implements `java.lang.AutoCloseable` which defines a `close()` method.

[source,java,indent=0]
.A test class using `@AutoClose` to close a resource
----
include::{testDir}/example/AutoCloseDemo.java[tags=user_guide_example]
----
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@

package example.registration;

public class WebClient {
public class WebClient implements AutoCloseable {

public WebResponse get(String string) {
return new WebResponse();
}

@Override
public void close() {
/* no-op for demo */
}

}
38 changes: 14 additions & 24 deletions documentation/src/test/java/example/AutoCloseDemo.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2023 the original author or authors.
* Copyright 2015-2024 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
Expand All @@ -10,40 +10,30 @@

package example;

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import example.registration.WebClient;

import org.junit.jupiter.api.AutoClose;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

@Disabled
// tag::user_guide_example[]
class AutoCloseDemo {

// WebClient implements AutoCloseable
@AutoClose
Connection connection = getJdbcConnection("jdbc:mysql://localhost/testdb");
WebClient webClient = new WebClient();

@Test
void usersTableHasEntries() throws SQLException {
ResultSet resultSet = connection.createStatement().executeQuery("SELECT * FROM users");

assertTrue(resultSet.next());
}
String serverUrl = // specify server URL ...
// end::user_guide_example[]
"https://localhost";
// tag::user_guide_example[]

// ...
// end::user_guide_example[]
private static Connection getJdbcConnection(String url) {
try {
return DriverManager.getConnection(url);
}
catch (SQLException ex) {
throw new RuntimeException(ex);
}
@Test
void getProductList() {
// Use WebClient to connect to web server and verify response
assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus());
}

}
// end::user_guide_example[]
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class WebServerDemo {

@Test
void getProductList() {
// end::user_guide[]
@SuppressWarnings("resource")
// tag::user_guide[]
WebClient webClient = new WebClient();
String serverUrl = server.getServerUrl();
// Use WebClient to connect to web server using serverUrl and verify response
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2023 the original author or authors.
* Copyright 2015-2024 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
Expand All @@ -10,49 +10,84 @@

package org.junit.jupiter.api;

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

import java.lang.annotation.Documented;
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;
import org.junit.jupiter.api.extension.ExtendWith;

/**
* The {@code AutoClose} annotation is used to automatically close resources
* used in tests.
* {@code @AutoClose} is used to indicate that an annotated field will be
* automatically closed after test execution.
*
* <p>{@code @AutoClose} fields may be either {@code static} or non-static. If
* the value of an {@code @AutoClose} field is {@code null} when it is evaluated
* the field will be ignored, but a warning message will be logged to inform you.
*
* <p>By default, {@code @AutoClose} expects the value of the annotated field to
* implement a {@code close()} method that will be invoked to close the resource.
* However, developers can customize the name of the {@code close} method via the
* {@link #value} attribute. For example, {@code @AutoClose("shutdown")} instructs
* JUnit to look for a {@code shutdown()} method to close the resource.
*
* <p>{@code @AutoClose} may be used as a meta-annotation in order to create a
* custom <em>composed annotation</em> that inherits the semantics of
* {@code @AutoClose}.
*
* <h2>Inheritance</h2>
*
* <p>{@code @AutoClose} fields are inherited from superclasses as long as they
* are not <em>hidden</em>. Furthermore, {@code @AutoClose} fields from subclasses
* will be closed before {@code @AutoClose} fields in superclasses.
*
* <h2>Evaluation Order</h2>
*
* <p>When multiple {@code @AutoClose} fields exist within a given test class,
* the order in which the resources are closed depends on an algorithm that is
* deterministic but intentionally nonobvious. This ensures that subsequent runs
* of a test suite close resources in the same order, thereby allowing for
* repeatable builds.
*
* <p>This annotation should be applied to fields within test classes. It
* indicates that the annotated resource should be automatically closed after
* the test execution.
* <h2>Scope and Lifecycle</h2>
*
* <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 {@link #value} attribute. For
* example, setting {@code value = "shutdown"} will look for a method named
* {@code shutdown()} to close the resource. When multiple annotated resources
* exist the order of closing them is unspecified.
* <p>The extension that closes {@code @AutoClose} fields implements the
* {@link org.junit.jupiter.api.extension.AfterAllCallback AfterAllCallback} and
* {@link org.junit.jupiter.api.extension.TestInstancePreDestroyCallback
* TestInstancePreDestroyCallback} extension APIs. Consequently, a {@code static}
* {@code @AutoClose} field will be closed after all tests in the current test
* class have completed, effectively after {@code @AfterAll} methods have executed
* for the test class. A non-static {@code @AutoClose} field will be closed before
* the current test class instance is destroyed. Specifically, if the test class
* is configured with
* {@link TestInstance.Lifecycle#PER_METHOD @TestInstance(Lifecycle.PER_METHOD)}
* semantics, a non-static {@code @AutoClose} field will be closed after the
* execution of each test method, test factory method, or test template method.
* However, if the test class is configured with
* {@link TestInstance.Lifecycle#PER_CLASS @TestInstance(Lifecycle.PER_CLASS)}
* semantics, a non-static {@code @AutoClose} field will not be closed until the
* current test class instance is no longer needed, which means after
* {@code @AfterAll} methods and after all {@code static} {@code @AutoClose} fields
* have been closed.
*
* @since 5.11
* @see java.lang.annotation.Retention
* @see java.lang.annotation.Target
*/
@Target(ElementType.FIELD)
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ExtendWith(AutoCloseExtension.class)
@API(status = API.Status.EXPERIMENTAL, since = "5.11")
@SuppressWarnings("exports")
@API(status = EXPERIMENTAL, since = "5.11")
public @interface AutoClose {

/**
* Specifies the name of the method to invoke for closing the resource.
* Specify the name of the method to invoke to close the resource.
*
* <p>The default value is {@code close}.
* <p>The default value is {@code "close"} which works with any type that
* implements {@link AutoCloseable} or has a {@code close()} method.
*
* @return the method name for closing the resource
* @return the name of the method to invoke to close the resource
*/
String value() default "close";

Expand Down
Loading

0 comments on commit 70d9fe8

Please sign in to comment.