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[] 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 provideForClass(Class testClass) { return emptySet(); } + /** + * Add shared resources to a nested test class. + * + * @param testClass a nested 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 nested test class. + */ default Set provideForNestedClass(Class testClass) { return emptySet(); } + /** + * Add shared resources to a test method. + * + * @param testClass a test class containing the {@code testMethod} + * @param testMethod a test method 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 method. + */ default Set provideForMethod(Class testClass, Method testMethod) { return emptySet(); } + /** + * + *

{@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