cucumber.runtime.formatter.UsageFormatter Maven / Gradle / Ivy
package cucumber.runtime.formatter;
import cucumber.api.Result;
import cucumber.api.PickleStepTestStep;
import cucumber.api.event.EventHandler;
import cucumber.api.event.EventPublisher;
import cucumber.api.event.TestRunFinished;
import cucumber.api.event.TestStepFinished;
import cucumber.api.formatter.Formatter;
import cucumber.api.formatter.NiceAppendable;
import gherkin.deps.com.google.gson.Gson;
import gherkin.deps.com.google.gson.GsonBuilder;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Formatter to measure performance of steps. Aggregated results for all steps can be computed
* by adding {@link UsageStatisticStrategy} to the usageFormatter
*/
final class UsageFormatter implements Formatter {
private static final BigDecimal NANOS_PER_SECOND = BigDecimal.valueOf(1000000000);
final Map> usageMap = new HashMap>();
private final Map statisticStrategies = new HashMap();
private final NiceAppendable out;
private EventHandler stepFinishedHandler = new EventHandler() {
@Override
public void receive(TestStepFinished event) {
handleTestStepFinished(event);
}
};
private EventHandler runFinishedHandler = new EventHandler() {
@Override
public void receive(TestRunFinished event) {
finishReport();
}
};
/**
* Constructor
*
* @param out {@link Appendable} to print the result
*/
@SuppressWarnings("WeakerAccess") // Used by PluginFactory
public UsageFormatter(Appendable out) {
this.out = new NiceAppendable(out);
addUsageStatisticStrategy("median", new MedianUsageStatisticStrategy());
addUsageStatisticStrategy("average", new AverageUsageStatisticStrategy());
}
@Override
public void setEventPublisher(EventPublisher publisher) {
publisher.registerHandlerFor(TestStepFinished.class, stepFinishedHandler);
publisher.registerHandlerFor(TestRunFinished.class, runFinishedHandler);
}
void handleTestStepFinished(TestStepFinished event) {
if (event.testStep instanceof PickleStepTestStep && event.result.is(Result.Type.PASSED)) {
PickleStepTestStep testStep = (PickleStepTestStep) event.testStep;
addUsageEntry(event.result, testStep.getPattern(), testStep.getStepText(), testStep.getStepLocation());
}
}
void finishReport() {
List stepDefContainers = new ArrayList();
for (Map.Entry> usageEntry : usageMap.entrySet()) {
StepDefContainer stepDefContainer = new StepDefContainer();
stepDefContainers.add(stepDefContainer);
stepDefContainer.source = usageEntry.getKey();
stepDefContainer.steps = createStepContainer(usageEntry.getValue());
}
out.append(gson().toJson(stepDefContainers));
out.close();
}
private List createStepContainer(List stepContainers) {
for (StepContainer stepContainer : stepContainers) {
stepContainer.aggregatedDurations = createAggregatedDurations(stepContainer);
formatDurationAsSeconds(stepContainer.durations);
}
return stepContainers;
}
private void formatDurationAsSeconds(List durations) {
for (StepDuration duration : durations) {
duration.duration = toSeconds(duration.duration.longValue());
}
}
private Map createAggregatedDurations(StepContainer stepContainer) {
Map aggregatedResults = new HashMap();
for (Map.Entry calculatorEntry : statisticStrategies.entrySet()) {
UsageStatisticStrategy statisticStrategy = calculatorEntry.getValue();
List rawDurations = getRawDurations(stepContainer.durations);
Long calculationResult = statisticStrategy.calculate(rawDurations);
String strategy = calculatorEntry.getKey();
aggregatedResults.put(strategy, toSeconds(calculationResult));
}
return aggregatedResults;
}
private BigDecimal toSeconds(Long nanoSeconds) {
return BigDecimal.valueOf(nanoSeconds).divide(NANOS_PER_SECOND);
}
private List getRawDurations(List stepDurations) {
List rawDurations = new ArrayList();
for (StepDuration stepDuration : stepDurations) {
rawDurations.add(stepDuration.duration.longValue());
}
return rawDurations;
}
private Gson gson() {
return new GsonBuilder().setPrettyPrinting().create();
}
private void addUsageEntry(Result result, String stepDefinition, String stepNameWithArgs, String stepLocation) {
List stepContainers = usageMap.get(stepDefinition);
if (stepContainers == null) {
stepContainers = new ArrayList();
usageMap.put(stepDefinition, stepContainers);
}
StepContainer stepContainer = findOrCreateStepContainer(stepNameWithArgs, stepContainers);
Long duration = result.getDuration();
StepDuration stepDuration = createStepDuration(duration, stepLocation);
stepContainer.durations.add(stepDuration);
}
private StepDuration createStepDuration(Long duration, String location) {
StepDuration stepDuration = new StepDuration();
if (duration == null) {
stepDuration.duration = BigDecimal.ZERO;
} else {
stepDuration.duration = BigDecimal.valueOf(duration);
}
stepDuration.location = location;
return stepDuration;
}
private StepContainer findOrCreateStepContainer(String stepNameWithArgs, List stepContainers) {
for (StepContainer container : stepContainers) {
if (stepNameWithArgs.equals(container.name)) {
return container;
}
}
StepContainer stepContainer = new StepContainer();
stepContainer.name = stepNameWithArgs;
stepContainers.add(stepContainer);
return stepContainer;
}
/**
* Add a {@link UsageStatisticStrategy} to the formatter
*
* @param key the key, will be displayed in the output
* @param strategy the strategy
*/
public void addUsageStatisticStrategy(String key, UsageStatisticStrategy strategy) {
statisticStrategies.put(key, strategy);
}
/**
* Container of Step Definitions (patterns)
*/
static class StepDefContainer {
/**
* The StepDefinition (pattern)
*/
public String source;
/**
* A list of Steps
*/
public List steps;
}
/**
* Contains for usage-entries of steps
*/
static class StepContainer {
public String name;
public Map aggregatedDurations = new HashMap();
public List durations = new ArrayList();
}
static class StepDuration {
public BigDecimal duration;
public String location;
}
/**
* Calculate a statistical value to be displayed in the usage-file
*/
static interface UsageStatisticStrategy {
/**
* @param durationEntries list of execution times of steps as nanoseconds
* @return a statistical value (e.g. median, average, ..)
*/
Long calculate(List durationEntries);
}
/**
* Calculate the average of a list of duration entries
*/
static class AverageUsageStatisticStrategy implements UsageStatisticStrategy {
@Override
public Long calculate(List durationEntries) {
if (verifyNoNulls(durationEntries)) {
return 0L;
}
long sum = 0;
for (Long duration : durationEntries) {
sum += duration;
}
return sum / durationEntries.size();
}
private boolean verifyNoNulls(List durationEntries) {
return durationEntries == null || durationEntries.isEmpty() || durationEntries.contains(null);
}
}
/**
* Calculate the median of a list of duration entries
*/
static class MedianUsageStatisticStrategy implements UsageStatisticStrategy {
@Override
public Long calculate(List durationEntries) {
if (verifyNoNulls(durationEntries)) {
return 0L;
}
Collections.sort(durationEntries);
int middle = durationEntries.size() / 2;
if (durationEntries.size() % 2 == 1) {
return durationEntries.get(middle);
} else {
return (durationEntries.get(middle - 1) + durationEntries.get(middle)) / 2;
}
}
private boolean verifyNoNulls(List durationEntries) {
return durationEntries == null || durationEntries.isEmpty() || durationEntries.contains(null);
}
}
}