Skip to content

Commit

Permalink
Implement parallel execution at class and method level
Browse files Browse the repository at this point in the history
Issue: #4058
  • Loading branch information
YongGoose committed Jan 14, 2025
1 parent be5234f commit 36ac248
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.TestEngine;
import org.junit.platform.engine.UniqueId;
import org.junit.vintage.engine.descriptor.RunnerScheduler;
import org.junit.vintage.engine.descriptor.RunnerTestDescriptor;
import org.junit.vintage.engine.descriptor.VintageEngineDescriptor;
import org.junit.vintage.engine.discovery.VintageDiscoverer;
Expand Down Expand Up @@ -120,15 +121,21 @@ private boolean executeInParallel(VintageEngineDescriptor engineDescriptor,
ExecutorService executorService = Executors.newFixedThreadPool(getThreadPoolSize(request));
RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener);

List<RunnerTestDescriptor> runnerTestDescriptors = collectRunnerTestDescriptors(engineDescriptor,
executorService);

List<CompletableFuture<Void>> futures = new ArrayList<>();
for (Iterator<TestDescriptor> iterator = engineDescriptor.getModifiableChildren().iterator(); iterator.hasNext();) {
TestDescriptor descriptor = iterator.next();
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
runnerExecutor.execute((RunnerTestDescriptor) descriptor);
}, executorService);
if (!classes) {
for (RunnerTestDescriptor runnerTestDescriptor : runnerTestDescriptors) {
runnerExecutor.execute(runnerTestDescriptor);
}
return false;
}

for (RunnerTestDescriptor runnerTestDescriptor : runnerTestDescriptors) {
CompletableFuture<Void> future = CompletableFuture.runAsync(
() -> runnerExecutor.execute(runnerTestDescriptor), executorService);
futures.add(future);
iterator.remove();
}

CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0]));
Expand All @@ -149,6 +156,41 @@ private boolean executeInParallel(VintageEngineDescriptor engineDescriptor,
return wasInterrupted;
}

private RunnerTestDescriptor parallelMethodExecutor(RunnerTestDescriptor runnerTestDescriptor,
ExecutorService executorService) {
runnerTestDescriptor.setScheduler(new RunnerScheduler() {
@Override
public void schedule(Runnable childStatement) {
executorService.submit(childStatement);
}

@Override
public void finished() {
try {
executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
}
catch (InterruptedException e) {
logger.warn(e, () -> "Interruption while waiting for parallel test execution to finish");
}
}
});

return runnerTestDescriptor;
}

private List<RunnerTestDescriptor> collectRunnerTestDescriptors(VintageEngineDescriptor engineDescriptor,
ExecutorService executorService) {
List<RunnerTestDescriptor> runnerTestDescriptors = new ArrayList<>();
for (TestDescriptor descriptor : engineDescriptor.getModifiableChildren()) {
RunnerTestDescriptor runnerTestDescriptor = (RunnerTestDescriptor) descriptor;
if (methods) {
runnerTestDescriptors.add(parallelMethodExecutor(runnerTestDescriptor, executorService));
}
}
return runnerTestDescriptors;
}

private void shutdownExecutorService(ExecutorService executorService) {
try {
executorService.shutdown();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2015-2025 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.vintage.engine.descriptor;

import static org.apiguardian.api.API.Status.*;

import org.apiguardian.api.API;

/**
* Represents a strategy for scheduling when individual test methods
* should be run (in serial or parallel)
*
* @since 5.13
*/
@API(status = INTERNAL, since = "5.13")
public interface RunnerScheduler {
/**
* Schedule a child statement to run
*/
void schedule(Runnable childStatement);

/**
* Override to implement any behavior that must occur
* after all children have been scheduled (for example,
* waiting for them all to finish)
*/
void finished();
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ public class RunnerTestDescriptor extends VintageTestDescriptor {
private boolean wasFiltered;
private List<Filter> filters = new ArrayList<>();

private volatile RunnerScheduler scheduler = new RunnerScheduler() {
public void schedule(Runnable childStatement) {
childStatement.run();
}

public void finished() {
// do nothing
}
};

public RunnerTestDescriptor(UniqueId uniqueId, Class<?> testClass, Runner runner, boolean ignored) {
super(uniqueId, runner.getDescription(), testClass.getSimpleName(), ClassSource.from(testClass));
this.runner = runner;
Expand Down Expand Up @@ -161,6 +171,10 @@ public boolean isIgnored() {
return ignored;
}

public void setScheduler(RunnerScheduler scheduler) {
this.scheduler = scheduler;
}

private static class ExcludeDescriptionFilter extends Filter {

private final Description description;
Expand Down

0 comments on commit 36ac248

Please sign in to comment.