Skip to content

Commit

Permalink
Document LauncherInterceptor
Browse files Browse the repository at this point in the history
  • Loading branch information
marcphilipp committed Jan 31, 2023
1 parent 39bf72b commit 872146d
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 3 deletions.
1 change: 1 addition & 0 deletions documentation/src/docs/asciidoc/link-attributes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ endif::[]
:LauncherDiscoveryRequest: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherDiscoveryRequest.html[LauncherDiscoveryRequest]
:LauncherDiscoveryRequestBuilder: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.html[LauncherDiscoveryRequestBuilder]
:LauncherFactory: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherFactory.html[LauncherFactory]
:LauncherInterceptor: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherInterceptor.html[LauncherInterceptor]
:LauncherSession: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSession.html[LauncherSession]
:LauncherSessionListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSessionListener.html[LauncherSessionListener]
:LoggingListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/listeners/LoggingListener.html[LoggingListener]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ repository on GitHub.
methods are called in reverse order compared to the former when multiple listeners are
registered. This affects the following listener interfaces: `TestExecutionListener`,
`EngineExecutionListener`, `LauncherDiscoveryListener`, and `LauncherSessionListener`.
* Introduce `LauncherInterceptor` SPI for intercepting the creation of instances of
`Launcher` and `LauncherSessionlistener` as well as calls for `discover` and `execute`
of the former. Please refer to the
<<../user-guide/index.adoc#launcher-api-launcher-interceptors-custom, User Guide>> for
details.

[[release-notes-5.10.0-M1-junit-jupiter]]
=== JUnit Jupiter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,24 @@ include::{testDir}/example/session/HttpTests.java[tags=user_guide]
<3> Send a request to the server
<4> Check the status code of the response

[[launcher-api-launcher-interceptors-custom]]
==== Registering a LauncherInterceptor

In order to intercept the creation of instances of `{Launcher}` and
`{LauncherSessionListener}` and calls to the `discover` and `execute` methods of the
former, clients can registercustom implementations of `{LauncherInterceptor}` via Java's
`{ServiceLoader}` mechanism by additionally setting the
`junit.platform.launcher.interceptors.enabled` <<running-tests-config-params,
configuration parameter>> to `true`.

A typical use case is to create a custom replace the `ClassLoader` used by the JUnit
Platform to load test classes and engine implementations.

[source,java]
----
include::{testDir}/example/CustomLauncherInterceptor.java[tags=user_guide]
----

[[launcher-api-launcher-discovery-listeners-custom]]
==== Registering a LauncherDiscoveryListener

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -979,14 +979,16 @@ because particularly when
to attribute it to a specific test or container.

[[running-tests-listeners]]
=== Using Listeners
=== Using Listeners and Interceptors

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

* `{LauncherSessionListener}`: receives events when a `{LauncherSession}` is opened and
closed.
* `{LauncherInterceptor}`: intercepts test discovery and execution in the context of a
`LauncherSession`.
* `{LauncherDiscoveryListener}`: receives events that occur during test discovery.
* `{TestExecutionListener}`: receives events that occur during test execution.

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

* <<launcher-api-launcher-session-listeners-custom>>
* <<launcher-api-launcher-interceptors-custom>>
* <<launcher-api-launcher-discovery-listeners-custom>>
* <<launcher-api-listeners-custom>>
* <<launcher-api-listeners-config>>
Expand Down
55 changes: 55 additions & 0 deletions documentation/src/test/java/example/CustomLauncherInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 example;

// tag::user_guide[]

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;

import org.junit.platform.launcher.LauncherInterceptor;

public class CustomLauncherInterceptor implements LauncherInterceptor {

private final URLClassLoader customClassLoader;

public CustomLauncherInterceptor() throws Exception {
ClassLoader parent = Thread.currentThread().getContextClassLoader();
customClassLoader = new URLClassLoader(new URL[] { URI.create("some.jar").toURL() }, parent);
}

@Override
public <T> T intercept(Invocation<T> invocation) {
Thread currentThread = Thread.currentThread();
ClassLoader originalClassLoader = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(customClassLoader);
try {
return invocation.proceed();
}
finally {
currentThread.setContextClassLoader(originalClassLoader);
}
}

@Override
public void close() {
try {
customClassLoader.close();
}
catch (IOException e) {
throw new UncheckedIOException("Failed to close custom class loader", e);
}
}
}
// end::user_guide[]
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.junit.platform.launcher;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.STABLE;

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

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

private LauncherConstants() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,37 @@
import org.apiguardian.api.API;

/**
* Interceptor for test discovery and execution by a {@link Launcher}.
* Interceptor for test discovery and execution by a {@link Launcher} in the
* context of a {@link LauncherSession}.
*
* <p>Interceptors are instantiated once per {@link LauncherSession} and closed
* when the session is about to be closed.
* after the session is closed. They can
* {@linkplain #intercept(Invocation) intercept} the following invocations:
* <ul>
* <li>
* creation of {@link LauncherSessionListener} instances registered via the
* {@link java.util.ServiceLoader ServiceLoader} mechanism
* </li>
* <li>
* creation of {@link Launcher} instances
* </li>
* <li>
* calls to {@link Launcher#discover(LauncherDiscoveryRequest)},
* {@link Launcher#execute(TestPlan, TestExecutionListener...)}, and
* {@link Launcher#execute(LauncherDiscoveryRequest, TestExecutionListener...)}
* </li>
* </ul>
*
* <p>Implementations of this interface can be registered via the
* {@link java.util.ServiceLoader ServiceLoader} mechanism by additionally
* setting the {@value LauncherConstants#ENABLE_LAUNCHER_INTERCEPTORS}
* configuration parameter to {@code true}.
*
* <p>A typical use case is to create a custom {@link ClassLoader} in the
* constructor of the implementing class, replace the
* {@link Thread#setContextClassLoader(ClassLoader) contextClassLoader} of the
* current thread while {@link #intercept(Invocation) intercepting} invocations,
* and close the custom {@code ClassLoader} in {@link #close()}
*
* @since 1.10
* @see Launcher
Expand Down

0 comments on commit 872146d

Please sign in to comment.