Skip to content
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

Merged
merged 7 commits into from
Jul 16, 2013
Merged
51 changes: 40 additions & 11 deletions core/src/main/java/cucumber/runtime/Runtime.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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());
Copy link
Contributor

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?

Copy link
Contributor Author

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

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you remove this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 :-).
I'll put it back and add the test.


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) {
Expand All @@ -95,8 +106,8 @@ public void run() {
Formatter formatter = runtimeOptions.formatter(classLoader);

formatter.done();
printSummary();
formatter.close();
printSummary();
}

private void run(CucumberFeature cucumberFeature) {
Expand All @@ -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();
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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);
}
}
Expand All @@ -292,4 +307,18 @@ public static boolean isPending(Throwable t) {
public void writeStepdefsJson() throws IOException {
glue.writeStepdefsJson(runtimeOptions.featurePaths, runtimeOptions.dotCucumber);
}

public SummaryCounter getSummaryCounter() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this (see below)

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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());
}
}
2 changes: 1 addition & 1 deletion core/src/main/java/cucumber/runtime/ScenarioImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import static java.util.Arrays.asList;

public class ScenarioImpl implements Scenario {
private static final List<String> SEVERITY = asList("passed", "undefined", "pending", "skipped", "failed");
private static final List<String> SEVERITY = asList("passed", "skipped", "undefined", "pending", "failed");
private final List<Result> stepResults = new ArrayList<Result>();
private final Reporter reporter;
private final Set<Tag> tags;
Expand Down
135 changes: 135 additions & 0 deletions core/src/main/java/cucumber/runtime/SummaryCounter.java
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
Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tell don't ask. Remove getSummaryCounter() and add a Runtime.printSummary(out) method instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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);
Expand Down
Loading