Skip to content

Commit 3e62413

Browse files
committed
Introduce LauncherInterceptor
1 parent f9fe707 commit 3e62413

13 files changed

+461
-72
lines changed

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

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

138+
public static final String ENABLE_LAUNCHER_INTERCEPTORS = "junit.platform.launcher.interceptors.enabled";
139+
138140
private LauncherConstants() {
139141
/* no-op */
140142
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2015-2022 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 org.junit.platform.launcher;
12+
13+
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
14+
15+
import org.apiguardian.api.API;
16+
17+
/**
18+
* Interceptor for test discovery and execution by a {@link Launcher}.
19+
*
20+
* <p>Interceptors are instantiated once per {@link LauncherSession} and closed
21+
* when the session is about to be closed.
22+
*
23+
* @since 1.10
24+
* @see Launcher
25+
* @see LauncherSession
26+
* @see LauncherConstants#ENABLE_LAUNCHER_INTERCEPTORS
27+
*/
28+
@API(status = EXPERIMENTAL, since = "1.10")
29+
public interface LauncherInterceptor {
30+
31+
/**
32+
* Intercept the supplied invocation.
33+
*
34+
* <p>Implementations must call {@link Invocation#proceed()} exactly once.
35+
*
36+
* @param invocation the intercepted invocation; never {@code null}
37+
* @return the result of the invocation
38+
*/
39+
<T> T intercept(Invocation<T> invocation);
40+
41+
/**
42+
* Closes this interceptor.
43+
*
44+
* <p>Any resources held by this interceptor should be released by this
45+
* method.
46+
*/
47+
void close();
48+
49+
/**
50+
* An invocation that can be intercepted.
51+
*
52+
* <p>This interface is not intended to be implemented by clients.
53+
*/
54+
interface Invocation<T> {
55+
T proceed();
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2015-2022 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 org.junit.platform.launcher.core;
12+
13+
/**
14+
* @since 1.10
15+
*/
16+
interface CloseableInternalLauncher extends InternalLauncher, AutoCloseable {
17+
@Override
18+
void close();
19+
}

junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java

+33-38
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010

1111
package org.junit.platform.launcher.core;
1212

13+
import java.util.List;
14+
1315
import org.junit.platform.commons.PreconditionViolationException;
1416
import org.junit.platform.launcher.Launcher;
1517
import org.junit.platform.launcher.LauncherDiscoveryListener;
1618
import org.junit.platform.launcher.LauncherDiscoveryRequest;
19+
import org.junit.platform.launcher.LauncherInterceptor;
1720
import org.junit.platform.launcher.LauncherSession;
1821
import org.junit.platform.launcher.LauncherSessionListener;
1922
import org.junit.platform.launcher.TestExecutionListener;
@@ -27,8 +30,9 @@ class DefaultLauncherSession implements LauncherSession {
2730
private final DelegatingLauncher launcher;
2831
private final LauncherSessionListener listener;
2932

30-
DefaultLauncherSession(Launcher launcher, LauncherSessionListener listener) {
31-
this.launcher = new DelegatingLauncher(launcher);
33+
DefaultLauncherSession(InternalLauncher launcher, LauncherSessionListener listener,
34+
List<LauncherInterceptor> interceptors) {
35+
this.launcher = new DelegatingLauncher(InterceptingInternalLauncher.decorate(launcher, interceptors));
3236
this.listener = listener;
3337
listener.launcherSessionOpened(this);
3438
}
@@ -44,55 +48,31 @@ LauncherSessionListener getListener() {
4448

4549
@Override
4650
public void close() {
47-
if (launcher.getDelegate() != ClosedLauncher.INSTANCE) {
48-
launcher.setDelegate(ClosedLauncher.INSTANCE);
51+
if (launcher.isClosed()) {
52+
launcher.close();
4953
listener.launcherSessionClosed(this);
5054
}
5155
}
5256

53-
private static class DelegatingLauncher implements Launcher {
54-
55-
private Launcher delegate;
56-
57-
DelegatingLauncher(Launcher delegate) {
58-
this.delegate = delegate;
59-
}
60-
61-
public Launcher getDelegate() {
62-
return delegate;
63-
}
64-
65-
public void setDelegate(Launcher delegate) {
66-
this.delegate = delegate;
67-
}
68-
69-
@Override
70-
public void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) {
71-
delegate.registerLauncherDiscoveryListeners(listeners);
72-
}
73-
74-
@Override
75-
public void registerTestExecutionListeners(TestExecutionListener... listeners) {
76-
delegate.registerTestExecutionListeners(listeners);
77-
}
57+
private static class DelegatingLauncher extends DelegatingInternalLauncher<CloseableInternalLauncher>
58+
implements CloseableInternalLauncher {
7859

79-
@Override
80-
public TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest) {
81-
return delegate.discover(launcherDiscoveryRequest);
60+
DelegatingLauncher(CloseableInternalLauncher delegate) {
61+
super(delegate);
8262
}
8363

84-
@Override
85-
public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) {
86-
delegate.execute(launcherDiscoveryRequest, listeners);
64+
boolean isClosed() {
65+
return delegate != ClosedLauncher.INSTANCE;
8766
}
8867

8968
@Override
90-
public void execute(TestPlan testPlan, TestExecutionListener... listeners) {
91-
delegate.execute(testPlan, listeners);
69+
public void close() {
70+
delegate.close();
71+
delegate = ClosedLauncher.INSTANCE;
9272
}
9373
}
9474

95-
private static class ClosedLauncher implements Launcher {
75+
private static class ClosedLauncher implements CloseableInternalLauncher {
9676

9777
static final ClosedLauncher INSTANCE = new ClosedLauncher();
9878

@@ -123,5 +103,20 @@ public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecu
123103
public void execute(TestPlan testPlan, TestExecutionListener... listeners) {
124104
throw new PreconditionViolationException("Launcher session has already been closed");
125105
}
106+
107+
@Override
108+
public ListenerRegistry<TestExecutionListener> getTestExecutionListenerRegistry() {
109+
throw new PreconditionViolationException("Launcher session has already been closed");
110+
}
111+
112+
@Override
113+
public ListenerRegistry<LauncherDiscoveryListener> getLauncherDiscoveryListenerRegistry() {
114+
throw new PreconditionViolationException("Launcher session has already been closed");
115+
}
116+
117+
@Override
118+
public void close() {
119+
// do nothing
120+
}
126121
}
127122
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2015-2022 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 org.junit.platform.launcher.core;
12+
13+
/**
14+
* @since 1.10
15+
*/
16+
class DelegatingCloseableInternalLauncher<T extends InternalLauncher> extends DelegatingInternalLauncher<T>
17+
implements CloseableInternalLauncher {
18+
19+
private final Runnable onClose;
20+
21+
public DelegatingCloseableInternalLauncher(T delegate, Runnable onClose) {
22+
super(delegate);
23+
this.onClose = onClose;
24+
}
25+
26+
@Override
27+
public final void close() {
28+
onClose.run();
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2015-2022 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 org.junit.platform.launcher.core;
12+
13+
import org.junit.platform.launcher.LauncherDiscoveryListener;
14+
import org.junit.platform.launcher.LauncherDiscoveryRequest;
15+
import org.junit.platform.launcher.TestExecutionListener;
16+
import org.junit.platform.launcher.TestPlan;
17+
18+
/**
19+
* @since 1.10
20+
*/
21+
class DelegatingInternalLauncher<T extends InternalLauncher> implements InternalLauncher {
22+
23+
protected T delegate;
24+
25+
DelegatingInternalLauncher(T delegate) {
26+
this.delegate = delegate;
27+
}
28+
29+
@Override
30+
public void registerLauncherDiscoveryListeners(LauncherDiscoveryListener... listeners) {
31+
delegate.registerLauncherDiscoveryListeners(listeners);
32+
}
33+
34+
@Override
35+
public void registerTestExecutionListeners(TestExecutionListener... listeners) {
36+
delegate.registerTestExecutionListeners(listeners);
37+
}
38+
39+
@Override
40+
public TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest) {
41+
return delegate.discover(launcherDiscoveryRequest);
42+
}
43+
44+
@Override
45+
public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) {
46+
delegate.execute(launcherDiscoveryRequest, listeners);
47+
}
48+
49+
@Override
50+
public void execute(TestPlan testPlan, TestExecutionListener... listeners) {
51+
delegate.execute(testPlan, listeners);
52+
}
53+
54+
@Override
55+
public ListenerRegistry<TestExecutionListener> getTestExecutionListenerRegistry() {
56+
return delegate.getTestExecutionListenerRegistry();
57+
}
58+
59+
@Override
60+
public ListenerRegistry<LauncherDiscoveryListener> getLauncherDiscoveryListenerRegistry() {
61+
return delegate.getLauncherDiscoveryListenerRegistry();
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2015-2022 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 org.junit.platform.launcher.core;
12+
13+
import java.util.List;
14+
import java.util.Optional;
15+
16+
import org.junit.platform.launcher.LauncherDiscoveryRequest;
17+
import org.junit.platform.launcher.LauncherInterceptor;
18+
import org.junit.platform.launcher.TestExecutionListener;
19+
import org.junit.platform.launcher.TestPlan;
20+
21+
/**
22+
* @since 1.10
23+
*/
24+
class InterceptingInternalLauncher extends DelegatingCloseableInternalLauncher<InternalLauncher> {
25+
26+
static CloseableInternalLauncher decorate(InternalLauncher launcher, List<LauncherInterceptor> interceptors) {
27+
return composite(interceptors) //
28+
.map(combinedInterceptor -> (CloseableInternalLauncher) new InterceptingInternalLauncher(launcher,
29+
combinedInterceptor)) //
30+
.orElse(new DelegatingCloseableInternalLauncher<>(launcher, () -> {
31+
// do nothing
32+
}));
33+
}
34+
35+
private static Optional<LauncherInterceptor> composite(List<LauncherInterceptor> interceptors) {
36+
if (interceptors.isEmpty()) {
37+
return Optional.empty();
38+
}
39+
return Optional.of(interceptors.stream() //
40+
.skip(1) //
41+
.reduce(interceptors.get(0), (a, b) -> new LauncherInterceptor() {
42+
@Override
43+
public void close() {
44+
try {
45+
a.close();
46+
}
47+
finally {
48+
b.close();
49+
}
50+
}
51+
52+
@Override
53+
public <T> T intercept(Invocation<T> invocation) {
54+
return a.intercept(() -> b.intercept(invocation));
55+
}
56+
}));
57+
}
58+
59+
private final LauncherInterceptor interceptor;
60+
61+
private InterceptingInternalLauncher(InternalLauncher delegate, LauncherInterceptor interceptor) {
62+
super(delegate, interceptor::close);
63+
this.interceptor = interceptor;
64+
}
65+
66+
@Override
67+
public TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest) {
68+
return interceptor.intercept(() -> super.discover(launcherDiscoveryRequest));
69+
}
70+
71+
@Override
72+
public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) {
73+
interceptor.intercept(() -> {
74+
super.execute(launcherDiscoveryRequest, listeners);
75+
return null;
76+
});
77+
}
78+
79+
@Override
80+
public void execute(TestPlan testPlan, TestExecutionListener... listeners) {
81+
interceptor.intercept(() -> {
82+
super.execute(testPlan, listeners);
83+
return null;
84+
});
85+
}
86+
}

0 commit comments

Comments
 (0)