From 38601317416df35acbbd7394e732fe35f32f92e8 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 16 Feb 2020 16:33:58 +0100 Subject: [PATCH 01/14] Add virtual thread support --- .github/actions/run-gradle/action.yml | 2 +- .github/workflows/main.yml | 2 +- gradle/config/checkstyle/suppressions.xml | 2 + ...uild.java-multi-release-sources.gradle.kts | 7 +- .../junitbuild/exec/RunConsoleLauncher.kt | 10 ++ .../org/junit/jupiter/engine/Constants.java | 2 + .../jupiter/engine/JupiterTestEngine.java | 11 ++- .../config/CachingJupiterConfiguration.java | 6 ++ .../config/DefaultJupiterConfiguration.java | 9 ++ .../engine/config/JupiterConfiguration.java | 6 ++ .../junit-platform-engine.gradle.kts | 22 +++++ ...arallelExecutionConfigurationStrategy.java | 2 +- ...inPoolHierarchicalTestExecutorService.java | 5 +- ...ierarchicalTestExecutorServiceFactory.java | 25 +++++ ...ThreadHierarchicalTestExecutorService.java | 99 +++++++++++++++++++ ...ierarchicalTestExecutorServiceFactory.java | 24 +++++ platform-tests/platform-tests.gradle.kts | 2 +- ...PoolParallelExecutionIntegrationTests.java | 18 ++++ .../ParallelExecutionIntegrationTests.java | 38 ++++--- ...tualParallelExecutionIntegrationTests.java | 25 +++++ 20 files changed, 294 insertions(+), 23 deletions(-) create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java create mode 100644 junit-platform-engine/src/main/java21/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorService.java create mode 100644 junit-platform-engine/src/main/java21/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java create mode 100644 platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolParallelExecutionIntegrationTests.java create mode 100644 platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/VirtualParallelExecutionIntegrationTests.java diff --git a/.github/actions/run-gradle/action.yml b/.github/actions/run-gradle/action.yml index a4b1bc129f6c..dff59fd576d3 100644 --- a/.github/actions/run-gradle/action.yml +++ b/.github/actions/run-gradle/action.yml @@ -12,7 +12,7 @@ runs: id: setup-gradle-jdk with: distribution: temurin - java-version: 17 + java-version: 19 - uses: gradle/gradle-build-action@v2 env: JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 676ec1a2f87a..075ecf2766f8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,7 +31,7 @@ jobs: uses: graalvm/setup-graalvm@v1 with: version: 'latest' - java-version: '17' + java-version: '19' components: 'native-image' github-token: ${{ secrets.GITHUB_TOKEN }} - name: Build diff --git a/gradle/config/checkstyle/suppressions.xml b/gradle/config/checkstyle/suppressions.xml index 05801fccce21..aec4637950ab 100644 --- a/gradle/config/checkstyle/suppressions.xml +++ b/gradle/config/checkstyle/suppressions.xml @@ -4,4 +4,6 @@ files="junit-platform-commons[\\/]src[\\/]main[\\/]java.+?[\\/]org[\\/]junit[\\/]platform[\\/]commons[\\/]util[\\/]*"/> + diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-multi-release-sources.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-multi-release-sources.gradle.kts index d1b05c6e00ce..2d8677d9c12b 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-multi-release-sources.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-multi-release-sources.gradle.kts @@ -6,7 +6,7 @@ plugins { val mavenizedProjects: List by rootProject.extra -listOf(9, 17).forEach { javaVersion -> +listOf(9, 17, 21).forEach { javaVersion -> val sourceSet = sourceSets.register("mainRelease${javaVersion}") { compileClasspath += sourceSets.main.get().output runtimeClasspath += sourceSets.main.get().output @@ -27,6 +27,11 @@ listOf(9, 17).forEach { javaVersion -> named(sourceSet.get().compileJavaTaskName).configure { options.release = javaVersion + if (javaVersion == 21) { + javaCompiler.set(javaToolchains.compilerFor { + languageVersion.set(JavaLanguageVersion.of(javaVersion)) + }) + } } named("checkstyle${sourceSet.name.capitalized()}").configure { diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt index a92f31195ad2..05f0a5b90397 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt @@ -25,6 +25,9 @@ abstract class RunConsoleLauncher @Inject constructor(private val execOperations @get:Classpath abstract val runtimeClasspath: ConfigurableFileCollection + @get:Input + abstract val jvmArgs: ListProperty + @get:Input abstract val args: ListProperty @@ -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(this@RunConsoleLauncher.jvmArgs.get()) classpath = runtimeClasspath mainClass.set("org.junit.platform.console.ConsoleLauncher") args(this@RunConsoleLauncher.args.get()) @@ -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) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java index 2c7b509c9964..49692893bf80 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java @@ -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; + /** * Property name used to set the default test execution mode: {@value} * diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java index dbd799b7f5b2..a00f0690f4c6 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java @@ -11,6 +11,7 @@ package org.junit.jupiter.engine; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.engine.config.JupiterConfiguration.ParallelExecutor.VIRTUAL; import java.util.Optional; @@ -22,6 +23,7 @@ import org.junit.jupiter.engine.discovery.DiscoverySelectorResolver; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory; +import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; @@ -31,6 +33,7 @@ import org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine; import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; +import org.junit.platform.engine.support.hierarchical.VirtualThreadHierarchicalTestExecutorServiceFactory; /** * The JUnit Jupiter {@link org.junit.platform.engine.TestEngine TestEngine}. @@ -74,8 +77,12 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId protected HierarchicalTestExecutorService createExecutorService(ExecutionRequest request) { JupiterConfiguration configuration = getJupiterConfiguration(request); if (configuration.isParallelExecutionEnabled()) { - return new ForkJoinPoolHierarchicalTestExecutorService(new PrefixedConfigurationParameters( - request.getConfigurationParameters(), Constants.PARALLEL_CONFIG_PREFIX)); + ConfigurationParameters configurationParameters = new PrefixedConfigurationParameters( + request.getConfigurationParameters(), Constants.PARALLEL_CONFIG_PREFIX); + if (configuration.getParallelExecutor() == VIRTUAL) { + return VirtualThreadHierarchicalTestExecutorServiceFactory.create(configurationParameters); + } + return new ForkJoinPoolHierarchicalTestExecutorService(configurationParameters); } return super.createExecutorService(request); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java index 2d61b58c1c32..ce3a482718ac 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java @@ -67,6 +67,12 @@ public boolean isExtensionAutoDetectionEnabled() { key -> delegate.isExtensionAutoDetectionEnabled()); } + @Override + public ParallelExecutor getParallelExecutor() { + return (ParallelExecutor) cache.computeIfAbsent(PARALLEL_EXECUTOR_PROPERTY_NAME, + key -> delegate.getParallelExecutor()); + } + @Override public ExecutionMode getDefaultExecutionMode() { return (ExecutionMode) cache.computeIfAbsent(DEFAULT_EXECUTION_MODE_PROPERTY_NAME, diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java index d64c4ceee318..8696eb50648f 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java @@ -41,6 +41,9 @@ @API(status = INTERNAL, since = "5.4") public class DefaultJupiterConfiguration implements JupiterConfiguration { + private static final EnumConfigurationParameterConverter parallelExecutorConverter = // + new EnumConfigurationParameterConverter<>(ParallelExecutor.class, "parallel executor"); + private static final EnumConfigurationParameterConverter executionModeConverter = // new EnumConfigurationParameterConverter<>(ExecutionMode.class, "parallel execution mode"); @@ -89,6 +92,12 @@ public boolean isExtensionAutoDetectionEnabled() { return configurationParameters.getBoolean(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME).orElse(false); } + @Override + public ParallelExecutor getParallelExecutor() { + return parallelExecutorConverter.get(configurationParameters, PARALLEL_EXECUTOR_PROPERTY_NAME, + ParallelExecutor.FORK_JOIN_POOL); + } + @Override public ExecutionMode getDefaultExecutionMode() { return executionModeConverter.get(configurationParameters, DEFAULT_EXECUTION_MODE_PROPERTY_NAME, diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java index 559b4d7d5715..0303c08009b2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java @@ -36,6 +36,7 @@ public interface JupiterConfiguration { String DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME = "junit.jupiter.conditions.deactivate"; String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.parallel.enabled"; + String PARALLEL_EXECUTOR_PROPERTY_NAME = "junit.jupiter.execution.parallel.executor"; String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.enabled"; @@ -52,6 +53,8 @@ public interface JupiterConfiguration { boolean isExtensionAutoDetectionEnabled(); + ParallelExecutor getParallelExecutor(); + ExecutionMode getDefaultExecutionMode(); ExecutionMode getDefaultClassesExecutionMode(); @@ -70,4 +73,7 @@ public interface JupiterConfiguration { Supplier getDefaultTempDirFactorySupplier(); + enum ParallelExecutor { + FORK_JOIN_POOL, VIRTUAL + } } diff --git a/junit-platform-engine/junit-platform-engine.gradle.kts b/junit-platform-engine/junit-platform-engine.gradle.kts index 416b227b00c1..e3d8a1419d7f 100644 --- a/junit-platform-engine/junit-platform-engine.gradle.kts +++ b/junit-platform-engine/junit-platform-engine.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.java-multi-release-sources") `java-test-fixtures` } @@ -17,3 +18,24 @@ dependencies { osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } + +tasks.jar { + val release21ClassesDir = project.sourceSets.mainRelease21.get().output.classesDirs.singleFile + inputs.dir(release21ClassesDir).withPathSensitivity(PathSensitivity.RELATIVE) + doLast(objects.newInstance(junitbuild.java.ExecJarAction::class).apply { + javaLauncher.set(project.javaToolchains.launcherFor(java.toolchain)) + args.addAll( + "--update", + "--file", archiveFile.get().asFile.absolutePath, + "--release", "21", + "-C", release21ClassesDir.absolutePath, "." + ) + }) +} + + +eclipse { + classpath { + sourceSets -= project.sourceSets.mainRelease21.get() + } +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java index a37d9ea35c1c..1dd6dbdbb0cd 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java @@ -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) { return valueOf( configurationParameters.get(CONFIG_STRATEGY_PROPERTY_NAME).orElse("dynamic").toUpperCase(Locale.ROOT)); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java index 7f3fc2214c08..61307fd5e030 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java @@ -208,10 +208,13 @@ public void compute() { } - static class WorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { + public static class WorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { private final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + public WorkerThreadFactory() { + } + @Override public ForkJoinWorkerThread newThread(ForkJoinPool pool) { return new WorkerThread(pool, contextClassLoader); diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java new file mode 100644 index 000000000000..0a9178e8a96e --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java @@ -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(); + } +} diff --git a/junit-platform-engine/src/main/java21/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorService.java b/junit-platform-engine/src/main/java21/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorService.java new file mode 100644 index 000000000000..85069057b270 --- /dev/null +++ b/junit-platform-engine/src/main/java21/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorService.java @@ -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 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 testTasks) { + var futures = submitAll(testTasks, CONCURRENT).collect(toCollection(ArrayList::new)); + submitAll(testTasks, SAME_THREAD).forEach(futures::add); + allOf(futures).join(); + } + + private Stream> submitAll(List testTasks, ExecutionMode mode) { + return testTasks.stream().filter(where(TestTask::getExecutionMode, isEqual(mode))).map(this::submit); + } + + private CompletableFuture allOf(List> futures) { + return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)); + } + + @Override + public void close() { + executorService.close(); + forkJoinPool.close(); + } +} diff --git a/junit-platform-engine/src/main/java21/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java b/junit-platform-engine/src/main/java21/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java new file mode 100644 index 000000000000..c8ac3bf42c4d --- /dev/null +++ b/junit-platform-engine/src/main/java21/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java @@ -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(); + } +} diff --git a/platform-tests/platform-tests.gradle.kts b/platform-tests/platform-tests.gradle.kts index f32a2852349d..58d4a5695bb1 100644 --- a/platform-tests/platform-tests.gradle.kts +++ b/platform-tests/platform-tests.gradle.kts @@ -63,7 +63,7 @@ tasks { useJUnitPlatform { excludeTags("exclude") } - jvmArgs("-Xmx1g") + jvmArgs("-Xmx1g", "--enable-preview") distribution { // Retry in a new JVM on Windows to improve chances of successful retries when // cached resources are used (e.g. in ClasspathScannerTests) diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolParallelExecutionIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolParallelExecutionIntegrationTests.java new file mode 100644 index 000000000000..912b76027d61 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolParallelExecutionIntegrationTests.java @@ -0,0 +1,18 @@ +/* + * 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; + +public class ForkJoinPoolParallelExecutionIntegrationTests extends ParallelExecutionIntegrationTests { + @Override + protected String getParallelExecutor() { + return "FORK_JOIN_POOL"; + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java index ca7433377bf1..fa140a4b6331 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.engine.Constants.PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME; import static org.junit.jupiter.engine.Constants.PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME; import static org.junit.jupiter.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; +import static org.junit.jupiter.engine.Constants.PARALLEL_EXECUTOR_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.event; @@ -74,7 +75,9 @@ /** * @since 1.3 */ -class ParallelExecutionIntegrationTests { +abstract class ParallelExecutionIntegrationTests { + + protected abstract String getParallelExecutor(); @Test void successfulParallelTest(TestReporter reporter) { @@ -89,7 +92,7 @@ void successfulParallelTest(TestReporter reporter) { assertThat(finishedTimestamps).hasSize(3); assertThat(startedTimestamps).allMatch(startTimestamp -> finishedTimestamps.stream().noneMatch( finishedTimestamp -> finishedTimestamp.isBefore(startTimestamp))); - assertThat(ThreadReporter.getThreadNames(events)).hasSize(3); + assertThreadNamesCount(events, 3); } @Test @@ -103,7 +106,7 @@ void successfulTestWithMethodLock() { var events = executeConcurrently(3, SuccessfulWithMethodLockTestCase.class); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); - assertThat(ThreadReporter.getThreadNames(events)).hasSize(3); + assertThreadNamesCount(events, 3); } @Test @@ -111,7 +114,7 @@ void successfulTestWithClassLock() { var events = executeConcurrently(3, SuccessfulWithClassLockTestCase.class); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); - assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); + assertThreadNamesCount(events, 1); } @Test @@ -119,7 +122,7 @@ void testCaseWithFactory() { var events = executeConcurrently(3, TestCaseWithTestFactory.class); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); - assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); + assertThreadNamesCount(events, 1); } @Test @@ -132,8 +135,8 @@ void customContextClassLoader() { var events = executeConcurrently(3, SuccessfulWithMethodLockTestCase.class); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); - assertThat(ThreadReporter.getThreadNames(events)).hasSize(3); assertThat(ThreadReporter.getLoaderNames(events)).containsExactly("(-:"); + assertThreadNamesCount(events, 3); } finally { currentThread.setContextClassLoader(currentLoader); @@ -153,7 +156,7 @@ void locksOnNestedTests() { var events = executeConcurrently(3, TestCaseWithNestedLocks.class); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(6); - assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); + assertThreadNamesCount(events, 1); } @Test @@ -181,7 +184,7 @@ void executesTestTemplatesWithResourceLocksInSameThread() { var events = executeConcurrently(2, ConcurrentTemplateTestCase.class); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(10); - assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); + assertThreadNamesCount(events, 1); } @Test @@ -193,13 +196,13 @@ void executesClassesInParallelIfEnabledViaConfigurationParameter() { ParallelClassesTestCaseB.class, ParallelClassesTestCaseC.class); results.testEvents().assertStatistics(stats -> stats.succeeded(9)); - assertThat(ThreadReporter.getThreadNames(results.allEvents().list())).hasSize(3); + assertThreadNamesCount(results.allEvents().list(), 3); var testClassA = findFirstTestDescriptor(results, container(ParallelClassesTestCaseA.class)); - assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassA))).hasSize(1); + assertThreadNamesCount(getEventsOfChildren(results, testClassA), 1); var testClassB = findFirstTestDescriptor(results, container(ParallelClassesTestCaseB.class)); - assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassB))).hasSize(1); + assertThreadNamesCount(getEventsOfChildren(results, testClassB), 1); var testClassC = findFirstTestDescriptor(results, container(ParallelClassesTestCaseC.class)); - assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassC))).hasSize(1); + assertThreadNamesCount(getEventsOfChildren(results, testClassC), 1); } @Test @@ -215,11 +218,11 @@ void executesMethodsInParallelIfEnabledViaConfigurationParameter() { results.testEvents().assertStatistics(stats -> stats.succeeded(9)); assertThat(ThreadReporter.getThreadNames(results.allEvents().list())).hasSizeGreaterThanOrEqualTo(3); var testClassA = findFirstTestDescriptor(results, container(ParallelMethodsTestCaseA.class)); - assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassA))).hasSize(3); + assertThreadNamesCount(getEventsOfChildren(results, testClassA), 3); var testClassB = findFirstTestDescriptor(results, container(ParallelMethodsTestCaseB.class)); - assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassB))).hasSize(3); + assertThreadNamesCount(getEventsOfChildren(results, testClassB), 3); var testClassC = findFirstTestDescriptor(results, container(ParallelMethodsTestCaseC.class)); - assertThat(ThreadReporter.getThreadNames(getEventsOfChildren(results, testClassC))).hasSize(3); + assertThreadNamesCount(getEventsOfChildren(results, testClassC), 3); } @Test @@ -394,6 +397,7 @@ private EngineExecutionResults executeWithFixedParallelism(int parallelism, Map< var discoveryRequest = request() .selectors(Arrays.stream(testClasses).map(DiscoverySelectors::selectClass).collect(toList())) .configurationParameter(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, String.valueOf(true)) + .configurationParameter(PARALLEL_EXECUTOR_PROPERTY_NAME, getParallelExecutor()) .configurationParameter(PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME, "fixed") .configurationParameter(PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, String.valueOf(parallelism)) .configurationParameters(configParams) @@ -402,6 +406,10 @@ private EngineExecutionResults executeWithFixedParallelism(int parallelism, Map< return EngineTestKit.execute("junit-jupiter", discoveryRequest); } + protected void assertThreadNamesCount(List events, int expectedCount) { + assertThat(ThreadReporter.getThreadNames(events)).hasSize(expectedCount); + } + // ------------------------------------------------------------------------- @ExtendWith(ThreadReporter.class) diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/VirtualParallelExecutionIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/VirtualParallelExecutionIntegrationTests.java new file mode 100644 index 000000000000..8f1e748ff2cc --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/VirtualParallelExecutionIntegrationTests.java @@ -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 static org.junit.jupiter.api.condition.JRE.JAVA_19; + +import org.junit.jupiter.api.condition.EnabledOnJre; + +@EnabledOnJre(value = JAVA_19, disabledReason = "Use Java 19 preview features") +public class VirtualParallelExecutionIntegrationTests extends ParallelExecutionIntegrationTests { + + @Override + protected String getParallelExecutor() { + return "virtual"; + } + +} From 3d2c43a700e576d410bd5358c722a372dc58b212 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 1 Sep 2023 14:22:00 +0200 Subject: [PATCH 02/14] Run Gradle and Graal with Java 17 --- .github/actions/run-gradle/action.yml | 2 +- .github/workflows/main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/run-gradle/action.yml b/.github/actions/run-gradle/action.yml index dff59fd576d3..a4b1bc129f6c 100644 --- a/.github/actions/run-gradle/action.yml +++ b/.github/actions/run-gradle/action.yml @@ -12,7 +12,7 @@ runs: id: setup-gradle-jdk with: distribution: temurin - java-version: 19 + java-version: 17 - uses: gradle/gradle-build-action@v2 env: JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 075ecf2766f8..676ec1a2f87a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,7 +31,7 @@ jobs: uses: graalvm/setup-graalvm@v1 with: version: 'latest' - java-version: '19' + java-version: '17' components: 'native-image' github-token: ${{ secrets.GITHUB_TOKEN }} - name: Build From 8cab1aa3f8a668b5c8a2ace011504d95ca747763 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 1 Sep 2023 14:25:57 +0200 Subject: [PATCH 03/14] Install JDK 21 from https://jdk.java.net --- .github/actions/setup-test-jdk/action.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/actions/setup-test-jdk/action.yml b/.github/actions/setup-test-jdk/action.yml index 4e8c96266c69..7c143de62601 100644 --- a/.github/actions/setup-test-jdk/action.yml +++ b/.github/actions/setup-test-jdk/action.yml @@ -9,3 +9,9 @@ runs: java-version: 8 - shell: bash run: echo "JDK8=$JAVA_HOME" >> $GITHUB_ENV + - uses: oracle-actions/setup-java@v1 + with: + website: jdk.java.net + release: 21 + - shell: bash + run: echo "JDK21=$JAVA_HOME" >> $GITHUB_ENV From 0d65d968c0d7cf3b9f55275e5f772b6ef897c064 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 1 Sep 2023 17:45:45 +0200 Subject: [PATCH 04/14] Only take into account main source set when compiling module descriptor --- .../main/kotlin/junitbuild.java-library-conventions.gradle.kts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts index 6eb7eb308ec8..992b59509871 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts @@ -147,9 +147,8 @@ val allMainClasses by tasks.registering { val prepareModuleSourceDir by tasks.registering(Sync::class) { from(moduleSourceDir) - from(sourceSets.matching { it.name.startsWith("main") }.map { it.allJava }) + from(sourceSets.main.map { it.allJava }) into(combinedModuleSourceDir.map { it.dir(javaModuleName) }) - duplicatesStrategy = DuplicatesStrategy.EXCLUDE } val compileModule by tasks.registering(JavaCompile::class) { From baa5793458e53b9a6f4406d2ea7cae41dd6d70d9 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 1 Sep 2023 17:46:47 +0200 Subject: [PATCH 05/14] Use at least JDK 21 when building an MR-JAR for JDK 21 --- junit-platform-engine/junit-platform-engine.gradle.kts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/junit-platform-engine/junit-platform-engine.gradle.kts b/junit-platform-engine/junit-platform-engine.gradle.kts index e3d8a1419d7f..cf45fe45b30c 100644 --- a/junit-platform-engine/junit-platform-engine.gradle.kts +++ b/junit-platform-engine/junit-platform-engine.gradle.kts @@ -23,7 +23,11 @@ tasks.jar { val release21ClassesDir = project.sourceSets.mainRelease21.get().output.classesDirs.singleFile inputs.dir(release21ClassesDir).withPathSensitivity(PathSensitivity.RELATIVE) doLast(objects.newInstance(junitbuild.java.ExecJarAction::class).apply { - javaLauncher.set(project.javaToolchains.launcherFor(java.toolchain)) + javaLauncher.set(project.javaToolchains.launcherFor { + languageVersion.set(java.toolchain.languageVersion.map { + if (it.canCompileOrRun(21)) it else JavaLanguageVersion.of(21) + }) + }) args.addAll( "--update", "--file", archiveFile.get().asFile.absolutePath, From 18aee3bcc0b5d8359cf015c8eafa5a119ddb70d7 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 1 Sep 2023 17:53:07 +0200 Subject: [PATCH 06/14] Add JDK 21 to all Gradle builds on GitHub Actions --- .github/actions/run-gradle/action.yml | 4 ++++ .github/actions/setup-test-jdk/action.yml | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/actions/run-gradle/action.yml b/.github/actions/run-gradle/action.yml index a4b1bc129f6c..b2a7f6c45725 100644 --- a/.github/actions/run-gradle/action.yml +++ b/.github/actions/run-gradle/action.yml @@ -13,6 +13,10 @@ runs: with: distribution: temurin java-version: 17 + - uses: oracle-actions/setup-java@v1 + with: + website: jdk.java.net + release: 21 - uses: gradle/gradle-build-action@v2 env: JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} diff --git a/.github/actions/setup-test-jdk/action.yml b/.github/actions/setup-test-jdk/action.yml index 7c143de62601..a22c1d70af7e 100644 --- a/.github/actions/setup-test-jdk/action.yml +++ b/.github/actions/setup-test-jdk/action.yml @@ -9,9 +9,5 @@ runs: java-version: 8 - shell: bash run: echo "JDK8=$JAVA_HOME" >> $GITHUB_ENV - - uses: oracle-actions/setup-java@v1 - with: - website: jdk.java.net - release: 21 - shell: bash run: echo "JDK21=$JAVA_HOME" >> $GITHUB_ENV From 54b68db206833a50c4f6394c8cb69d935761525b Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 1 Sep 2023 17:56:22 +0200 Subject: [PATCH 07/14] Move env var export as well --- .github/actions/run-gradle/action.yml | 2 ++ .github/actions/setup-test-jdk/action.yml | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/run-gradle/action.yml b/.github/actions/run-gradle/action.yml index b2a7f6c45725..0127f231d44b 100644 --- a/.github/actions/run-gradle/action.yml +++ b/.github/actions/run-gradle/action.yml @@ -17,6 +17,8 @@ runs: with: website: jdk.java.net release: 21 + - shell: bash + run: echo "JDK21=$JAVA_HOME" >> $GITHUB_ENV - uses: gradle/gradle-build-action@v2 env: JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} diff --git a/.github/actions/setup-test-jdk/action.yml b/.github/actions/setup-test-jdk/action.yml index a22c1d70af7e..4e8c96266c69 100644 --- a/.github/actions/setup-test-jdk/action.yml +++ b/.github/actions/setup-test-jdk/action.yml @@ -9,5 +9,3 @@ runs: java-version: 8 - shell: bash run: echo "JDK8=$JAVA_HOME" >> $GITHUB_ENV - - shell: bash - run: echo "JDK21=$JAVA_HOME" >> $GITHUB_ENV From fb9f2b7d9d32b8c5e8d2dbfe7a1c3238c97e361d Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 1 Sep 2023 18:04:19 +0200 Subject: [PATCH 08/14] Update test condition to Java 21 Co-authored-by: Marc Philipp --- .../hierarchical/VirtualParallelExecutionIntegrationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/VirtualParallelExecutionIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/VirtualParallelExecutionIntegrationTests.java index 8f1e748ff2cc..ad8c7c0e5c0b 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/VirtualParallelExecutionIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/VirtualParallelExecutionIntegrationTests.java @@ -14,7 +14,7 @@ import org.junit.jupiter.api.condition.EnabledOnJre; -@EnabledOnJre(value = JAVA_19, disabledReason = "Use Java 19 preview features") +@EnabledOnJre(value = JAVA_21, disabledReason = "Use Java 21 features") public class VirtualParallelExecutionIntegrationTests extends ParallelExecutionIntegrationTests { @Override From 105948f9f0f02fff969f83165b85015efd914789 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 1 Sep 2023 18:20:53 +0200 Subject: [PATCH 09/14] Clean up --- .../VirtualThreadHierarchicalTestExecutorServiceFactory.java | 4 ++++ platform-tests/platform-tests.gradle.kts | 2 +- .../VirtualParallelExecutionIntegrationTests.java | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java index 0a9178e8a96e..9e3bad09c9cb 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java @@ -10,8 +10,12 @@ package org.junit.platform.engine.support.hierarchical; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; import org.junit.platform.engine.ConfigurationParameters; +@API(status = EXPERIMENTAL, since = "1.10.1") public class VirtualThreadHierarchicalTestExecutorServiceFactory { public static HierarchicalTestExecutorService create( diff --git a/platform-tests/platform-tests.gradle.kts b/platform-tests/platform-tests.gradle.kts index 58d4a5695bb1..f32a2852349d 100644 --- a/platform-tests/platform-tests.gradle.kts +++ b/platform-tests/platform-tests.gradle.kts @@ -63,7 +63,7 @@ tasks { useJUnitPlatform { excludeTags("exclude") } - jvmArgs("-Xmx1g", "--enable-preview") + jvmArgs("-Xmx1g") distribution { // Retry in a new JVM on Windows to improve chances of successful retries when // cached resources are used (e.g. in ClasspathScannerTests) diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/VirtualParallelExecutionIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/VirtualParallelExecutionIntegrationTests.java index ad8c7c0e5c0b..952e41dec58e 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/VirtualParallelExecutionIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/VirtualParallelExecutionIntegrationTests.java @@ -10,7 +10,7 @@ package org.junit.platform.engine.support.hierarchical; -import static org.junit.jupiter.api.condition.JRE.JAVA_19; +import static org.junit.jupiter.api.condition.JRE.JAVA_21; import org.junit.jupiter.api.condition.EnabledOnJre; From 9ff45679fe9a9112a920192bf5a0dd5e70ee950a Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 1 Sep 2023 21:11:16 +0200 Subject: [PATCH 10/14] Use Java 17 in reproducible build check --- .github/workflows/reproducible-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/reproducible-build.yml b/.github/workflows/reproducible-build.yml index a32a51ac0002..0385a237729b 100644 --- a/.github/workflows/reproducible-build.yml +++ b/.github/workflows/reproducible-build.yml @@ -26,6 +26,8 @@ jobs: with: arguments: --quiet - name: Build and compare checksums + env: + JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} shell: bash run: | ./gradle/scripts/checkBuildReproducibility.sh From d356b2b89088548d8d29eb75a1598859e1971001 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 1 Sep 2023 21:19:45 +0200 Subject: [PATCH 11/14] Fix JAVA_HOME value --- .github/workflows/reproducible-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reproducible-build.yml b/.github/workflows/reproducible-build.yml index 0385a237729b..84b2f6ddae49 100644 --- a/.github/workflows/reproducible-build.yml +++ b/.github/workflows/reproducible-build.yml @@ -27,7 +27,7 @@ jobs: arguments: --quiet - name: Build and compare checksums env: - JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} + JAVA_HOME: ${{env.JAVA_HOME_17_X64}} shell: bash run: | ./gradle/scripts/checkBuildReproducibility.sh From 9bc713819e01e89ab69e39b2e51880029759bc38 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 9 Sep 2023 14:05:49 +0200 Subject: [PATCH 12/14] Restore reproducibility of jar junit-platform-engine jar --- junit-platform-engine/junit-platform-engine.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/junit-platform-engine/junit-platform-engine.gradle.kts b/junit-platform-engine/junit-platform-engine.gradle.kts index cf45fe45b30c..dd1745fb5357 100644 --- a/junit-platform-engine/junit-platform-engine.gradle.kts +++ b/junit-platform-engine/junit-platform-engine.gradle.kts @@ -1,6 +1,7 @@ plugins { id("junitbuild.java-library-conventions") id("junitbuild.java-multi-release-sources") + id("junitbuild.java-repackage-jars") `java-test-fixtures` } From c8bb1a0e3cd37daa02c336b12aefcdba25c85887 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 9 Sep 2023 14:25:28 +0200 Subject: [PATCH 13/14] Use jar --date to make jar files reproducible --- .../junitbuild.java-repackage-jars.gradle.kts | 52 ------------------- .../kotlin/junitbuild/java/ExecJarAction.kt | 24 --------- .../kotlin/junitbuild/java/UpdateJarAction.kt | 37 +++++++++++++ .../junit-platform-commons.gradle.kts | 7 +-- .../junit-platform-console.gradle.kts | 5 +- .../junit-platform-engine.gradle.kts | 6 +-- 6 files changed, 40 insertions(+), 91 deletions(-) delete mode 100644 gradle/plugins/common/src/main/kotlin/junitbuild.java-repackage-jars.gradle.kts delete mode 100644 gradle/plugins/common/src/main/kotlin/junitbuild/java/ExecJarAction.kt create mode 100644 gradle/plugins/common/src/main/kotlin/junitbuild/java/UpdateJarAction.kt diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-repackage-jars.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-repackage-jars.gradle.kts deleted file mode 100644 index a000989b0327..000000000000 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-repackage-jars.gradle.kts +++ /dev/null @@ -1,52 +0,0 @@ -import java.util.jar.JarEntry -import java.util.jar.JarFile -import java.util.jar.JarOutputStream -import org.gradle.api.internal.file.archive.ZipCopyAction -import java.nio.file.Files - -// This registers a `doLast` action to rewrite the timestamps of the project's output JAR -afterEvaluate { - val jarTask = (tasks.findByName("shadowJar") ?: tasks["jar"]) as Jar - - jarTask.doLast { - - val newFile = Files.createTempFile("rewrite-timestamp", null).toFile() - val originalOutput = jarTask.archiveFile.get().asFile - - newFile.outputStream().use { os -> - - val newJarStream = JarOutputStream(os) - val oldJar = JarFile(originalOutput) - - fun sortAlwaysFirst(name: String): Comparator = - Comparator { a, b -> - when { - a.name == name -> -1 - b.name == name -> 1 - else -> 0 - } - } - - oldJar.entries() - .toList() - .distinctBy { it.name } - .sortedWith(sortAlwaysFirst("META-INF/") - .then(sortAlwaysFirst("META-INF/MANIFEST.MF")) - .thenBy { it.name }) - .forEach { entry -> - val jarEntry = JarEntry(entry.name) - - // Use the same constant as the fixed timestamps in normal copy actions - jarEntry.time = ZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES - - newJarStream.putNextEntry(jarEntry) - - oldJar.getInputStream(entry).copyTo(newJarStream) - } - - newJarStream.finish() - } - - newFile.renameTo(originalOutput) - } -} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/java/ExecJarAction.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/java/ExecJarAction.kt deleted file mode 100644 index 8a3cf49ad70e..000000000000 --- a/gradle/plugins/common/src/main/kotlin/junitbuild/java/ExecJarAction.kt +++ /dev/null @@ -1,24 +0,0 @@ -package junitbuild.java - -import org.gradle.api.Action -import org.gradle.api.Task -import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.Property -import org.gradle.jvm.toolchain.JavaLauncher -import org.gradle.process.ExecOperations -import javax.inject.Inject - -abstract class ExecJarAction @Inject constructor(private val operations: ExecOperations): Action { - - abstract val javaLauncher: Property - - abstract val args: ListProperty - - override fun execute(t: Task) { - operations.exec { - executable = javaLauncher.get() - .metadata.installationPath.file("bin/jar").asFile.absolutePath - args = this@ExecJarAction.args.get() - } - } -} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/java/UpdateJarAction.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/java/UpdateJarAction.kt new file mode 100644 index 000000000000..41ddfdf3339c --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/java/UpdateJarAction.kt @@ -0,0 +1,37 @@ +package junitbuild.java + +import org.gradle.api.Action +import org.gradle.api.Task +import org.gradle.api.internal.file.archive.ZipCopyAction +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.bundling.AbstractArchiveTask +import org.gradle.jvm.toolchain.JavaLauncher +import org.gradle.process.ExecOperations +import java.time.Instant +import javax.inject.Inject + +abstract class UpdateJarAction @Inject constructor(private val operations: ExecOperations) : Action { + + abstract val javaLauncher: Property + + abstract val args: ListProperty + + abstract val date: Property + + init { + date.convention(Instant.ofEpochMilli(ZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES)) + } + + override fun execute(t: Task) { + operations.exec { + executable = javaLauncher.get() + .metadata.installationPath.file("bin/jar").asFile.absolutePath + args = listOf( + "--update", + "--file", (t as AbstractArchiveTask).archiveFile.get().asFile.absolutePath, + "--date=${date.get()}" + ) + this@UpdateJarAction.args.get() + } + } +} diff --git a/junit-platform-commons/junit-platform-commons.gradle.kts b/junit-platform-commons/junit-platform-commons.gradle.kts index bb6a7c336eab..696912b7ed9b 100644 --- a/junit-platform-commons/junit-platform-commons.gradle.kts +++ b/junit-platform-commons/junit-platform-commons.gradle.kts @@ -1,9 +1,6 @@ -import junitbuild.java.ExecJarAction - plugins { id("junitbuild.java-library-conventions") id("junitbuild.java-multi-release-sources") - id("junitbuild.java-repackage-jars") `java-test-fixtures` } @@ -18,11 +15,9 @@ dependencies { tasks.jar { val release9ClassesDir = sourceSets.mainRelease9.get().output.classesDirs.singleFile inputs.dir(release9ClassesDir).withPathSensitivity(PathSensitivity.RELATIVE) - doLast(objects.newInstance(ExecJarAction::class).apply { + doLast(objects.newInstance(junitbuild.java.UpdateJarAction::class).apply { javaLauncher = javaToolchains.launcherFor(java.toolchain) args.addAll( - "--update", - "--file", archiveFile.get().asFile.absolutePath, "--release", "9", "-C", release9ClassesDir.absolutePath, "." ) diff --git a/junit-platform-console/junit-platform-console.gradle.kts b/junit-platform-console/junit-platform-console.gradle.kts index 91acfd91b6d7..43e2aa56bbea 100644 --- a/junit-platform-console/junit-platform-console.gradle.kts +++ b/junit-platform-console/junit-platform-console.gradle.kts @@ -2,7 +2,6 @@ plugins { id("junitbuild.java-library-conventions") id("junitbuild.shadow-conventions") id("junitbuild.java-multi-release-sources") - id("junitbuild.java-repackage-jars") } description = "JUnit Platform Console" @@ -40,11 +39,9 @@ tasks { into("META-INF") } from(sourceSets.mainRelease9.get().output.classesDirs) - doLast(objects.newInstance(junitbuild.java.ExecJarAction::class).apply { + doLast(objects.newInstance(junitbuild.java.UpdateJarAction::class).apply { javaLauncher = project.javaToolchains.launcherFor(java.toolchain) args.addAll( - "--update", - "--file", archiveFile.get().asFile.absolutePath, "--main-class", "org.junit.platform.console.ConsoleLauncher", "--release", "17", "-C", release17ClassesDir.absolutePath, "." diff --git a/junit-platform-engine/junit-platform-engine.gradle.kts b/junit-platform-engine/junit-platform-engine.gradle.kts index dd1745fb5357..c5c6edcd7465 100644 --- a/junit-platform-engine/junit-platform-engine.gradle.kts +++ b/junit-platform-engine/junit-platform-engine.gradle.kts @@ -1,7 +1,6 @@ plugins { id("junitbuild.java-library-conventions") id("junitbuild.java-multi-release-sources") - id("junitbuild.java-repackage-jars") `java-test-fixtures` } @@ -23,22 +22,19 @@ dependencies { tasks.jar { val release21ClassesDir = project.sourceSets.mainRelease21.get().output.classesDirs.singleFile inputs.dir(release21ClassesDir).withPathSensitivity(PathSensitivity.RELATIVE) - doLast(objects.newInstance(junitbuild.java.ExecJarAction::class).apply { + doLast(objects.newInstance(junitbuild.java.UpdateJarAction::class).apply { javaLauncher.set(project.javaToolchains.launcherFor { languageVersion.set(java.toolchain.languageVersion.map { if (it.canCompileOrRun(21)) it else JavaLanguageVersion.of(21) }) }) args.addAll( - "--update", - "--file", archiveFile.get().asFile.absolutePath, "--release", "21", "-C", release21ClassesDir.absolutePath, "." ) }) } - eclipse { classpath { sourceSets -= project.sourceSets.mainRelease21.get() From 673be2e0d0a73912471e3252bfb24e010fe19c1c Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 9 Sep 2023 14:27:32 +0200 Subject: [PATCH 14/14] Add API annotation to JDK 21 implementation --- .../VirtualThreadHierarchicalTestExecutorServiceFactory.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/junit-platform-engine/src/main/java21/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java b/junit-platform-engine/src/main/java21/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java index c8ac3bf42c4d..b01c9432c5e1 100644 --- a/junit-platform-engine/src/main/java21/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java +++ b/junit-platform-engine/src/main/java21/org/junit/platform/engine/support/hierarchical/VirtualThreadHierarchicalTestExecutorServiceFactory.java @@ -10,8 +10,12 @@ package org.junit.platform.engine.support.hierarchical; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; import org.junit.platform.engine.ConfigurationParameters; +@API(status = EXPERIMENTAL, since = "1.10.1") public class VirtualThreadHierarchicalTestExecutorServiceFactory { public static HierarchicalTestExecutorService create(ConfigurationParameters configurationParameters) {