Skip to content

Commit e6d74ab

Browse files
committed
Acquire global read lock in presence of other exclusive resources
Prior to this commit, the global read lock was not acquired for test classes with `@ResourceLock` annotations causing `@Isolated` tests to run in parallel. Fixes #2605.
1 parent 267ce62 commit e6d74ab

File tree

4 files changed

+35
-11
lines changed

4 files changed

+35
-11
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.7.2.adoc

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@ GitHub.
1818
* Method `getRootUrisForPackage()` in class `ClasspathScanner` now returns a valid list of
1919
class names when the package name is equal to the name of a module on the module path
2020
and when running on Java 8.
21+
* Direct child descriptors of the engine descriptor now also acquire the global read lock
22+
when they require other exclusive resources.
2123

2224

2325
[[release-notes-5.7.2-junit-jupiter]]
2426
=== JUnit Jupiter
2527

2628
==== Bug Fixes
2729

28-
* ❓
30+
* Test classes annotated with `@ResourceLock` no longer run in parallel with `@Isolated`
31+
ones.
2932

3033

3134
[[release-notes-5.7.2-junit-vintage]]

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

+6-5
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,16 @@ private void walk(TestDescriptor globalLockDescriptor, TestDescriptor testDescri
7474
forceDescendantExecutionModeRecursively(advisor, globalLockDescriptor);
7575
advisor.useResourceLock(globalLockDescriptor, globalReadWriteLock);
7676
}
77+
if (globalLockDescriptor.equals(testDescriptor) && !allResources.contains(GLOBAL_READ_WRITE)) {
78+
allResources.add(GLOBAL_READ);
79+
}
7780
advisor.useResourceLock(testDescriptor, lockManager.getLockForResources(allResources));
7881
}
7982
}
8083

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));
84+
private void forceDescendantExecutionModeRecursively(NodeExecutionAdvisor advisor, TestDescriptor testDescriptor) {
85+
advisor.forceDescendantExecutionMode(testDescriptor, SAME_THREAD);
86+
doForChildrenRecursively(testDescriptor, child -> advisor.forceDescendantExecutionMode(child, SAME_THREAD));
8687
}
8788

8889
private boolean isReadOnly(Set<ExclusiveResource> exclusiveResources) {

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ void pullUpExclusiveChildResourcesToTestClass() {
4848

4949
var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren());
5050
assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) //
51-
.isEqualTo(List.of(getReadWriteLock("a"), getReadWriteLock("b")));
51+
.isEqualTo(List.of(getLock(GLOBAL_READ), getReadWriteLock("a"), getReadWriteLock("b")));
5252
assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty();
5353

5454
var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren());
@@ -64,7 +64,7 @@ void setsForceExecutionModeForChildrenWithWriteLocksOnClass() {
6464

6565
var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren());
6666
assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) //
67-
.isEqualTo(List.of(getReadWriteLock("a")));
67+
.isEqualTo(List.of(getLock(GLOBAL_READ), getReadWriteLock("a")));
6868
assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty();
6969

7070
var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren());
@@ -80,7 +80,7 @@ void doesntSetForceExecutionModeForChildrenWithReadLocksOnClass() {
8080

8181
var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren());
8282
assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) //
83-
.isEqualTo(List.of(getReadLock("a")));
83+
.isEqualTo(List.of(getLock(GLOBAL_READ), getReadLock("a")));
8484
assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty();
8585

8686
var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren());
@@ -96,7 +96,7 @@ void setsForceExecutionModeForChildrenWithReadLocksOnClassAndWriteLockOnTest() {
9696

9797
var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren());
9898
assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) //
99-
.isEqualTo(List.of(getReadWriteLock("a")));
99+
.isEqualTo(List.of(getLock(GLOBAL_READ), getReadWriteLock("a")));
100100
assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty();
101101

102102
var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren());
@@ -112,7 +112,7 @@ void doesntSetForceExecutionModeForChildrenWithReadLocksOnClassAndReadLockOnTest
112112

113113
var testClassDescriptor = getOnlyElement(engineDescriptor.getChildren());
114114
assertThat(advisor.getResourceLock(testClassDescriptor)).extracting(allLocks()) //
115-
.isEqualTo(List.of(getReadLock("a"), getReadLock("b")));
115+
.isEqualTo(List.of(getLock(GLOBAL_READ), getReadLock("a"), getReadLock("b")));
116116
assertThat(advisor.getForcedExecutionMode(testClassDescriptor)).isEmpty();
117117

118118
var testMethodDescriptor = getOnlyElement(testClassDescriptor.getChildren());

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

+20
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,13 @@ void canRunTestsIsolatedFromEachOtherAcrossClasses() {
243243
assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty();
244244
}
245245

246+
@RepeatedTest(10)
247+
void canRunTestsIsolatedFromEachOtherAcrossClassesWithOtherResourceLocks() {
248+
var events = executeConcurrently(4, IndependentClasses.B.class, IndependentClasses.C.class);
249+
250+
assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty();
251+
}
252+
246253
@Isolated("testing")
247254
static class IsolatedTestCase {
248255
static AtomicInteger sharedResource;
@@ -343,6 +350,19 @@ void b() throws Exception {
343350
storeAndBlockAndCheck(sharedResource, countDownLatch);
344351
}
345352
}
353+
354+
@ResourceLock("other")
355+
static class C {
356+
@Test
357+
void a() throws Exception {
358+
storeAndBlockAndCheck(sharedResource, countDownLatch);
359+
}
360+
361+
@Test
362+
void b() throws Exception {
363+
storeAndBlockAndCheck(sharedResource, countDownLatch);
364+
}
365+
}
346366
}
347367

348368
private List<Event> getEventsOfChildren(EngineExecutionResults results, TestDescriptor container) {

0 commit comments

Comments
 (0)