From 3f34c8d847e3a384b0456222b2266c0960aac3d6 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 25 Jan 2025 13:50:48 +0100 Subject: [PATCH 1/8] Provide runtime enclosing instance types to ResourceLocksProviders Resolves #4163. --- .../DynamicSharedResourcesDemo.java | 4 +- .../api/parallel/ResourceLocksProvider.java | 17 ++++--- .../descriptor/MethodBasedTestDescriptor.java | 7 ++- .../descriptor/NestedClassTestDescriptor.java | 2 +- .../parallel/ResourceLockAnnotationTests.java | 18 +++++--- .../parallel/ResourceLocksProviderTests.java | 46 ++++++++++++++----- 6 files changed, 67 insertions(+), 27 deletions(-) diff --git a/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java b/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java index 31f4c37e6bb3..4abe7297e357 100644 --- a/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java +++ b/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java @@ -19,6 +19,7 @@ import java.lang.reflect.Method; import java.util.Collections; +import java.util.List; import java.util.Properties; import java.util.Set; @@ -68,7 +69,8 @@ void canSetCustomPropertyToBanana() { static class Provider implements ResourceLocksProvider { @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { ResourceAccessMode mode = testMethod.getName().startsWith("canSet") ? READ_WRITE : READ; return Collections.singleton(new Lock(SYSTEM_PROPERTIES, mode)); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java index d94024d99f3b..a629249f9fde 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java @@ -14,6 +14,7 @@ import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.reflect.Method; +import java.util.List; import java.util.Objects; import java.util.Set; @@ -43,7 +44,7 @@ public interface ResourceLocksProvider { /** - * Add shared resources to a test class. + * Add shared resources for a test class. * *

Invoked in case a test class or its parent class is annotated with * {@code @ResourceLock(providers)}. @@ -60,8 +61,8 @@ default Set provideForClass(Class testClass) { } /** - * Add shared resources to a {@linkplain org.junit.jupiter.api.Nested @Nested} - * test class. + * Add shared resources for a + * {@linkplain org.junit.jupiter.api.Nested @Nested} test class. * *

Invoked in case: *

    @@ -75,16 +76,18 @@ default Set provideForClass(Class testClass) { * the same semantics as annotating a nested test class with an analogous * {@code @ResourceLock(value, mode)} declaration. * + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances; never {@code null} * @param testClass a nested test class for which to add shared resources * @return a set of {@link Lock}; may be empty * @see org.junit.jupiter.api.Nested @Nested */ - default Set provideForNestedClass(Class testClass) { + default Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return emptySet(); } /** - * Add shared resources to a test method. + * Add shared resources for a test method. * *

    Invoked in case: *

      @@ -97,13 +100,15 @@ default Set provideForNestedClass(Class testClass) { * has the same semantics as annotating a test method * with analogous {@code @ResourceLock(value, mode)}. * + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances; never {@code null} * @param testClass the test class or {@link org.junit.jupiter.api.Nested @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 * @see org.junit.jupiter.api.Nested @Nested */ - default Set provideForMethod(Class testClass, Method testMethod) { + default Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return emptySet(); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java index 52bf3c4ef8b9..f7d6e18bd319 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java @@ -16,6 +16,7 @@ import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import java.lang.reflect.Method; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; @@ -98,7 +99,11 @@ public ExclusiveResourceCollector getExclusiveResourceCollector() { @Override public Set evaluateResourceLocksProvider(ResourceLocksProvider provider) { - return provider.provideForMethod(getTestClass(), getTestMethod()); + List> enclosingInstanceTypes = getParent() // + .filter(ClassBasedTestDescriptor.class::isInstance) // + .map(parent -> ((ClassBasedTestDescriptor) parent).getEnclosingTestClasses()) // + .orElseGet(Collections::emptyList); + return provider.provideForMethod(enclosingInstanceTypes, getTestClass(), getTestMethod()); } @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java index 72d11ce5b09e..79f3a962c61b 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java @@ -95,7 +95,7 @@ protected TestInstances instantiateTestClass(JupiterEngineExecutionContext paren @Override public Set evaluateResourceLocksProvider(ResourceLocksProvider provider) { - return provider.provideForNestedClass(getTestClass()); + return provider.provideForNestedClass(getEnclosingTestClasses(), getTestClass()); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java index b095a8d28e7c..60e7c36b808f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java @@ -361,7 +361,8 @@ public Set provideForClass(Class testClass) { } @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return Set.of(new Lock("b1")); } } @@ -369,7 +370,8 @@ public Set provideForMethod(Class testClass, Method testMethod) { static class MethodLevelProvider implements ResourceLocksProvider { @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return Set.of(new Lock("b2")); } } @@ -377,7 +379,7 @@ public Set provideForMethod(Class testClass, Method testMethod) { static class NestedClassLevelProvider implements ResourceLocksProvider { @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return Set.of(new Lock("c1"), new Lock("c2", ResourceAccessMode.READ)); } } @@ -417,12 +419,13 @@ public Set provideForClass(Class testClass) { static class SecondClassLevelProvider implements ResourceLocksProvider { @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return Set.of(new Lock("b2", ResourceAccessMode.READ)); } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return Set.of(new Lock("c2")); } } @@ -430,7 +433,7 @@ public Set provideForNestedClass(Class testClass) { static class NestedClassLevelProvider implements ResourceLocksProvider { @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return Set.of(new Lock("c3")); } } @@ -452,7 +455,8 @@ void test() { static class Provider implements ResourceLocksProvider { @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return Set.of(new Lock("a1")); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java index 8a73be42b8ee..609f15d1832b 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java @@ -20,6 +20,7 @@ import static org.junit.platform.testkit.engine.EventConditions.test; import java.lang.reflect.Method; +import java.util.List; import java.util.Set; import java.util.stream.Stream; @@ -60,8 +61,14 @@ void methodLevelProviderInNestedClass() { assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); } + @Test + void providesAccessToRuntimeEnclosingInstances() { + var events = execute(SubClassLevelProviderTestCase.class); + assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); + } + private Stream execute(Class testCase) { - return executeTestsForClass(testCase).allEvents().stream(); + return executeTestsForClass(testCase).allEvents().debug().stream(); } // ------------------------------------------------------------------------- @@ -108,28 +115,35 @@ static class Provider implements ResourceLocksProvider { private static boolean isProvideForNestedClassCalled = false; private static boolean isProvideForNestedTestMethodCalled = false; + private Class testClass; + @Override public Set provideForClass(Class testClass) { + this.testClass = testClass; isProvideForClassCalled = true; - assertEquals(ClassLevelProviderTestCase.class, testClass); + assertThat(testClass).isAssignableTo(ClassLevelProviderTestCase.class); return emptySet(); } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { isProvideForNestedClassCalled = true; + assertEquals(List.of(this.testClass), enclosingInstanceTypes); assertEquals(ClassLevelProviderTestCase.NestedClass.class, testClass); return emptySet(); } @Override - public Set provideForMethod(Class testClass, Method testMethod) { - if (testClass == ClassLevelProviderTestCase.class) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + if (ClassLevelProviderTestCase.class.isAssignableFrom(testClass)) { + assertEquals(List.of(), enclosingInstanceTypes); assertEquals("test", testMethod.getName()); isProvideForTestMethodCalled = true; return emptySet(); } if (testClass == ClassLevelProviderTestCase.NestedClass.class) { + assertEquals(List.of(this.testClass), enclosingInstanceTypes); assertEquals("nestedTest", testMethod.getName()); isProvideForNestedTestMethodCalled = true; return emptySet(); @@ -140,6 +154,9 @@ public Set provideForMethod(Class testClass, Method testMethod) { } } + static class SubClassLevelProviderTestCase extends ClassLevelProviderTestCase { + } + @SuppressWarnings("JUnitMalformedDeclaration") static class NestedClassLevelProviderTestCase { @@ -177,15 +194,18 @@ public Set provideForClass(Class testClass) { } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { isProvideForNestedClassCalled = true; + assertEquals(List.of(NestedClassLevelProviderTestCase.class), enclosingInstanceTypes); assertEquals(NestedClass.class, testClass); return emptySet(); } @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { isProvideForMethodCalled = true; + assertEquals(List.of(NestedClassLevelProviderTestCase.class), enclosingInstanceTypes); assertEquals(NestedClassLevelProviderTestCase.NestedClass.class, testClass); assertEquals("nestedTest", testMethod.getName()); return emptySet(); @@ -226,14 +246,16 @@ public Set provideForClass(Class testClass) { } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { fail("'provideForNestedClass' should not be called"); return emptySet(); } @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { isProvideForMethodCalled = true; + assertEquals(List.of(), enclosingInstanceTypes); assertEquals(MethodLevelProviderTestCase.class, testClass); assertEquals("test", testMethod.getName()); return emptySet(); @@ -274,14 +296,16 @@ public Set provideForClass(Class testClass) { } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { fail("'provideForNestedClass' should not be called"); return emptySet(); } @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { isProvideForMethodCalled = true; + assertEquals(List.of(MethodLevelProviderInNestedClassTestCase.class), enclosingInstanceTypes); assertEquals(MethodLevelProviderInNestedClassTestCase.NestedClass.class, testClass); assertEquals("nestedTest", testMethod.getName()); return emptySet(); From 282a09132ce2052a55e702e76974720d99194ed6 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 25 Jan 2025 18:38:37 +0100 Subject: [PATCH 2/8] Remove debug Co-authored-by: Sam Brannen <104798+sbrannen@users.noreply.github.com> --- .../junit/jupiter/api/parallel/ResourceLocksProviderTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java index 609f15d1832b..f5bc7a17b77b 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java @@ -68,7 +68,7 @@ void providesAccessToRuntimeEnclosingInstances() { } private Stream execute(Class testCase) { - return executeTestsForClass(testCase).allEvents().debug().stream(); + return executeTestsForClass(testCase).allEvents().stream(); } // ------------------------------------------------------------------------- From 54496a827e971d51ac3b5ce79b7becb1821c4e79 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 25 Jan 2025 18:40:21 +0100 Subject: [PATCH 3/8] Use monospace for code link Co-authored-by: Sam Brannen <104798+sbrannen@users.noreply.github.com> --- .../org/junit/jupiter/api/parallel/ResourceLocksProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java index a629249f9fde..b0a384d64da7 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java @@ -62,7 +62,7 @@ default Set provideForClass(Class testClass) { /** * Add shared resources for a - * {@linkplain org.junit.jupiter.api.Nested @Nested} test class. + * {@link org.junit.jupiter.api.Nested @Nested} test class. * *

      Invoked in case: *

        From 96fdfbdae6797a3e6091df1061568c756f67d126 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 25 Jan 2025 18:42:58 +0100 Subject: [PATCH 4/8] Improve Javadoc for enclosingInstanceTypes --- .../api/parallel/ResourceLocksProvider.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java index b0a384d64da7..21d75579d728 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java @@ -76,8 +76,14 @@ default Set provideForClass(Class testClass) { * the same semantics as annotating a nested test class with an analogous * {@code @ResourceLock(value, mode)} declaration. * + * @implNote The classes supplied as {@code enclosingInstanceTypes} may + * differ from the classes returned from invocations of + * {@link Class#getEnclosingClass()} — for example, when a nested test + * class is inherited from a superclass. + * * @param enclosingInstanceTypes the runtime types of the enclosing - * instances; never {@code null} + * instances of {@code clazz}, ordered from outermost to innermost, + * excluding {@code class}; never {@code null} * @param testClass a nested test class for which to add shared resources * @return a set of {@link Lock}; may be empty * @see org.junit.jupiter.api.Nested @Nested @@ -100,8 +106,17 @@ default Set provideForNestedClass(List> enclosingInstanceTypes, C * has the same semantics as annotating a test method * with analogous {@code @ResourceLock(value, mode)}. * + * @implNote The classes supplied as {@code enclosingInstanceTypes} may + * differ from the classes returned from invocations of + * {@link Class#getEnclosingClass()} — for example, when a nested test + * class is inherited from a superclass. Similarly, the class instance + * supplied as {@code testClass} may differ from the class returned by + * {@code testMethod.getDeclaringClass()} — for example, when a test + * method is inherited from a superclass. + * * @param enclosingInstanceTypes the runtime types of the enclosing - * instances; never {@code null} + * instances of {@code clazz}, ordered from outermost to innermost, + * excluding {@code class}; never {@code null} * @param testClass the test class or {@link org.junit.jupiter.api.Nested @Nested} * test class that contains the {@code testMethod} * @param testMethod a test method for which to add shared resources From cea2e5a61cf8529d0d26840ddccee5d39ba82eab Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 25 Jan 2025 18:44:19 +0100 Subject: [PATCH 5/8] Use dynamic cast Co-authored-by: Sam Brannen <104798+sbrannen@users.noreply.github.com> --- .../jupiter/engine/descriptor/MethodBasedTestDescriptor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java index f7d6e18bd319..d8fbd326e9d9 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java @@ -101,7 +101,8 @@ public ExclusiveResourceCollector getExclusiveResourceCollector() { public Set evaluateResourceLocksProvider(ResourceLocksProvider provider) { List> enclosingInstanceTypes = getParent() // .filter(ClassBasedTestDescriptor.class::isInstance) // - .map(parent -> ((ClassBasedTestDescriptor) parent).getEnclosingTestClasses()) // + .map(ClassBasedTestDescriptor.class::cast) // + .map(ClassBasedTestDescriptor::getEnclosingTestClasses) // .orElseGet(Collections::emptyList); return provider.provideForMethod(enclosingInstanceTypes, getTestClass(), getTestMethod()); } From fb57bebb60e3b3ff97492d81b53162c73282e3d9 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 26 Jan 2025 10:31:24 +0100 Subject: [PATCH 6/8] Avoid repeatedly computing enclosing instance types --- .../engine/descriptor/ClassTestDescriptor.java | 5 +++-- .../descriptor/MethodBasedTestDescriptor.java | 5 +++-- .../descriptor/NestedClassTestDescriptor.java | 6 ++++-- .../engine/descriptor/ResourceLockAware.java | 17 +++++++++++------ 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java index 7c2a5571694c..e935db38e782 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.TestInstances; @@ -79,8 +80,8 @@ protected TestInstances instantiateTestClass(JupiterEngineExecutionContext paren } @Override - public Set evaluateResourceLocksProvider(ResourceLocksProvider provider) { - return provider.provideForClass(getTestClass()); + public Function> getResourceLocksProviderEvaluator() { + return provider -> provider.provideForClass(getTestClass()); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java index d8fbd326e9d9..d786fd5dc64c 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java @@ -22,6 +22,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; import org.apiguardian.api.API; @@ -98,13 +99,13 @@ public ExclusiveResourceCollector getExclusiveResourceCollector() { } @Override - public Set evaluateResourceLocksProvider(ResourceLocksProvider provider) { + public Function> getResourceLocksProviderEvaluator() { List> enclosingInstanceTypes = getParent() // .filter(ClassBasedTestDescriptor.class::isInstance) // .map(ClassBasedTestDescriptor.class::cast) // .map(ClassBasedTestDescriptor::getEnclosingTestClasses) // .orElseGet(Collections::emptyList); - return provider.provideForMethod(enclosingInstanceTypes, getTestClass(), getTestMethod()); + return provider -> provider.provideForMethod(enclosingInstanceTypes, getTestClass(), getTestMethod()); } @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java index 79f3a962c61b..6c3a526b7804 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import java.util.function.Supplier; import org.apiguardian.api.API; @@ -94,8 +95,9 @@ protected TestInstances instantiateTestClass(JupiterEngineExecutionContext paren } @Override - public Set evaluateResourceLocksProvider(ResourceLocksProvider provider) { - return provider.provideForNestedClass(getEnclosingTestClasses(), getTestClass()); + public Function> getResourceLocksProviderEvaluator() { + List> enclosingTestClasses = getEnclosingTestClasses(); + return provider -> provider.provideForNestedClass(enclosingTestClasses, getTestClass()); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java index e7aa88edea52..f97fd722de78 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java @@ -15,6 +15,7 @@ import java.util.ArrayDeque; import java.util.Deque; import java.util.Set; +import java.util.function.Function; import java.util.stream.Stream; import org.junit.jupiter.api.parallel.ResourceLocksProvider; @@ -35,8 +36,10 @@ default Stream determineExclusiveResources() { parent = parent.getParent().orElse(null); } + Function> evaluator = getResourceLocksProviderEvaluator(); + if (ancestors.isEmpty()) { - return determineOwnExclusiveResources(); + return determineOwnExclusiveResources(evaluator); } Stream parentStaticResourcesForChildren = ancestors.getLast() // @@ -44,18 +47,20 @@ default Stream determineExclusiveResources() { Stream ancestorDynamicResources = ancestors.stream() // .map(ResourceLockAware::getExclusiveResourceCollector) // - .flatMap(collector -> collector.getDynamicResources(this::evaluateResourceLocksProvider)); + .flatMap(collector -> collector.getDynamicResources(evaluator)); - return Stream.of(ancestorDynamicResources, parentStaticResourcesForChildren, determineOwnExclusiveResources())// + return Stream.of(ancestorDynamicResources, parentStaticResourcesForChildren, + determineOwnExclusiveResources(evaluator))// .flatMap(s -> s); } - default Stream determineOwnExclusiveResources() { - return this.getExclusiveResourceCollector().getAllExclusiveResources(this::evaluateResourceLocksProvider); + default Stream determineOwnExclusiveResources( + Function> providerToLocks) { + return this.getExclusiveResourceCollector().getAllExclusiveResources(providerToLocks); } ExclusiveResourceCollector getExclusiveResourceCollector(); - Set evaluateResourceLocksProvider(ResourceLocksProvider provider); + Function> getResourceLocksProviderEvaluator(); } From 254c78d3a1419b39709e959d550bcfcbe79efd5b Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 26 Jan 2025 17:37:40 +0100 Subject: [PATCH 7/8] Improve Javadoc Co-authored-by: Sam Brannen <104798+sbrannen@users.noreply.github.com> --- .../junit/jupiter/api/parallel/ResourceLocksProvider.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java index 21d75579d728..2cffc5d6101f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java @@ -82,8 +82,8 @@ default Set provideForClass(Class testClass) { * class is inherited from a superclass. * * @param enclosingInstanceTypes the runtime types of the enclosing - * instances of {@code clazz}, ordered from outermost to innermost, - * excluding {@code class}; never {@code null} + * instances for the test class, ordered from outermost to innermost, + * excluding {@code testClass}; never {@code null} * @param testClass a nested test class for which to add shared resources * @return a set of {@link Lock}; may be empty * @see org.junit.jupiter.api.Nested @Nested @@ -115,8 +115,8 @@ default Set provideForNestedClass(List> enclosingInstanceTypes, C * method is inherited from a superclass. * * @param enclosingInstanceTypes the runtime types of the enclosing - * instances of {@code clazz}, ordered from outermost to innermost, - * excluding {@code class}; never {@code null} + * instances for the test class, ordered from outermost to innermost, + * excluding {@code testClass}; never {@code null} * @param testClass the test class or {@link org.junit.jupiter.api.Nested @Nested} * test class that contains the {@code testMethod} * @param testMethod a test method for which to add shared resources From 48461277df10c8a9b21dfe2f9a15019ea31144ed Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 26 Jan 2025 10:41:32 +0100 Subject: [PATCH 8/8] Avoid computing enclosing instance types unnecessarily --- .../descriptor/MethodBasedTestDescriptor.java | 10 ++++++++-- .../descriptor/NestedClassTestDescriptor.java | 5 +++-- .../engine/descriptor/ResourceLockAware.java | 20 +++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java index d786fd5dc64c..3a5b785591f8 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.api.parallel.ResourceLockTarget.CHILDREN; import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.determineDisplayNameForMethod; +import static org.junit.jupiter.engine.descriptor.ResourceLockAware.enclosingInstanceTypesDependentResourceLocksProviderEvaluator; import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import java.lang.reflect.Method; @@ -100,12 +101,17 @@ public ExclusiveResourceCollector getExclusiveResourceCollector() { @Override public Function> getResourceLocksProviderEvaluator() { - List> enclosingInstanceTypes = getParent() // + return enclosingInstanceTypesDependentResourceLocksProviderEvaluator(this::getEnclosingTestClasses, + (provider, enclosingInstanceTypes) -> provider.provideForMethod(enclosingInstanceTypes, getTestClass(), + getTestMethod())); + } + + private List> getEnclosingTestClasses() { + return getParent() // .filter(ClassBasedTestDescriptor.class::isInstance) // .map(ClassBasedTestDescriptor.class::cast) // .map(ClassBasedTestDescriptor::getEnclosingTestClasses) // .orElseGet(Collections::emptyList); - return provider -> provider.provideForMethod(enclosingInstanceTypes, getTestClass(), getTestMethod()); } @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java index 6c3a526b7804..b0619fa09cff 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java @@ -13,6 +13,7 @@ import static java.util.Collections.emptyList; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForNestedClass; +import static org.junit.jupiter.engine.descriptor.ResourceLockAware.enclosingInstanceTypesDependentResourceLocksProviderEvaluator; import java.util.ArrayList; import java.util.LinkedHashSet; @@ -96,8 +97,8 @@ protected TestInstances instantiateTestClass(JupiterEngineExecutionContext paren @Override public Function> getResourceLocksProviderEvaluator() { - List> enclosingTestClasses = getEnclosingTestClasses(); - return provider -> provider.provideForNestedClass(enclosingTestClasses, getTestClass()); + return enclosingInstanceTypesDependentResourceLocksProviderEvaluator(this::getEnclosingTestClasses, (provider, + enclosingInstanceTypes) -> provider.provideForNestedClass(enclosingInstanceTypes, getTestClass())); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java index f97fd722de78..c4f427989036 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java @@ -14,8 +14,11 @@ import java.util.ArrayDeque; import java.util.Deque; +import java.util.List; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Stream; import org.junit.jupiter.api.parallel.ResourceLocksProvider; @@ -63,4 +66,21 @@ default Stream determineOwnExclusiveResources( Function> getResourceLocksProviderEvaluator(); + static Function> enclosingInstanceTypesDependentResourceLocksProviderEvaluator( + Supplier>> enclosingInstanceTypesSupplier, + BiFunction>, Set> evaluator) { + return new Function>() { + + private List> enclosingInstanceTypes; + + @Override + public Set apply(ResourceLocksProvider provider) { + if (this.enclosingInstanceTypes == null) { + this.enclosingInstanceTypes = enclosingInstanceTypesSupplier.get(); + } + return evaluator.apply(provider, this.enclosingInstanceTypes); + } + }; + } + }