diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorService.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorService.java index bca7957b1d68..2dda88e233c4 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorService.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorService.java @@ -10,8 +10,7 @@ package org.junit.platform.engine.support.hierarchical; -import static java.util.Comparator.naturalOrder; -import static java.util.Comparator.reverseOrder; +import static java.util.Comparator.comparing; import static java.util.Objects.requireNonNull; import static java.util.concurrent.CompletableFuture.completedFuture; import static java.util.concurrent.TimeUnit.SECONDS; @@ -23,11 +22,13 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; import java.util.Deque; import java.util.EnumMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; @@ -291,14 +292,20 @@ void processQueueEntries(WorkerLease workerLease, BooleanSupplier doneCondition) } private void processQueueEntries() { - var queueEntriesByResult = tryToStealWorkWithoutBlocking(workQueue); - var queueModified = queueEntriesByResult.containsKey(WorkStealResult.EXECUTED_BY_THIS_WORKER) // - || queueEntriesByResult.containsKey(WorkStealResult.EXECUTED_BY_DIFFERENT_WORKER); - if (queueModified) { - return; + var entriesRequiringResourceLocks = new ArrayList(); + + for (var entry : workQueue) { + var result = tryToStealWork(entry, BlockingMode.NON_BLOCKING); + if (result == WorkStealResult.EXECUTED_BY_THIS_WORKER) { + // After executing a test a significant amount of time has passed. + // Process the queue from the beginning + return; + } + if (result == WorkStealResult.RESOURCE_LOCK_UNAVAILABLE) { + entriesRequiringResourceLocks.add(entry); + } } - var entriesRequiringResourceLocks = queueEntriesByResult.get(WorkStealResult.RESOURCE_LOCK_UNAVAILABLE); - if (entriesRequiringResourceLocks != null) { + if (!entriesRequiringResourceLocks.isEmpty()) { // One entry at a time to avoid blocking too much tryToStealWork(entriesRequiringResourceLocks.get(0), BlockingMode.BLOCKING); } @@ -333,11 +340,11 @@ void invokeAll(List testTasks) { List isolatedTasks = new ArrayList<>(testTasks.size()); List sameThreadTasks = new ArrayList<>(testTasks.size()); - var reverseQueueEntries = forkConcurrentChildren(testTasks, isolatedTasks::add, sameThreadTasks); + var queueEntries = forkConcurrentChildren(testTasks, isolatedTasks::add, sameThreadTasks); executeAll(sameThreadTasks); - var reverseQueueEntriesByResult = tryToStealWorkWithoutBlocking(reverseQueueEntries); - tryToStealWorkWithBlocking(reverseQueueEntriesByResult); - waitFor(reverseQueueEntriesByResult); + var queueEntriesByResult = tryToStealWorkWithoutBlocking(queueEntries); + tryToStealWorkWithBlocking(queueEntriesByResult); + waitFor(queueEntriesByResult); executeAll(isolatedTasks); } @@ -345,7 +352,6 @@ private List forkConcurrentChildren(List ch Consumer isolatedTaskCollector, List sameThreadTasks) { List queueEntries = new ArrayList<>(children.size()); - int index = 0; for (TestTask child : children) { if (requiresGlobalReadWriteLock(child)) { isolatedTaskCollector.accept(child); @@ -354,20 +360,19 @@ else if (child.getExecutionMode() == SAME_THREAD) { sameThreadTasks.add(child); } else { - queueEntries.add(workQueue.createEntry(child, index++)); + queueEntries.add(new WorkQueue.Entry(child, nextChildIndex())); } } if (!queueEntries.isEmpty()) { + queueEntries.sort(WorkQueue.Entry.CHILD_COMPARATOR); if (sameThreadTasks.isEmpty()) { // hold back one task for this thread - var lastEntry = queueEntries.stream().max(naturalOrder()).orElseThrow(); - queueEntries.remove(lastEntry); - sameThreadTasks.add(lastEntry.task); + var firstEntry = queueEntries.remove(0); + sameThreadTasks.add(firstEntry.task); } forkAll(queueEntries); } - queueEntries.sort(reverseOrder()); return queueEntries; } @@ -390,8 +395,8 @@ private void tryToStealWorkWithBlocking(Map entries, BlockingMode blocking, Map> queueEntriesByResult) { for (var entry : entries) { - var state = tryToStealWork(entry, blocking); - queueEntriesByResult.computeIfAbsent(state, __ -> new ArrayList<>()).add(entry); + var result = tryToStealWork(entry, blocking); + queueEntriesByResult.computeIfAbsent(result, __ -> new ArrayList<>()).add(entry); } } @@ -562,9 +567,10 @@ private void tryToStealWorkFromSubmittedChildren() { if (currentSubmittedChildren == null || currentSubmittedChildren.isEmpty()) { return; } - var iterator = currentSubmittedChildren.listIterator(currentSubmittedChildren.size()); - while (iterator.hasPrevious()) { - WorkQueue.Entry entry = iterator.previous(); + currentSubmittedChildren.sort(WorkQueue.Entry.CHILD_COMPARATOR); + var iterator = currentSubmittedChildren.iterator(); + while (iterator.hasNext()) { + WorkQueue.Entry entry = iterator.next(); var result = tryToStealWork(entry, BlockingMode.NON_BLOCKING); if (result.isExecuted()) { iterator.remove(); @@ -653,19 +659,15 @@ private enum BlockingMode { } private static class WorkQueue implements Iterable { - private final Set queue = new ConcurrentSkipListSet<>(); + + private final Set queue = new ConcurrentSkipListSet<>(Entry.QUEUE_COMPARATOR); Entry add(TestTask task, int index) { - Entry entry = createEntry(task, index); + Entry entry = new Entry(task, index); LOGGER.trace(() -> "forking: " + entry.task); return doAdd(entry); } - Entry createEntry(TestTask task, int index) { - var uniqueId = task.getTestDescriptor().getUniqueId(); - return new Entry(uniqueId, task, new CompletableFuture<>(), index); - } - void addAll(Collection entries) { entries.forEach(this::doAdd); } @@ -696,68 +698,106 @@ public Iterator iterator() { return queue.iterator(); } - private record Entry(UniqueId id, TestTask task, CompletableFuture<@Nullable Void> future, int index) - implements Comparable { + private static final class Entry { + + private static final Comparator QUEUE_COMPARATOR = comparing(Entry::level).reversed() // + .thenComparing(Entry::isContainer) // tests before containers + .thenComparing(Entry::index) // + .thenComparing(Entry::uniqueId, new SameLengthUniqueIdComparator()); + + private static final Comparator CHILD_COMPARATOR = comparing(Entry::isContainer).reversed() // containers before tests + .thenComparing(Entry::index); + + private final TestTask task; + private final CompletableFuture<@Nullable Void> future; + private final int index; @SuppressWarnings("FutureReturnValueIgnored") - Entry { - future.whenComplete((__, t) -> { + Entry(TestTask task, int index) { + this.future = new CompletableFuture<>(); + this.future.whenComplete((__, t) -> { if (t == null) { - LOGGER.trace(() -> "completed normally: " + this.task()); + LOGGER.trace(() -> "completed normally: " + task); } else { - LOGGER.trace(t, () -> "completed exceptionally: " + this.task()); + LOGGER.trace(t, () -> "completed exceptionally: " + task); } }); + this.task = task; + this.index = index; } - @Override - public int compareTo(Entry that) { - var result = Integer.compare(that.getLevel(), getLevel()); - if (result != 0) { - return result; - } - result = Boolean.compare(this.isContainer(), that.isContainer()); - if (result != 0) { - return result; - } - result = Integer.compare(that.index(), this.index()); - if (result != 0) { - return result; - } - return compareBy(that.id(), this.id()); + private int index() { + return this.index; } - private int compareBy(UniqueId a, UniqueId b) { - var aIterator = a.getSegments().iterator(); - var bIterator = b.getSegments().iterator(); + private int level() { + return uniqueId().getSegments().size(); + } - // ids have the same length - while (aIterator.hasNext()) { - var aCurrent = aIterator.next(); - var bCurrent = bIterator.next(); - int result = compareBy(aCurrent, bCurrent); - if (result != 0) { - return result; - } - } - return 0; + private boolean isContainer() { + return task.getTestDescriptor().isContainer(); + } + + private UniqueId uniqueId() { + return task.getTestDescriptor().getUniqueId(); + } + + CompletableFuture<@Nullable Void> future() { + return future; } - private int compareBy(UniqueId.Segment a, UniqueId.Segment b) { - int result = a.getType().compareTo(b.getType()); - if (result != 0) { - return result; + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; } - return a.getValue().compareTo(b.getValue()); + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + var that = (Entry) obj; + return Objects.equals(this.uniqueId(), that.uniqueId()) && this.index == that.index; } - private int getLevel() { - return this.id.getSegments().size(); + @Override + public int hashCode() { + return Objects.hash(uniqueId(), index); } - private boolean isContainer() { - return task.getTestDescriptor().isContainer(); + @Override + public String toString() { + return new ToStringBuilder(this) // + .append("task", task) // + .append("index", index) // + .toString(); + } + + private static class SameLengthUniqueIdComparator implements Comparator { + + @Override + public int compare(UniqueId a, UniqueId b) { + var aIterator = a.getSegments().iterator(); + var bIterator = b.getSegments().iterator(); + + // ids have the same length + while (aIterator.hasNext()) { + var aCurrent = aIterator.next(); + var bCurrent = bIterator.next(); + int result = compareBy(aCurrent, bCurrent); + if (result != 0) { + return result; + } + } + return 0; + } + + private static int compareBy(UniqueId.Segment a, UniqueId.Segment b) { + int result = a.getType().compareTo(b.getType()); + if (result != 0) { + return result; + } + return a.getValue().compareTo(b.getValue()); + } } } diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorServiceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorServiceTests.java index d8106368f6ae..2aa907489101 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorServiceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/WorkerThreadPoolHierarchicalTestExecutorServiceTests.java @@ -29,7 +29,6 @@ import java.net.URL; import java.net.URLClassLoader; import java.time.Instant; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -272,23 +271,26 @@ void invokeAllQueueEntriesSkipsOverUnavailableResources() throws Exception { var resourceLock = new SingleLock(exclusiveResource(LockMode.READ_WRITE), new ReentrantLock()); var lockFreeChildrenStarted = new CountDownLatch(2); - var child4Started = new CountDownLatch(1); + var child2Started = new CountDownLatch(1); Executable child1Behaviour = () -> { lockFreeChildrenStarted.countDown(); - child4Started.await(); + child2Started.await(); }; - Executable child4Behaviour = () -> { - child4Started.countDown(); + Executable child2Behaviour = () -> { + child2Started.countDown(); lockFreeChildrenStarted.await(); }; var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, child1Behaviour) // .withName("child1"); - var child2 = new TestTaskStub(ExecutionMode.CONCURRENT).withResourceLock(resourceLock) // - .withName("child2"); // - var child3 = new TestTaskStub(ExecutionMode.CONCURRENT, lockFreeChildrenStarted::countDown).withName("child3"); - var child4 = new TestTaskStub(ExecutionMode.CONCURRENT, child4Behaviour).withResourceLock(resourceLock) // + var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, child2Behaviour) // + .withResourceLock(resourceLock) // + .withName("child2"); + var child3 = new TestTaskStub(ExecutionMode.CONCURRENT) // + .withResourceLock(resourceLock) // + .withName("child3"); // + var child4 = new TestTaskStub(ExecutionMode.CONCURRENT, lockFreeChildrenStarted::countDown) // .withName("child4"); var children = List.of(child1, child2, child3, child4); var root = new TestTaskStub(ExecutionMode.CONCURRENT, () -> requiredService().invokeAll(children)) // @@ -298,24 +300,33 @@ void invokeAllQueueEntriesSkipsOverUnavailableResources() throws Exception { root.assertExecutedSuccessfully(); assertThat(children).allSatisfy(TestTaskStub::assertExecutedSuccessfully); - assertThat(child1.executionThread).isEqualTo(child3.executionThread); - assertThat(child2.startTime).isAfterOrEqualTo(child3.startTime); + assertThat(child1.executionThread).isEqualTo(child4.executionThread); + assertThat(child3.startTime).isAfterOrEqualTo(child4.startTime); } @Test void prioritizesChildrenOfStartedContainers() throws Exception { - service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2)); + service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2, 2)); - var leavesStarted = new CountDownLatch(2); + var leafSubmitted = new CountDownLatch(1); + var child2AndLeafStarted = new CountDownLatch(2); - var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, leavesStarted::await) // + var leaf = new TestTaskStub(ExecutionMode.CONCURRENT, child2AndLeafStarted::countDown) // + .withName("leaf").withLevel(3); + + Executable child3Behavior = () -> { + var future = requiredService().submit(leaf); + leafSubmitted.countDown(); + child2AndLeafStarted.await(); + future.get(); + }; + + var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, leafSubmitted::await) // .withName("child1").withLevel(2); - var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, leavesStarted::countDown) // + var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, child2AndLeafStarted::countDown) // .withName("child2").withLevel(2); - var leaf = new TestTaskStub(ExecutionMode.CONCURRENT, leavesStarted::countDown) // - .withName("leaf").withLevel(3); - var child3 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> requiredService().submit(leaf).get()) // - .withName("child3").withLevel(2); + var child3 = new TestTaskStub(ExecutionMode.CONCURRENT, child3Behavior) // + .withType(CONTAINER).withName("child3").withLevel(2); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> requiredService().invokeAll(List.of(child1, child2, child3))) // @@ -324,11 +335,10 @@ void prioritizesChildrenOfStartedContainers() throws Exception { service.submit(root).get(); root.assertExecutedSuccessfully(); - assertThat(List.of(child1, child2, leaf, child3)).allSatisfy(TestTaskStub::assertExecutedSuccessfully); - leaf.assertExecutedSuccessfully(); + assertThat(List.of(root, child1, child2, leaf, child3)).allSatisfy(TestTaskStub::assertExecutedSuccessfully); assertThat(leaf.startTime).isBeforeOrEqualTo(child2.startTime); - assertThat(leaf.executionThread).isSameAs(child3.executionThread); + assertThat(leaf.executionThread).isSameAs(child2.executionThread).isNotSameAs(child3.executionThread); } @Test @@ -453,33 +463,6 @@ public void release() { void executesChildrenInOrder() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(1, 1)); - var leaf1a = new TestTaskStub(ExecutionMode.CONCURRENT) // - .withName("leaf1a").withLevel(2); - var leaf1b = new TestTaskStub(ExecutionMode.CONCURRENT) // - .withName("leaf1b").withLevel(2); - var leaf1c = new TestTaskStub(ExecutionMode.CONCURRENT) // - .withName("leaf1c").withLevel(2); - var leaf1d = new TestTaskStub(ExecutionMode.CONCURRENT) // - .withName("leaf1d").withLevel(2); - - var root = new TestTaskStub(ExecutionMode.SAME_THREAD, - () -> requiredService().invokeAll(List.of(leaf1a, leaf1b, leaf1c, leaf1d))) // - .withName("root").withLevel(1); - - service.submit(root).get(); - - assertThat(List.of(root, leaf1a, leaf1b, leaf1c, leaf1d)) // - .allSatisfy(TestTaskStub::assertExecutedSuccessfully); - - assertThat(Stream.of(leaf1a, leaf1b, leaf1c, leaf1d)) // - .extracting(TestTaskStub::startTime) // - .isSorted(); - } - - @Test - void executesChildrenInInvokeAllOrder() throws Exception { - service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(1, 1)); - var leaf1a = new TestTaskStub(ExecutionMode.CONCURRENT) // .withName("leaf1a").withLevel(2); var leaf1b = new TestTaskStub(ExecutionMode.CONCURRENT) // @@ -507,52 +490,52 @@ void executesChildrenInInvokeAllOrder() throws Exception { } @Test - void workIsStolenInReverseOrder() throws Exception { + void testsAreStolenRatherThanContainers() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2, 2)); // Execute tasks pairwise CyclicBarrier cyclicBarrier = new CyclicBarrier(2); Executable behavior = cyclicBarrier::await; - // With half of the leaves to be executed normally - var leaf1a = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // - .withName("leaf1a").withLevel(2); - var leaf1b = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // - .withName("leaf1b").withLevel(2); - var leaf1c = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // - .withName("leaf1c").withLevel(2); - - // And half of the leaves to be stolen - var leaf2a = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // - .withName("leaf2a").withLevel(2); - var leaf2b = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // - .withName("leaf2b").withLevel(2); - var leaf2c = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // - .withName("leaf2c").withLevel(2); + // With half of the leaves being containers + var container1 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // + .withName("container1").withType(CONTAINER).withLevel(2); + var container2 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // + .withName("container2").withType(CONTAINER).withLevel(2); + var container3 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // + .withName("container3").withType(CONTAINER).withLevel(2); + + // And half of the leaves being tests, to be stolen + var test1 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // + .withName("test1").withType(TEST).withLevel(2); + var test2 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // + .withName("test2").withType(TEST).withLevel(2); + var test3 = new TestTaskStub(ExecutionMode.CONCURRENT, behavior) // + .withName("test3").withType(TEST).withLevel(2); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, - () -> requiredService().invokeAll(List.of(leaf1a, leaf1b, leaf1c, leaf2a, leaf2b, leaf2c))) // + () -> requiredService().invokeAll(List.of(container1, container2, container3, test1, test2, test3))) // .withName("root").withLevel(1); service.submit(root).get(); - assertThat(List.of(root, leaf1a, leaf1b, leaf1c, leaf2a, leaf2b, leaf2c)) // + assertThat(List.of(root, container1, container2, container3, test1, test2, test3)) // .allSatisfy(TestTaskStub::assertExecutedSuccessfully); - // If the last node was stolen. - assertThat(leaf1a.executionThread).isNotEqualTo(leaf2c.executionThread); - // Then it must follow that the last half of the nodes were stolen - assertThat(Stream.of(leaf1a, leaf1b, leaf1c)) // + // If the last test node was stolen + assertThat(container1.executionThread).isNotEqualTo(test3.executionThread); + // Then it must follow that the test nodes were stolen + assertThat(Stream.of(container1, container2, container3)) // .extracting(TestTaskStub::executionThread) // - .containsOnly(leaf1a.executionThread); - assertThat(Stream.of(leaf2a, leaf2b, leaf2c)) // + .containsOnly(container1.executionThread); + assertThat(Stream.of(test1, test2, test3)) // .extracting(TestTaskStub::executionThread) // - .containsOnly(leaf2c.executionThread); + .containsOnly(test3.executionThread); - assertThat(Stream.of(leaf1a, leaf1b, leaf1c)) // + assertThat(Stream.of(container1, container2, container3)) // .extracting(TestTaskStub::startTime) // .isSorted(); - assertThat(Stream.of(leaf2c, leaf2b, leaf2a)) // + assertThat(Stream.of(test1, test2, test3)) // .extracting(TestTaskStub::startTime) // .isSorted(); } @@ -587,6 +570,45 @@ void stealsDynamicChildren() throws Exception { assertThat(child2.executionThread).isEqualTo(root.executionThread).isNotEqualTo(child1.executionThread); } + @Test + void stealsDynamicChildrenInOrder() throws Exception { + service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2, 2)); + + var child1Started = new CountDownLatch(1); + var childrenSubmitted = new CountDownLatch(1); + var childrenFinished = new CountDownLatch(2); + var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> { + child1Started.countDown(); + childrenSubmitted.await(); + }) // + .withName("child1").withLevel(2); + var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, childrenFinished::countDown) // + .withName("child2").withLevel(2); + var child3 = new TestTaskStub(ExecutionMode.CONCURRENT, childrenFinished::countDown) // + .withName("child3").withLevel(2); + + var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> { + var future1 = requiredService().submit(child1); + child1Started.await(); + var future2 = requiredService().submit(child2); + var future3 = requiredService().submit(child3); + childrenSubmitted.countDown(); + childrenFinished.await(); + future1.get(); + future2.get(); + future3.get(); + }) // + .withName("root").withLevel(1); + + service.submit(root).get(); + + assertThat(Stream.of(root, child1, child2, child3)) // + .allSatisfy(TestTaskStub::assertExecutedSuccessfully); + assertThat(List.of(child1, child2, child3)) // + .extracting(TestTaskStub::startTime) // + .isSorted(); + } + @Test void executesDynamicChildrenInSubmitOrder() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(1, 1)); @@ -600,7 +622,7 @@ void executesDynamicChildrenInSubmitOrder() throws Exception { var child4 = new TestTaskStub(ExecutionMode.CONCURRENT) // .withName("child3").withLevel(2); - List children = new ArrayList<>(List.of(child1, child2, child3, child4)); + List children = Arrays.asList(child1, child2, child3, child4); Collections.shuffle(children); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> { @@ -682,31 +704,31 @@ void stealsSiblingDynamicChildrenOnly() throws Exception { service = new WorkerThreadPoolHierarchicalTestExecutorService(configuration(2, 3)); var child1Started = new CountDownLatch(1); - var child2Started = new CountDownLatch(1); - var leaf1ASubmitted = new CountDownLatch(1); - var leaf1AStarted = new CountDownLatch(1); - - var leaf1a = new TestTaskStub(ExecutionMode.CONCURRENT, () -> { - leaf1AStarted.countDown(); - child2Started.await(); - }) // - .withName("leaf1a").withLevel(3); + var child3Started = new CountDownLatch(1); + var leaf2ASubmitted = new CountDownLatch(1); + var leaf2AStarted = new CountDownLatch(1); var child1 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> { child1Started.countDown(); - leaf1ASubmitted.await(); + leaf2ASubmitted.await(); }) // .withName("child1").withLevel(2); - var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, child2Started::countDown) // - .withName("child2").withLevel(2); + var leaf2a = new TestTaskStub(ExecutionMode.CONCURRENT, () -> { + leaf2AStarted.countDown(); + child3Started.await(); + }) // + .withName("leaf1a").withLevel(3); - var child3 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> { - var futureA = requiredService().submit(leaf1a); - leaf1ASubmitted.countDown(); - leaf1AStarted.await(); + var child2 = new TestTaskStub(ExecutionMode.CONCURRENT, () -> { + var futureA = requiredService().submit(leaf2a); + leaf2ASubmitted.countDown(); + leaf2AStarted.await(); futureA.get(); }) // + .withName("child2").withType(CONTAINER).withLevel(2); + + var child3 = new TestTaskStub(ExecutionMode.CONCURRENT, child3Started::countDown) // .withName("child3").withLevel(2); var root = new TestTaskStub(ExecutionMode.SAME_THREAD, () -> { @@ -722,12 +744,12 @@ void stealsSiblingDynamicChildrenOnly() throws Exception { service.submit(root).get(); - assertThat(Stream.of(root, child1, child2, child3, leaf1a)) // + assertThat(Stream.of(root, child1, child2, leaf2a, child3)) // .allSatisfy(TestTaskStub::assertExecutedSuccessfully); - assertThat(child3.executionThread).isNotEqualTo(child1.executionThread).isNotEqualTo(child2.executionThread); - assertThat(child1.executionThread).isNotEqualTo(child2.executionThread); - assertThat(child1.executionThread).isEqualTo(leaf1a.executionThread); + assertThat(child2.executionThread).isNotEqualTo(child1.executionThread).isNotEqualTo(child3.executionThread); + assertThat(child1.executionThread).isNotEqualTo(child3.executionThread); + assertThat(child1.executionThread).isEqualTo(leaf2a.executionThread); } private static ExclusiveResource exclusiveResource(LockMode lockMode) {