All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cucumber.runtime.formatter.UsageFormatter Maven / Gradle / Ivy

package cucumber.runtime.formatter;

import cucumber.runtime.StepDefinitionMatch;
import gherkin.deps.com.google.gson.Gson;
import gherkin.deps.com.google.gson.GsonBuilder;
import gherkin.formatter.Formatter;
import gherkin.formatter.NiceAppendable;
import gherkin.formatter.Reporter;
import gherkin.formatter.model.Background;
import gherkin.formatter.model.Examples;
import gherkin.formatter.model.Feature;
import gherkin.formatter.model.Match;
import gherkin.formatter.model.Result;
import gherkin.formatter.model.Scenario;
import gherkin.formatter.model.ScenarioOutline;
import gherkin.formatter.model.Step;

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
 */
class UsageFormatter implements Formatter, Reporter {
    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 Match match;

    /**
     * Constructor
     *
     * @param out {@link Appendable} to print the result
     */
    public UsageFormatter(Appendable out) {
        this.out = new NiceAppendable(out);

        addUsageStatisticStrategy("median", new MedianUsageStatisticStrategy());
        addUsageStatisticStrategy("average", new AverageUsageStatisticStrategy());
    }

    @Override
    public void uri(String uri) {
    }

    @Override
    public void feature(Feature feature) {
    }

    @Override
    public void background(Background background) {
    }

    @Override
    public void scenario(Scenario scenario) {
    }

    @Override
    public void scenarioOutline(ScenarioOutline scenarioOutline) {
    }

    @Override
    public void examples(Examples examples) {
    }

    @Override
    public void embedding(String mimeType, byte[] data) {
    }

    @Override
    public void write(String text) {
    }

    @Override
    public void step(Step step) {
    }

    @Override
    public void eof() {
    }

    @Override
    public void syntaxError(String state, String event, List legalEvents, String uri, Integer line) {
    }

    @Override
    public void done() {
        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));
    }

    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();
    }

    @Override
    public void close() {
        out.close();
    }

    @Override
    public void result(Result result) {
        if (result.getStatus().equals(Result.PASSED)) {
            addUsageEntry(result, getStepDefinition(), getStepName());
        }
    }

    @Override
    public void before(Match match, Result result) {
    }

    @Override
    public void after(Match match, Result result) {
    }

    private String getStepName() {
        return ((StepDefinitionMatch) match).getStepName();
    }

    private String getStepDefinition() {
        return ((StepDefinitionMatch) match).getPattern();
    }

    private void addUsageEntry(Result result, String stepDefinition, String stepNameWithArgs) {
        List stepContainers = usageMap.get(stepDefinition);
        if (stepContainers == null) {
            stepContainers = new ArrayList();
            usageMap.put(stepDefinition, stepContainers);
        }
        StepContainer stepContainer = findOrCreateStepContainer(stepNameWithArgs, stepContainers);

        String stepLocation = getStepLocation();
        Long duration = result.getDuration();
        StepDuration stepDuration = createStepDuration(duration, stepLocation);
        stepContainer.durations.add(stepDuration);
    }

    private String getStepLocation() {
        StackTraceElement stepLocation = ((StepDefinitionMatch) match).getStepLocation();
        return stepLocation.getFileName() + ":" + stepLocation.getLineNumber();
    }

    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;
    }

    @Override
    public void match(Match match) {
        this.match = match;
    }

    /**
     * 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);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy