Skip to content

Commit

Permalink
Add support for junit 5 parameterized tests (#120)
Browse files Browse the repository at this point in the history
  • Loading branch information
ILadis authored Jun 7, 2024
1 parent df1a8e4 commit 2463bf5
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.github.noconnor.junitperf.examples;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Arrays;
import java.util.List;

import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import com.github.noconnor.junitperf.JUnitPerfInterceptor;
import com.github.noconnor.junitperf.JUnitPerfTest;
import com.github.noconnor.junitperf.TestContextSupplier;
import com.github.noconnor.junitperf.data.TestContext;

@ExtendWith(JUnitPerfInterceptor.class)
public class ExampleParameterizedTests {

static List<String> hostnames() {
return Arrays.asList("www.google.com", "www.example.com");
}

@MethodSource("hostnames")
@ParameterizedTest(name = "test1(hostname = {0})")
@JUnitPerfTest(durationMs = 3_000, maxExecutionsPerSecond = 1)
public void test1(String hostname) throws IOException {
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(hostname, 80), 1000);
}
}

@MethodSource("hostnames")
@ParameterizedTest(name = "test2(hostname = {0})")
@JUnitPerfTest(durationMs = 3_000, maxExecutionsPerSecond = 1)
public void test2(String hostname, TestContextSupplier supplier) {
TestContext context = supplier.startMeasurement();
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(hostname, 80), 1000);
context.success();
} catch (IOException e) {
context.fail();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

Expand Down Expand Up @@ -88,6 +89,16 @@ public synchronized void postProcessTestInstance(Object testInstance, ExtensionC
sharedContexts.put(context.getUniqueId(), test);
}

@Override
public void interceptTestTemplateMethod(Invocation<Void> invocation,
ReflectiveInvocationContext<Method> invocationContext,
ExtensionContext extensionContext) throws Throwable {


// Will be called for every instance of @ParameterizedTest
interceptTestMethod(invocation, invocationContext, extensionContext);
}

@Override
public void interceptTestMethod(Invocation<Void> invocation,
ReflectiveInvocationContext<Method> invocationContext,
Expand All @@ -104,8 +115,7 @@ public void interceptTestMethod(Invocation<Void> invocation,
if (nonNull(perfTestAnnotation)) {
log.trace("Using {} for {} : {}", perfTestAnnotation, getUniqueId(extensionContext), getUniqueId(extensionContext.getRoot()));

boolean isAsync = invocationContext.getArguments().stream().anyMatch(arg -> arg instanceof TestContextSupplier);
EvaluationContext context = createEvaluationContext(method, isAsync);
EvaluationContext context = createEvaluationContext(extensionContext, invocationContext);
context.loadConfiguration(perfTestAnnotation);
context.loadRequirements(requirementsAnnotation);

Expand Down Expand Up @@ -167,9 +177,13 @@ protected JUnitPerfTest getJUnitPerfTestDetails(Method method, ExtensionContext
return nonNull(specifiedAnnotation) ? specifiedAnnotation : suiteAnnotation;
}

protected EvaluationContext createEvaluationContext(Method method, boolean isAsync) {
EvaluationContext ctx = new EvaluationContext(method.getName(), nanoTime(), isAsync);
ctx.setGroupName(method.getDeclaringClass().getName());
protected EvaluationContext createEvaluationContext(ExtensionContext extensionContext, ReflectiveInvocationContext<Method> invocationContext) {
String testName = extensionContext.getDisplayName();
String groupName = invocationContext.getExecutable().getDeclaringClass().getName();
boolean isAsync = invocationContext.getArguments().stream().anyMatch(arg -> arg instanceof TestContextSupplier);

EvaluationContext ctx = new EvaluationContext(testName, nanoTime(), isAsync);
ctx.setGroupName(groupName);
return ctx;
}

Expand Down Expand Up @@ -215,15 +229,28 @@ private static String getUniqueId(ExtensionContext extensionContext) {
private static TestDetails getTestDetails(ExtensionContext extensionContext) {
String testId = extensionContext.getUniqueId();
testContexts.computeIfAbsent(testId, newTestId -> {
String parentId = extensionContext.getParent().map(ExtensionContext::getUniqueId).orElse("");
SharedConfig parentDetails = sharedContexts.getOrDefault(parentId, new SharedConfig());
SharedConfig sharedDetails = getParentSharedContext(extensionContext);
TestDetails testDetails = new TestDetails();
testDetails.setStatementBuilder(parentDetails.getStatementBuilder().get());
testDetails.setActiveReporters(parentDetails.getActiveReporters());
testDetails.setStatsCalculator(parentDetails.getStatsSupplier().get());
testDetails.setStatementBuilder(sharedDetails.getStatementBuilder().get());
testDetails.setActiveReporters(sharedDetails.getActiveReporters());
testDetails.setStatsCalculator(sharedDetails.getStatsSupplier().get());
return testDetails;
});
return testContexts.get(testId);
}

private static SharedConfig getParentSharedContext(ExtensionContext extensionContext) {
Optional<ExtensionContext> parentContext = extensionContext.getParent();
Optional<SharedConfig> sharedDetails = parentContext.map(ExtensionContext::getUniqueId).map(sharedContexts::get);

if (sharedDetails.isPresent()) {
return sharedDetails.get();
}

if (parentContext.isPresent()) {
return getParentSharedContext(parentContext.get());
}

return new SharedConfig();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ void whenTestHasBeenAnnotatedWithPerfAnnotations_andTestHasBeforeAndAfterMethods
PerformanceEvaluationStatement statementMock = mock(PerformanceEvaluationStatement.class);
Invocation<Void> invocationMock = mock(Invocation.class);
ReflectiveInvocationContext<Method> invocationContextMock = mock(ReflectiveInvocationContext.class);
when(invocationContextMock.getExecutable()).thenReturn(methodMock);

when(extensionContextMock.getRequiredTestMethod()).thenReturn(methodMock);
when(extensionContextMock.getRequiredTestClass()).thenReturn((Class) test.getClass());
Expand Down Expand Up @@ -191,6 +192,7 @@ void whenTestHasBeenAnnotatedWithPerfAnnotations_thenTestStatementShouldBeBuilt(
PerformanceEvaluationStatement statementMock = mock(PerformanceEvaluationStatement.class);
Invocation<Void> invocationMock = mock(Invocation.class);
ReflectiveInvocationContext<Method> invocationContextMock = mock(ReflectiveInvocationContext.class);
when(invocationContextMock.getExecutable()).thenReturn(methodMock);

when(extensionContextMock.getRequiredTestMethod()).thenReturn(methodMock);
when(extensionContextMock.getRequiredTestClass()).thenReturn((Class) test.getClass());
Expand Down Expand Up @@ -223,6 +225,7 @@ void whenTestHasBeenAnnotatedWithPerfAnnotations_thenMeasurementStartMsShouldBeC
PerformanceEvaluationStatement statementMock = mock(PerformanceEvaluationStatement.class);
Invocation<Void> invocationMock = mock(Invocation.class);
ReflectiveInvocationContext<Method> invocationContextMock = mock(ReflectiveInvocationContext.class);
when(invocationContextMock.getExecutable()).thenReturn(methodMock);

when(extensionContextMock.getRequiredTestMethod()).thenReturn(methodMock);
when(extensionContextMock.getRequiredTestClass()).thenReturn((Class) test.getClass());
Expand Down Expand Up @@ -253,6 +256,7 @@ void whenAsyncTestHasBeenAnnotatedWithPerfAnnotations_thenContextShouldBeMarkedA
PerformanceEvaluationStatement statementMock = mock(PerformanceEvaluationStatement.class);
Invocation<Void> invocationMock = mock(Invocation.class);
ReflectiveInvocationContext<Method> invocationContextMock = mock(ReflectiveInvocationContext.class);
when(invocationContextMock.getExecutable()).thenReturn(methodMock);

when(invocationContextMock.getArguments()).thenReturn(mockAsyncArgs());
when(extensionContextMock.getRequiredTestMethod()).thenReturn(methodMock);
Expand All @@ -271,6 +275,36 @@ void whenAsyncTestHasBeenAnnotatedWithPerfAnnotations_thenContextShouldBeMarkedA
assertTrue(context.isAsyncEvaluation());
}

@Test
void whenTestMethodHasCustomDisplayName_thenEvaluationContextShouldUseThisAsTestName() throws Throwable {
ExtensionContext extensionContextMock = mockTestContext();
assertNull(getSharedContext(extensionContextMock));

SampleAnnotatedTest test = new SampleAnnotatedTest();

Method methodMock = test.getClass().getMethod("someTestMethod");
PerformanceEvaluationStatement statementMock = mock(PerformanceEvaluationStatement.class);
Invocation<Void> invocationMock = mock(Invocation.class);
ReflectiveInvocationContext<Method> invocationContextMock = mock(ReflectiveInvocationContext.class);
when(invocationContextMock.getExecutable()).thenReturn(methodMock);

String displayName = "Custom display name for Test";
when(extensionContextMock.getDisplayName()).thenReturn(displayName);
when(extensionContextMock.getRequiredTestMethod()).thenReturn(methodMock);
when(extensionContextMock.getRequiredTestInstance()).thenReturn(test);

when(statementBuilderMock.build()).thenReturn(statementMock);

interceptor.postProcessTestInstance(test, getParent(extensionContextMock));

getSharedContext(extensionContextMock).setStatementBuilder(() -> statementBuilderMock);

interceptor.interceptTestMethod(invocationMock, invocationContextMock, extensionContextMock);

EvaluationContext context = captureEvaluationContext();
assertEquals(displayName, context.getTestName());
}

@Test
void whenASuiteAnnotationsAreAvailable_thenSuiteAnnotationsShouldBeUsed() throws Throwable {
ExtensionContext extensionContextMock = mockTestContext();
Expand All @@ -282,6 +316,7 @@ void whenASuiteAnnotationsAreAvailable_thenSuiteAnnotationsShouldBeUsed() throws
PerformanceEvaluationStatement statementMock = mock(PerformanceEvaluationStatement.class);
Invocation<Void> invocationMock = mock(Invocation.class);
ReflectiveInvocationContext<Method> invocationContextMock = mock(ReflectiveInvocationContext.class);
when(invocationContextMock.getExecutable()).thenReturn(methodMock);
mockActiveSuite(extensionContextMock, SuiteSampleTest.class);

when(extensionContextMock.getRequiredTestMethod()).thenReturn(methodMock);
Expand Down Expand Up @@ -321,6 +356,7 @@ void whenAChildClassInheritsFromABaseClassWithReportingConfig_thenChildClassShou
PerformanceEvaluationStatement statementMock = mock(PerformanceEvaluationStatement.class);
Invocation<Void> invocationMock = mock(Invocation.class);
ReflectiveInvocationContext<Method> invocationContextMock = mock(ReflectiveInvocationContext.class);
when(invocationContextMock.getExecutable()).thenReturn(methodMock);

when(extensionContextMock.getRequiredTestMethod()).thenReturn(methodMock);
when(extensionContextMock.getRequiredTestClass()).thenReturn((Class) test.getClass());
Expand Down Expand Up @@ -359,6 +395,7 @@ void whenProceedThrowsAnAssertionError_thenTestShouldNotFail() throws Throwable
PerformanceEvaluationStatement statementMock = mock(PerformanceEvaluationStatement.class);
Invocation<Void> invocationMock = mock(Invocation.class);
ReflectiveInvocationContext<Method> invocationContextMock = mock(ReflectiveInvocationContext.class);
when(invocationContextMock.getExecutable()).thenReturn(methodMock);

when(extensionContextMock.getRequiredTestMethod()).thenReturn(methodMock);
when(extensionContextMock.getRequiredTestClass()).thenReturn((Class) test.getClass());
Expand Down Expand Up @@ -417,6 +454,36 @@ void whenReportingConfigIsNonStatic_thenPostProcessTestInstanceShouldThrowAnExce
assertThrows(IllegalStateException.class, () -> interceptor.postProcessTestInstance(test, getParent(extensionContextMock)));
}

@Test
void whenReportingConfigIsFoundInParentSharedContext_thenThisConfigShouldBeUsedForCurrentTestContext() throws Throwable {
ExtensionContext postProcessContextMock = mockTestContext();
assertNull(getSharedContext(postProcessContextMock));

SampleChildTest test = new SampleChildTest();

Method methodMock = test.getClass().getMethod("someTestMethod");
PerformanceEvaluationStatement statementMock = mock(PerformanceEvaluationStatement.class);
Invocation<Void> invocationMock = mock(Invocation.class);
ReflectiveInvocationContext<Method> invocationContextMock = mock(ReflectiveInvocationContext.class);
when(invocationContextMock.getExecutable()).thenReturn(methodMock);

when(statementBuilderMock.build()).thenReturn(statementMock);

interceptor.postProcessTestInstance(test, getParent(postProcessContextMock));

getSharedContext(postProcessContextMock).setStatementBuilder(() -> statementBuilderMock);

ExtensionContext interceptContextMock = mockTestContext(postProcessContextMock);
when(interceptContextMock.getRequiredTestMethod()).thenReturn(methodMock);
when(interceptContextMock.getRequiredTestClass()).thenReturn((Class) test.getClass());
when(interceptContextMock.getRequiredTestInstance()).thenReturn(test);

interceptor.interceptTestMethod(invocationMock, invocationContextMock, interceptContextMock);

assertEquals(SampleChildTest.config.getReportGenerators(), getSharedContext(postProcessContextMock).getActiveReporters());
assertEquals(SampleChildTest.config.getReportGenerators(), getTestContext(interceptContextMock).getActiveReporters());
}

@Test
void whenInterceptorSupportsParameterIsCalled_thenParameterTypeShouldBeChecked() throws NoSuchMethodException {
assertTrue(interceptor.supportsParameter(mockTestContextSupplierParameterType(), null));
Expand Down Expand Up @@ -486,6 +553,13 @@ private static ExtensionContext mockTestContext() {
return test;
}

private static ExtensionContext mockTestContext(ExtensionContext parent) {
ExtensionContext test = mock(ExtensionContext.class);
when(test.getUniqueId()).thenReturn("context:" + ThreadLocalRandom.current().nextInt());
when(test.getParent()).thenReturn(Optional.of(parent));
return test;
}

private static SharedConfig getSharedContext(ExtensionContext context) {
return JUnitPerfInterceptor.sharedContexts.get(context.getParent().get().getUniqueId());
}
Expand Down

0 comments on commit 2463bf5

Please sign in to comment.