-
-
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
Print summary at the end of the run #536
Changes from 2 commits
5fb1b76
3b0f7bd
07902e1
c8feb10
3a77cff
442c4ed
40a223a
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 |
---|---|---|
|
@@ -42,7 +42,8 @@ public class Runtime implements UnreportedStepExecutor { | |
private static final Object DUMMY_ARG = new Object(); | ||
private static final byte ERRORS = 0x1; | ||
|
||
final UndefinedStepsTracker undefinedStepsTracker = new UndefinedStepsTracker(); | ||
private final SummaryCounter summaryCounter; | ||
final UndefinedStepsTracker undefinedStepsTracker; | ||
|
||
private final Glue glue; | ||
private final RuntimeOptions runtimeOptions; | ||
|
@@ -62,19 +63,29 @@ public Runtime(ResourceLoader resourceLoader, ClassLoader classLoader, RuntimeOp | |
} | ||
|
||
public Runtime(ResourceLoader resourceLoader, ClassLoader classLoader, Collection<? extends Backend> backends, RuntimeOptions runtimeOptions) { | ||
this(resourceLoader, classLoader, backends, runtimeOptions, new UndefinedStepsTracker()); | ||
} | ||
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. Why did you remove this? 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. That was just a mistake. The assignments to fields ware performed all over the constructor, before the check for empty backends, after this check, and after loading backends, so when I moved the code to the new constructor I missed this. Which on the other hand points out that there is a test missing, since it could be removed without any test failure :-). |
||
|
||
public Runtime(ResourceLoader resourceLoader, ClassLoader classLoader, Collection<? extends Backend> backends, | ||
RuntimeOptions runtimeOptions, UndefinedStepsTracker undefinedStepsTracker) { | ||
this(resourceLoader, classLoader, backends, runtimeOptions, undefinedStepsTracker, | ||
new RuntimeGlue(undefinedStepsTracker, new LocalizedXStreams(classLoader))); | ||
} | ||
|
||
public Runtime(ResourceLoader resourceLoader, ClassLoader classLoader, Collection<? extends Backend> backends, | ||
RuntimeOptions runtimeOptions, UndefinedStepsTracker undefinedStepsTracker, RuntimeGlue glue) { | ||
this.resourceLoader = resourceLoader; | ||
this.classLoader = classLoader; | ||
if (backends.isEmpty()) { | ||
throw new CucumberException("No backends were found. Please make sure you have a backend module on your CLASSPATH."); | ||
} | ||
this.backends = backends; | ||
glue = new RuntimeGlue(undefinedStepsTracker, new LocalizedXStreams(classLoader)); | ||
this.runtimeOptions = runtimeOptions; | ||
this.undefinedStepsTracker = undefinedStepsTracker; | ||
this.glue = glue; | ||
this.summaryCounter = new SummaryCounter(runtimeOptions.monochrome); | ||
|
||
for (Backend backend : backends) { | ||
backend.loadGlue(glue, runtimeOptions.glue); | ||
backend.setUnreportedStepExecutor(this); | ||
} | ||
this.runtimeOptions = runtimeOptions; | ||
} | ||
|
||
private static Collection<? extends Backend> loadBackends(ResourceLoader resourceLoader, ClassLoader classLoader) { | ||
|
@@ -95,8 +106,8 @@ public void run() { | |
Formatter formatter = runtimeOptions.formatter(classLoader); | ||
|
||
formatter.done(); | ||
printSummary(); | ||
formatter.close(); | ||
printSummary(); | ||
} | ||
|
||
private void run(CucumberFeature cucumberFeature) { | ||
|
@@ -121,6 +132,7 @@ public void buildBackendWorlds(Reporter reporter, Set<Tag> tags) { | |
} | ||
|
||
public void disposeBackendWorlds() { | ||
summaryCounter.addScenario(scenarioResult.getStatus()); | ||
for (Backend backend : backends) { | ||
backend.disposeWorld(); | ||
} | ||
|
@@ -203,7 +215,7 @@ private void runHookIfTagsMatch(HookDefinition hook, Reporter reporter, Set<Tag> | |
} finally { | ||
long duration = System.nanoTime() - start; | ||
Result result = new Result(status, duration, error, DUMMY_ARG); | ||
scenarioResult.add(result); | ||
addHookToCounterAndResult(result); | ||
if (isBefore) { | ||
reporter.before(match, result); | ||
} else { | ||
|
@@ -240,7 +252,9 @@ public void runStep(String uri, Step step, Reporter reporter, I18n i18n) { | |
match = glue.stepDefinitionMatch(uri, step, i18n); | ||
} catch (AmbiguousStepDefinitionsException e) { | ||
reporter.match(e.getMatches().get(0)); | ||
reporter.result(new Result(Result.FAILED, 0L, e, DUMMY_ARG)); | ||
Result result = new Result(Result.FAILED, 0L, e, DUMMY_ARG); | ||
reporter.result(result); | ||
addStepToCounterAndResult(result); | ||
addError(e); | ||
skipNextStep = true; | ||
return; | ||
|
@@ -251,6 +265,7 @@ public void runStep(String uri, Step step, Reporter reporter, I18n i18n) { | |
} else { | ||
reporter.match(Match.UNDEFINED); | ||
reporter.result(Result.UNDEFINED); | ||
addStepToCounterAndResult(Result.UNDEFINED); | ||
skipNextStep = true; | ||
return; | ||
} | ||
|
@@ -260,7 +275,7 @@ public void runStep(String uri, Step step, Reporter reporter, I18n i18n) { | |
} | ||
|
||
if (skipNextStep) { | ||
scenarioResult.add(Result.SKIPPED); | ||
addStepToCounterAndResult(Result.SKIPPED); | ||
reporter.result(Result.SKIPPED); | ||
} else { | ||
String status = Result.PASSED; | ||
|
@@ -276,7 +291,7 @@ public void runStep(String uri, Step step, Reporter reporter, I18n i18n) { | |
} finally { | ||
long duration = System.nanoTime() - start; | ||
Result result = new Result(status, duration, error, DUMMY_ARG); | ||
scenarioResult.add(result); | ||
addStepToCounterAndResult(result); | ||
reporter.result(result); | ||
} | ||
} | ||
|
@@ -292,4 +307,18 @@ public static boolean isPending(Throwable t) { | |
public void writeStepdefsJson() throws IOException { | ||
glue.writeStepdefsJson(runtimeOptions.featurePaths, runtimeOptions.dotCucumber); | ||
} | ||
|
||
public SummaryCounter getSummaryCounter() { | ||
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. Remove this (see below) 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'll fix it |
||
return summaryCounter; | ||
} | ||
|
||
private void addStepToCounterAndResult(Result result) { | ||
scenarioResult.add(result); | ||
summaryCounter.addStep(result); | ||
} | ||
|
||
private void addHookToCounterAndResult(Result result) { | ||
scenarioResult.add(result); | ||
summaryCounter.addHookTime(result.getDuration()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package cucumber.runtime; | ||
|
||
import gherkin.formatter.AnsiFormats; | ||
import gherkin.formatter.Format; | ||
import gherkin.formatter.Formats; | ||
import gherkin.formatter.MonochromeFormats; | ||
import gherkin.formatter.model.Result; | ||
|
||
import java.io.PrintStream; | ||
import java.text.DecimalFormat; | ||
import java.text.DecimalFormatSymbols; | ||
import java.util.Locale; | ||
|
||
public class SummaryCounter { | ||
public static final long ONE_SECOND = 1000000000; | ||
public static final long ONE_MINUTE = 60 * ONE_SECOND; | ||
public static final String PENDING = "pending"; | ||
private SubCounts scenarioSubCounts = new SubCounts(); | ||
private SubCounts stepSubCounts = new SubCounts(); | ||
private long totalDuration = 0; | ||
private Formats formats; | ||
private Locale locale; | ||
|
||
public SummaryCounter(boolean monochrome) { | ||
this(monochrome, Locale.getDefault()); | ||
} | ||
|
||
public SummaryCounter(boolean monochrome, Locale locale) { | ||
this.locale = locale; | ||
if (monochrome) { | ||
formats = new MonochromeFormats(); | ||
} else { | ||
formats = new AnsiFormats(); | ||
} | ||
} | ||
|
||
public void printSummary(PrintStream out) { | ||
if (stepSubCounts.getTotal() == 0) { | ||
out.println("0 Scenarios"); | ||
out.println("0 Steps"); | ||
} else { | ||
printScenarioCounts(out); | ||
printStepCounts(out); | ||
} | ||
printDuration(out); | ||
} | ||
|
||
private void printStepCounts(PrintStream out) { | ||
out.print(stepSubCounts.getTotal()); | ||
out.print(" Steps ("); | ||
printSubCounts(out, stepSubCounts); | ||
out.println(")"); | ||
} | ||
|
||
private void printScenarioCounts(PrintStream out) { | ||
out.print(scenarioSubCounts.getTotal()); | ||
out.print(" Scenarios ("); | ||
printSubCounts(out, scenarioSubCounts); | ||
out.println(")"); | ||
} | ||
|
||
private void printSubCounts(PrintStream out, SubCounts subCounts) { | ||
boolean addComma = false; | ||
addComma = printSubCount(out, subCounts.failed, Result.FAILED, addComma); | ||
addComma = printSubCount(out, subCounts.skipped, Result.SKIPPED.getStatus(), addComma); | ||
addComma = printSubCount(out, subCounts.pending, PENDING, addComma); | ||
addComma = printSubCount(out, subCounts.undefined, Result.UNDEFINED.getStatus(), addComma); | ||
addComma = printSubCount(out, subCounts.passed, Result.PASSED, addComma); | ||
} | ||
|
||
private boolean printSubCount(PrintStream out, int count, String type, boolean addComma) { | ||
if (count != 0) { | ||
if (addComma) { | ||
out.print(", "); | ||
} | ||
Format format = formats.get(type); | ||
out.print(format.text(count + " " + type)); | ||
addComma = true; | ||
} | ||
return addComma; | ||
} | ||
|
||
private void printDuration(PrintStream out) { | ||
out.print(String.format("%dm", (totalDuration/ONE_MINUTE))); | ||
DecimalFormat format = new DecimalFormat("0.000", new DecimalFormatSymbols(locale)); | ||
out.println(format.format(((double)(totalDuration%ONE_MINUTE))/ONE_SECOND) + "s"); | ||
} | ||
|
||
public void addStep(Result result) { | ||
addResultToSubCount(stepSubCounts, result.getStatus()); | ||
// the following constant defined in the Gherkin libaray have duration == null, so calls to getDuration() | ||
// will result in a NullPointerException | ||
if (!result.equals(Result.SKIPPED) && !result.equals(Result.UNDEFINED)) { | ||
addTime(result.getDuration()); | ||
} | ||
} | ||
|
||
public void addScenario(String resultStatus) { | ||
addResultToSubCount(scenarioSubCounts, resultStatus); | ||
} | ||
|
||
public void addHookTime(long duration) { | ||
addTime(duration); | ||
} | ||
|
||
private void addTime(long duration) { | ||
totalDuration += duration; | ||
} | ||
|
||
private void addResultToSubCount(SubCounts subCounts, String resultStatus) { | ||
if (resultStatus.equals(Result.FAILED)) { | ||
subCounts.failed++; | ||
} else if (resultStatus.equals(PENDING)) { | ||
subCounts.pending++; | ||
} else if (resultStatus.equals(Result.UNDEFINED.getStatus())) { | ||
subCounts.undefined++; | ||
} else if (resultStatus.equals(Result.SKIPPED.getStatus())) { | ||
subCounts.skipped++; | ||
} else if (resultStatus.equals(Result.PASSED)) { | ||
subCounts.passed++; | ||
} | ||
} | ||
} | ||
|
||
class SubCounts { | ||
public int passed = 0; | ||
public int failed = 0; | ||
public int skipped = 0; | ||
public int pending = 0; | ||
public int undefined = 0; | ||
|
||
public int getTotal() { | ||
return passed + failed + skipped + pending + undefined; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,11 +11,17 @@ public SummaryPrinter(PrintStream out) { | |
} | ||
|
||
public void print(cucumber.runtime.Runtime runtime) { | ||
out.println(); | ||
printSummary(runtime); | ||
out.println(); | ||
printErrors(runtime); | ||
printSnippets(runtime); | ||
} | ||
|
||
private void printSummary(cucumber.runtime.Runtime runtime) { | ||
runtime.getSummaryCounter().printSummary(out); | ||
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. Tell don't ask. Remove 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'll fix it. |
||
} | ||
|
||
private void printErrors(cucumber.runtime.Runtime runtime) { | ||
for (Throwable error : runtime.getErrors()) { | ||
error.printStackTrace(out); | ||
|
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.
This class isn't in a public API, so there is no need to keep old constructors for backwards compatibility. Can you cut this back to two (or preferrably - one) constructors please?
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.
It is not so easy. It is not possible to first create a UndefinedStepsTracker and the pass it to the creation of the RuntimeGlue in the same constructor, which calls another constructor, so I think both the two new constructors are needed, but they does not need to be public, the third can be private and the forth package private.
The first constructor is used by cli.Main and the second one is used by tests to pass backends to the Runtime. It is used in tests both in the runtime package and in the runtime.formatter package so need to be public.
I can cut it back to three constructors, but this will affect a hand full of tests, see the gist: https://gist.github.com/brasmusson/5755281