Skip to content

Commit f9a1d9a

Browse files
committed
Document LauncherInterceptor
1 parent 651c17c commit f9a1d9a

File tree

7 files changed

+123
-3
lines changed

7 files changed

+123
-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

+11
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
package org.junit.platform.launcher;
1212

13+
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
1314
import static org.apiguardian.api.API.Status.STABLE;
1415

1516
import org.apiguardian.api.API;
@@ -135,6 +136,16 @@ public class LauncherConstants {
135136
*/
136137
public static final String DEACTIVATE_ALL_LISTENERS_PATTERN = ClassNamePatternFilterUtils.DEACTIVATE_ALL_PATTERN;
137138

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

140151
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)