-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce InvocationInterceptor extension API
The new extension API allows intercepting the invocation of test class constructors, lifecycle methods, testable methods, and dynamic tests. It validates that an invocation is asked to proceed exactly once. The user guide is updated with an example that executes all test methods in Swing's EDT. Resolves #157.
- Loading branch information
1 parent
b1c66e4
commit 8bb09ad
Showing
23 changed files
with
1,150 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
* Copyright 2015-2019 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.interceptor; | ||
|
||
import java.lang.reflect.Method; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
|
||
import javax.swing.SwingUtilities; | ||
|
||
import org.junit.jupiter.api.extension.ExtensionContext; | ||
import org.junit.jupiter.api.extension.InvocationInterceptor; | ||
import org.junit.jupiter.api.extension.ReflectiveInvocationContext; | ||
|
||
// @formatter:off | ||
// tag::user_guide[] | ||
public class SwingEdtInterceptor implements InvocationInterceptor { | ||
|
||
@Override | ||
public void interceptTestMethod(Invocation<Void> invocation, | ||
ReflectiveInvocationContext<Method> invocationContext, | ||
ExtensionContext extensionContext) throws Throwable { | ||
AtomicReference<Throwable> throwable = new AtomicReference<>(); | ||
SwingUtilities.invokeAndWait(() -> { | ||
try { | ||
invocation.proceed(); | ||
} | ||
catch (Throwable t) { | ||
throwable.set(t); | ||
} | ||
}); | ||
Throwable t = throwable.get(); | ||
if (t != null) { | ||
throw t; | ||
} | ||
} | ||
} | ||
// end::user_guide[] | ||
// @formatter:on |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
193 changes: 193 additions & 0 deletions
193
junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
/* | ||
* Copyright 2015-2019 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.extension; | ||
|
||
import static org.apiguardian.api.API.Status.EXPERIMENTAL; | ||
|
||
import java.lang.reflect.Constructor; | ||
import java.lang.reflect.Method; | ||
|
||
import org.apiguardian.api.API; | ||
import org.junit.jupiter.api.AfterAll; | ||
import org.junit.jupiter.api.AfterEach; | ||
import org.junit.jupiter.api.BeforeAll; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.DynamicTest; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.TestFactory; | ||
import org.junit.jupiter.api.TestTemplate; | ||
|
||
/** | ||
* {@code InvocationInterceptor} defines the API for {@link Extension | ||
* Extensions} that wish to intercept calls to test code. | ||
* | ||
* <h3>Invocation Contract</h3> | ||
* | ||
* <p>Each method in this class must execute the passed {@link Invocation} | ||
* exactly once. Otherwise, the enclosing test or container will be reported as | ||
* failed. | ||
* | ||
* <h3>Constructor Requirements</h3> | ||
* | ||
* <p>Consult the documentation in {@link Extension} for details on | ||
* constructor requirements. | ||
* | ||
* @since 5.5 | ||
* @see Invocation | ||
* @see ReflectiveInvocationContext | ||
* @see ExtensionContext | ||
*/ | ||
@API(status = EXPERIMENTAL, since = "5.5") | ||
public interface InvocationInterceptor extends Extension { | ||
|
||
/** | ||
* Intercept the invocation of a test class constructor. | ||
* | ||
* @param invocation the invocation that is being intercepted; never | ||
* {@code null} | ||
* @param extensionContext the current extension context; never {@code null} | ||
* @param <T> the result type | ||
* @return the result of the invocation; never {@code null} | ||
* @throws Throwable in case of failures | ||
*/ | ||
default <T> T interceptTestClassConstructor(Invocation<T> invocation, | ||
ReflectiveInvocationContext<Constructor<T>> invocationContext, ExtensionContext extensionContext) | ||
throws Throwable { | ||
return invocation.proceed(); | ||
} | ||
|
||
/** | ||
* Intercept the invocation of a {@link BeforeAll @BeforeAll} method. | ||
* | ||
* @param invocation the invocation that is being intercepted; never | ||
* {@code null} | ||
* @param extensionContext the current extension context; never {@code null} | ||
* @throws Throwable in case of failures | ||
*/ | ||
default void interceptBeforeAllMethod(Invocation<Void> invocation, | ||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable { | ||
invocation.proceed(); | ||
} | ||
|
||
/** | ||
* Intercept the invocation of a {@link BeforeEach @BeforeEach} method. | ||
* | ||
* @param invocation the invocation that is being intercepted; never | ||
* {@code null} | ||
* @param extensionContext the current extension context; never {@code null} | ||
* @throws Throwable in case of failures | ||
*/ | ||
default void interceptBeforeEachMethod(Invocation<Void> invocation, | ||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable { | ||
invocation.proceed(); | ||
} | ||
|
||
/** | ||
* Intercept the invocation of a {@link Test @Test} method. | ||
* | ||
* @param invocation the invocation that is being intercepted; never | ||
* {@code null} | ||
* @param extensionContext the current extension context; never {@code null} | ||
* @throws Throwable in case of failures | ||
*/ | ||
default void interceptTestMethod(Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext, | ||
ExtensionContext extensionContext) throws Throwable { | ||
invocation.proceed(); | ||
} | ||
|
||
/** | ||
* Intercept the invocation of a {@link TestFactory @TestFactory} method. | ||
* | ||
* @param invocation the invocation that is being intercepted; never | ||
* {@code null} | ||
* @param extensionContext the current extension context; never {@code null} | ||
* @param <T> the result type | ||
* @return the result of the invocation; potentially {@code null} | ||
* @throws Throwable in case of failures | ||
*/ | ||
default <T> T interceptTestFactoryMethod(Invocation<T> invocation, | ||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable { | ||
return invocation.proceed(); | ||
} | ||
|
||
/** | ||
* Intercept the invocation of a {@link TestTemplate @TestTemplate} method. | ||
* | ||
* @param invocation the invocation that is being intercepted; never | ||
* {@code null} | ||
* @param extensionContext the current extension context; never {@code null} | ||
* @throws Throwable in case of failures | ||
*/ | ||
default void interceptTestTemplateMethod(Invocation<Void> invocation, | ||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable { | ||
invocation.proceed(); | ||
} | ||
|
||
/** | ||
* Intercept the invocation of a {@link DynamicTest}. | ||
* | ||
* @param invocation the invocation that is being intercepted; never | ||
* {@code null} | ||
* @param extensionContext the current extension context; never {@code null} | ||
* @throws Throwable in case of failures | ||
*/ | ||
default void interceptDynamicTest(Invocation<Void> invocation, ExtensionContext extensionContext) throws Throwable { | ||
invocation.proceed(); | ||
} | ||
|
||
/** | ||
* Intercept the invocation of an {@link AfterEach @AfterEach} method. | ||
* | ||
* @param invocation the invocation that is being intercepted; never | ||
* {@code null} | ||
* @param extensionContext the current extension context; never {@code null} | ||
* @throws Throwable in case of failures | ||
*/ | ||
default void interceptAfterEachMethod(Invocation<Void> invocation, | ||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable { | ||
invocation.proceed(); | ||
} | ||
|
||
/** | ||
* Intercept the invocation of an {@link AfterAll @AfterAll} method. | ||
* | ||
* @param invocation the invocation that is being intercepted; never | ||
* {@code null} | ||
* @param extensionContext the current extension context; never {@code null} | ||
* @throws Throwable in case of failures | ||
*/ | ||
default void interceptAfterAllMethod(Invocation<Void> invocation, | ||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable { | ||
invocation.proceed(); | ||
} | ||
|
||
/** | ||
* An invocation that returns a result and may throw a {@link Throwable}. | ||
* | ||
* <p>This interface is not intended to be implemented by clients. | ||
* | ||
* @param <T> the result type | ||
* @since 5.5 | ||
*/ | ||
@API(status = EXPERIMENTAL, since = "5.5") | ||
interface Invocation<T> { | ||
|
||
/** | ||
* Proceed with this invocation. | ||
* | ||
* @return the result of this invocation; potentially {@code null}. | ||
* @throws Throwable in case the invocation failed | ||
*/ | ||
T proceed() throws Throwable; | ||
|
||
} | ||
|
||
} |
74 changes: 74 additions & 0 deletions
74
...upiter-api/src/main/java/org/junit/jupiter/api/extension/ReflectiveInvocationContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* Copyright 2015-2019 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.extension; | ||
|
||
import static org.apiguardian.api.API.Status.EXPERIMENTAL; | ||
|
||
import java.lang.reflect.Executable; | ||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
import org.apiguardian.api.API; | ||
|
||
/** | ||
* {@code ReflectiveInvocationContext} encapsulates the <em>context</em> of | ||
* a reflective invocation of an executable (method or constructor). | ||
* | ||
* <p>This interface is not intended to be implemented by clients. | ||
* | ||
* @since 5.5 | ||
*/ | ||
@API(status = EXPERIMENTAL, since = "5.5") | ||
public interface ReflectiveInvocationContext<T extends Executable> { | ||
|
||
/** | ||
* Get the target class of this invocation context. | ||
* | ||
* <p>If this invocation context represents an instance method, this | ||
* method returns the class of the object the method will be invoked on, | ||
* not the class it is declared in. Otherwise, i.e. if this invocation | ||
* represents a static method or constructor, this method returns the | ||
* class the method or constructor is declared in. | ||
* | ||
* @return the target class of this invocation context; never | ||
* {@code null} | ||
*/ | ||
Class<?> getTargetClass(); | ||
|
||
/** | ||
* Get the method or constructor of this invocation context. | ||
* | ||
* @return the executable of this invocation context; never {@code null} | ||
*/ | ||
T getExecutable(); | ||
|
||
/** | ||
* Get the arguments of the executable in this invocation context. | ||
* | ||
* @return the arguments of the executable in this invocation context; | ||
* never {@code null} | ||
*/ | ||
List<Object> getArguments(); | ||
|
||
/** | ||
* Get the target object of this invocation context, if available. | ||
* | ||
* <p>If this invocation context represents an instance method, this | ||
* method returns the object the method will be invoked on. Otherwise, | ||
* i.e. if this invocation context represents a static method or | ||
* constructor, this method returns {@link Optional#empty() empty()}. | ||
* | ||
* @return the target of the executable of this invocation context; never | ||
* {@code null} but potentially empty | ||
*/ | ||
Optional<Object> getTarget(); | ||
|
||
} |
Oops, something went wrong.