Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring interceptor #101

Merged
merged 5 commits into from
Jun 18, 2023
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion docs/junit5.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ private final static JUnitPerfReportingConfig PERF_CONFIG = JUnitPerfReportingCo
.build();
```

**NOTE:** the `JUnitPerfReportingConfig` should be a **static** field instance to prevent a new/different instance being created for each `@Test`
**NOTE:** the `JUnitPerfReportingConfig` must be a **static** field instance to prevent a new/different instance being created for each `@Test`
instance

To generate an **CSV report**
Expand Down
6 changes: 6 additions & 0 deletions junit5-examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>${skipTests}</skipTests>
<!-- <properties>-->
<!-- <configurationParameters>-->
<!-- junit.jupiter.execution.parallel.enabled = true-->
<!-- junit.jupiter.execution.parallel.mode.default = concurrent-->
<!-- </configurationParameters>-->
<!-- </properties>-->
</configuration>
</plugin>
</plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public HtmlReportGenerator(String reportPath) {
}

@Override
public void generateReport(LinkedHashSet<EvaluationContext> testContexts) {
public synchronized void generateReport(LinkedHashSet<EvaluationContext> testContexts) {
history.addAll(testContexts);
renderTemplate();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.github.noconnor.junitperf.statistics.StatisticsCalculator;
import com.github.noconnor.junitperf.statistics.providers.DescriptiveStatisticsCalculator;
import com.github.noconnor.junitperf.suite.SuiteRegistry;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.InvocationInterceptor;
Expand All @@ -25,6 +26,7 @@
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

import static java.lang.System.currentTimeMillis;
import static java.lang.System.nanoTime;
Expand All @@ -36,67 +38,79 @@
public class JUnitPerfInterceptor implements InvocationInterceptor, TestInstancePostProcessor, ParameterResolver {

protected static final ReportGenerator DEFAULT_REPORTER = new ConsoleReportGenerator();
protected static final Map<String, LinkedHashSet<EvaluationContext>> ACTIVE_CONTEXTS = new ConcurrentHashMap<>();

protected Collection<ReportGenerator> activeReporters;
protected StatisticsCalculator activeStatisticsCalculator;
protected long measurementsStartTimeMs;
protected PerformanceEvaluationStatementBuilder statementBuilder;

protected static final Map<String, TestDetails> testContexts = new ConcurrentHashMap<>();
protected static final Map<String, SharedConfig> sharedContexts = new ConcurrentHashMap<>();

@Data
protected static class TestDetails {
private Class<?> testClass;
private Method testMethod;
private long measurementsStartTimeMs;
private EvaluationContext context;
private StatisticsCalculator statsCalculator;
private Collection<ReportGenerator> activeReporters;
private PerformanceEvaluationStatementBuilder statementBuilder;
}

@Data
protected static class SharedConfig {
private Collection<ReportGenerator> activeReporters = singletonList(DEFAULT_REPORTER);
private Supplier<StatisticsCalculator> statsSupplier = DescriptiveStatisticsCalculator::new;
private Supplier<PerformanceEvaluationStatementBuilder> statementBuilder = PerformanceEvaluationStatement::builder;
}

@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {

SuiteRegistry.register(context);

public synchronized void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
if (sharedContexts.containsKey(context.getUniqueId())) {
log.debug("Test already configured");
return;
}

SharedConfig test = new SharedConfig();
SuiteRegistry.scanForSuiteDetails(context);
JUnitPerfReportingConfig reportingConfig = findTestActiveConfigField(testInstance, context);

if (nonNull(reportingConfig)) {
activeReporters = reportingConfig.getReportGenerators();
activeStatisticsCalculator = reportingConfig.getStatisticsCalculatorSupplier().get();
}

// Defaults if no overrides provided
if (isNull(activeReporters) || activeReporters.isEmpty()) {
activeReporters = singletonList(DEFAULT_REPORTER);
test.setActiveReporters(reportingConfig.getReportGenerators());
test.setStatsSupplier(reportingConfig.getStatisticsCalculatorSupplier());
}
if (isNull(activeStatisticsCalculator)) {
activeStatisticsCalculator = new DescriptiveStatisticsCalculator();
}
statementBuilder = PerformanceEvaluationStatement.builder();
sharedContexts.put(context.getUniqueId(), test);
}

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


// Will be called for every instance of @Test
Method method = extensionContext.getRequiredTestMethod();

JUnitPerfTest perfTestAnnotation = getJUnitPerfTestDetails(method, extensionContext);
JUnitPerfTestRequirement requirementsAnnotation = getJUnitPerfTestRequirementDetails(method, extensionContext);

if (nonNull(perfTestAnnotation)) {
measurementsStartTimeMs = currentTimeMillis() + perfTestAnnotation.warmUpMs();
boolean isAsync = invocationContext.getArguments().stream().anyMatch(arg -> arg instanceof TestContextSupplier);

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

ACTIVE_CONTEXTS.putIfAbsent(extensionContext.getUniqueId(), new LinkedHashSet<>());
ACTIVE_CONTEXTS.get(extensionContext.getUniqueId()).add(context);
TestDetails test = getTestDetails(extensionContext);
test.setTestClass(method.getDeclaringClass());
test.setTestMethod(method);
test.setMeasurementsStartTimeMs(currentTimeMillis() + perfTestAnnotation.warmUpMs());
test.setContext(context);

SimpleTestStatement testStatement = () -> method.invoke(
extensionContext.getRequiredTestInstance(),
invocationContext.getArguments().toArray()
);

PerformanceEvaluationStatement parallelExecution = statementBuilder
PerformanceEvaluationStatement parallelExecution = test.getStatementBuilder()
.baseStatement(testStatement)
.statistics(activeStatisticsCalculator)
.statistics(test.getStatsCalculator())
.context(context)
.listener(complete -> updateReport(extensionContext))
.listener(complete -> updateReport(test))
.build();

parallelExecution.runParallelEvaluation();
Expand All @@ -106,17 +120,18 @@ public void interceptTestMethod(Invocation<Void> invocation,
} else {
invocation.proceed();
}

}

}

@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return parameterContext.getParameter().getType() == TestContextSupplier.class;
}

@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return new TestContextSupplier(measurementsStartTimeMs, activeStatisticsCalculator);
TestDetails test = getTestDetails(extensionContext);
return new TestContextSupplier(test.getMeasurementsStartTimeMs(), test.getStatsCalculator());
}

protected JUnitPerfTestRequirement getJUnitPerfTestRequirementDetails(Method method, ExtensionContext ctxt) {
Expand All @@ -143,16 +158,18 @@ protected EvaluationContext createEvaluationContext(Method method, boolean isAsy
return ctx;
}

private synchronized void updateReport(ExtensionContext ctxt) {
activeReporters.forEach(r -> {
r.generateReport(ACTIVE_CONTEXTS.get(ctxt.getUniqueId()));
private synchronized void updateReport(TestDetails test) {
test.getActiveReporters().forEach(r -> {
LinkedHashSet<EvaluationContext> ctxt = new LinkedHashSet<>();
ctxt.add(test.getContext());
r.generateReport(ctxt);
});
}

private static void warnIfNonStatic(Field field) {
private static void failIfNonStatic(Field field) {
boolean isStatic = Modifier.isStatic(field.getModifiers());
if (!isStatic) {
log.warn("Warning: JUnitPerfTestConfig should be static or a new instance will be created for each @Test method");
throw new IllegalStateException("JUnitPerfTestConfig should be static ");
}
}

Expand All @@ -168,7 +185,7 @@ private static JUnitPerfReportingConfig scanForReportingConfig(Object testInstan
}
for (Field field : testClass.getDeclaredFields()) {
if (field.isAnnotationPresent(JUnitPerfTestActiveConfig.class)) {
warnIfNonStatic(field);
failIfNonStatic(field);
field.setAccessible(true);
return (JUnitPerfReportingConfig) field.get(testInstance);
}
Expand All @@ -185,4 +202,18 @@ private static void proceedQuietly(Invocation<Void> invocation) throws Throwable
}
}

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());
TestDetails testDetails = new TestDetails();
testDetails.setStatementBuilder(parentDetails.getStatementBuilder().get());
testDetails.setActiveReporters(parentDetails.getActiveReporters());
testDetails.setStatsCalculator(parentDetails.getStatsSupplier().get());
return testDetails;
});
return testContexts.get(testId);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class SuiteRegistry {
private static final Map<String, SuiteSettings> settingsCache = new HashMap<>();
private static final Pattern suiteClassPattern = Pattern.compile(".*\\[suite:([^\\]]*)\\].*");

public static void register(ExtensionContext context) {
public static void scanForSuiteDetails(ExtensionContext context) {

String rootUniqueId = getRootId(context);
Class<?> clazz = getSuiteClass(rootUniqueId);
Expand Down
Loading