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

Introduce adding 'shared resources' programmatically #3889

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7fc2b96
Introduce adding 'resource locks' programmatically.
VladimirDmitrienko Jul 31, 2024
cde4bc8
Add 'ResourceLocksProviderIntegrationTests'.
VladimirDmitrienko Aug 1, 2024
00788b6
Add JavaDoc.
VladimirDmitrienko Aug 6, 2024
ff3c786
Add 'locks from provider' cases to 'NodeTreeWalkerIntegrationTests'.
VladimirDmitrienko Aug 6, 2024
1c6eef8
Add 'LockTests'.
VladimirDmitrienko Aug 6, 2024
7d894c5
Add user guide.
VladimirDmitrienko Aug 7, 2024
1f8a6d0
Revert "Add 'locks from provider' cases to 'NodeTreeWalkerIntegration…
VladimirDmitrienko Aug 12, 2024
e1b5473
Move '@ResourceLocksFrom' functionality to '@ResourceLock'.
VladimirDmitrienko Aug 12, 2024
4460685
Add tests for '@ResourceLock' and 'ResourceLocksProvider'.
VladimirDmitrienko Aug 15, 2024
e14bfda
Merge branch 'main' of https://github.com/VladimirDmitrienko/junit5 i…
VladimirDmitrienko Aug 15, 2024
ba476e5
Fix formatting.
VladimirDmitrienko Aug 15, 2024
1d8488c
Polish Javadoc
marcphilipp Sep 18, 2024
3fddd55
Merge branch 'refs/heads/main' into #2677_add_exclusive_resource_prog…
VladimirDmitrienko Sep 20, 2024
49f9a28
Merge branch 'refs/heads/main' into #2677_add_exclusive_resource_prog…
VladimirDmitrienko Sep 25, 2024
d56196d
Remove version from User Guide.
VladimirDmitrienko Sep 25, 2024
c8d26ef
Revert formatting changes in User Guide.
VladimirDmitrienko Sep 25, 2024
9383c39
Mention that `ResourceLocksProvider` must have a no-args constructor.
VladimirDmitrienko Sep 27, 2024
249ab3e
Avoid searching for the annotation twice.
VladimirDmitrienko Sep 27, 2024
67e7dc5
Polish `DynamicSharedResourcesDemo`.
VladimirDmitrienko Sep 27, 2024
792b43a
Add Release Notes.
VladimirDmitrienko Sep 27, 2024
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
1 change: 1 addition & 0 deletions documentation/src/docs/asciidoc/link-attributes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ endif::[]
:Execution: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Execution.html[@Execution]
:Isolated: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Isolated.html[@Isolated]
:ResourceLock: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLock.html[@ResourceLock]
:ResourceLocksProvider: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLocksProvider.html[ResourceLocksProvider]
:Resources: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Resources.html[Resources]
// Jupiter Extension APIs
:extension-api-package: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/package-summary.html[org.junit.jupiter.api.extension]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ JUnit repository on GitHub.
`@ConvertWith`), and `ArgumentsAggregator` (declared via `@AggregateWith`)
implementations can now use constructor injection from registered `ParameterResolver`
extensions.
* Introduce adding `shared resources` in runtime via new `@ResourceLocks#provider` attribute
which accepts implementations of `ResourceLocksProvider`.


[[release-notes-5.12.0-M1-junit-vintage]]
Expand Down
25 changes: 20 additions & 5 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2943,10 +2943,18 @@ execution. The shared resource is identified by a unique name which is a `String
name can be user-defined or one of the predefined constants in `{Resources}`:
`SYSTEM_PROPERTIES`, `SYSTEM_OUT`, `SYSTEM_ERR`, `LOCALE`, or `TIME_ZONE`.

If the tests in the following example were run in parallel _without_ the use of
{ResourceLock}, they would be _flaky_. Sometimes they would pass, and at other times they
would fail due to the inherent race condition of writing and then reading the same JVM
System Property.
`{ResourceLock}` annotation has 'providers' attribute
which accepts an array of one or more classes implementing `{ResourceLocksProvider}`
interface. This interface allows to add shared resources in runtime.

Note that resources declared "statically" with `{ResourceLock}` annotation
are combined with resources added "dynamically" with `{ResourceLocksProvider}`
interface.

If the tests in the following examples were run in parallel _without_ the use of
`{ResourceLock}` or `{ResourceLocksProvider}`, they would be _flaky_.
Sometimes they would pass, and at other times they would fail due to
the inherent race condition of writing and then reading the same JVM System Property.

When access to shared resources is declared using the `{ResourceLock}` annotation, the
JUnit Jupiter engine uses this information to ensure that no conflicting tests are run in
Expand All @@ -2970,8 +2978,15 @@ parallel with each other but not while any other test that requires `READ_WRITE`
to the same shared resource is running.

[source,java]
.Declaring shared resources "statically" with `{ResourceLock}` annotation
----
include::{testDir}/example/sharedresources/StaticSharedResourcesDemo.java[tags=user_guide]
----

[source,java]
.Adding shared resources "dynamically" with `{ResourceLocksProvider}` interface
----
include::{testDir}/example/SharedResourcesDemo.java[tags=user_guide]
include::{testDir}/example/sharedresources/DynamicSharedResourcesDemo.java[tags=user_guide]
----


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package example.sharedresources;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT;
import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ;
import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE;
import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ResourceAccessMode;
import org.junit.jupiter.api.parallel.ResourceLock;
import org.junit.jupiter.api.parallel.ResourceLocksProvider;

// tag::user_guide[]
@Execution(CONCURRENT)
@ResourceLock(providers = DynamicSharedResourcesDemo.Provider.class)
class DynamicSharedResourcesDemo {

private Properties backup;

@BeforeEach
void backup() {
backup = new Properties();
backup.putAll(System.getProperties());
}

@AfterEach
void restore() {
System.setProperties(backup);
}

@Test
void customPropertyIsNotSetByDefault() {
assertNull(System.getProperty("my.prop"));
}

@Test
void canSetCustomPropertyToApple() {
System.setProperty("my.prop", "apple");
assertEquals("apple", System.getProperty("my.prop"));
}

@Test
void canSetCustomPropertyToBanana() {
System.setProperty("my.prop", "banana");
assertEquals("banana", System.getProperty("my.prop"));
}

static class Provider implements ResourceLocksProvider {

@Override
public Set<Lock> provideForMethod(Class<?> testClass, Method testMethod) {
ResourceAccessMode mode;
if (testMethod.getName().startsWith("canSet")) {
mode = READ_WRITE;
}
else {
mode = READ;
}
Set<Lock> locks = new HashSet<>();
locks.add(new Lock(SYSTEM_PROPERTIES, mode));
return locks;
}
}

}
// end::user_guide[]
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* https://www.eclipse.org/legal/epl-v20.html
*/

package example;
package example.sharedresources;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
Expand All @@ -27,7 +27,7 @@

// tag::user_guide[]
@Execution(CONCURRENT)
class SharedResourcesDemo {
class StaticSharedResourcesDemo {

private Properties backup;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*
* @since 5.3
* @see ResourceLock
* @see ResourceLocksProvider.Lock
*/
@API(status = STABLE, since = "5.10")
public enum ResourceAccessMode {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.junit.jupiter.api.parallel;

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

import java.lang.annotation.ElementType;
Expand Down Expand Up @@ -48,10 +49,20 @@
* <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
* in runtime via {@link ResourceLock#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".
*
* @see Isolated
* @see Resources
* @see ResourceAccessMode
* @see ResourceLocks
* @see ResourceLocksProvider
* @since 5.3
*/
@API(status = STABLE, since = "5.10")
Expand All @@ -64,17 +75,32 @@
/**
* The resource key.
*
* <p>Defaults to an empty string.
*
* @see Resources
* @see ResourceLocksProvider.Lock#getKey()
*/
String value();
String value() default "";

/**
* The resource access mode.
*
* <p>Defaults to {@link ResourceAccessMode#READ_WRITE READ_WRITE}.
*
* @see ResourceAccessMode
* @see ResourceLocksProvider.Lock#getAccessMode()
*/
ResourceAccessMode mode() default ResourceAccessMode.READ_WRITE;

/**
* An array of one or more classes implementing {@link ResourceLocksProvider}.
*
* <p>Defaults to an empty array.
*
* @see ResourceLocksProvider.Lock
* @since 5.12
*/
@API(status = EXPERIMENTAL, since = "5.12")
Class<? extends ResourceLocksProvider>[] providers() default {};

}
Loading