diff --git a/core/src/main/java/cucumber/runtime/Runtime.java b/core/src/main/java/cucumber/runtime/Runtime.java index 0ab99d7f96..ca211daf0e 100644 --- a/core/src/main/java/cucumber/runtime/Runtime.java +++ b/core/src/main/java/cucumber/runtime/Runtime.java @@ -13,6 +13,7 @@ import gherkin.formatter.model.*; import java.io.IOException; +import java.io.PrintStream; import java.util.*; /** @@ -31,6 +32,7 @@ public class Runtime implements UnreportedStepExecutor { private static final Object DUMMY_ARG = new Object(); private static final byte ERRORS = 0x1; + private final SummaryCounter summaryCounter; final UndefinedStepsTracker undefinedStepsTracker = new UndefinedStepsTracker(); private final Glue glue; @@ -64,6 +66,7 @@ public Runtime(ResourceLoader resourceLoader, ClassLoader classLoader, Collectio this.backends = backends; this.runtimeOptions = runtimeOptions; this.glue = optionalGlue != null ? optionalGlue : new RuntimeGlue(undefinedStepsTracker, new LocalizedXStreams(classLoader)); + this.summaryCounter = new SummaryCounter(runtimeOptions.isMonochrome()); for (Backend backend : backends) { backend.loadGlue(glue, runtimeOptions.getGlue()); @@ -89,8 +92,8 @@ public void run() { Formatter formatter = runtimeOptions.formatter(classLoader); formatter.done(); - printSummary(); formatter.close(); + printSummary(); } private void run(CucumberFeature cucumberFeature) { @@ -115,6 +118,7 @@ public void buildBackendWorlds(Reporter reporter, Set tags) { } public void disposeBackendWorlds() { + summaryCounter.addScenario(scenarioResult.getStatus()); for (Backend backend : backends) { backend.disposeWorld(); } @@ -197,7 +201,7 @@ private void runHookIfTagsMatch(HookDefinition hook, Reporter reporter, Set } 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 { @@ -234,7 +238,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; @@ -245,6 +251,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; } @@ -254,7 +261,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; @@ -270,7 +277,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); } } @@ -286,4 +293,18 @@ public static boolean isPending(Throwable t) { public void writeStepdefsJson() throws IOException { glue.writeStepdefsJson(runtimeOptions.getFeaturePaths(), runtimeOptions.getDotCucumber()); } + + public void printSummary(PrintStream out) { + summaryCounter.printSummary(out); + } + + private void addStepToCounterAndResult(Result result) { + scenarioResult.add(result); + summaryCounter.addStep(result); + } + + private void addHookToCounterAndResult(Result result) { + scenarioResult.add(result); + summaryCounter.addHookTime(result.getDuration()); + } } diff --git a/core/src/main/java/cucumber/runtime/RuntimeOptions.java b/core/src/main/java/cucumber/runtime/RuntimeOptions.java index ba9bca50e3..e4eb13a642 100644 --- a/core/src/main/java/cucumber/runtime/RuntimeOptions.java +++ b/core/src/main/java/cucumber/runtime/RuntimeOptions.java @@ -198,4 +198,8 @@ public List getFormatters() { public List getFilters() { return filters; } + + public boolean isMonochrome() { + return monochrome; + } } diff --git a/core/src/main/java/cucumber/runtime/ScenarioImpl.java b/core/src/main/java/cucumber/runtime/ScenarioImpl.java index 6939cc4dd1..513cbd95df 100644 --- a/core/src/main/java/cucumber/runtime/ScenarioImpl.java +++ b/core/src/main/java/cucumber/runtime/ScenarioImpl.java @@ -14,7 +14,7 @@ import static java.util.Arrays.asList; public class ScenarioImpl implements Scenario { - private static final List SEVERITY = asList("passed", "undefined", "pending", "skipped", "failed"); + private static final List SEVERITY = asList("passed", "skipped", "undefined", "pending", "failed"); private final List stepResults = new ArrayList(); private final Reporter reporter; private final Set tags; diff --git a/core/src/main/java/cucumber/runtime/SummaryCounter.java b/core/src/main/java/cucumber/runtime/SummaryCounter.java new file mode 100755 index 0000000000..d8bc7cdfcb --- /dev/null +++ b/core/src/main/java/cucumber/runtime/SummaryCounter.java @@ -0,0 +1,131 @@ +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()); + 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 != null ? duration : 0; + } + + 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; + } +} diff --git a/core/src/main/java/cucumber/runtime/snippets/SummaryPrinter.java b/core/src/main/java/cucumber/runtime/snippets/SummaryPrinter.java index 823b117a96..43e89745f6 100644 --- a/core/src/main/java/cucumber/runtime/snippets/SummaryPrinter.java +++ b/core/src/main/java/cucumber/runtime/snippets/SummaryPrinter.java @@ -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.printSummary(out); + } + private void printErrors(cucumber.runtime.Runtime runtime) { for (Throwable error : runtime.getErrors()) { error.printStackTrace(out); diff --git a/core/src/test/java/cucumber/runtime/RuntimeTest.java b/core/src/test/java/cucumber/runtime/RuntimeTest.java index fe70ab34fb..9fc7b92a76 100644 --- a/core/src/test/java/cucumber/runtime/RuntimeTest.java +++ b/core/src/test/java/cucumber/runtime/RuntimeTest.java @@ -1,16 +1,21 @@ package cucumber.runtime; import cucumber.api.PendingException; +import cucumber.api.Scenario; import cucumber.runtime.io.ClasspathResourceLoader; import cucumber.runtime.io.ResourceLoader; import cucumber.runtime.model.CucumberFeature; import gherkin.I18n; import gherkin.formatter.JSONFormatter; +import gherkin.formatter.Reporter; import gherkin.formatter.model.Step; +import gherkin.formatter.model.Tag; import org.junit.Ignore; import org.junit.Test; import org.junit.internal.AssumptionViolatedException; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -19,9 +24,16 @@ import static cucumber.runtime.TestHelper.feature; import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.startsWith; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyCollectionOf; +import static org.mockito.Matchers.anyString; public class RuntimeTest { @@ -177,6 +189,164 @@ public void should_throw_cucumer_exception_if_no_backends_are_found() throws Exc } } + @Test + public void should_add_passed_result_to_the_summary_counter() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Reporter reporter = mock(Reporter.class); + StepDefinitionMatch match = mock(StepDefinitionMatch.class); + + Runtime runtime = createRuntimeWithMockedGlue(match, "--monochrome"); + runtime.buildBackendWorlds(reporter, Collections.emptySet()); + runStep(reporter, runtime); + runtime.disposeBackendWorlds(); + runtime.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), startsWith(String.format( + "1 Scenarios (1 passed)%n" + + "1 Steps (1 passed)%n"))); + } + + @Test + public void should_add_pending_result_to_the_summary_counter() throws Throwable { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Reporter reporter = mock(Reporter.class); + StepDefinitionMatch match = createExceptionThrowingMatch(new PendingException()); + + Runtime runtime = createRuntimeWithMockedGlue(match, "--monochrome"); + runtime.buildBackendWorlds(reporter, Collections.emptySet()); + runStep(reporter, runtime); + runtime.disposeBackendWorlds(); + runtime.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), startsWith(String.format( + "1 Scenarios (1 pending)%n" + + "1 Steps (1 pending)%n"))); + } + + @Test + public void should_add_failed_result_to_the_summary_counter() throws Throwable { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Reporter reporter = mock(Reporter.class); + StepDefinitionMatch match = createExceptionThrowingMatch(new Exception()); + + Runtime runtime = createRuntimeWithMockedGlue(match, "--monochrome"); + runtime.buildBackendWorlds(reporter, Collections.emptySet()); + runStep(reporter, runtime); + runtime.disposeBackendWorlds(); + runtime.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), startsWith(String.format( + "1 Scenarios (1 failed)%n" + + "1 Steps (1 failed)%n"))); + } + + @Test + public void should_add_ambiguous_match_as_failed_result_to_the_summary_counter() throws Throwable { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Reporter reporter = mock(Reporter.class); + + Runtime runtime = createRuntimeWithMockedGlueWithAmbiguousMatch("--monochrome"); + runtime.buildBackendWorlds(reporter, Collections.emptySet()); + runStep(reporter, runtime); + runtime.disposeBackendWorlds(); + runtime.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), startsWith(String.format( + "1 Scenarios (1 failed)%n" + + "1 Steps (1 failed)%n"))); + } + + @Test + public void should_add_skipped_result_to_the_summary_counter() throws Throwable { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Reporter reporter = mock(Reporter.class); + StepDefinitionMatch match = createExceptionThrowingMatch(new Exception()); + + Runtime runtime = createRuntimeWithMockedGlue(match, "--monochrome"); + runtime.buildBackendWorlds(reporter, Collections.emptySet()); + runStep(reporter, runtime); + runStep(reporter, runtime); + runtime.disposeBackendWorlds(); + runtime.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), startsWith(String.format( + "1 Scenarios (1 failed)%n" + + "2 Steps (1 failed, 1 skipped)%n"))); + } + + @Test + public void should_add_undefined_result_to_the_summary_counter() throws Throwable { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Reporter reporter = mock(Reporter.class); + + Runtime runtime = createRuntimeWithMockedGlue(null, "--monochrome"); + runtime.buildBackendWorlds(reporter, Collections.emptySet()); + runStep(reporter, runtime); + runtime.disposeBackendWorlds(); + runtime.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), startsWith(String.format( + "1 Scenarios (1 undefined)%n" + + "1 Steps (1 undefined)%n"))); + } + + @Test + public void should_fail_the_scenario_if_before_fails() throws Throwable { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Reporter reporter = mock(Reporter.class); + StepDefinitionMatch match = mock(StepDefinitionMatch.class); + HookDefinition hook = createExceptionThrowingHook(); + + Runtime runtime = createRuntimeWithMockedGlue(match, hook, true, "--monochrome"); + runtime.buildBackendWorlds(reporter, Collections.emptySet()); + runtime.runBeforeHooks(reporter, Collections.emptySet()); + runStep(reporter, runtime); + runtime.disposeBackendWorlds(); + runtime.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), startsWith(String.format( + "1 Scenarios (1 failed)%n" + + "1 Steps (1 skipped)%n"))); + } + + @Test + public void should_fail_the_scenario_if_after_fails() throws Throwable { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Reporter reporter = mock(Reporter.class); + StepDefinitionMatch match = mock(StepDefinitionMatch.class); + HookDefinition hook = createExceptionThrowingHook(); + + Runtime runtime = createRuntimeWithMockedGlue(match, hook, false, "--monochrome"); + runtime.buildBackendWorlds(reporter, Collections.emptySet()); + runStep(reporter, runtime); + runtime.runAfterHooks(reporter, Collections.emptySet()); + runtime.disposeBackendWorlds(); + runtime.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), startsWith(String.format( + "1 Scenarios (1 failed)%n" + + "1 Steps (1 passed)%n"))); + } + + private StepDefinitionMatch createExceptionThrowingMatch(Exception exception) throws Throwable { + StepDefinitionMatch match = mock(StepDefinitionMatch.class); + doThrow(exception).when(match).runStep((I18n)any()); + return match; + } + + private HookDefinition createExceptionThrowingHook() throws Throwable { + HookDefinition hook = mock(HookDefinition.class); + when(hook.matches(anyCollectionOf(Tag.class))).thenReturn(true); + doThrow(new Exception()).when(hook).execute((Scenario)any()); + return hook; + } + + public void runStep(Reporter reporter, Runtime runtime) { + Step step = mock(Step.class); + I18n i18n = mock(I18n.class); + runtime.runStep("", step, reporter, i18n); + } + private Runtime createStrictRuntime() { return createRuntime("-g", "anything", "--strict"); } @@ -194,4 +364,48 @@ private Runtime createRuntime(String... runtimeArgs) { return new Runtime(resourceLoader, classLoader, backends, runtimeOptions); } + + private Runtime createRuntimeWithMockedGlue(StepDefinitionMatch match, String... runtimeArgs) { + return createRuntimeWithMockedGlue(match, false, null, false, runtimeArgs); + } + + private Runtime createRuntimeWithMockedGlue(StepDefinitionMatch match, HookDefinition hook, boolean isBefore, + String... runtimeArgs){ + return createRuntimeWithMockedGlue(match, false, hook, isBefore, runtimeArgs); + } + + private Runtime createRuntimeWithMockedGlueWithAmbiguousMatch(String... runtimeArgs) { + return createRuntimeWithMockedGlue(mock(StepDefinitionMatch.class), true, null, false, runtimeArgs); + } + + private Runtime createRuntimeWithMockedGlue(StepDefinitionMatch match, boolean isAmbiguous, HookDefinition hook, + boolean isBefore, String... runtimeArgs) { + ResourceLoader resourceLoader = mock(ResourceLoader.class); + ClassLoader classLoader = mock(ClassLoader.class); + RuntimeOptions runtimeOptions = new RuntimeOptions(new Properties(), runtimeArgs); + Backend backend = mock(Backend.class); + RuntimeGlue glue = mock(RuntimeGlue.class); + mockMatch(glue, match, isAmbiguous); + mockHook(glue, hook, isBefore); + Collection backends = Arrays.asList(backend); + + return new Runtime(resourceLoader, classLoader, backends, runtimeOptions, glue); + } + + private void mockMatch(RuntimeGlue glue, StepDefinitionMatch match, boolean isAmbiguous) { + if (isAmbiguous) { + Exception exception = new AmbiguousStepDefinitionsException(Arrays.asList(match, match)); + doThrow(exception).when(glue).stepDefinitionMatch(anyString(), (Step)any(), (I18n)any()); + } else { + when(glue.stepDefinitionMatch(anyString(), (Step)any(), (I18n)any())).thenReturn(match); + } + } + + private void mockHook(RuntimeGlue glue, HookDefinition hook, boolean isBefore) { + if (isBefore) { + when(glue.getBeforeHooks()).thenReturn(Arrays.asList(hook)); + } else { + when(glue.getAfterHooks()).thenReturn(Arrays.asList(hook)); + } + } } diff --git a/core/src/test/java/cucumber/runtime/ScenarioResultTest.java b/core/src/test/java/cucumber/runtime/ScenarioResultTest.java index f53317dd8b..f2515c53dd 100644 --- a/core/src/test/java/cucumber/runtime/ScenarioResultTest.java +++ b/core/src/test/java/cucumber/runtime/ScenarioResultTest.java @@ -22,9 +22,12 @@ public void no_steps_is_passed() throws Exception { } @Test - public void passed_and_failed_is_passed() throws Exception { + public void passed_failed_pending_undefined_skipped_is_failed() throws Exception { s.add(new Result("passed", 0L, null, null)); s.add(new Result("failed", 0L, null, null)); + s.add(new Result("pending", 0L, null, null)); + s.add(new Result("undefined", 0L, null, null)); + s.add(new Result("skipped", 0L, null, null)); assertEquals("failed", s.getStatus()); } @@ -36,12 +39,22 @@ public void passed_and_skipped_is_skipped_although_we_cant_have_skipped_without_ } @Test - public void undefined_and_pending_is_pending() throws Exception { - s.add(new Result("undefined", 0L, null, null)); + public void passed_pending_undefined_skipped_is_pending() throws Exception { + s.add(new Result("passed", 0L, null, null)); s.add(new Result("pending", 0L, null, null)); + s.add(new Result("undefined", 0L, null, null)); + s.add(new Result("skipped", 0L, null, null)); assertEquals("pending", s.getStatus()); } + @Test + public void passed_undefined_skipped_is_undefined() throws Exception { + s.add(new Result("passed", 0L, null, null)); + s.add(new Result("undefined", 0L, null, null)); + s.add(new Result("skipped", 0L, null, null)); + assertEquals("undefined", s.getStatus()); + } + @Test public void embeds_data() { byte[] data = new byte[]{1, 2, 3}; diff --git a/core/src/test/java/cucumber/runtime/SummaryCounterTest.java b/core/src/test/java/cucumber/runtime/SummaryCounterTest.java new file mode 100755 index 0000000000..d611e5b457 --- /dev/null +++ b/core/src/test/java/cucumber/runtime/SummaryCounterTest.java @@ -0,0 +1,172 @@ +package cucumber.runtime; + +import static org.junit.Assert.assertThat; +import static org.hamcrest.CoreMatchers.endsWith; +import static org.hamcrest.CoreMatchers.startsWith; + +import gherkin.formatter.ansi.AnsiEscapes; +import gherkin.formatter.model.Result; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Locale; + +import org.junit.Test; + +public class SummaryCounterTest { + public static final long ONE_MILLI_SECOND = 1000000; + private static final long ONE_HOUR = 60 * SummaryCounter.ONE_MINUTE; + + @Test + public void should_print_zero_scenarios_zero_steps_if_nothing_has_executed() { + SummaryCounter counter = createMonochromeSummaryCounter(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + counter.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), startsWith(String.format( + "0 Scenarios%n" + + "0 Steps%n"))); + } + + @Test + public void should_only_print_sub_counts_if_not_zero() { + SummaryCounter counter = createMonochromeSummaryCounter(); + Result passedResult = createResultWithStatus(Result.PASSED); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + counter.addStep(passedResult); + counter.addStep(passedResult); + counter.addStep(passedResult); + counter.addScenario(Result.PASSED); + counter.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), startsWith(String.format( + "1 Scenarios (1 passed)%n" + + "3 Steps (3 passed)%n"))); + } + + @Test + public void should_print_sub_counts_in_order_failed_skipped_pending_undefined_passed() { + SummaryCounter counter = createMonochromeSummaryCounter(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + addOneStepScenario(counter, Result.PASSED); + addOneStepScenario(counter, Result.FAILED); + addOneStepScenario(counter, SummaryCounter.PENDING); + addOneStepScenario(counter, Result.UNDEFINED.getStatus()); + addOneStepScenario(counter, Result.SKIPPED.getStatus()); + counter.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), startsWith(String.format( + "5 Scenarios (1 failed, 1 skipped, 1 pending, 1 undefined, 1 passed)%n" + + "5 Steps (1 failed, 1 skipped, 1 pending, 1 undefined, 1 passed)%n"))); + } + + @Test + public void should_print_sub_counts_in_order_failed_skipped_undefined_passed_in_color() { + SummaryCounter counter = createColorSummaryCounter(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + addOneStepScenario(counter, Result.PASSED); + addOneStepScenario(counter, Result.FAILED); + addOneStepScenario(counter, SummaryCounter.PENDING); + addOneStepScenario(counter, Result.UNDEFINED.getStatus()); + addOneStepScenario(counter, Result.SKIPPED.getStatus()); + counter.printSummary(new PrintStream(baos)); + + String colorSubCounts = + AnsiEscapes.RED + "1 failed" + AnsiEscapes.RESET + ", " + + AnsiEscapes.CYAN + "1 skipped" + AnsiEscapes.RESET + ", " + + AnsiEscapes.YELLOW + "1 pending" + AnsiEscapes.RESET + ", " + + AnsiEscapes.YELLOW + "1 undefined" + AnsiEscapes.RESET + ", " + + AnsiEscapes.GREEN + "1 passed" + AnsiEscapes.RESET; + assertThat(baos.toString(), startsWith(String.format( + "5 Scenarios (" + colorSubCounts + ")%n" + + "5 Steps (" + colorSubCounts + ")%n"))); + } + + @Test + public void should_print_zero_m_zero_s_if_nothing_has_executed() { + SummaryCounter counter = createMonochromeSummaryCounter(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + counter.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), endsWith(String.format( + "0m0.000s%n"))); + } + + @Test + public void should_include_hook_time_and_step_time_has_executed() { + SummaryCounter counter = createMonochromeSummaryCounter(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + counter.addHookTime(ONE_MILLI_SECOND); + counter.addStep(new Result(Result.PASSED, ONE_MILLI_SECOND, null)); + counter.addStep(new Result(Result.PASSED, ONE_MILLI_SECOND, null)); + counter.addHookTime(ONE_MILLI_SECOND); + counter.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), endsWith(String.format( + "0m0.004s%n"))); + } + + @Test + public void should_print_minutes_seconds_and_milliseconds() { + SummaryCounter counter = createMonochromeSummaryCounter(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + counter.addStep(new Result(Result.PASSED, SummaryCounter.ONE_MINUTE, null)); + counter.addStep(new Result(Result.PASSED, SummaryCounter.ONE_SECOND, null)); + counter.addStep(new Result(Result.PASSED, ONE_MILLI_SECOND, null)); + counter.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), endsWith(String.format( + "1m1.001s%n"))); + } + + @Test + public void should_print_minutes_instead_of_hours() { + SummaryCounter counter = createMonochromeSummaryCounter(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + counter.addStep(new Result(Result.PASSED, ONE_HOUR, null)); + counter.addStep(new Result(Result.PASSED, SummaryCounter.ONE_MINUTE, null)); + counter.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), endsWith(String.format( + "61m0.000s%n"))); + } + + @Test + public void should_use_locale_for_decimal_separator() { + SummaryCounter counter = new SummaryCounter(true, Locale.GERMANY); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + counter.addStep(new Result(Result.PASSED, SummaryCounter.ONE_MINUTE, null)); + counter.addStep(new Result(Result.PASSED, SummaryCounter.ONE_SECOND, null)); + counter.addStep(new Result(Result.PASSED, ONE_MILLI_SECOND, null)); + counter.printSummary(new PrintStream(baos)); + + assertThat(baos.toString(), endsWith(String.format( + "1m1,001s%n"))); + } + + private void addOneStepScenario(SummaryCounter counter, String status) { + counter.addStep(createResultWithStatus(status)); + counter.addScenario(status); + } + + private Result createResultWithStatus(String status) { + return new Result(status, 0l, null); + } + + private SummaryCounter createMonochromeSummaryCounter() { + return new SummaryCounter(true, Locale.US); + } + + private SummaryCounter createColorSummaryCounter() { + return new SummaryCounter(false, Locale.US); + } +} diff --git a/junit/src/main/java/cucumber/api/junit/Cucumber.java b/junit/src/main/java/cucumber/api/junit/Cucumber.java index 5751174425..a65b4f90fa 100644 --- a/junit/src/main/java/cucumber/api/junit/Cucumber.java +++ b/junit/src/main/java/cucumber/api/junit/Cucumber.java @@ -81,8 +81,8 @@ protected void runChild(FeatureRunner child, RunNotifier notifier) { public void run(RunNotifier notifier) { super.run(notifier); jUnitReporter.done(); - new SummaryPrinter(System.out).print(runtime); jUnitReporter.close(); + new SummaryPrinter(System.out).print(runtime); } private void addChildren(List cucumberFeatures) throws InitializationError {