-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Add virtual thread support #3443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
3860131
3d2c43a
8cab1aa
0d65d96
baa5793
18aee3b
54b68db
fb9f2b7
105948f
9ff4567
d356b2b
9bc7138
c8bb1a0
673be2e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,9 @@ abstract class RunConsoleLauncher @Inject constructor(private val execOperations | |
@get:Classpath | ||
abstract val runtimeClasspath: ConfigurableFileCollection | ||
|
||
@get:Input | ||
abstract val jvmArgs: ListProperty<String> | ||
|
||
@get:Input | ||
abstract val args: ListProperty<String> | ||
|
||
|
@@ -62,6 +65,7 @@ abstract class RunConsoleLauncher @Inject constructor(private val execOperations | |
val output = ByteArrayOutputStream() | ||
val result = execOperations.javaexec { | ||
executable = javaLauncher.get().executablePath.asFile.absolutePath | ||
jvmArgs([email protected]()) | ||
classpath = runtimeClasspath | ||
mainClass.set("org.junit.platform.console.ConsoleLauncher") | ||
args([email protected]()) | ||
|
@@ -82,6 +86,12 @@ abstract class RunConsoleLauncher @Inject constructor(private val execOperations | |
result.rethrowFailure().assertNormalExitValue() | ||
} | ||
|
||
@Suppress("unused") | ||
@Option(option = "jvm-args", description = "JVM args for the console launcher") | ||
fun setVMArgs(args: String) { | ||
jvmArgs.set(Commandline.translateCommandline(args).toList()) | ||
} | ||
|
||
@Suppress("unused") | ||
@Option(option = "args", description = "Additional command line arguments for the console launcher") | ||
fun setCliArgs(args: String) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -124,6 +124,8 @@ public final class Constants { | |
@API(status = STABLE, since = "5.10") | ||
public static final String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = JupiterConfiguration.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; | ||
|
||
public static final String PARALLEL_EXECUTOR_PROPERTY_NAME = JupiterConfiguration.PARALLEL_EXECUTOR_PROPERTY_NAME; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ needs an |
||
|
||
/** | ||
* Property name used to set the default test execution mode: {@value} | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -216,7 +216,7 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter | |
*/ | ||
public static final String CONFIG_CUSTOM_CLASS_PROPERTY_NAME = "custom.class"; | ||
|
||
static ParallelExecutionConfigurationStrategy getStrategy(ConfigurationParameters configurationParameters) { | ||
public static ParallelExecutionConfigurationStrategy getStrategy(ConfigurationParameters configurationParameters) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ Doesn't need to be public |
||
return valueOf( | ||
configurationParameters.get(CONFIG_STRATEGY_PROPERTY_NAME).orElse("dynamic").toUpperCase(Locale.ROOT)); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -208,10 +208,13 @@ public void compute() { | |
|
||
} | ||
|
||
static class WorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { | ||
public static class WorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ Doesn't need to be public |
||
|
||
private final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); | ||
|
||
public WorkerThreadFactory() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❓ Can be removed? |
||
} | ||
|
||
@Override | ||
public ForkJoinWorkerThread newThread(ForkJoinPool pool) { | ||
return new WorkerThread(pool, contextClassLoader); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/* | ||
* Copyright 2015-2023 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.platform.engine.support.hierarchical; | ||
|
||
import org.junit.platform.engine.ConfigurationParameters; | ||
|
||
public class VirtualThreadHierarchicalTestExecutorServiceFactory { | ||
|
||
public static HierarchicalTestExecutorService create( | ||
@SuppressWarnings("unused") ConfigurationParameters configurationParameters) { | ||
throw new IllegalArgumentException("The virtual executor is only supported on Java 21 and above"); | ||
} | ||
|
||
private VirtualThreadHierarchicalTestExecutorServiceFactory() { | ||
throw new AssertionError(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/* | ||
* Copyright 2015-2023 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.platform.engine.support.hierarchical; | ||
|
||
import static java.util.concurrent.CompletableFuture.completedFuture; | ||
import static java.util.function.Predicate.isEqual; | ||
import static java.util.stream.Collectors.toCollection; | ||
import static org.junit.platform.commons.util.FunctionUtils.where; | ||
import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.CONCURRENT; | ||
import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
import java.util.concurrent.ForkJoinPool; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.stream.Stream; | ||
|
||
import org.junit.platform.commons.util.ExceptionUtils; | ||
import org.junit.platform.engine.ConfigurationParameters; | ||
import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; | ||
|
||
class VirtualThreadHierarchicalTestExecutorService implements HierarchicalTestExecutorService { | ||
|
||
private final ClassLoader contextClassLoader; | ||
private final ForkJoinPool forkJoinPool; | ||
private final ExecutorService executorService; | ||
|
||
VirtualThreadHierarchicalTestExecutorService(ConfigurationParameters configurationParameters) { | ||
contextClassLoader = Thread.currentThread().getContextClassLoader(); | ||
var strategy = DefaultParallelExecutionConfigurationStrategy.getStrategy(configurationParameters); | ||
var configuration = strategy.createConfiguration(configurationParameters); | ||
var systemThreadFactory = new ForkJoinPoolHierarchicalTestExecutorService.WorkerThreadFactory(); | ||
forkJoinPool = new ForkJoinPool(configuration.getParallelism(), systemThreadFactory, null, false, | ||
configuration.getCorePoolSize(), configuration.getMaxPoolSize(), configuration.getMinimumRunnable(), null, | ||
configuration.getKeepAliveSeconds(), TimeUnit.SECONDS); | ||
var virtualThreadFactory = Thread.ofVirtual().name("junit-executor", 1).factory(); | ||
executorService = Executors.newThreadPerTaskExecutor(virtualThreadFactory); | ||
} | ||
|
||
@Override | ||
public CompletableFuture<Void> submit(TestTask testTask) { | ||
if (testTask.getExecutionMode() == CONCURRENT) { | ||
return CompletableFuture.runAsync(() -> executeWithLocksAndContextClassLoader(testTask), executorService); | ||
} | ||
executeWithLocks(testTask); | ||
return completedFuture(null); | ||
} | ||
|
||
private void executeWithLocksAndContextClassLoader(TestTask testTask) { | ||
Thread.currentThread().setContextClassLoader(contextClassLoader); | ||
executeWithLocks(testTask); | ||
} | ||
|
||
private void executeWithLocks(TestTask testTask) { | ||
var lock = testTask.getResourceLock(); | ||
try { | ||
lock.acquire(); | ||
testTask.execute(); | ||
} | ||
catch (InterruptedException e) { | ||
ExceptionUtils.throwAsUncheckedException(e); | ||
} | ||
finally { | ||
lock.release(); | ||
} | ||
} | ||
|
||
@Override | ||
public void invokeAll(List<? extends TestTask> testTasks) { | ||
var futures = submitAll(testTasks, CONCURRENT).collect(toCollection(ArrayList::new)); | ||
submitAll(testTasks, SAME_THREAD).forEach(futures::add); | ||
allOf(futures).join(); | ||
} | ||
|
||
private Stream<CompletableFuture<Void>> submitAll(List<? extends TestTask> testTasks, ExecutionMode mode) { | ||
return testTasks.stream().filter(where(TestTask::getExecutionMode, isEqual(mode))).map(this::submit); | ||
} | ||
|
||
private CompletableFuture<Void> allOf(List<CompletableFuture<Void>> futures) { | ||
return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)); | ||
} | ||
|
||
@Override | ||
public void close() { | ||
executorService.close(); | ||
forkJoinPool.close(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* Copyright 2015-2023 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.platform.engine.support.hierarchical; | ||
|
||
import org.junit.platform.engine.ConfigurationParameters; | ||
|
||
public class VirtualThreadHierarchicalTestExecutorServiceFactory { | ||
|
||
public static HierarchicalTestExecutorService create(ConfigurationParameters configurationParameters) { | ||
return new VirtualThreadHierarchicalTestExecutorService(configurationParameters); | ||
} | ||
|
||
private VirtualThreadHierarchicalTestExecutorServiceFactory() { | ||
throw new AssertionError(); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.