-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Issue #207 UsageFormatter #214
Changes from 3 commits
9c93be9
e6d6e4b
3a7ca5f
3d93be7
2e56457
3503811
05cc113
7530377
a865428
3302b9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
package cucumber.formatter; | ||
|
||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import cucumber.formatter.usage.UsageStatisticStrategy; | ||
import gherkin.deps.com.google.gson.Gson; | ||
import gherkin.deps.com.google.gson.GsonBuilder; | ||
import gherkin.formatter.Format; | ||
import gherkin.formatter.Formatter; | ||
import gherkin.formatter.MonochromeFormats; | ||
import gherkin.formatter.NiceAppendable; | ||
import gherkin.formatter.Reporter; | ||
import gherkin.formatter.StepPrinter; | ||
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; | ||
|
||
/** | ||
* Formatter to measure performance of steps. Aggregated results for all steps can be computed | ||
* by adding {@link UsageStatisticStrategy} to the usageFormatter | ||
*/ | ||
public class UsageFormatter implements Formatter, Reporter | ||
{ | ||
private final MonochromeFormats monochromeFormat = new MonochromeFormats(); | ||
private final StepPrinter stepPrinter = new StepPrinter(); | ||
|
||
final Map<String, List<Long>> usageMap = new HashMap<String, List<Long>>(); | ||
final Map<String, UsageStatisticStrategy> statisticStrategies = new HashMap<String, UsageStatisticStrategy>(); | ||
|
||
private final List<Step> steps = new ArrayList<Step>(); | ||
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); | ||
} | ||
|
||
@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 step(Step step) | ||
{ | ||
steps.add(step); | ||
} | ||
|
||
@Override | ||
public void eof() | ||
{ | ||
} | ||
|
||
@Override | ||
public void syntaxError(String state, String event, List<String> legalEvents, String uri, int line) | ||
{ | ||
} | ||
|
||
@Override | ||
public void done() | ||
{ | ||
List<StepContainer> stepContainers = new ArrayList<StepContainer>(); | ||
|
||
for (Map.Entry<String, List<Long>> usageEntry : usageMap.entrySet()) | ||
{ | ||
StepContainer stepContainer = new StepContainer(); | ||
stepContainers.add(stepContainer); | ||
|
||
stepContainer.stepName = usageEntry.getKey(); | ||
stepContainer.durations = formatDurationEntries(usageEntry.getValue()); | ||
|
||
stepContainer.aggregatedResults = createAggregatedResults(usageEntry); | ||
} | ||
|
||
out.append(gson().toJson(stepContainers)); | ||
} | ||
|
||
private List<String> formatDurationEntries(List<Long> durationEntries) | ||
{ | ||
ArrayList<String> formattedDuration = new ArrayList<String>(); | ||
for(Long duration : durationEntries) | ||
{ | ||
formattedDuration.add(formatDuration(duration)); | ||
} | ||
return formattedDuration; | ||
} | ||
|
||
private List<AggregatedResult> createAggregatedResults(Map.Entry<String, List<Long>> usageEntry) | ||
{ | ||
ArrayList<AggregatedResult> aggregatedResults = new ArrayList<AggregatedResult>(); | ||
for (Map.Entry<String, UsageStatisticStrategy> calculatorEntry : statisticStrategies.entrySet()) | ||
{ | ||
AggregatedResult aggregatedResult = new AggregatedResult(); | ||
aggregatedResults.add(aggregatedResult); | ||
|
||
UsageStatisticStrategy statisticStrategy = calculatorEntry.getValue(); | ||
Long calculationResult = statisticStrategy.calculate(usageEntry.getValue()); | ||
|
||
aggregatedResult.strategy = calculatorEntry.getKey(); | ||
aggregatedResult.value = formatDuration(calculationResult); | ||
} | ||
return aggregatedResults; | ||
} | ||
|
||
private String formatDuration(Long duration) | ||
{ | ||
long seconds = TimeUnit.MICROSECONDS.toSeconds(duration); | ||
long microSeconds = duration - TimeUnit.SECONDS.toMicros(seconds); | ||
return String.format("%d.%06d", seconds, microSeconds); | ||
} | ||
|
||
private Gson gson() { | ||
return new GsonBuilder().setPrettyPrinting().create(); | ||
} | ||
|
||
@Override | ||
public void close() | ||
{ | ||
out.close(); | ||
} | ||
|
||
@Override | ||
public void result(Result result) | ||
{ | ||
if (!steps.isEmpty()) | ||
{ | ||
Step step = steps.remove(0); | ||
String stepNameWithArgs = formatStepNameWithArgs(result, step); | ||
addUsageEntry(result, stepNameWithArgs); | ||
} | ||
} | ||
|
||
private String formatStepNameWithArgs(Result result, Step step) | ||
{ | ||
StringBuffer buffer = new StringBuffer(); | ||
buffer.append(step.getKeyword()).append(" "); | ||
Format format = getFormat(result.getStatus()); | ||
Format argFormat = getArgFormat(result.getStatus()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did use the MonochromeFormat in order to reuse the StepPrinter for printing Steps and their corresponding arguments. |
||
stepPrinter.writeStep(new NiceAppendable(buffer), format, argFormat, step.getName(), match.getArguments()); | ||
|
||
return buffer.toString(); | ||
} | ||
|
||
private void addUsageEntry(Result result, String stepNameWithArgs) | ||
{ | ||
List<Long> durationEntries = usageMap.get(stepNameWithArgs); | ||
if (durationEntries == null) | ||
{ | ||
durationEntries = new ArrayList<Long>(); | ||
usageMap.put(stepNameWithArgs, durationEntries); | ||
} | ||
durationEntries.add(durationInMillis(result)); | ||
} | ||
|
||
private Long durationInMillis(Result result) | ||
{ | ||
long duration; | ||
if (result.getDuration() == null) | ||
{ | ||
duration = 0; | ||
} else | ||
{ | ||
duration = result.getDuration() / 1000; | ||
} | ||
return duration; | ||
} | ||
|
||
@Override | ||
public void match(Match match) | ||
{ | ||
this.match = match; | ||
} | ||
|
||
@Override | ||
public void embedding(String mimeType, byte[] data) | ||
{ | ||
} | ||
|
||
private Format getFormat(String key) { | ||
return monochromeFormat.get(key); | ||
} | ||
|
||
private Format getArgFormat(String key) { | ||
return monochromeFormat.get(key + "_arg"); | ||
} | ||
|
||
/** | ||
* 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); | ||
} | ||
|
||
/** | ||
* Contains for usage-entries of steps | ||
*/ | ||
private static class StepContainer { | ||
public String stepName; | ||
public List<AggregatedResult> aggregatedResults = new ArrayList<AggregatedResult>(); | ||
public List<String> durations = new ArrayList<String>(); | ||
} | ||
|
||
/** | ||
* Container for aggregated results, computed by a specific strategy (e.g. average, median, ..) | ||
*/ | ||
private static class AggregatedResult { | ||
public String strategy; | ||
public String value; | ||
} | ||
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package cucumber.formatter.usage; | ||
|
||
import java.util.List; | ||
|
||
/** | ||
* Calculate the average of a list of duration entries | ||
*/ | ||
public class AverageUsageStatisticStrategy implements UsageStatisticStrategy | ||
{ | ||
@Override | ||
public Long calculate(List<Long> durationEntries) | ||
{ | ||
if (verifyNoNulls(durationEntries)) | ||
{ | ||
return 0L; | ||
} | ||
|
||
long sum = 0; | ||
for (Long duration : durationEntries) | ||
{ | ||
sum += duration; | ||
} | ||
return sum / durationEntries.size(); | ||
} | ||
|
||
private boolean verifyNoNulls(List<Long> durationEntries) | ||
{ | ||
return durationEntries == null || durationEntries.isEmpty() || durationEntries.contains(null); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package cucumber.formatter.usage; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
/** | ||
* Calculate the median of a list of duration entries | ||
*/ | ||
public class MedianUsageStatisticStrategy implements UsageStatisticStrategy | ||
{ | ||
@Override | ||
public Long calculate(List<Long> 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<Long> durationEntries) | ||
{ | ||
return durationEntries == null || durationEntries.isEmpty() || durationEntries.contains(null); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package cucumber.formatter.usage; | ||
|
||
import java.util.List; | ||
|
||
/** | ||
* Calculate a statistical value to be displayed in the usage-file | ||
*/ | ||
public interface UsageStatisticStrategy | ||
{ | ||
/** | ||
* @param durationEntries list of execution times of steps as nanoseconds | ||
* @return a statistical value (e.g. median, average, ..) | ||
*/ | ||
Long calculate(List<Long> durationEntries); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The keyword already contains a space, so son't add an extra one.