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

com.github.mkolisnyk.cucumber.reporting.CucumberUsageReporting Maven / Gradle / Ivy

There is a newer version: 1.3.5
Show newest version
/**
 * .
 */
package com.github.mkolisnyk.cucumber.reporting;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.maven.reporting.MavenReportException;

import com.cedarsoftware.util.io.JsonObject;
import com.cedarsoftware.util.io.JsonReader;
import com.github.mkolisnyk.cucumber.reporting.types.usage.CucumberStep;
import com.github.mkolisnyk.cucumber.reporting.types.usage.CucumberStepDuration;
import com.github.mkolisnyk.cucumber.reporting.types.usage.CucumberStepSource;
import com.github.mkolisnyk.cucumber.reporting.utils.helpers.MapUtils;

public class CucumberUsageReporting {

    private String       jsonUsageFile;

    private String       outputDirectory;

    public String getDescription(Locale arg0) {
        return "HTML formatted Cucumber keywords usage report";
    }

    public String getName(Locale arg0) {
        return "Cucumber usage report";
    }

    public String getOutputName() {
        return "cucumber-usage-report";
    }

    protected String getOutputDirectory() {
        return this.outputDirectory;
    }

    public String getJsonUsageFile() {
        return jsonUsageFile;
    }

    public void setJsonUsageFile(String jsonUsageFileValue) {
        this.jsonUsageFile = jsonUsageFileValue;
    }

    public void setOutputDirectory(String outputDirectoryValue) {
        this.outputDirectory = outputDirectoryValue;
    }

    public LinkedHashMap calculateStepsUsageScore(CucumberStepSource[] sources) {
        LinkedHashMap map = new LinkedHashMap();

        for (CucumberStepSource source:sources) {
            int totalSteps = 0;
            for (CucumberStep step:source.getSteps()) {
                totalSteps += step.getDurations().length;
            }
            map.put(source.getSource(), totalSteps);
        }

        map = (LinkedHashMap) MapUtils.sortByValue(map);

        return map;
    }

    public SortedMap calculateStepsUsageCounts(CucumberStepSource[] sources) {
        SortedMap map = new TreeMap();
        for (CucumberStepSource source:sources) {
            int stepsCount = 0;

            for (CucumberStep step:source.getSteps()) {
                stepsCount += step.getDurations().length;
            }

            if (!map.containsKey(stepsCount)) {
                map.put(stepsCount, 1);
            } else {
                int prevNum = map.get(stepsCount);
                prevNum++;
                map.remove(stepsCount);
                map.put(stepsCount, prevNum);
            }
        }
        return map;
    }

    public double calculateStepsUsageAverage(SortedMap statistics) {
        int totalSteps = 0;
        int totalUniqueSteps = 0;

        for (int i:statistics.keySet()) {
            totalSteps += i * statistics.get(i);
            totalUniqueSteps += statistics.get(i);
        }
        if (totalUniqueSteps == 0) {
            totalUniqueSteps = 1;
        }
        return (double) totalSteps / (double) totalUniqueSteps;
    }

    public int calculateStepsUsageMedian(SortedMap statistics) {
        int totalSteps = 0;
        int usedSteps = 0;
        int median = 0;
        for (int i:statistics.keySet()) {
            totalSteps += statistics.get(i);
        }
        for (int i:statistics.keySet()) {
            usedSteps += statistics.get(i);
            if (usedSteps * 2 >= totalSteps) {
                median = i;
                break;
            }
        }
        return median;
    }

    public int calculateTotalSteps(SortedMap statistics) {
        int totalSteps = 0;

        for (int i:statistics.keySet()) {
            totalSteps += i * statistics.get(i);
        }
        return totalSteps;
    }

    public int calculateUsedSteps(SortedMap statistics) {
        int usedSteps = 0;

        for (int i:statistics.keySet()) {
            usedSteps += statistics.get(i);
        }
        return usedSteps;
    }

    public int calculateStepsUsageMax(SortedMap statistics) {
        int max = 0;
        for (int i:statistics.keySet()) {
            max = Math.max(max, statistics.get(i));
        }
        return max;
    }

    protected String generateUsageOverviewGraphReport(
            CucumberStepSource[] sources) {
        double hscale;
        double vscale;
        final int hsize = 400;
        final int vsize = 400;
        final int hstart = 40;
        final int vstart = 30;
        final int hend = 350;
        final int vend = 300;

        int hstep = 0;
        int vstep = 0;

        int median;
        double average;

        SortedMap map = calculateStepsUsageCounts(sources);
        hscale = (double) (hend - 2 * hstart)
                / ((double) map.lastKey() + 1);
        vscale = (double) (vend - 2 * vstart)
                / ((double) calculateStepsUsageMax(map) + 1);

        final double stepScale = 30.f;

        hstep = (int) (stepScale / hscale) + 1;
        vstep = (int) (stepScale / vscale) + 1;

        median = calculateStepsUsageMedian(map);
        average = calculateStepsUsageAverage(map);

        final int hsizeMargin = 100;
        final int smallMargin = 5;
        final int midMargin = 10;
        final int largeMargin = 20;
        String htmlContent = ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + ""
                + "";

        for (int i = 0; i <= map.lastKey(); i += hstep) {
            htmlContent += ""
                    + "" + i + "";
        }

        for (int i = 0; i <= calculateStepsUsageMax(map); i += vstep) {
            htmlContent += ""
                    + "" + i + "";
        }

        final float usage30 = 30.f;
        final float usage70 = 70.f;
        final float usage100 = 100.f;
        float usage = usage100 * (1.f - (float) calculateUsedSteps(map)
                / (float) calculateTotalSteps(map));
        String statusColor = "silver";

        if (usage <= usage30) {
            statusColor = "red";
        } else if (usage >= usage70) {
            statusColor = "green";
        } else {
            statusColor = "#BBBB00";
        }

        htmlContent += ""
                + ""
                + ""
                + "Average: " + String.format("%.1f", average) + ""
                + "Median: " + median + ""
                + "" + String.format("%.1f", usage)
                + "%"
                + "Re-use"
                + "Step re-use count"
                + "Steps count"
                + "";
        return htmlContent;
    }

    private CucumberStepSource getSourceByString(CucumberStepSource[] sources, String text) {
        for (CucumberStepSource source : sources) {
            if (source.getSource().equals(text)) {
                return source;
            }
        }
        return null;
    }
    private String getGroupColor(LinkedHashMap map, int groupsCount, int index) {
        String color = "silver";
        if (map.keySet().size() >= groupsCount) {
            switch (index / (map.keySet().size() / groupsCount)) {
                case 0:
                    color = "lightgreen";
                    break;
                case 1:
                    color = "gold";
                    break;
                case 2:
                    color = "tomato";
                    break;
                default:
                    break;
            }
        } else {
            color = "red";
        }
        return color;
    }
    protected String generateUsageOverviewTableReport(CucumberStepSource[] sources) throws Exception {
        final int groupsCount = 3;
        LinkedHashMap map = calculateStepsUsageScore(sources);
        String content = ""
                + ""
                + ""
                + ""
                + "";
        int index = 0;
        for (String key:map.keySet()) {
            String color = getGroupColor(map, groupsCount, index);
            content += ""
                    + ""
                    + "";
            CucumberStepSource source = getSourceByString(sources, key);
            if (source == null) {
                content += "";
            } else {
                List durations = source.getDurations();
                if (durations.size() <= 0) {
                    return "";
                }
                Collections.sort(durations);
                Double median = durations.get(durations.size() / 2);
                Double total = 0.;
                for (Double duration : durations) {
                    total += duration;
                }
                Double average = total / durations.size();
                Double min = Collections.min(durations);
                Double max = Collections.max(durations);
                content += String.format(
                        "",
                        average, median, min, max, total);
            }
            content += "";
        }
        content += "
#ExpressionOccurencesDuration
AverageMedianMinimalMaximalTotal
" + (++index) + "" + key + "" + map.get(key) + "-----%.2fs%.2fs%.2fs%.2fs%.2fs
"; return content; } private String getEmptyHystogram() throws IOException { InputStream is = this.getClass().getResourceAsStream("/usage-na-graph-tmpl.html"); String result = IOUtils.toString(is); return result; } public int getDurationGroupsCount(CucumberStepSource source) { final int minimalDurations = 5; final int minimalDurationSize = 3; final int maxDurationGroups = 10; List durations = source.getDurations(); if (durations.size() <= minimalDurations) { return 0; } if (durations.size() < minimalDurationSize * maxDurationGroups) { return durations.size() / minimalDurationSize; } return maxDurationGroups; } public int[] getFrequencyData(CucumberStepSource source) { final int minimalDurations = 5; int[] result = new int[]{}; List durations = source.getDurations(); if (durations.size() <= minimalDurations) { return result; } double minDuration = this.getMinDuration(source); double maxDuration = this.getMaxDuration(source); int count = getDurationGroupsCount(source); result = new int[count]; for (int i = 0; i < count; i++) { result[i] = 0; } double step = (maxDuration - minDuration) / (double) count; for (Double duration : durations) { int index = (int) ((duration - minDuration) / step); if (index >= count) { index = count - 1; } result[index] = result[index] + 1; } return result; } private String getFrequencyPolyPoints(int[] data) { final int startX = 40; final int endX = 320; final int topY = 50; final int bottomY = 240; int max = 0; int stepX = (endX - startX) / data.length; for (int item : data) { max = Math.max(max, item); } float stepY = ((float) bottomY - (float) topY) / (float) max; String result = ""; for (int i = 0; i < data.length; i++) { result = result.concat( String.format("%d,%d %d,%d %d,%d %d,%d ", startX + i * stepX, bottomY, startX + i * stepX, bottomY - (int) (data[i] * stepY), startX + (i + 1) * stepX, bottomY - (int) (data[i] * stepY), startX + (i + 1) * stepX, bottomY ) ); } return result; } private Double getMaxDuration(CucumberStepSource source) { List durations = source.getDurations(); double maxDuration = durations.get(0); for (Double duration : durations) { maxDuration = Math.max(maxDuration, duration); } return maxDuration; } private Double getMinDuration(CucumberStepSource source) { List durations = source.getDurations(); double minDuration = durations.get(0); for (Double duration : durations) { minDuration = Math.min(minDuration, duration); } return minDuration; } private String getFrequencyLabels(CucumberStepSource source, int[] data) { final int startX = 40; final int endX = 320; final int topY = 50; final int bottomY = 240; final int shiftX = 7; final int shiftY = 8; int max = 0; int stepX = (endX - startX) / data.length; for (int item : data) { max = Math.max(max, item); } int stepY = (bottomY - topY) / max; String result = ""; for (int i = 0; i < data.length; i++) { result = result.concat( String.format("%d", startX + i * stepX + stepX / 2 - (int) (Math.log10(data[i])) * shiftX, bottomY - data[i] * stepY - 2, data[i]) ); double step = (this.getMaxDuration(source) - this.getMinDuration(source)) / (double) data.length; result = result.concat( String.format("" + "%.2f" , startX + i * stepX, bottomY, startX + i * stepX, bottomY + 2, startX + i * stepX + 2, bottomY + shiftY, this.getMinDuration(source) + (double) i * step) ); } result = result.concat( String.format("" + "%.2f" , endX, bottomY, endX, bottomY + 2, endX + 2, bottomY + shiftY, this.getMaxDuration(source)) ); return result; } private String getFilledHystogram(CucumberStepSource source) throws IOException { InputStream is = this.getClass().getResourceAsStream("/usage-base-graph-tmpl.html"); String result = IOUtils.toString(is); int[] data = getFrequencyData(source); String polypoints = ""; String frequencies = ""; if (data.length > 0) { polypoints = getFrequencyPolyPoints(data); frequencies = getFrequencyLabels(source, data); } result = result.replaceAll("\\{POLYPOINTS\\}", polypoints); result = result.replaceAll("\\{FREQUENCIES\\}", frequencies); return result; } private String getHystogram(CucumberStepSource source) throws Exception { final int minimalDurations = 5; int durations = 0; for (CucumberStep step : source.getSteps()) { durations += step.getDurations().length; } if (durations <= minimalDurations) { return getEmptyHystogram(); } return getFilledHystogram(source); } private String generateSourceDurationOverview(CucumberStepSource source) { List durations = source.getDurations(); if (durations.size() <= 0) { return ""; } Collections.sort(durations); Double median = durations.get(durations.size() / 2); Double total = 0.; for (Double duration : durations) { total += duration; } Double average = total / durations.size(); Double min = Collections.min(durations); Double max = Collections.max(durations); return String.format("

" + "" + "" + "" + "" + "" + "" + "
Duration Characteristic
Average%.2fs
Median%.2fs
Minimum%.2fs
Maximum%.2fs
Total%.2fs

", average, median, min, max, total); } private String getKeyId(String key) throws UnsupportedEncodingException { String result = URLEncoder.encode(key, "UTF-8").replaceAll("\\+", "-") /*.replaceAll("\\%21", "_") .replaceAll("\\%27", "_") .replaceAll("\\%28", "_") .replaceAll("\\%29", "_") .replaceAll("\\%7E", "_")*/ .replaceAll("\\%", "_"); return result; } protected String generateUsageDetailedReport(CucumberStepSource[] sources) throws Exception { String content = ""; for (CucumberStepSource source:sources) { content += "

" + source.getSource() + "

" + "

"; for (CucumberStep step:source.getSteps()) { content += ""; for (CucumberStepDuration duration:step.getDurations()) { content += ""; } } content += "
Step NameDurationLocation
" + step.getName() + " - -
" + duration.getDuration() + "" + duration.getLocation() + "

"; content += "

" + "" + "
" + this.getHystogram(source) + "" + generateSourceDurationOverview(source) + "

To Overview Table

"; } return content; } @SuppressWarnings("unchecked") public CucumberStepSource[] getStepSources(String filePath) throws Exception { FileInputStream fis = null; JsonReader jr = null; File file = new File(filePath); if (!(file.exists() && file.isFile())) { throw new FileNotFoundException(); } fis = new FileInputStream(file); jr = new JsonReader(fis, true); JsonObject source = (JsonObject) jr.readObject(); Object[] objs = (Object[]) source.get("@items"); CucumberStepSource[] sources = new CucumberStepSource[objs.length]; for (int i = 0; i < objs.length; i++) { sources[i] = new CucumberStepSource((JsonObject) objs[i]); } jr.close(); fis.close(); return sources; } private String generateStyle() { return "h1 {background-color:#9999CC}" + System.lineSeparator() + "h2 {background-color:#BBBBCC}" + System.lineSeparator() + "h3 {background-color:#DDDDFF}" + System.lineSeparator() + "th {border:1px solid black;background-color:#CCCCDD;}" + System.lineSeparator() + "td{border:1px solid black;}" + System.lineSeparator() + ".none {border:0px none black;}" + System.lineSeparator() + "table{border:1px solid black;border-collapse: collapse;}" + System.lineSeparator() + "tr:nth-child(even) {background: #CCC}" + System.lineSeparator() + "tr:nth-child(odd) {background: #FFF}"; } public void executeReport() throws MavenReportException { try { CucumberStepSource[] sources = getStepSources(jsonUsageFile); String output = "" + "Cucumber Steps Usage Report" + "

Cucumber Usage Statistics

" + "

Overview Graph

" + generateUsageOverviewGraphReport(sources) + "

" + "

Overview Table

" + generateUsageOverviewTableReport(sources) + "

" + "

Cucumber Usage Detailed Information

" + generateUsageDetailedReport(sources) + "

"; File report = new File(this.getOutputDirectory() + File.separator + this.getOutputName() + ".html"); FileUtils.writeStringToFile(report, output); } catch (Exception e) { throw new MavenReportException( "Error occured while generating Cucumber usage report", e); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy