diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java index adb45cd2fcf4..6ce5f3b2d360 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java @@ -46,6 +46,7 @@ * * @see Isolated * @see ResourceLock + * @see ResourceLocksFrom * @since 5.3 */ @API(status = STABLE, since = "5.10") diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java index 8683cd470969..c8fc7247e092 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java @@ -32,6 +32,7 @@ * @since 5.7 * @see ExecutionMode * @see ResourceLock + * @see ResourceLocksFrom */ @API(status = STABLE, since = "5.10") @Retention(RetentionPolicy.RUNTIME) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java index 24cdc2240e21..cc973b0dd0dd 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java @@ -19,6 +19,7 @@ * * @since 5.3 * @see ResourceLock + * @see ResourceLocksProvider.Lock */ @API(status = STABLE, since = "5.10") public enum ResourceAccessMode { diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java index f9839322b37a..93bbd214babc 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java @@ -52,6 +52,9 @@ * @see Resources * @see ResourceAccessMode * @see ResourceLocks + * @see ResourceLocksFrom + * @see ResourceLocksProvider + * @see ResourceLocksProvider.Lock * @since 5.3 */ @API(status = STABLE, since = "5.10") diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksFrom.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksFrom.java index c12fca116f87..3d2ea44978a1 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksFrom.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksFrom.java @@ -10,7 +10,7 @@ package org.junit.jupiter.api.parallel; -import static org.apiguardian.api.API.Status.STABLE; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; @@ -20,12 +20,46 @@ import org.apiguardian.api.API; -@API(status = STABLE, since = "5.10") +/** + * {@code @ResourceLocksFrom} is used to add shared resources + * to an annotated test class and / or its test methods in runtime. + * + *
The {@link #value} should represent + * one or more classes implementing {@link ResourceLocksProvider}. + * + *
This annotation (in conjunction with {@link ResourceLocksProvider}) + * serves the same purpose as {@link ResourceLock} annotation + * but for certain cases may be a more flexible and less verbose alternative + * since it allows to add shared resources programmatically. + * + *
Given this annotation is {@linkplain Inherited inherited}, + * if it's applied to a parent test class, + * then {@link ResourceLocksProvider} methods will be called also + * for child test classes and their test methods. + * + * @apiNote If both {@code @ResourceLock} and {@code @ResourceLocksFrom} + * are used then shared resources from them are summed up. + * It means that if resource 'A' is declared via {@code @ResourceLock} + * and resource 'B' added via {@code @ResourceLocksFrom} + * then a test class or test method will have both resources: 'A' and 'B'. + * + * @since 5.12 + * @see Isolated + * @see Resources + * @see ResourceAccessMode + * @see ResourceLock + * @see ResourceLocksProvider + * @see ResourceLocksProvider.Lock + */ +@API(status = EXPERIMENTAL, since = "5.12") @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited public @interface ResourceLocksFrom { + /** + * An array of one or more classes implementing {@link ResourceLocksProvider}. + */ Class extends ResourceLocksProvider>[] value(); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java index 7a9ced97595f..28e109bde02e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java @@ -11,59 +11,161 @@ package org.junit.jupiter.api.parallel; import static java.util.Collections.emptySet; -import static org.apiguardian.api.API.Status.STABLE; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.reflect.Method; import java.util.Objects; import java.util.Set; import org.apiguardian.api.API; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; -@API(status = STABLE, since = "5.10") +/** + * {@code @ResourceLocksProvider} is used to add shared resources + * to a test class and / or its test methods in runtime. + * + *
Each shared resource is represented by an instance of {@link Lock}. + * + *
This interface (in conjunction with {@link ResourceLocksFrom})
+ * serves the same purpose as {@link ResourceLock} annotation
+ * but for certain cases may be a more flexible and less verbose alternative
+ * since it allows to add shared resources programmatically.
+ *
+ * @apiNote If both {@code @ResourceLock} and {@code ResourceLocksProvider}
+ * are used then shared resources from them are summed up.
+ * It means that if resource 'A' is declared via {@code @ResourceLock}
+ * and resource 'B' added via {@code ResourceLocksProvider}
+ * then a test class or test method will have both resources: 'A' and 'B'.
+ *
+ * @since 5.12
+ * @see Isolated
+ * @see Resources
+ * @see ResourceAccessMode
+ * @see ResourceLock
+ * @see ResourceLocksFrom
+ * @see Lock
+ */
+@API(status = EXPERIMENTAL, since = "5.12")
public interface ResourceLocksProvider {
+
+ /**
+ * Add shared resources to a test class.
+ *
+ * @param testClass a test class to add shared resources
+ * @return a set of {@link Lock}; may be empty
+ *
+ * @apiNote has the same semantics as adding {@code @ResourceLock} to a test class.
+ */
default Set {@link Lock} represents a shared resource.
+ *
+ * Each resource is identified by {@link #key}.
+ * In addition,{@link #accessMode} allows you to specify
+ * whether a test class or test
+ * method requires {@link ResourceAccessMode#READ_WRITE READ_WRITE}
+ * or only {@link ResourceAccessMode#READ READ} access to the resource.
+ * In the former case, execution of the test will occur while no other test
+ * that uses the shared resource is being executed.
+ * In the latter case, the test class or test method
+ * may be executed concurrently with other tests
+ * that also require {@code READ} access but not at the same time
+ * as any other test that requires {@code READ_WRITE} access.
+ *
+ * This guarantee extends to lifecycle methods of a test class or method.
+ * For example, if a {@code Lock} is added to a test method
+ * then the "lock" will be acquired before any
+ * {@link BeforeEach @BeforeEach} methods are executed and released after all
+ * {@link AfterEach @AfterEach} methods have been executed.
+ *
+ * @since 5.12
+ * @see Isolated
+ * @see Resources
+ * @see ResourceAccessMode
+ * @see ResourceLock
+ * @see ResourceLocksFrom
+ * @see ResourceLocksProvider
+ */
final class Lock {
private final String key;
private final ResourceAccessMode accessMode;
+ /**
+ * Create a new {@code Lock} with {@code accessMode = READ_WRITE}.
+ *
+ * @param key the identifier of the resource; never {@code null} or blank
+ */
public Lock(String key) {
this(key, ResourceAccessMode.READ_WRITE);
}
+ /**
+ * Create a new {@code Lock}.
+ *
+ * @param key the identifier of the resource; never {@code null} or blank
+ * @param accessMode the lock mode to use to synchronize access to the resource; never {@code null}
+ */
public Lock(String key, ResourceAccessMode accessMode) {
- this.key = Preconditions.notBlank(key, "key must not be blank");
+ this.key = Preconditions.notBlank(key, "key must not be null or blank");
this.accessMode = Preconditions.notNull(accessMode, "accessMode must not be null");
}
+ /**
+ * Get the key of this lock.
+ */
public String getKey() {
return key;
}
+ /**
+ * Get the access mode of this lock.
+ */
public ResourceAccessMode getAccessMode() {
return accessMode;
}
@Override
public boolean equals(Object o) {
- if (this == o)
+ if (this == o) {
return true;
- if (o == null || getClass() != o.getClass())
+ }
+ if (o == null || getClass() != o.getClass()) {
return false;
+ }
Lock lock = (Lock) o;
return Objects.equals(key, lock.key) && accessMode == lock.accessMode;
}
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java
index c9fa33ff23f0..d6c4be3056a7 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java
@@ -19,6 +19,7 @@
*
* @since 5.3
* @see ResourceLock
+ * @see ResourceLocksProvider.Lock
*/
@API(status = STABLE, since = "5.10")
public class Resources {
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java
index d9971829c04e..7223780c4221 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java
@@ -68,7 +68,6 @@
import org.junit.jupiter.engine.extension.ExtensionRegistry;
import org.junit.jupiter.engine.extension.MutableExtensionRegistry;
import org.junit.platform.commons.JUnitException;
-import org.junit.platform.commons.util.CollectionUtils;
import org.junit.platform.commons.util.ExceptionUtils;
import org.junit.platform.commons.util.ReflectionUtils;
import org.junit.platform.commons.util.StringUtils;
diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ResourceLocksProviderIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ResourceLocksProviderIntegrationTests.java
index 60f7c622cc77..e1609661615d 100644
--- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ResourceLocksProviderIntegrationTests.java
+++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ResourceLocksProviderIntegrationTests.java
@@ -31,6 +31,9 @@
import org.junit.platform.testkit.engine.EngineTestKit;
import org.junit.platform.testkit.engine.Event;
+/**
+ * @since 5.12
+ */
class ResourceLocksProviderIntegrationTests {
@Test