Skip to content

Commit

Permalink
Introduce adding 'resource locks' programmatically.
Browse files Browse the repository at this point in the history
  • Loading branch information
VladimirDmitrienko committed Aug 6, 2024
1 parent 539df08 commit 745f989
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 org.junit.jupiter.api.parallel;

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

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;

@API(status = STABLE, since = "5.10")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface ResourceLocksFrom {

Class<? extends ResourceLocksProvider>[] value();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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 org.junit.jupiter.api.parallel;

import static java.util.Collections.emptySet;
import static org.apiguardian.api.API.Status.STABLE;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Set;

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

@API(status = STABLE, since = "5.10")
public interface ResourceLocksProvider {
default Set<Lock> provideForClass(Class<?> testClass) {
return emptySet();
}

default Set<Lock> provideForNestedClass(Class<?> testClass) {
return emptySet();
}

default Set<Lock> provideForMethod(Class<?> testClass, Method testMethod) {
return emptySet();
}

final class Lock {

private final String key;

private final ResourceAccessMode accessMode;

public Lock(String key) {
this(key, ResourceAccessMode.READ_WRITE);
}

public Lock(String key, ResourceAccessMode accessMode) {
this.key = Preconditions.notBlank(key, "key must not be blank");
this.accessMode = Preconditions.notNull(accessMode, "accessMode must not be null");
}

public String getKey() {
return key;
}

public ResourceAccessMode getAccessMode() {
return accessMode;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Lock lock = (Lock) o;
return Objects.equals(key, lock.key) && accessMode == lock.accessMode;
}

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

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package org.junit.jupiter.engine.descriptor;

import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
import static org.apiguardian.api.API.Status.INTERNAL;
import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation;
import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromConstructorParameters;
Expand All @@ -34,6 +35,7 @@
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;

import org.apiguardian.api.API;
import org.junit.jupiter.api.TestInstance.Lifecycle;
Expand Down Expand Up @@ -66,6 +68,7 @@
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;
Expand Down Expand Up @@ -142,7 +145,15 @@ public void setDefaultChildExecutionMode(ExecutionMode defaultChildExecutionMode

@Override
public Set<ExclusiveResource> getExclusiveResources() {
return getExclusiveResourcesFromAnnotation(getTestClass());
// @formatter:off
return Stream.concat(
getExclusiveResourcesFromAnnotation(getTestClass()),
getExclusiveResourcesFromProvider(
getTestClass(),
provider -> provider.provideForClass(getTestClass())
)
).collect(toSet());
// @formatter:on
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@

import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toSet;
import static org.apiguardian.api.API.Status.INTERNAL;
import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.determineDisplayName;
import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation;
import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations;

import java.lang.reflect.AnnotatedElement;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;

import org.apiguardian.api.API;
import org.junit.jupiter.api.Tag;
Expand All @@ -33,6 +35,8 @@
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.ResourceLocksFrom;
import org.junit.jupiter.api.parallel.ResourceLocksProvider;
import org.junit.jupiter.engine.config.JupiterConfiguration;
import org.junit.jupiter.engine.execution.ConditionEvaluator;
import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext;
Expand All @@ -41,6 +45,7 @@
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.junit.platform.commons.util.ExceptionUtils;
import org.junit.platform.commons.util.ReflectionUtils;
import org.junit.platform.commons.util.UnrecoverableExceptions;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestSource;
Expand Down Expand Up @@ -180,11 +185,24 @@ public static ExecutionMode toExecutionMode(org.junit.jupiter.api.parallel.Execu
throw new JUnitException("Unknown ExecutionMode: " + mode);
}

Set<ExclusiveResource> getExclusiveResourcesFromAnnotation(AnnotatedElement element) {
Stream<ExclusiveResource> getExclusiveResourcesFromAnnotation(AnnotatedElement element) {
// @formatter:off
return findRepeatableAnnotations(element, ResourceLock.class).stream()
.map(resource -> new ExclusiveResource(resource.value(), toLockMode(resource.mode())))
.collect(toSet());
.map(resource -> new ExclusiveResource(resource.value(), toLockMode(resource.mode())));
// @formatter:on
}

@SuppressWarnings("Convert2MethodRef")
Stream<ExclusiveResource> getExclusiveResourcesFromProvider(Class<?> testClass,
Function<ResourceLocksProvider, Set<ResourceLocksProvider.Lock>> providerToLocks) {
// @formatter:off
return findAnnotation(testClass, ResourceLocksFrom.class)
.map(annotation -> Stream.of(annotation.value()))
.orElseGet(Stream::empty)
.map(providerClass -> ReflectionUtils.newInstance(providerClass))
.map(providerToLocks)
.flatMap(Collection::stream)
.map(lock -> new ExclusiveResource(lock.getKey(), toLockMode(lock.getAccessMode())));
// @formatter:on
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.junit.jupiter.engine.descriptor;

import static java.util.stream.Collectors.toSet;
import static org.apiguardian.api.API.Status.INTERNAL;
import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.determineDisplayNameForMethod;
import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder;
Expand All @@ -20,6 +21,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Stream;

import org.apiguardian.api.API;
import org.junit.jupiter.api.extension.ExtensionContext;
Expand Down Expand Up @@ -81,7 +83,15 @@ public final Set<TestTag> getTags() {

@Override
public Set<ExclusiveResource> getExclusiveResources() {
return getExclusiveResourcesFromAnnotation(getTestMethod());
// @formatter:off
return Stream.concat(
getExclusiveResourcesFromAnnotation(getTestMethod()),
getExclusiveResourcesFromProvider(
getTestClass(),
provider -> provider.provideForMethod(getTestClass(), getTestMethod())
)
).collect(toSet());
// @formatter:on
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package org.junit.jupiter.engine.descriptor;

import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toSet;
import static org.apiguardian.api.API.Status.INTERNAL;
import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForNestedClass;

Expand All @@ -19,6 +20,7 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import org.apiguardian.api.API;
import org.junit.jupiter.api.extension.ExtensionContext;
Expand All @@ -30,6 +32,7 @@
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestTag;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.support.hierarchical.ExclusiveResource;
import org.junit.platform.engine.support.hierarchical.ThrowableCollector;

/**
Expand Down Expand Up @@ -87,4 +90,17 @@ protected TestInstances instantiateTestClass(JupiterEngineExecutionContext paren
return instantiateTestClass(Optional.of(outerInstances), registry, extensionContext);
}

@Override
public Set<ExclusiveResource> getExclusiveResources() {
// @formatter:off
return Stream.concat(
getExclusiveResourcesFromAnnotation(getTestClass()),
getExclusiveResourcesFromProvider(
getTestClass(),
provider -> provider.provideForNestedClass(getTestClass())
)
).collect(toSet());
// @formatter:on
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public class TestTemplateInvocationTestDescriptor extends TestMethodTestDescript

@Override
public Set<ExclusiveResource> getExclusiveResources() {
// @ResourceLock annotations are already collected and returned by the enclosing container
// Resources are already collected and returned by the enclosing container
return emptySet();
}

Expand Down

0 comments on commit 745f989

Please sign in to comment.