Skip to content

Commit da8e76f

Browse files
committed
Document LauncherInterceptor
1 parent 651c17c commit da8e76f

File tree

7 files changed

+121
-3
lines changed

7 files changed

+121
-3
lines changed

documentation/src/docs/asciidoc/link-attributes.adoc

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ endif::[]
3333
:LauncherDiscoveryRequest: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherDiscoveryRequest.html[LauncherDiscoveryRequest]
3434
:LauncherDiscoveryRequestBuilder: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.html[LauncherDiscoveryRequestBuilder]
3535
:LauncherFactory: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherFactory.html[LauncherFactory]
36+
:LauncherInterceptor: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherInterceptor.html[LauncherInterceptor]
3637
:LauncherSession: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSession.html[LauncherSession]
3738
:LauncherSessionListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSessionListener.html[LauncherSessionListener]
3839
:LoggingListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/LoggingListener.html[LoggingListener]

documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0-M1.adoc

+5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ repository on GitHub.
3131
methods are called in reverse order compared to the former when multiple listeners are
3232
registered. This affects the following listener interfaces: `TestExecutionListener`,
3333
`EngineExecutionListener`, `LauncherDiscoveryListener`, and `LauncherSessionListener`.
34+
* Introduce `LauncherInterceptor` SPI for intercepting the creation of instances of
35+
`Launcher` and `LauncherSessionlistener` as well as calls for `discover` and `execute`
36+
of the former. Please refer to the
37+
<<../user-guide/index.adoc#launcher-api-launcher-interceptors-custom, User Guide>> for
38+
details.
3439

3540
[[release-notes-5.10.0-M1-junit-jupiter]]
3641
=== JUnit Jupiter

documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc

+18
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,24 @@ include::{testDir}/example/session/HttpTests.java[tags=user_guide]
163163
<3> Send a request to the server
164164
<4> Check the status code of the response
165165

166+
[[launcher-api-launcher-interceptors-custom]]
167+
==== Registering a LauncherInterceptor
168+
169+
In order to intercept the creation of instances of `{Launcher}` and
170+
`{LauncherSessionListener}` and calls to the `discover` and `execute` methods of the
171+
former, clients can registercustom implementations of `{LauncherInterceptor}` via Java's
172+
`{ServiceLoader}` mechanism by additionally setting the
173+
`junit.platform.launcher.interceptors.enabled` <<running-tests-config-params,
174+
configuration parameter>> to `true`.
175+
176+
A typical use case is to create a custom replace the `ClassLoader` used by the JUnit
177+
Platform to load test classes and engine implementations.
178+
179+
[source,java]
180+
----
181+
include::{testDir}/example/CustomLauncherInterceptor.java[tags=user_guide]
182+
----
183+
166184
[[launcher-api-launcher-discovery-listeners-custom]]
167185
==== Registering a LauncherDiscoveryListener
168186

documentation/src/docs/asciidoc/user-guide/running-tests.adoc

+4-1
Original file line numberDiff line numberDiff line change
@@ -979,14 +979,16 @@ because particularly when
979979
to attribute it to a specific test or container.
980980

981981
[[running-tests-listeners]]
982-
=== Using Listeners
982+
=== Using Listeners and Interceptors
983983

984984
The JUnit Platform provides the following listener APIs that allow JUnit, third parties,
985985
and custom user code to react to events fired at various points during the discovery and
986986
execution of a `TestPlan`.
987987

988988
* `{LauncherSessionListener}`: receives events when a `{LauncherSession}` is opened and
989989
closed.
990+
* `{LauncherInterceptor}`: intercepts test discovery and execution in the context of a
991+
`LauncherSession`.
990992
* `{LauncherDiscoveryListener}`: receives events that occur during test discovery.
991993
* `{TestExecutionListener}`: receives events that occur during test execution.
992994

@@ -1003,6 +1005,7 @@ For details on registering and configuring listeners, see the following sections
10031005
guide.
10041006

10051007
* <<launcher-api-launcher-session-listeners-custom>>
1008+
* <<launcher-api-launcher-interceptors-custom>>
10061009
* <<launcher-api-launcher-discovery-listeners-custom>>
10071010
* <<launcher-api-listeners-custom>>
10081011
* <<launcher-api-listeners-config>>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2015-2023 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package example;
12+
13+
// tag::user_guide[]
14+
15+
import java.io.IOException;
16+
import java.io.UncheckedIOException;
17+
import java.net.URI;
18+
import java.net.URL;
19+
import java.net.URLClassLoader;
20+
21+
import org.junit.platform.launcher.LauncherInterceptor;
22+
23+
public class CustomLauncherInterceptor implements LauncherInterceptor {
24+
25+
private final URLClassLoader customClassLoader;
26+
27+
public CustomLauncherInterceptor() throws Exception {
28+
ClassLoader parent = Thread.currentThread().getContextClassLoader();
29+
customClassLoader = new URLClassLoader(new URL[] { URI.create("some.jar").toURL() }, parent);
30+
}
31+
32+
@Override
33+
public <T> T intercept(Invocation<T> invocation) {
34+
Thread currentThread = Thread.currentThread();
35+
ClassLoader originalClassLoader = currentThread.getContextClassLoader();
36+
currentThread.setContextClassLoader(customClassLoader);
37+
try {
38+
return invocation.proceed();
39+
}
40+
finally {
41+
currentThread.setContextClassLoader(originalClassLoader);
42+
}
43+
}
44+
45+
@Override
46+
public void close() {
47+
try {
48+
customClassLoader.close();
49+
}
50+
catch (IOException e) {
51+
throw new UncheckedIOException("Failed to close custom class loader", e);
52+
}
53+
}
54+
}
55+
// end::user_guide[]

junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java

+9
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,15 @@ public class LauncherConstants {
135135
*/
136136
public static final String DEACTIVATE_ALL_LISTENERS_PATTERN = ClassNamePatternFilterUtils.DEACTIVATE_ALL_PATTERN;
137137

138+
/**
139+
* Property name used to enable support for
140+
* {@link LauncherInterceptor} instances to be registered via the
141+
* {@link java.util.ServiceLoader ServiceLoader} mechanism: {@value}
142+
*
143+
* <p>By default, interceptor registration is disabled.
144+
*
145+
* @see LauncherInterceptor
146+
*/
138147
public static final String ENABLE_LAUNCHER_INTERCEPTORS = "junit.platform.launcher.interceptors.enabled";
139148

140149
private LauncherConstants() {

junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherInterceptor.java

+29-2
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,37 @@
1818
import org.apiguardian.api.API;
1919

2020
/**
21-
* Interceptor for test discovery and execution by a {@link Launcher}.
21+
* Interceptor for test discovery and execution by a {@link Launcher} in the
22+
* context of a {@link LauncherSession}.
2223
*
2324
* <p>Interceptors are instantiated once per {@link LauncherSession} and closed
24-
* when the session is about to be closed.
25+
* after the session is closed. They can
26+
* {@linkplain #intercept(Invocation) intercept} the following invocations:
27+
* <ul>
28+
* <li>
29+
* creation of {@link LauncherSessionListener} instances registered via the
30+
* {@link java.util.ServiceLoader ServiceLoader} mechanism
31+
* </li>
32+
* <li>
33+
* creation of {@link Launcher} instances
34+
* </li>
35+
* <li>
36+
* calls to {@link Launcher#discover(LauncherDiscoveryRequest)},
37+
* {@link Launcher#execute(TestPlan, TestExecutionListener...)}, and
38+
* {@link Launcher#execute(LauncherDiscoveryRequest, TestExecutionListener...)}
39+
* </li>
40+
* </ul>
41+
*
42+
* <p>Implementations of this interface can be registered via the
43+
* {@link java.util.ServiceLoader ServiceLoader} mechanism by additionally
44+
* setting the {@value LauncherConstants#ENABLE_LAUNCHER_INTERCEPTORS}
45+
* configuration parameter to {@code true}.
46+
*
47+
* <p>A typical use case is to create a custom {@link ClassLoader} in the
48+
* constructor of the implementing class, replace the
49+
* {@link Thread#setContextClassLoader(ClassLoader) contextClassLoader} of the
50+
* current thread while {@link #intercept(Invocation) intercepting} invocations,
51+
* and close the custom {@code ClassLoader} in {@link #close()}
2552
*
2653
* @since 1.10
2754
* @see Launcher

0 commit comments

Comments
 (0)