Skip to content

Commit

Permalink
Defaults to SELF to preserve existing behavior.
Browse files Browse the repository at this point in the history
Using CHILDREN has the same effect as declaring @ResourceLock on every @test method of a test class.

Issue: junit-team#3102
  • Loading branch information
anatolyD committed Apr 1, 2023
1 parent e188b1e commit 8cc8e7f
Show file tree
Hide file tree
Showing 10 changed files with 417 additions and 38 deletions.
Expand Up @@ -36,6 +36,9 @@ repository on GitHub.
of the former. Please refer to the
<<../user-guide/index.adoc#launcher-api-launcher-interceptors-custom, User Guide>> for
details.
* Introduced `@ResourceLock(target = SELF | CHILDREN)` where target defaults to SELF to
preserve existing behavior. Using CHILDREN has the same effect as declaring @ResourceLock
on every @Test method of a test class. Please refer to the <<../user-guide/writing-tests.adoc#specifying-target-for-resource-lock, User Guide. Writing Tests>> for details.

[[release-notes-5.10.0-M1-junit-jupiter]]
=== JUnit Jupiter
Expand Down
10 changes: 10 additions & 0 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Expand Up @@ -2490,6 +2490,16 @@ to the same shared resource is running.
include::{testDir}/example/SharedResourcesDemo.java[tags=user_guide]
----

[[specifying-target-for-resource-lock]]
You can declare a resource lock on the class level which will apply only to its children,
and will not be applied to the class itself. This is useful if you want to keep `CONCURRENT`
execution mode for each method.

[source,java]
----
include::{testDir}/example/SharedResourcesChildrenTargetDemo.java[tags=user_guide]
----


[[writing-tests-built-in-extensions]]
=== Built-in Extensions
Expand Down
@@ -0,0 +1,71 @@
/*
* Copyright 2015-2023 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;

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_OUT;
import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES;
import static org.junit.jupiter.api.parallel.Resources.TIME_ZONE;

import java.util.Properties;
import java.util.TimeZone;

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.ResourceLock;
import org.junit.jupiter.api.parallel.ResourceLockTarget;

// tag::user_guide[]
@Execution(CONCURRENT)
@ResourceLock(value = TIME_ZONE, mode = READ, target = ResourceLockTarget.CHILDREN)
class SharedResourcesChildrenTargetDemo {

private Properties backup;

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

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

@Test
@ResourceLock(value = SYSTEM_PROPERTIES, mode = READ)
void usePropertiesAndTimeZoneWithoutModification() {
assertNull(System.getProperty("my.prop"));
}

@Test
@ResourceLock(value = SYSTEM_PROPERTIES, mode = READ)
void usePropertiesAndTimeZoneWithoutModificationAgain() {
assertNull(System.getProperty("my.prop"));
}

@Test
@ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE)
void canSetCustomPropertyToTimeZone() {
String timezone = TimeZone.getDefault().getDisplayName();
System.setProperty("my.timezone", timezone);
assertEquals(timezone, System.getProperty("my.timezone"));
}

}
// end::user_guide[]
Expand Up @@ -40,6 +40,9 @@
* <p>Since JUnit Jupiter 5.4, this annotation is {@linkplain Inherited inherited}
* within class hierarchies.
*
* <p>Since JUnit Jupiter 5.10, this annotation can be used to specify the target
* of the lock with {@link ResourceLockTarget}.
*
* @see Isolated
* @see Resources
* @see ResourceAccessMode
Expand Down Expand Up @@ -69,4 +72,13 @@
*/
ResourceAccessMode mode() default ResourceAccessMode.READ_WRITE;

/**
* Resource lock target.
*
* <p>Defaults to {@link ResourceLockTarget#SELF SELF}.
*
* @see ResourceLockTarget
*/
ResourceLockTarget target() default ResourceLockTarget.SELF;

}
@@ -0,0 +1,36 @@
/*
* Copyright 2015-2023 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 org.junit.jupiter.api.parallel;

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

import org.apiguardian.api.API;

/**
* Indicates the target of a {@link ResourceLock}.
*
* @since 5.10
* @see ResourceLock
*/
@API(status = EXPERIMENTAL, since = "5.10")
public enum ResourceLockTarget {

/**
* Point to the test descriptor itself
*/
SELF,

/**
* Skip the test descriptor itself and apply annotation {@link ResourceLock} to all its children
*/
CHILDREN

}
Expand Up @@ -33,6 +33,7 @@
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.ResourceLockTarget;
import org.junit.jupiter.engine.config.JupiterConfiguration;
import org.junit.jupiter.engine.execution.ConditionEvaluator;
import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext;
Expand All @@ -49,6 +50,7 @@
import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor;
import org.junit.platform.engine.support.hierarchical.ExclusiveResource;
import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode;
import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockScope;
import org.junit.platform.engine.support.hierarchical.Node;

/**
Expand Down Expand Up @@ -183,7 +185,11 @@ public static ExecutionMode toExecutionMode(org.junit.jupiter.api.parallel.Execu
Set<ExclusiveResource> getExclusiveResourcesFromAnnotation(AnnotatedElement element) {
// @formatter:off
return findRepeatableAnnotations(element, ResourceLock.class).stream()
.map(resource -> new ExclusiveResource(resource.value(), toLockMode(resource.mode())))
.map(resource -> new ExclusiveResource(
resource.value(),
toLockMode(resource.mode()),
toLockScope(resource.target()))
)
.collect(toSet());
// @formatter:on
}
Expand All @@ -198,6 +204,16 @@ private static LockMode toLockMode(ResourceAccessMode mode) {
throw new JUnitException("Unknown ResourceAccessMode: " + mode);
}

private static LockScope toLockScope(ResourceLockTarget lockTarget) {
switch (lockTarget) {
case SELF:
return LockScope.SELF;
case CHILDREN:
return LockScope.CHILDREN;
}
throw new JUnitException("Unknown ResourceLockTarget: " + lockTarget);
}

@Override
public SkipResult shouldBeSkipped(JupiterEngineExecutionContext context) throws Exception {
context.getThrowableCollector().assertEmpty();
Expand Down
Expand Up @@ -52,18 +52,34 @@ public class ExclusiveResource {

private final String key;
private final LockMode lockMode;
private final LockScope lockScope;
private int hash;

/**
* Create a new {@code ExclusiveResource}.
* Create a new {@code ExclusiveResource} with a default lock scope {@link LockScope#SELF}
*
* @param key the identifier of the resource; never {@code null} or blank
* @param lockMode the lock mode to use to synchronize access to the
* resource; never {@code null}
*
*/
public ExclusiveResource(String key, LockMode lockMode) {
this(key, lockMode, LockScope.SELF);
}

/**
* Create a new {@code ExclusiveResource}.
*
* @param key the identifier of the resource; never {@code null} or blank
* @param lockMode the lock mode to use to synchronize access to the
* resource; never {@code null}
* @param lockScope the lock scope to use to synchronize access to the
* resource; never {@code null}
*/
public ExclusiveResource(String key, LockMode lockMode, LockScope lockScope) {
this.key = Preconditions.notBlank(key, "key must not be blank");
this.lockMode = Preconditions.notNull(lockMode, "lockMode must not be null");
this.lockScope = Preconditions.notNull(lockScope, "lockScope must not be null");
}

/**
Expand All @@ -80,6 +96,13 @@ public LockMode getLockMode() {
return lockMode;
}

/**
* Get the lock scope of this resource.
*/
public LockScope getLockScope() {
return lockScope;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -89,21 +112,26 @@ public boolean equals(Object o) {
return false;
}
ExclusiveResource that = (ExclusiveResource) o;
return Objects.equals(key, that.key) && lockMode == that.lockMode;
return Objects.equals(key, that.key) && lockMode == that.lockMode && lockScope == that.lockScope;
}

@Override
public int hashCode() {
int h = hash;
if (h == 0) {
h = hash = Objects.hash(key, lockMode);
h = hash = Objects.hash(key, lockMode, lockScope);
}
return h;
}

@Override
public String toString() {
return new ToStringBuilder(this).append("key", key).append("lockMode", lockMode).toString();
return new ToStringBuilder(this).append("key", key).append("lockMode", lockMode).append("lockScope",
lockScope).toString();
}

public ExclusiveResource convertToSelfTarget() {
return new ExclusiveResource(key, lockMode, LockScope.SELF);
}

/**
Expand Down Expand Up @@ -131,4 +159,21 @@ public enum LockMode {

}

/**
* {@code LockTarget} defines the scope of the lock.
*/
public enum LockScope {

/**
* Lock the resource for the node itself.
*/
SELF,

/**
* Lock the resource for all children of the node. Bypass the node itself.
*/
CHILDREN

}

}

0 comments on commit 8cc8e7f

Please sign in to comment.