Skip to content

Commit b39a2ec

Browse files
leonard84marcphilipp
authored andcommitted
Allow parallel execution of child nodes if only read locks are acquired (#2515)
1 parent 0984740 commit b39a2ec

File tree

2 files changed

+126
-8
lines changed

2 files changed

+126
-8
lines changed

junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,21 +57,39 @@ private void walk(TestDescriptor globalLockDescriptor, TestDescriptor testDescri
5757
}
5858
else {
5959
Set<ExclusiveResource> allResources = new HashSet<>(exclusiveResources);
60-
advisor.forceDescendantExecutionMode(testDescriptor, SAME_THREAD);
61-
doForChildrenRecursively(testDescriptor, child -> {
62-
allResources.addAll(getExclusiveResources(child));
63-
advisor.forceDescendantExecutionMode(child, SAME_THREAD);
64-
});
60+
if (isReadOnly(allResources)) {
61+
doForChildrenRecursively(testDescriptor, child -> allResources.addAll(getExclusiveResources(child)));
62+
if (!isReadOnly(allResources)) {
63+
forceDescendantExecutionModeRecursively(advisor, testDescriptor);
64+
}
65+
}
66+
else {
67+
advisor.forceDescendantExecutionMode(testDescriptor, SAME_THREAD);
68+
doForChildrenRecursively(testDescriptor, child -> {
69+
allResources.addAll(getExclusiveResources(child));
70+
advisor.forceDescendantExecutionMode(child, SAME_THREAD);
71+
});
72+
}
6573
if (!globalLockDescriptor.equals(testDescriptor) && allResources.contains(GLOBAL_READ_WRITE)) {
66-
advisor.forceDescendantExecutionMode(globalLockDescriptor, SAME_THREAD);
67-
doForChildrenRecursively(globalLockDescriptor,
68-
child -> advisor.forceDescendantExecutionMode(child, SAME_THREAD));
74+
forceDescendantExecutionModeRecursively(advisor, globalLockDescriptor);
6975
advisor.useResourceLock(globalLockDescriptor, globalReadWriteLock);
7076
}
7177
advisor.useResourceLock(testDescriptor, lockManager.getLockForResources(allResources));
7278
}
7379
}
7480

81+
private void forceDescendantExecutionModeRecursively(NodeExecutionAdvisor advisor,
82+
TestDescriptor globalLockDescriptor) {
83+
advisor.forceDescendantExecutionMode(globalLockDescriptor, SAME_THREAD);
84+
doForChildrenRecursively(globalLockDescriptor,
85+
child -> advisor.forceDescendantExecutionMode(child, SAME_THREAD));
86+
}
87+
88+
private boolean isReadOnly(Set<ExclusiveResource> exclusiveResources) {
89+
return exclusiveResources.stream().allMatch(
90+
exclusiveResource -> exclusiveResource.getLockMode() == ExclusiveResource.LockMode.READ);
91+
}
92+
7593
private Set<ExclusiveResource> getExclusiveResources(TestDescriptor testDescriptor) {
7694
return NodeUtils.asNode(testDescriptor).getExclusiveResources();
7795
}

platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalkerIntegrationTests.java

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
1616
import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ;
1717
import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE;
18+
import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ;
1819
import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ_WRITE;
1920
import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD;
2021
import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
@@ -25,6 +26,7 @@
2526

2627
import org.junit.jupiter.api.Nested;
2728
import org.junit.jupiter.api.Test;
29+
import org.junit.jupiter.api.parallel.ResourceAccessMode;
2830
import org.junit.jupiter.api.parallel.ResourceLock;
2931
import org.junit.jupiter.engine.JupiterTestEngine;
3032
import org.junit.platform.engine.TestDescriptor;
@@ -54,6 +56,70 @@ void pullUpExclusiveChildResourcesToTestClass() {
5456
assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD);
5557
}
5658

59+
@Test
60+
void setsForceExecutionModeForChildrenWithWriteLocksOnClass() {
61+
var engineDescriptor = discover(TestCaseWithResourceWriteLockOnClass.class);
62+
63+
var advisor = nodeTreeWalker.walk(engineDescriptor);
64+
65+
var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren());
66+
assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) //
67+
.isEqualTo(List.of(getReadWriteLock("a")));
68+
assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty();
69+
70+
var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren());
71+
assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of());
72+
assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD);
73+
}
74+
75+
@Test
76+
void doesntSetForceExecutionModeForChildrenWithReadLocksOnClass() {
77+
var engineDescriptor = discover(TestCaseWithResourceReadLockOnClass.class);
78+
79+
var advisor = nodeTreeWalker.walk(engineDescriptor);
80+
81+
var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren());
82+
assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) //
83+
.isEqualTo(List.of(getReadLock("a")));
84+
assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty();
85+
86+
var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren());
87+
assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of());
88+
assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).isEmpty();
89+
}
90+
91+
@Test
92+
void setsForceExecutionModeForChildrenWithReadLocksOnClassAndWriteLockOnTest() {
93+
var engineDescriptor = discover(TestCaseWithResourceReadLockOnClassAndWriteClockOnTest.class);
94+
95+
var advisor = nodeTreeWalker.walk(engineDescriptor);
96+
97+
var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren());
98+
assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) //
99+
.isEqualTo(List.of(getReadWriteLock("a")));
100+
assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty();
101+
102+
var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren());
103+
assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of());
104+
assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD);
105+
}
106+
107+
@Test
108+
void doesntSetForceExecutionModeForChildrenWithReadLocksOnClassAndReadLockOnTest() {
109+
var engineDescriptor = discover(TestCaseWithResourceReadLockOnClassAndReadClockOnTest.class);
110+
111+
var advisor = nodeTreeWalker.walk(engineDescriptor);
112+
113+
var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren());
114+
assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) //
115+
.isEqualTo(List.of(getReadLock("a"), getReadLock("b")));
116+
assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty();
117+
118+
var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren());
119+
assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()).isEqualTo(List.of());
120+
assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).isEmpty();
121+
}
122+
57123
@Test
58124
void leavesResourceLockOnTestMethodWhenClassDoesNotUseResource() {
59125
var engineDescriptor = discover(TestCaseWithoutResourceLock.class);
@@ -112,6 +178,10 @@ private Lock getReadWriteLock(String key) {
112178
return getLock(new ExclusiveResource(key, READ_WRITE));
113179
}
114180

181+
private Lock getReadLock(String key) {
182+
return getLock(new ExclusiveResource(key, READ));
183+
}
184+
115185
private Lock getLock(ExclusiveResource exclusiveResource) {
116186
return getOnlyElement(ResourceLockSupport.getLocks(lockManager.getLockForResource(exclusiveResource)));
117187
}
@@ -154,4 +224,34 @@ void test() {
154224
}
155225
}
156226
}
227+
228+
@ResourceLock("a")
229+
static class TestCaseWithResourceWriteLockOnClass {
230+
@Test
231+
void test() {
232+
}
233+
}
234+
235+
@ResourceLock(value = "a", mode = ResourceAccessMode.READ)
236+
static class TestCaseWithResourceReadLockOnClass {
237+
@Test
238+
void test() {
239+
}
240+
}
241+
242+
@ResourceLock(value = "a", mode = ResourceAccessMode.READ)
243+
static class TestCaseWithResourceReadLockOnClassAndWriteClockOnTest {
244+
@Test
245+
@ResourceLock("a")
246+
void test() {
247+
}
248+
}
249+
250+
@ResourceLock(value = "a", mode = ResourceAccessMode.READ)
251+
static class TestCaseWithResourceReadLockOnClassAndReadClockOnTest {
252+
@Test
253+
@ResourceLock(value = "b", mode = ResourceAccessMode.READ)
254+
void test() {
255+
}
256+
}
157257
}

0 commit comments

Comments
 (0)