Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

package org.junit.jupiter.engine.extension;

import static java.util.function.Function.identity;
import static java.util.function.Predicate.isEqual;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
Expand All @@ -22,17 +24,23 @@

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.EnumSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;

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.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.TestReporter;
import org.junit.jupiter.api.extension.DynamicTestInvocationContext;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -42,8 +50,6 @@
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.engine.AbstractJupiterTestEngineTests;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.platform.commons.JUnitException;
import org.junit.platform.engine.reporting.ReportEntry;
import org.junit.platform.testkit.engine.EngineExecutionResults;
Expand Down Expand Up @@ -130,28 +136,76 @@ void test() {
}
}

@ParameterizedTest(name = "{0}")
@EnumSource(InvocationType.class)
void callsInterceptors(InvocationType invocationType) {
@TestFactory
Stream<DynamicTest> callsInterceptors() {
var results = executeTestsForClass(TestCaseWithThreeInterceptors.class);

results.testEvents().assertStatistics(stats -> stats.failed(0).succeeded(3));

assertThat(getEvents(results, EnumSet.of(invocationType)).distinct()) //
.containsExactly("before:foo", "before:bar", "before:baz", "test", "after:baz", "after:bar",
"after:foo");
return Arrays.stream(InvocationType.values()) //
.map(it -> dynamicTest(it.name(), () -> verifyEvents(results, it)));
}

private Stream<String> getEvents(EngineExecutionResults results, EnumSet<InvocationType> types) {
return results.allEvents().reportingEntryPublished() //
.map(event -> event.getPayload(ReportEntry.class).orElseThrow()) //
.map(ReportEntry::getKeyValuePairs) //
.filter(map -> map.keySet().stream().map(InvocationType::valueOf).anyMatch(types::contains)) //
.flatMap(map -> map.values().stream());
private void verifyEvents(EngineExecutionResults results, InvocationType invocationType) {
var beforeEvents = List.of("before:foo", "before:bar", "before:baz");
var testEvent = List.of("test");
var afterEvents = List.of("after:baz", "after:bar", "after:foo");
var allEvents = Stream.of(beforeEvents, testEvent, afterEvents).flatMap(Collection::stream).toList();
String testClassName = TestCaseWithThreeInterceptors.class.getName();

var expectedElements = switch (invocationType) {
case BEFORE_ALL, AFTER_ALL -> prefixed(allEvents, testClassName);
case CONSTRUCTOR -> concatStreams(
prefixed(allEvents, it -> it.endsWith(":bar") ? testClassName : "test(TestReporter)"),
prefixed(allEvents, it -> it.endsWith(":bar") ? testClassName : "testTemplate(TestReporter)[1]"),
prefixed(allEvents, it -> it.endsWith(":bar") ? testClassName : "testFactory(TestReporter)"));
case BEFORE_EACH, AFTER_EACH -> concatStreams(prefixed(allEvents, "test(TestReporter)"),
prefixed(allEvents, "testTemplate(TestReporter)[1]"), prefixed(allEvents, "testFactory(TestReporter)"));
case TEST_METHOD -> prefixed(allEvents, "test(TestReporter)");
case TEST_TEMPLATE_METHOD -> prefixed(allEvents, "testTemplate(TestReporter)[1]");
case TEST_FACTORY_METHOD -> prefixed(allEvents, "testFactory(TestReporter)");
case DYNAMIC_TEST -> concatStreams(prefixed(beforeEvents, "testFactory(TestReporter)[1]"),
prefixed(testEvent, "testFactory(TestReporter)"),
prefixed(afterEvents, "testFactory(TestReporter)[1]"));
};

assertThat(getEvents(results, invocationType)) //
.containsExactlyElementsOf(expectedElements.toList());
}

@SafeVarargs
@SuppressWarnings("varargs")
private static <T> Stream<T> concatStreams(Stream<T>... items) {
return Stream.of(items).flatMap(identity());
}

private static Stream<String> prefixed(List<String> values, String prefix) {
return prefixed(values, __ -> prefix);
}

private static Stream<String> prefixed(List<String> values, UnaryOperator<String> prefixGenerator) {
return values.stream() //
.map(it -> "[%s] %s".formatted(prefixGenerator.apply(it), it));
}

private Stream<String> getEvents(EngineExecutionResults results, InvocationType invocationType) {
return results.allEvents().reportingEntryPublished().stream() //
.flatMap(event -> {
var reportEntry = event.getPayload(ReportEntry.class).orElseThrow();
var keyValuePairs = reportEntry.getKeyValuePairs();
if (keyValuePairs.keySet().stream() //
.map(InvocationType::valueOf) //
.anyMatch(isEqual(invocationType))) {
return keyValuePairs.values().stream() //
.map(it -> "[%s] %s".formatted(event.getTestDescriptor().getLegacyReportingName(), it));
}
return Stream.empty();
});
}

@SuppressWarnings("JUnitMalformedDeclaration")
@ExtendWith({ FooInvocationInterceptor.class, BarInvocationInterceptor.class, BazInvocationInterceptor.class })
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
static class TestCaseWithThreeInterceptors {

public TestCaseWithThreeInterceptors(TestReporter reporter) {
Expand All @@ -169,16 +223,19 @@ void beforeEach(TestReporter reporter) {
publish(reporter, InvocationType.BEFORE_EACH);
}

@Order(1)
@Test
void test(TestReporter reporter) {
publish(reporter, InvocationType.TEST_METHOD);
}

@Order(2)
@RepeatedTest(1)
void testTemplate(TestReporter reporter) {
publish(reporter, InvocationType.TEST_TEMPLATE_METHOD);
}

@Order(3)
@TestFactory
DynamicTest testFactory(TestReporter reporter) {
publish(reporter, InvocationType.TEST_FACTORY_METHOD);
Expand Down Expand Up @@ -224,6 +281,11 @@ abstract static class ReportingInvocationInterceptor implements InvocationInterc
this.name = name;
}

@Override
public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) {
return ExtensionContextScope.TEST_METHOD;
}

@Override
public void interceptBeforeAllMethod(Invocation<Void> invocation,
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext)
Expand Down Expand Up @@ -299,8 +361,8 @@ public void interceptDynamicTest(Invocation<Void> invocation, DynamicTestInvocat
assertThat(invocationContext.getExecutable()).isNotNull();
assertThat(extensionContext.getUniqueId()).isNotBlank();
assertThat(extensionContext.getElement()).isEmpty();
assertThat(extensionContext.getParent().flatMap(ExtensionContext::getTestMethod)).contains(
testClass.getDeclaredMethod("testFactory", TestReporter.class));
assertThat(extensionContext.getParent().flatMap(ExtensionContext::getTestMethod)) //
.contains(testClass.getDeclaredMethod("testFactory", TestReporter.class));
reportAndProceed(invocation, extensionContext, InvocationType.DYNAMIC_TEST);
}

Expand Down Expand Up @@ -350,6 +412,12 @@ static class BarInvocationInterceptor extends ReportingInvocationInterceptor {
BarInvocationInterceptor() {
super("bar");
}

@SuppressWarnings("deprecation")
@Override
public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) {
return ExtensionContextScope.DEFAULT;
}
}

static class BazInvocationInterceptor extends ReportingInvocationInterceptor {
Expand Down