com.github.mkolisnyk.cucumber.reporting.CucumberUsageReporting Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cucumber-reports Show documentation
Show all versions of cucumber-reports Show documentation
Library generating different Cucumber reports
/**
* .
*/
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 = "";
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 = "# "
+ "Expression Occurences "
+ "Duration "
+ ""
+ "Average Median Minimal Maximal Total ";
int index = 0;
for (String key:map.keySet()) {
String color = getGroupColor(map, groupsCount, index);
content += "" + (++index) + " "
+ "" + key + " "
+ "" + map.get(key) + " ";
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(
"%.2fs %.2fs %.2fs %.2fs %.2fs ",
average, median, min, max, total);
}
content += " ";
}
content += "
";
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() + "
"
+ "Step Name Duration Location ";
for (CucumberStep step:source.getSteps()) {
content += "" + step.getName() + " - - ";
for (CucumberStepDuration duration:step.getDurations()) {
content += "" + duration.getDuration()
+ " " + duration.getLocation() + " ";
}
}
content += "
";
content += "" + this.getHystogram(source) + " "
+ "" + generateSourceDurationOverview(source) + " "
+ "
";
}
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);
}
}
}