diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java
index e978e174660..48efd489397 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java
@@ -13,6 +13,7 @@
import static org.junit.platform.commons.meta.API.Usage.Experimental;
import java.util.Iterator;
+import java.util.stream.Stream;
import org.junit.platform.commons.meta.API;
@@ -67,15 +68,19 @@ public interface TestTemplateInvocationContextProvider extends Extension {
*
This method is only called by the framework if {@link #supports} has
* previously returned {@code true} for the same
* {@link ContainerExtensionContext}. Thus, it must not return an empty
- * {@code Iterator}.
+ * {@code Stream}.
+ *
+ *
The returned {@code Stream} will be properly closed by calling
+ * {@link Stream#close()}, making it safe to use a resource such as
+ * {@link java.nio.file.Files#lines(java.nio.file.Path) Files.lines()}.
*
* @param context the container extension context for the test template
* method about to be invoked; never {@code null}
- * @return an Iterator of TestTemplateInvocationContext instances for the
+ * @return a Stream of TestTemplateInvocationContext instances for the
* invocation of the test template method; never {@code null} or empty
* @see #supports
* @see ContainerExtensionContext
*/
- Iterator provide(ContainerExtensionContext context);
+ Stream provide(ContainerExtensionContext context);
}
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java
index aa840626828..78b9f7ea880 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java
@@ -14,7 +14,6 @@
import static org.junit.platform.commons.meta.API.Usage.Internal;
import java.lang.reflect.Method;
-import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@@ -91,15 +90,12 @@ public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext conte
List providers = validateProviders(containerExtensionContext,
context.getExtensionRegistry());
AtomicInteger invocationIndex = new AtomicInteger();
- providers.forEach(provider -> {
- Iterator contextIterator = provider.provide(containerExtensionContext);
- contextIterator.forEachRemaining(invocationContext -> {
- int index = invocationIndex.incrementAndGet();
- TestDescriptor invocationTestDescriptor = createInvocationTestDescriptor(invocationContext, index);
- addChild(invocationTestDescriptor);
- dynamicTestExecutor.execute(invocationTestDescriptor);
- });
- });
+ // @formatter:off
+ providers.stream()
+ .flatMap(provider -> provider.provide(containerExtensionContext))
+ .map(invocationContext -> createInvocationTestDescriptor(invocationContext, invocationIndex.incrementAndGet()))
+ .forEach(invocationTestDescriptor -> execute(dynamicTestExecutor, invocationTestDescriptor));
+ // @formatter:on
validateWasAtLeastInvokedOnce(invocationIndex);
return context;
}
@@ -126,6 +122,11 @@ private TestDescriptor createInvocationTestDescriptor(TestTemplateInvocationCont
invocationContext, index);
}
+ private void execute(DynamicTestExecutor dynamicTestExecutor, TestDescriptor testDescriptor) {
+ addChild(testDescriptor);
+ dynamicTestExecutor.execute(testDescriptor);
+ }
+
private void validateWasAtLeastInvokedOnce(AtomicInteger invocationIndex) {
if (invocationIndex.get() == 0) {
throw new TestAbortedException("No supporting "
diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java
index a6ccf1f6349..5c5de18cabc 100644
--- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java
+++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java
@@ -11,7 +11,6 @@
package org.junit.jupiter.engine;
import static java.util.Arrays.asList;
-import static java.util.Collections.emptyIterator;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
@@ -38,8 +37,8 @@
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Iterator;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -77,7 +76,7 @@
/**
* @since 5.0
*/
-public class TestTemplateInvocationTests extends AbstractJupiterTestEngineTests {
+class TestTemplateInvocationTests extends AbstractJupiterTestEngineTests {
@Test
void templateWithoutRegisteredExtensionReportsFailure() {
@@ -311,6 +310,25 @@ void templateWithSupportingProviderButNoInvocationsReportsAbortedTest() {
message("No supporting TestTemplateInvocationContextProvider provided an invocation context")))));
}
+ @Test
+ void templateWithCloseableStream() {
+ LauncherDiscoveryRequest request = request().selectors(
+ selectMethod(MyTestTemplateTestCase.class, "templateWithCloseableStream")).build();
+
+ ExecutionEventRecorder eventRecorder = executeTests(request);
+
+ assertThat(InvocationContextProviderWithCloseableStream.streamClosed.get()).describedAs(
+ "streamClosed").isTrue();
+
+ assertRecordedExecutionEventsContainsExactly(eventRecorder.getExecutionEvents(), //
+ wrappedInContainerEvents(MyTestTemplateTestCase.class, //
+ event(container("templateWithCloseableStream"), started()), //
+ event(dynamicTestRegistered("test-template-invocation:#1")), //
+ event(test("test-template-invocation:#1"), started()), //
+ event(test("test-template-invocation:#1"), finishedSuccessfully()), //
+ event(container("templateWithCloseableStream"), finishedSuccessfully())));
+ }
+
private TestDescriptor findTestDescriptor(ExecutionEventRecorder eventRecorder,
Condition condition) {
// @formatter:off
@@ -338,10 +356,6 @@ private final Condition super ExecutionEvent>[] wrappedInContainerEvents(Class
static class MyTestTemplateTestCase {
- @Test
- void foo() {
- }
-
@TestTemplate
void templateWithoutRegisteredExtension() {
}
@@ -392,10 +406,10 @@ void templateWithDynamicParameterResolver(String parameter) {
@ExtendWith(StringParameterResolvingInvocationContextProvider.class)
@TestTemplate
void templateWithWrongParameterType(int parameter) {
- fail("never called");
+ fail("never called: " + parameter);
}
- private String parameterInstanceVariable;
+ String parameterInstanceVariable;
@ExtendWith(StringParameterInjectingInvocationContextProvider.class)
@TestTemplate
@@ -409,6 +423,10 @@ void templateWithSupportingProviderButNoInvocations() {
fail("never called");
}
+ @ExtendWith(InvocationContextProviderWithCloseableStream.class)
+ @TestTemplate
+ void templateWithCloseableStream() {
+ }
}
static class TestTemplateTestClassWithBeforeAndAfterEach {
@@ -452,8 +470,8 @@ public boolean supports(ContainerExtensionContext context) {
}
@Override
- public Iterator provide(ContainerExtensionContext context) {
- return singleton(emptyTestTemplateInvocationContext()).iterator();
+ public Stream provide(ContainerExtensionContext context) {
+ return Stream.of(emptyTestTemplateInvocationContext());
}
}
@@ -466,8 +484,8 @@ public boolean supports(ContainerExtensionContext context) {
}
@Override
- public Iterator provide(ContainerExtensionContext context) {
- return singleton(emptyTestTemplateInvocationContext()).iterator();
+ public Stream provide(ContainerExtensionContext context) {
+ return Stream.of(emptyTestTemplateInvocationContext());
}
}
@@ -479,8 +497,8 @@ public boolean supports(ContainerExtensionContext context) {
}
@Override
- public Iterator provide(ContainerExtensionContext context) {
- return asList(emptyTestTemplateInvocationContext(), emptyTestTemplateInvocationContext()).iterator();
+ public Stream provide(ContainerExtensionContext context) {
+ return Stream.of(emptyTestTemplateInvocationContext(), emptyTestTemplateInvocationContext());
}
}
@@ -507,13 +525,13 @@ public boolean supports(ContainerExtensionContext context) {
}
@Override
- public Iterator provide(ContainerExtensionContext context) {
+ public Stream provide(ContainerExtensionContext context) {
return Stream. generate(() -> new TestTemplateInvocationContext() {
@Override
public String getDisplayName(int invocationIndex) {
return invocationIndex + " --> " + context.getDisplayName();
}
- }).limit(1).iterator();
+ }).limit(1);
}
}
@@ -532,8 +550,8 @@ public boolean supports(ContainerExtensionContext context) {
}
@Override
- public Iterator provide(ContainerExtensionContext context) {
- return asList(createContext("foo"), createContext("bar")).iterator();
+ public Stream provide(ContainerExtensionContext context) {
+ return Stream.of(createContext("foo"), createContext("bar"));
}
private TestTemplateInvocationContext createContext(String argument) {
@@ -572,8 +590,8 @@ public boolean supports(ContainerExtensionContext context) {
}
@Override
- public Iterator provide(ContainerExtensionContext context) {
- return asList(createContext("foo"), createContext("bar")).iterator();
+ public Stream provide(ContainerExtensionContext context) {
+ return Stream.of(createContext("foo"), createContext("bar"));
}
private TestTemplateInvocationContext createContext(String argument) {
@@ -604,8 +622,8 @@ public boolean supports(ContainerExtensionContext context) {
}
@Override
- public Iterator provide(ContainerExtensionContext context) {
- return asList(createContext("foo"), createContext("bar")).iterator();
+ public Stream provide(ContainerExtensionContext context) {
+ return Stream.of(createContext("foo"), createContext("bar"));
}
private TestTemplateInvocationContext createContext(String argument) {
@@ -639,7 +657,7 @@ public void beforeTestExecution(TestExtensionContext context) throws Exception {
public void handleTestExecutionException(TestExtensionContext context, Throwable throwable)
throws Throwable {
TestTemplateTestClassWithDynamicLifecycleCallbacks.lifecycleEvents.add("handleTestExecutionException");
- throw throwable;
+ throw new AssertionError(throwable);
}
@Override
@@ -663,8 +681,23 @@ public boolean supports(ContainerExtensionContext context) {
}
@Override
- public Iterator provide(ContainerExtensionContext context) {
- return emptyIterator();
+ public Stream provide(ContainerExtensionContext context) {
+ return Stream.empty();
+ }
+ }
+
+ private static class InvocationContextProviderWithCloseableStream implements TestTemplateInvocationContextProvider {
+
+ private static AtomicBoolean streamClosed = new AtomicBoolean(false);
+
+ @Override
+ public boolean supports(ContainerExtensionContext context) {
+ return true;
+ }
+
+ @Override
+ public Stream provide(ContainerExtensionContext context) {
+ return Stream.of(emptyTestTemplateInvocationContext()).onClose(() -> streamClosed.set(true));
}
}
diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java
index 6d156ec85ed..e8df228a8d2 100644
--- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java
+++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java
@@ -16,7 +16,6 @@
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
-import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import java.util.Spliterators;
@@ -52,7 +51,7 @@ public boolean supports(ContainerExtensionContext context) {
}
@Override
- public Iterator provide(ContainerExtensionContext context) {
+ public Stream provide(ContainerExtensionContext context) {
Method templateMethod = Preconditions.notNull(context.getTestMethod().orElse(null),
"test method must not be null");
ParameterizedTestNameFormatter formatter = createNameFormatter(templateMethod);
@@ -64,8 +63,7 @@ public Iterator provide(ContainerExtensionContext
.peek(provider -> initialize(templateMethod, provider))
.flatMap(ParameterizedTestExtension::toArgumentsStream)
.map(Arguments::getArguments)
- .map(arguments -> toTestTemplateInvocationContext(formatter, arguments))
- .iterator();
+ .map(arguments -> toTestTemplateInvocationContext(formatter, arguments));
// @formatter:on
}