diff --git a/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/TemporaryClasspathExecutor.java b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/TemporaryClasspathExecutor.java new file mode 100644 index 000000000000..82f822da9e20 --- /dev/null +++ b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/TemporaryClasspathExecutor.java @@ -0,0 +1,54 @@ +/* + * 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.jupiter.api; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * Utility class for executing code with a temporary classpath. + * + * @since 5.13 + */ +public class TemporaryClasspathExecutor { + + private TemporaryClasspathExecutor() { + } + + /** + * Execute the {@link Runnable} within a custom classpath, temporarily modifying the + * thread's {@link Thread#getContextClassLoader() context class loader} to include + * the provided {@code classpathRoot}. + * + *

After the given {@code Runnable} completes, the original context class loader is + * restored. + * + * @param classpathRoot the root path to be added to the classpath, resolved relative + * to the current thread's context class loader. + * @param runnable the {@code Runnable} to execute with the temporary classpath. + */ + public static void withAdditionalClasspathRoot(String classpathRoot, Runnable runnable) { + var current = Thread.currentThread().getContextClassLoader(); + try (var classLoader = new URLClassLoader(new URL[] { current.getResource(classpathRoot) }, current)) { + Thread.currentThread().setContextClassLoader(classLoader); + runnable.run(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + finally { + Thread.currentThread().setContextClassLoader(current); + } + } + +} diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java index f1687ea26d97..c6f4a74cd64c 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java @@ -14,15 +14,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.TemporaryClasspathExecutor.withAdditionalClasspathRoot; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.LauncherConstants.DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.ENABLE_LAUNCHER_INTERCEPTORS; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.URL; -import java.net.URLClassLoader; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.LogRecord; @@ -434,18 +431,7 @@ private static void withSystemProperty(String key, String value, Runnable runnab } private static void withTestServices(Runnable runnable) { - var current = Thread.currentThread().getContextClassLoader(); - var url = LauncherFactoryTests.class.getClassLoader().getResource("testservices/"); - try (var classLoader = new URLClassLoader(new URL[] { url }, current)) { - Thread.currentThread().setContextClassLoader(classLoader); - runnable.run(); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - finally { - Thread.currentThread().setContextClassLoader(current); - } + withAdditionalClasspathRoot("testservices/", runnable); } private LauncherDiscoveryRequest createLauncherDiscoveryRequestForBothStandardEngineExampleClasses() {