Skip to content

Commit

Permalink
Revise recent changes to @⁠ResourceLock and related APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
sbrannen committed Jan 19, 2025
1 parent 30cbe62 commit 254ca62
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,14 @@
import java.lang.annotation.Target;

import org.apiguardian.api.API;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;

/**
* {@code @ResourceLock} is used to declare that the annotated test class or test
* method requires access to a shared resource identified by a key.
*
* <p>The resource key is specified via {@link #value}. In addition,
* {@link #mode} allows you to specify whether the annotated test class or test
* method requires {@link ResourceAccessMode#READ_WRITE READ_WRITE} or only
* <p>The resource key is specified via {@link #value}. In addition, {@link #mode}
* allows one to specify whether the annotated test class or test method requires
* {@link ResourceAccessMode#READ_WRITE READ_WRITE} or
* {@link ResourceAccessMode#READ READ} access to the resource. In the former case,
* execution of the annotated element will occur while no other test class or
* test method that uses the shared resource is being executed. In the latter case,
Expand All @@ -39,46 +37,38 @@
* other test that requires {@code READ_WRITE} access.
*
* <p>This guarantee extends to lifecycle methods of a test class or method. For
* example, if a test method is annotated with a {@code @ResourceLock}
* annotation the "lock" will be acquired before any
* {@link BeforeEach @BeforeEach} methods are executed and released after all
* {@link AfterEach @AfterEach} methods have been executed.
* example, if a test method is annotated with {@code @ResourceLock} the lock
* will be acquired before any {@link org.junit.jupiter.api.BeforeEach @BeforeEach}
* methods are executed and released after all
* {@link org.junit.jupiter.api.AfterEach @AfterEach} methods have been executed.
*
* <p>This annotation can be repeated to declare the use of multiple shared resources.
*
* <p>Uniqueness of a shared resource is identified by both {@link #value()} and
* {@link #mode()}. Duplicated shared resources do not cause errors.
* <p>Uniqueness of a shared resource is determined by both the {@link #value()}
* and the {@link #mode()}. Duplicated shared resources do not cause errors.
*
* <p>Since JUnit Jupiter 5.4, this annotation is {@linkplain Inherited inherited}
* within class hierarchies.
*
* <p>Since JUnit Jupiter 5.12, this annotation supports adding shared resources
* dynamically at runtime via {@link #providers}.
*
* <p>Resources declared "statically" using {@link #value()} and {@link #mode()}
* are combined with "dynamic" resources added via {@link #providers()}.
* For example, declaring resource "A" via {@code @ResourceLock("A")}
* and resource "B" via a provider returning {@code new Lock("B")} will result
* in two shared resources "A" and "B".
* dynamically at runtime via {@link #providers}. Resources declared "statically"
* using {@link #value()} and {@link #mode()} are combined with "dynamic" resources
* added via {@link #providers()}. For example, declaring resource "A" via
* {@code @ResourceLock("A")} and resource "B" via a provider returning
* {@code new Lock("B")} will result in two shared resources "A" and "B".
*
* <p>Since JUnit Jupiter 5.12, this annotation supports declaring "static"
* shared resources for <em>direct</em> child nodes via the {@link #target()}
* attribute.
*
* <p>Using the {@link ResourceLockTarget#CHILDREN} in a class-level
* annotation has the same semantics as adding an annotation with the same
* {@link #value()} and {@link #mode()} to each test method and nested test
* class declared in this class.
*
* <p>This may improve parallelization when a test class declares a
* attribute. Using {@link ResourceLockTarget#CHILDREN} in a class-level annotation
* has the same semantics as adding an annotation with the same {@link #value()}
* and {@link #mode()} to each test method and nested test class declared in the
* annotated class. This may improve parallelization when a test class declares a
* {@link ResourceAccessMode#READ READ} lock, but only a few methods hold
* {@link ResourceAccessMode#READ_WRITE READ_WRITE} lock.
*
* <p>Note that the {@code target = CHILDREN} means that
* {@link #value()} and {@link #mode()} no longer apply to a node
* declaring the annotation. However, the {@link #providers()} attribute
* remains applicable, and the target of "dynamic" shared resources
* added via implementations of {@link ResourceLocksProvider} is not changed.
* {@link ResourceAccessMode#READ_WRITE READ_WRITE} lock. Note that
* {@code target = CHILDREN} means that {@link #value()} and {@link #mode()} no
* longer apply to a node declaring the annotation. However, the {@link #providers()}
* attribute remains applicable, and the target of "dynamic" shared resources added
* via implementations of {@link ResourceLocksProvider} is not changed.
*
* @see Isolated
* @see Resources
Expand Down Expand Up @@ -116,28 +106,28 @@
ResourceAccessMode mode() default ResourceAccessMode.READ_WRITE;

/**
* The target of a resource created from {@link #value()} and {@link #mode()}.
*
* <p>Defaults to {@link ResourceLockTarget#SELF SELF}.
* An array of one or more classes implementing {@link ResourceLocksProvider}.
*
* <p>Note that using {@link ResourceLockTarget#CHILDREN} in
* a method-level annotation results in an exception.
* <p>Defaults to an empty array.
*
* @see ResourceLockTarget
* @see ResourceLocksProvider.Lock
* @since 5.12
*/
@API(status = EXPERIMENTAL, since = "5.12")
ResourceLockTarget target() default ResourceLockTarget.SELF;
Class<? extends ResourceLocksProvider>[] providers() default {};

/**
* An array of one or more classes implementing {@link ResourceLocksProvider}.
* The target of a resource created from {@link #value()} and {@link #mode()}.
*
* <p>Defaults to an empty array.
* <p>Defaults to {@link ResourceLockTarget#SELF SELF}.
*
* @see ResourceLocksProvider.Lock
* <p>Note that using {@link ResourceLockTarget#CHILDREN} in a method-level
* annotation results in an exception.
*
* @see ResourceLockTarget
* @since 5.12
*/
@API(status = EXPERIMENTAL, since = "5.12")
Class<? extends ResourceLocksProvider>[] providers() default {};
ResourceLockTarget target() default ResourceLockTarget.SELF;

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ public enum ResourceLockTarget {
/**
* Add a shared resource to the <em>direct</em> children of the current node.
*
* <p>Examples of "parent - child" relationship in the context of
* {@link ResourceLockTarget}:
* <p>Examples of "parent - child" relationships in the context of
* {@code ResourceLockTarget}:
* <ul>
* <li>a test class
* - test methods and nested test classes declared in the class.</li>
* <li>a nested test class
* - test methods and nested test classes declared in the nested class.
* </li>
* <li>a test method
* - considered to have no children. Using {@code CHILDREN} for
* a test method results in an exception.</li>
* <li><strong>test class</strong>: test methods and nested test classes
* declared in the test class are children of the test class.</li>
* <li><strong>nested test class</strong>: test methods and nested test
* classes declared in the nested class are children of the nested test class.
* </li>
* <li><strong>test method</strong>: test methods are not considered to have
* children. Using {@code CHILDREN} for a test method results in an
* exception.</li>
* </ul>
*/
CHILDREN
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,18 @@
import java.util.Set;

import org.apiguardian.api.API;
import org.junit.jupiter.api.Nested;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.ToStringBuilder;

/**
* {@code @ResourceLocksProvider} is used to add shared resources
* to a test class and / or its test methods dynamically at runtime.
* A {@code ResourceLocksProvider} is used to programmatically add shared resources
* to a test class or its test methods dynamically at runtime.
*
* <p>Each shared resource is represented by an instance of {@link Lock}.
*
* <p>Adding shared resources using this interface has the same semantics
* as declaring them via {@code @ResourceLock(value, mode)} annotation
* but for some cases may be a more flexible and less verbose alternative
* since it allows to add resources programmatically.
* <p>Adding shared resources via this API has the same semantics as declaring
* them declaratively via {@link ResouceLock @ResourceLock(value, mode)}, but for
* some use cases the programmatic approach may be more flexible and less verbose.
*
* <p>Implementations must provide a no-args constructor.
*
Expand All @@ -47,40 +45,37 @@ public interface ResourceLocksProvider {
/**
* Add shared resources to a test class.
*
* <p>Invoked in case a test class or its parent class
* is annotated with {@code @ResourceLock(providers)}.
* <p>Invoked in case a test class or its parent class is annotated with
* {@code @ResourceLock(providers)}.
*
* @apiNote Adding {@linkplain Lock a shared resource} with this method
* has the same semantics as annotating a test class
* with analogous {@code @ResourceLock(value, mode)}.
* @apiNote Adding {@linkplain Lock a shared resource} via this method has
* the same semantics as annotating a test class with an analogous
* {@code @ResourceLock(value, mode)} declaration.
*
* @param testClass a test class to add shared resources
* @param testClass a test class for which to add shared resources
* @return a set of {@link Lock}; may be empty
*/
default Set<Lock> provideForClass(Class<?> testClass) {
return emptySet();
}

/**
* Add shared resources to a {@linkplain Nested nested} test class.
* Add shared resources to a {@linkplain org.junit.jupiter.api.Nested @Nested}
* test class.
*
* <p>Invoked in case:
* <ul>
* <li>an enclosing test class of any level or its parent class
* is annotated with {@code @ResourceLock(providers)}.</li>
* <li>a nested test class or its parent class
* is annotated with {@code @ResourceLock(providers)}.</li>
* <li>an enclosing test class of any level or its parent class is
* annotated with {@code @ResourceLock(providers = ...)}.</li>
* <li>a nested test class or its parent class is annotated with
* {@code @ResourceLock(providers = ...)}.</li>
* </ul>
*
* <p>Invoked for a nested test class
* annotated with {@code @ResourceLock(providers)}
* and for its child classes.
*
* @apiNote Adding {@linkplain Lock a shared resource} with this method
* has the same semantics as annotating a nested test class
* with analogous {@code @ResourceLock(value, mode)}.
* @apiNote Adding {@linkplain Lock a shared resource} via this method has
* the same semantics as annotating a nested test class with an analogous
* {@code @ResourceLock(value, mode)} declaration.
*
* @param testClass a nested test class to add shared resources
* @param testClass a nested test class for which to add shared resources
* @return a set of {@link Lock}; may be empty
* @see Nested
*/
Expand All @@ -93,36 +88,33 @@ default Set<Lock> provideForNestedClass(Class<?> testClass) {
*
* <p>Invoked in case:
* <ul>
* <li>an enclosing test class of any level or its parent class
* is annotated with {@code @ResourceLock(providers)}.</li>
* <li>a test method
* is annotated with {@code @ResourceLock(providers)}.</li>
* <li>an enclosing test class of any level or its parent class is
* annotated with {@code @ResourceLock(providers)}.</li>
* <li>a test method is annotated with {@code @ResourceLock(providers)}.</li>
* </ul>
*
* @apiNote Adding {@linkplain Lock a shared resource} with this method
* has the same semantics as annotating a test method
* with analogous {@code @ResourceLock(value, mode)}.
*
* @param testClass a test class
* or {@linkplain Nested nested} test class containing the {@code testMethod}
* @param testMethod a test method to add shared resources
* @param testClass the test class or {@code @Nested} test class that contains
* the {@code testMethod}
* @param testMethod a test method for which to add shared resources
* @return a set of {@link Lock}; may be empty
*/
default Set<Lock> provideForMethod(Class<?> testClass, Method testMethod) {
return emptySet();
}

/**
* {@code Lock} represents a shared resource.
*
* <p>{@link Lock} represents a shared resource.
*
* <p>Each resource is identified by a {@link #key}.
* In addition, the {@link #accessMode} allows 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.
* <p>Each resource is identified by a {@linkplain #getKey() key}. In addition,
* the {@linkplain #getAccessMode() access mode} allows one to specify whether
* a test class or test method requires {@link ResourceAccessMode#READ_WRITE
* READ_WRITE} or {@link ResourceAccessMode#READ READ} access to the resource.
*
* @apiNote {@link Lock#key} and {@link Lock#accessMode} have the same
* @apiNote {@link #getKey()} and {@link #getAccessMode()} have the same
* semantics as {@link ResourceLock#value()} and {@link ResourceLock#mode()}
* respectively.
*
Expand All @@ -140,7 +132,7 @@ final class Lock {
private final ResourceAccessMode accessMode;

/**
* Create a new {@code Lock} with {@code accessMode = READ_WRITE}.
* Create a new {@code Lock} with {@link ResourceAccessMode#READ_WRITE}.
*
* @param key the identifier of the resource; never {@code null} or blank
* @see ResourceLock#value()
Expand All @@ -153,7 +145,8 @@ public Lock(String key) {
* 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}
* @param accessMode the lock mode to use to synchronize access to the
* resource; never {@code null}
* @see ResourceLock#value()
* @see ResourceLock#mode()
*/
Expand All @@ -163,21 +156,21 @@ public Lock(String key, ResourceAccessMode accessMode) {
}

/**
* Get the key of this lock.
* Get the key for this lock.
*
* @see ResourceLock#value()
*/
public String getKey() {
return key;
return this.key;
}

/**
* Get the access mode of this lock.
* Get the access mode for this lock.
*
* @see ResourceLock#mode()
*/
public ResourceAccessMode getAccessMode() {
return accessMode;
return this.accessMode;
}

@Override
Expand All @@ -188,20 +181,20 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
Lock lock = (Lock) o;
return Objects.equals(key, lock.key) && accessMode == lock.accessMode;
Lock that = (Lock) o;
return this.key.equals(that.key) && this.accessMode == that.accessMode;
}

@Override
public int hashCode() {
return Objects.hash(key, accessMode);
return Objects.hash(this.key, this.accessMode);
}

@Override
public String toString() {
return new ToStringBuilder(this) //
.append("key", key) //
.append("accessMode", accessMode) //
.append("key", this.key) //
.append("accessMode", this.accessMode) //
.toString();
}
}
Expand Down

0 comments on commit 254ca62

Please sign in to comment.