Skip to content

Commit

Permalink
Draft: Implement adding ExclusiveResource programmatically.
Browse files Browse the repository at this point in the history
Issue: #2677
  • Loading branch information
Vladimir Dmitrienko committed Jul 16, 2024
1 parent 6d2ae1d commit a51a66b
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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 java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Inherited
public @interface ExclusiveResources {

Class<? extends ExclusiveResourcesProvider>[] value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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 java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;

public interface ExclusiveResourcesProvider {
default Set<ExclusiveResource> provideForClass(Class<?> testClass) {
return Collections.emptySet();
}

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

final class ExclusiveResource {

private final String key;

private final ResourceAccessMode accessMode;

public ExclusiveResource(String key, ResourceAccessMode accessMode) {
this.key = key;
this.accessMode = accessMode;
}

public static ExclusiveResource readLockOf(String key) {
return new ExclusiveResource(key, ResourceAccessMode.READ);
}

public static ExclusiveResource readWriteLockOf(String key) {
return new ExclusiveResource(key, ResourceAccessMode.READ_WRITE);
}

public String getKey() {
return key;
}

public ResourceAccessMode getAccessMode() {
return accessMode;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.stream.Collectors.toSet;

import java.lang.reflect.Method;
import java.util.Set;
import java.util.stream.Stream;

public final class MyExclusiveResourcesProvider implements ExclusiveResourcesProvider {

public MyExclusiveResourcesProvider() {
}

@Override
public Set<ExclusiveResource> provideForClass(Class<?> testClass) {
// Distribute locks depending on class name, package name, declared fields etc.
// ...
return Stream.of(ExclusiveResource.readWriteLockOf("class_lock")).collect(toSet());
}

@Override
public Set<ExclusiveResource> provideForMethod(Class<?> testClass, Method testMethod) {
// Distribute locks depending on method name, parameters etc.
// ...
return Stream.of(ExclusiveResource.readWriteLockOf("method_lock")).collect(toSet());
}
}
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 @@ -33,6 +34,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 @@ -142,7 +144,15 @@ public void setDefaultChildExecutionMode(ExecutionMode defaultChildExecutionMode

@Override
public Set<ExclusiveResource> getExclusiveResources() {
return getExclusiveResourcesFromAnnotation(getTestClass());
// @formatter:off
Set<ExclusiveResource> resourcesFromAnnotation = getExclusiveResourcesFromAnnotation(getTestClass());
Set<ExclusiveResource> resourcesFromProvider = getExclusiveResourcesFromProvider(
getTestClass(),
provider -> provider.provideForClass(getTestClass())
);

return Stream.concat(resourcesFromAnnotation.stream(), resourcesFromProvider.stream()).collect(toSet());
// @formatter:on
}

@Override
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.Collections.emptySet;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toSet;
Expand All @@ -19,17 +20,22 @@
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;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.parallel.ExclusiveResources;
import org.junit.jupiter.api.parallel.ExclusiveResourcesProvider;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ResourceAccessMode;
import org.junit.jupiter.api.parallel.ResourceLock;
Expand All @@ -41,6 +47,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 @@ -188,6 +195,21 @@ Set<ExclusiveResource> getExclusiveResourcesFromAnnotation(AnnotatedElement elem
// @formatter:on
}

@SuppressWarnings("Convert2MethodRef")
Set<ExclusiveResource> getExclusiveResourcesFromProvider(AnnotatedElement element,
Function<ExclusiveResourcesProvider, Set<ExclusiveResourcesProvider.ExclusiveResource>> providerToResources) {
// @formatter:off
return findAnnotation(element, ExclusiveResources.class)
.map(annotation -> Stream.of(annotation.value())
.map(providerClass -> ReflectionUtils.newInstance(providerClass))
.map(providerToResources)
.flatMap(Collection::stream)
.map(resource -> new ExclusiveResource(resource.getKey(), toLockMode(resource.getAccessMode())))
.collect(toSet()))
.orElse(emptySet());
// @formatter:on
}

private static LockMode toLockMode(ResourceAccessMode mode) {
switch (mode) {
case READ:
Expand All @@ -202,7 +224,7 @@ private static LockMode toLockMode(ResourceAccessMode mode) {
public SkipResult shouldBeSkipped(JupiterEngineExecutionContext context) throws Exception {
context.getThrowableCollector().assertEmpty();
ConditionEvaluationResult evaluationResult = conditionEvaluator.evaluate(context.getExtensionRegistry(),
context.getConfiguration(), context.getExtensionContext());
context.getConfiguration(), context.getExtensionContext());
return toSkipResult(evaluationResult);
}

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
Set<ExclusiveResource> resourcesFromAnnotation = getExclusiveResourcesFromAnnotation(getTestMethod());
Set<ExclusiveResource> resourcesFromProvider = getExclusiveResourcesFromProvider(
getTestMethod(),
provider -> provider.provideForMethod(getTestClass(), getTestMethod())
);

return Stream.concat(resourcesFromAnnotation.stream(), resourcesFromProvider.stream()).collect(toSet());
// @formatter:on
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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.platform.engine.support.hierarchical;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.ExclusiveResources;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.jupiter.api.parallel.MyExclusiveResourcesProvider;

@Execution(ExecutionMode.CONCURRENT)
@ExclusiveResources(MyExclusiveResourcesProvider.class)
class ExclusiveResourcesProviderOnClassTests {

@Test
void a() {
sleep();
}

@Test
void b() {
sleep();
}

@Test
void c() {
sleep();
}

@Test
void d() {
sleep();
}

@Test
void e() {
sleep();
}

@Test
void f() {
sleep();
}

@Test
void g() {
sleep();
}

@Test
void h() {
sleep();
}

static void sleep() {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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.platform.engine.support.hierarchical;

import static org.junit.platform.engine.support.hierarchical.ExclusiveResourcesProviderOnClassTests.sleep;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.ExclusiveResources;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.jupiter.api.parallel.MyExclusiveResourcesProvider;

@Execution(ExecutionMode.CONCURRENT)
class ExclusiveResourcesProviderOnMethodTests {

@ExclusiveResources(MyExclusiveResourcesProvider.class)
@Test
void a() {
sleep();
}

@ExclusiveResources(MyExclusiveResourcesProvider.class)
@Test
void b() {
sleep();
}

@ExclusiveResources(MyExclusiveResourcesProvider.class)
@Test
void c() {
sleep();
}

@ExclusiveResources(MyExclusiveResourcesProvider.class)
@Test
void d() {
sleep();
}

@ExclusiveResources(MyExclusiveResourcesProvider.class)
@Test
void e() {
sleep();
}

@ExclusiveResources(MyExclusiveResourcesProvider.class)
@Test
void f() {
sleep();
}

@ExclusiveResources(MyExclusiveResourcesProvider.class)
@Test
void g() {
sleep();
}

@ExclusiveResources(MyExclusiveResourcesProvider.class)
@Test
void h() {
sleep();
}
}
Loading

0 comments on commit a51a66b

Please sign in to comment.