From 80a73eaeb7075e6ccc8f5f5d2fa23825b012c49d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 11 Sep 2025 16:44:47 +0200 Subject: [PATCH 1/9] Use latest query --- cucumber-bom/pom.xml | 10 +- .../cucumber/core/plugin/RerunFormatter.java | 104 +++++++++++------- 2 files changed, 72 insertions(+), 42 deletions(-) diff --git a/cucumber-bom/pom.xml b/cucumber-bom/pom.xml index df5838d732..7dda11103e 100644 --- a/cucumber-bom/pom.xml +++ b/cucumber-bom/pom.xml @@ -15,15 +15,15 @@ 10.0.1 18.0.1 - 0.1.3 + 0.1.4-SNAPSHOT 34.0.0 21.13.0 - 0.8.1 + 0.8.2-SNAPSHOT 29.0.1 - 2.1.0 - 13.6.0 + 2.1.1-SNAPSHOT + 14.0.0 6.1.2 - 0.5.0 + 0.5.1-SNAPSHOT diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java index e3063116f3..5cd1a038c1 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java @@ -1,22 +1,30 @@ package io.cucumber.core.plugin; import io.cucumber.core.feature.FeatureWithLines; +import io.cucumber.messages.types.Envelope; +import io.cucumber.messages.types.TestCaseFinished; +import io.cucumber.messages.types.TestStepResult; +import io.cucumber.messages.types.TestStepResultStatus; import io.cucumber.plugin.ConcurrentEventListener; import io.cucumber.plugin.event.EventPublisher; -import io.cucumber.plugin.event.TestCase; -import io.cucumber.plugin.event.TestCaseFinished; -import io.cucumber.plugin.event.TestRunFinished; +import io.cucumber.query.Query; +import io.cucumber.query.Repository; import java.io.File; import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collection; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; import static io.cucumber.core.feature.FeatureWithLines.create; +import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_GHERKIN_DOCUMENT; +import static java.util.Objects.requireNonNull; /** * Formatter for reporting all failed test cases and print their locations @@ -24,42 +32,22 @@ */ public final class RerunFormatter implements ConcurrentEventListener { - private final UTF8PrintWriter out; - private final Map> featureAndFailedLinesMapping = new LinkedHashMap<>(); + private final PrintWriter writer; + private final Map> featureAndFailedLinesMapping = new LinkedHashMap<>(); + private final Repository repository = Repository.builder() + .feature(INCLUDE_GHERKIN_DOCUMENT, true) + .build(); + private final Query query = new Query(repository); public RerunFormatter(OutputStream out) { - this.out = new UTF8PrintWriter(out); + this.writer = createPrintWriter(out); } - @Override - public void setEventPublisher(EventPublisher publisher) { - publisher.registerHandlerFor(TestCaseFinished.class, this::handleTestCaseFinished); - publisher.registerHandlerFor(TestRunFinished.class, event -> finishReport()); - } - - private void handleTestCaseFinished(TestCaseFinished event) { - if (!event.getResult().getStatus().isOk()) { - recordTestFailed(event.getTestCase()); - } - } - - private void finishReport() { - for (Map.Entry> entry : featureAndFailedLinesMapping.entrySet()) { - FeatureWithLines featureWithLines = create(relativize(entry.getKey()), entry.getValue()); - out.println(featureWithLines.toString()); - } - - out.close(); - } - - private void recordTestFailed(TestCase testCase) { - URI uri = testCase.getUri(); - Collection failedTestCaseLines = getFailedTestCaseLines(uri); - failedTestCaseLines.add(testCase.getLocation().getLine()); - } - - private Collection getFailedTestCaseLines(URI uri) { - return featureAndFailedLinesMapping.computeIfAbsent(uri, k -> new ArrayList<>()); + private static PrintWriter createPrintWriter(OutputStream out) { + return new PrintWriter( + new OutputStreamWriter( + requireNonNull(out), + StandardCharsets.UTF_8)); } static URI relativize(URI uri) { @@ -79,4 +67,46 @@ static URI relativize(URI uri) { throw new IllegalArgumentException(e.getMessage(), e); } } + + @Override + public void setEventPublisher(EventPublisher publisher) { + publisher.registerHandlerFor(Envelope.class, event -> { + repository.update(event); + event.getTestCaseFinished().ifPresent(this::handleTestCaseFinished); + event.getTestRunFinished().ifPresent(testRunFinished -> finishReport()); + }); + } + + private void handleTestCaseFinished(TestCaseFinished event) { + TestStepResultStatus testStepResultStatus = query.findMostSevereTestStepResultBy(event) + .map(TestStepResult::getStatus) + // By definition + .orElse(TestStepResultStatus.PASSED); + + if (testStepResultStatus == TestStepResultStatus.PASSED + || testStepResultStatus == TestStepResultStatus.SKIPPED) { + return; + } + + query.findPickleBy(event).ifPresent(pickle -> { + Set lines = featureAndFailedLinesMapping + .computeIfAbsent(pickle.getUri(), s -> new HashSet<>()); + query.findLocationOf(pickle).ifPresent(location -> { + // TODO: Messages are silly + lines.add((int) (long) location.getLine()); + }); + }); + } + + private void finishReport() { + for (Map.Entry> entry : featureAndFailedLinesMapping.entrySet()) { + String key = entry.getKey(); + // TODO: Should these be relative? + FeatureWithLines featureWithLines = create(relativize(URI.create(key)), entry.getValue()); + writer.println(featureWithLines); + } + + writer.close(); + } + } From 792503fe213722fd01810a0b1c363459031fde5e Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 17 Jul 2025 19:25:34 +0200 Subject: [PATCH 2/9] Simplify with better messages --- .../src/main/java/io/cucumber/core/plugin/RerunFormatter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java index 5cd1a038c1..a1c39aba5a 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java @@ -27,8 +27,7 @@ import static java.util.Objects.requireNonNull; /** - * Formatter for reporting all failed test cases and print their locations - * Failed means: results that make the exit code non-zero. + * Formatter for reporting all failed test cases and print their locations. */ public final class RerunFormatter implements ConcurrentEventListener { From 3817e2dc2dd05fb75e8054b0bbc0ded9314e8223 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Thu, 11 Sep 2025 17:34:52 +0200 Subject: [PATCH 3/9] WIP on DefaultSummaryPrinter --- .../core/plugin/DefaultSummaryPrinter.java | 41 +- .../java/io/cucumber/core/plugin/Stats.java | 212 ++----- .../io/cucumber/core/plugin/StatsTest.java | 578 +++++++++--------- 3 files changed, 381 insertions(+), 450 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java index 9c0c754be7..9add727d30 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java @@ -1,21 +1,37 @@ package io.cucumber.core.plugin; +import io.cucumber.messages.types.Envelope; +import io.cucumber.messages.types.Snippet; +import io.cucumber.messages.types.Suggestion; import io.cucumber.plugin.ColorAware; import io.cucumber.plugin.ConcurrentEventListener; import io.cucumber.plugin.event.EventPublisher; import io.cucumber.plugin.event.SnippetsSuggestedEvent; import io.cucumber.plugin.event.TestRunFinished; +import io.cucumber.query.Query; +import io.cucumber.query.Repository; import java.io.OutputStream; import java.io.PrintStream; +import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; +import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; + +import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_GHERKIN_DOCUMENT; +import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_SUGGESTIONS; public final class DefaultSummaryPrinter implements ColorAware, ConcurrentEventListener { - private final Set snippets = new LinkedHashSet<>(); + private final Repository repository = Repository.builder() + .feature(INCLUDE_GHERKIN_DOCUMENT, true) + .feature(INCLUDE_SUGGESTIONS, true) + .build(); + private final Query query = new Query(repository); + private final Stats stats; private final PrintStream out; @@ -25,20 +41,15 @@ public DefaultSummaryPrinter() { DefaultSummaryPrinter(OutputStream out, Locale locale) { this.out = new PrintStream(out); - this.stats = new Stats(locale); + this.stats = new Stats(query, locale); } @Override public void setEventPublisher(EventPublisher publisher) { - stats.setEventPublisher(publisher); - publisher.registerHandlerFor(SnippetsSuggestedEvent.class, this::handleSnippetsSuggestedEvent); + publisher.registerHandlerFor(Envelope.class, repository::update); publisher.registerHandlerFor(TestRunFinished.class, event -> print()); } - private void handleSnippetsSuggestedEvent(SnippetsSuggestedEvent event) { - this.snippets.addAll(event.getSuggestion().getSnippets()); - } - private void print() { out.println(); printStats(); @@ -65,6 +76,16 @@ private void printErrors() { } private void printSnippets() { + Set snippets = query.findAllTestCaseFinished().stream() + .map(query::findPickleBy) + .filter(Optional::isPresent) + .map(Optional::get) + .map(query::findSuggestionsBy) + .flatMap(Collection::stream) + .map(Suggestion::getSnippets) + .flatMap(Collection::stream) + .collect(Collectors.toCollection(LinkedHashSet::new)); + if (snippets.isEmpty()) { return; } @@ -72,8 +93,8 @@ private void printSnippets() { out.println(); out.println("You can implement missing steps with the snippets below:"); out.println(); - for (String snippet : snippets) { - out.println(snippet); + for (Snippet snippet : snippets) { + out.println(snippet.getCode()); out.println(); } } diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/Stats.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/Stats.java index 6cecc731b3..26a25c29f2 100755 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/Stats.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/Stats.java @@ -1,48 +1,40 @@ package io.cucumber.core.plugin; +import io.cucumber.messages.types.Location; +import io.cucumber.messages.types.TestStepFinished; +import io.cucumber.messages.types.TestStepResult; +import io.cucumber.messages.types.TestStepResultStatus; import io.cucumber.plugin.ColorAware; -import io.cucumber.plugin.ConcurrentEventListener; -import io.cucumber.plugin.event.EventPublisher; -import io.cucumber.plugin.event.PickleStepTestStep; -import io.cucumber.plugin.event.Result; -import io.cucumber.plugin.event.Status; -import io.cucumber.plugin.event.TestCase; -import io.cucumber.plugin.event.TestCaseFinished; -import io.cucumber.plugin.event.TestRunFinished; -import io.cucumber.plugin.event.TestRunStarted; -import io.cucumber.plugin.event.TestStepFinished; +import io.cucumber.query.Query; import java.io.PrintStream; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; -import java.time.Duration; -import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; import static io.cucumber.core.plugin.Formats.ansi; import static io.cucumber.core.plugin.Formats.monochrome; import static java.util.Locale.ROOT; import static java.util.concurrent.TimeUnit.SECONDS; -class Stats implements ConcurrentEventListener, ColorAware { +class Stats implements ColorAware { private static final long ONE_SECOND = SECONDS.toNanos(1); private static final long ONE_MINUTE = 60 * ONE_SECOND; - private final SubCounts scenarioSubCounts = new SubCounts(); - private final SubCounts stepSubCounts = new SubCounts(); + private final Query query; private final Locale locale; - private final List failedScenarios = new ArrayList<>(); - private final List ambiguousScenarios = new ArrayList<>(); - private final List pendingScenarios = new ArrayList<>(); - private final List undefinedScenarios = new ArrayList<>(); private final List errors = new ArrayList<>(); - private Instant startTime = Instant.EPOCH; - private Duration totalDuration = Duration.ZERO; private Formats formats = ansi(); - Stats(Locale locale) { + Stats(Query query, Locale locale) { + this.query = query; this.locale = locale; } @@ -51,102 +43,14 @@ public void setMonochrome(boolean monochrome) { formats = monochrome ? monochrome() : ansi(); } - @Override - public void setEventPublisher(EventPublisher publisher) { - publisher.registerHandlerFor(TestRunStarted.class, this::setStartTime); - publisher.registerHandlerFor(TestStepFinished.class, this::addStepResult); - publisher.registerHandlerFor(TestCaseFinished.class, this::addScenario); - publisher.registerHandlerFor(TestRunFinished.class, this::setFinishTime); - } - - private void setStartTime(TestRunStarted event) { - setStartTime(event.getInstant()); - } - - private void addStepResult(TestStepFinished event) { - Result result = event.getResult(); - if (result.getError() != null) { - addError(result.getError()); - } - if (event.getTestStep() instanceof PickleStepTestStep) { - addStep(result.getStatus()); - } - } - - private void addScenario(TestCaseFinished event) { - TestCase testCase = event.getTestCase(); - addScenario(event.getResult().getStatus(), testCase); - } - - private void setFinishTime(TestRunFinished event) { - setFinishTime(event.getInstant()); - } - - void setStartTime(Instant startTime) { - this.startTime = startTime; - } - - private void addError(Throwable error) { - errors.add(error); - } - - void addStep(Status resultStatus) { - addResultToSubCount(stepSubCounts, resultStatus); - } - - void addScenario(Status resultStatus, TestCase testCase) { - addResultToSubCount(scenarioSubCounts, resultStatus); - switch (resultStatus) { - case FAILED: - failedScenarios.add(testCase); - break; - case AMBIGUOUS: - ambiguousScenarios.add(testCase); - break; - case PENDING: - pendingScenarios.add(testCase); - break; - case UNDEFINED: - undefinedScenarios.add(testCase); - break; - default: - // intentionally left blank - } - } - - void setFinishTime(Instant finishTime) { - this.totalDuration = Duration.between(startTime, finishTime); - } - - private void addResultToSubCount(SubCounts subCounts, Status resultStatus) { - switch (resultStatus) { - case FAILED: - subCounts.failed++; - break; - case AMBIGUOUS: - subCounts.ambiguous++; - break; - case PENDING: - subCounts.pending++; - break; - case UNDEFINED: - subCounts.undefined++; - break; - case SKIPPED: - subCounts.skipped++; - break; - default: - subCounts.passed++; - } - } - public List getErrors() { return errors; } void printStats(PrintStream out) { printNonZeroResultScenarios(out); - if (stepSubCounts.getTotal() == 0) { + List testStepFinished = query.findAllTestStepFinished(); + if (testStepFinished.isEmpty()) { out.println("0 Scenarios"); out.println("0 Steps"); } else { @@ -157,30 +61,42 @@ void printStats(PrintStream out) { } private void printStepCounts(PrintStream out) { - out.print(stepSubCounts.getTotal()); + List testStepsFinished = query.findAllTestStepFinished(); + Map testStepResultStatus = query.countMostSevereTestStepResultStatus(); + + out.print(testStepsFinished.size()); out.print(" Steps ("); - printSubCounts(out, stepSubCounts); + printSubCounts(out, testStepResultStatus); out.println(")"); } private void printScenarioCounts(PrintStream out) { - out.print(scenarioSubCounts.getTotal()); + Map scenarioSubCounts = query.findAllTestCaseFinished() + .stream() + .map(query::findMostSevereTestStepResultBy) + .filter(Optional::isPresent) + .map(Optional::get) + .map(TestStepResult::getStatus) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + + out.print(query.findAllTestCaseFinished().size()); out.print(" Scenarios ("); printSubCounts(out, scenarioSubCounts); out.println(")"); } - private void printSubCounts(PrintStream out, SubCounts subCounts) { + private void printSubCounts(PrintStream out, Map subCounts) { boolean addComma = false; - addComma = printSubCount(out, subCounts.failed, Status.FAILED, addComma); - addComma = printSubCount(out, subCounts.ambiguous, Status.AMBIGUOUS, addComma); - addComma = printSubCount(out, subCounts.skipped, Status.SKIPPED, addComma); - addComma = printSubCount(out, subCounts.pending, Status.PENDING, addComma); - addComma = printSubCount(out, subCounts.undefined, Status.UNDEFINED, addComma); - addComma = printSubCount(out, subCounts.passed, Status.PASSED, addComma); + addComma = printSubCount(out, subCounts, TestStepResultStatus.FAILED, addComma); + addComma = printSubCount(out, subCounts, TestStepResultStatus.AMBIGUOUS, addComma); + addComma = printSubCount(out, subCounts, TestStepResultStatus.SKIPPED, addComma); + addComma = printSubCount(out, subCounts, TestStepResultStatus.PENDING, addComma); + addComma = printSubCount(out, subCounts, TestStepResultStatus.UNDEFINED, addComma); + addComma = printSubCount(out, subCounts, TestStepResultStatus.PASSED, addComma); } - private boolean printSubCount(PrintStream out, int count, Status type, boolean addComma) { + private boolean printSubCount(PrintStream out, Map subCounts, TestStepResultStatus type, boolean addComma) { + long count = subCounts.getOrDefault(type, 0L); if (count != 0) { if (addComma) { out.print(", "); @@ -193,50 +109,44 @@ private boolean printSubCount(PrintStream out, int count, Status type, boolean a } private void printDuration(PrintStream out) { - out.print(String.format("%dm", (totalDuration.toNanos() / ONE_MINUTE))); - DecimalFormat format = new DecimalFormat("0.000", new DecimalFormatSymbols(locale)); - out.println(format.format(((double) (totalDuration.toNanos() % ONE_MINUTE) / ONE_SECOND)) + "s"); + query.findTestRunDuration().ifPresent(duration -> { + out.printf("%dm", (duration.toNanos() / ONE_MINUTE)); + DecimalFormat format = new DecimalFormat("0.000", new DecimalFormatSymbols(locale)); + out.println(format.format(((double) (duration.toNanos() % ONE_MINUTE) / ONE_SECOND)) + "s"); + }); } private void printNonZeroResultScenarios(PrintStream out) { - printScenarios(out, failedScenarios, Status.FAILED); - printScenarios(out, ambiguousScenarios, Status.AMBIGUOUS); - printScenarios(out, pendingScenarios, Status.PENDING); - printScenarios(out, undefinedScenarios, Status.UNDEFINED); + Map> testCaseFinishedByStatus = query.findAllTestCaseFinished() + .stream() + .collect(Collectors.groupingBy(testCaseFinished -> query.findMostSevereTestStepResultBy(testCaseFinished).map(TestStepResult::getStatus).orElse(TestStepResultStatus.UNKNOWN))); + + printScenarios(out, testCaseFinishedByStatus, TestStepResultStatus.FAILED); + printScenarios(out, testCaseFinishedByStatus, TestStepResultStatus.AMBIGUOUS); + printScenarios(out, testCaseFinishedByStatus, TestStepResultStatus.PENDING); + printScenarios(out, testCaseFinishedByStatus, TestStepResultStatus.UNDEFINED); } - private void printScenarios(PrintStream out, List scenarios, Status type) { + private void printScenarios(PrintStream out, Map> testCaseFinishedByStatus, TestStepResultStatus type) { + List scenarios = testCaseFinishedByStatus.getOrDefault(type, Collections.emptyList()); Format format = formats.get(type.name().toLowerCase(ROOT)); if (!scenarios.isEmpty()) { out.println(format.text(firstLetterCapitalizedName(type) + " scenarios:")); } - for (TestCase scenario : scenarios) { - String location = scenario.getUri() + ":" + scenario.getLocation().getLine(); - out.println(location + " # " + scenario.getName()); + for (io.cucumber.messages.types.TestCaseFinished testCaseFinished : scenarios) { + query.findPickleBy(testCaseFinished).ifPresent(pickle -> { + String location = pickle.getUri() + query.findLocationOf(pickle).map(Location::getLine).map(line -> ":" + line).orElse(""); + out.println(location + " # " + pickle.getName()); + }); } if (!scenarios.isEmpty()) { out.println(); } } - private String firstLetterCapitalizedName(Status status) { + private String firstLetterCapitalizedName(TestStepResultStatus status) { String name = status.name(); - return name.substring(0, 1) + name.substring(1).toLowerCase(ROOT); - } - - static class SubCounts { - - public int passed = 0; - public int failed = 0; - public int ambiguous = 0; - public int skipped = 0; - public int pending = 0; - public int undefined = 0; - - int getTotal() { - return passed + failed + ambiguous + skipped + pending + undefined; - } - + return name.charAt(0) + name.substring(1).toLowerCase(ROOT); } } diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/StatsTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/StatsTest.java index fe180747ba..4b10145a2e 100755 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/StatsTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/StatsTest.java @@ -1,289 +1,289 @@ -package io.cucumber.core.plugin; - -import io.cucumber.plugin.event.Location; -import io.cucumber.plugin.event.Status; -import io.cucumber.plugin.event.TestCase; -import io.cucumber.plugin.event.TestStep; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.net.URI; -import java.time.Instant; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.UUID; - -import static java.time.Duration.ofHours; -import static java.time.Duration.ofMillis; -import static java.time.Duration.ofMinutes; -import static java.time.Duration.ofSeconds; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.endsWith; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.hamcrest.MatcherAssert.assertThat; - -class StatsTest { - - private static final Instant ANY_TIME = Instant.ofEpochMilli(1234567890); - - @Test - void should_print_zero_scenarios_zero_steps_if_nothing_has_executed() { - Stats counter = createMonochromeSummaryCounter(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - counter.printStats(new PrintStream(baos)); - - assertThat(baos.toString(), startsWith(String.format( - "0 Scenarios%n" + - "0 Steps%n"))); - } - - private Stats createMonochromeSummaryCounter() { - Stats stats = new Stats(Locale.US); - stats.setMonochrome(true); - return stats; - } - - @Test - void should_only_print_sub_counts_if_not_zero() { - Stats counter = createMonochromeSummaryCounter(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - counter.addStep(Status.PASSED); - counter.addStep(Status.PASSED); - counter.addStep(Status.PASSED); - counter.addScenario(Status.PASSED, createTestCase("classpath:com/example", 42, "scenario designation")); - counter.printStats(new PrintStream(baos)); - - assertThat(baos.toString(), startsWith(String.format( - "1 Scenarios (1 passed)%n" + - "3 Steps (3 passed)%n"))); - } - - @Test - void should_print_sub_counts_in_order_failed_ambiguous_skipped_pending_undefined_passed() { - Stats counter = createMonochromeSummaryCounter(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - addOneStepScenario(counter, Status.PASSED); - addOneStepScenario(counter, Status.FAILED); - addOneStepScenario(counter, Status.AMBIGUOUS); - addOneStepScenario(counter, Status.PENDING); - addOneStepScenario(counter, Status.UNDEFINED); - addOneStepScenario(counter, Status.SKIPPED); - counter.printStats(new PrintStream(baos)); - - assertThat(baos.toString(), containsString(String.format("" + - "6 Scenarios (1 failed, 1 ambiguous, 1 skipped, 1 pending, 1 undefined, 1 passed)%n" + - "6 Steps (1 failed, 1 ambiguous, 1 skipped, 1 pending, 1 undefined, 1 passed)%n"))); - } - - private void addOneStepScenario(Stats counter, Status status) { - counter.addStep(status); - counter.addScenario(status, createTestCase("classpath:com/example", 14, "scenario designation")); - } - - @Test - void should_print_sub_counts_in_order_failed_ambiguous_skipped_undefined_passed_in_color() { - Stats counter = createColorSummaryCounter(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - addOneStepScenario(counter, Status.PASSED); - addOneStepScenario(counter, Status.FAILED); - addOneStepScenario(counter, Status.AMBIGUOUS); - addOneStepScenario(counter, Status.PENDING); - addOneStepScenario(counter, Status.UNDEFINED); - addOneStepScenario(counter, Status.SKIPPED); - counter.printStats(new PrintStream(baos)); - - String colorSubCounts = "" + - AnsiEscapes.RED + "1 failed" + AnsiEscapes.RESET + ", " + - AnsiEscapes.RED + "1 ambiguous" + 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(), containsString(String.format("" + - "6 Scenarios (" + colorSubCounts + ")%n" + - "6 Steps (" + colorSubCounts + ")%n"))); - } - - private Stats createColorSummaryCounter() { - return new Stats(Locale.US); - } - - @Test - void should_print_zero_m_zero_s_if_nothing_has_executed() { - Stats counter = createMonochromeSummaryCounter(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - counter.printStats(new PrintStream(baos)); - - assertThat(baos.toString(), endsWith(String.format( - "0m0.000s%n"))); - } - - @Test - void should_report_the_difference_between_finish_time_and_start_time() { - Stats counter = createMonochromeSummaryCounter(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - counter.setStartTime(ANY_TIME); - counter.setFinishTime(ANY_TIME.plus(ofMillis(4))); - counter.printStats(new PrintStream(baos)); - - assertThat(baos.toString(), endsWith(String.format( - "0m0.004s%n"))); - } - - @Test - void should_print_minutes_seconds_and_milliseconds() { - Stats counter = createMonochromeSummaryCounter(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - counter.setStartTime(ANY_TIME); - counter.setFinishTime(ANY_TIME.plus(ofMinutes(1)).plus(ofSeconds(1)).plus(ofMillis(1))); - counter.printStats(new PrintStream(baos)); - - assertThat(baos.toString(), endsWith(String.format( - "1m1.001s%n"))); - } - - @Test - void should_print_minutes_instead_of_hours() { - Stats counter = createMonochromeSummaryCounter(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - counter.setStartTime(ANY_TIME); - counter.setFinishTime(ANY_TIME.plus(ofHours(1)).plus(ofMinutes(1))); - counter.printStats(new PrintStream(baos)); - - assertThat(baos.toString(), endsWith(String.format( - "61m0.000s%n"))); - } - - @Test - void should_use_locale_for_decimal_separator() { - Stats counter = new Stats(Locale.GERMANY); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - counter.setStartTime(ANY_TIME); - counter.setFinishTime(ANY_TIME.plus(ofMinutes(1)).plus(ofSeconds(1)).plus(ofMillis(1))); - counter.printStats(new PrintStream(baos)); - - assertThat(baos.toString(), endsWith(String.format("1m1,001s%n"))); - } - - @Test - void should_print_failed_ambiguous_scenarios() { - Stats counter = createMonochromeSummaryCounter(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - counter.addStep(Status.FAILED); - counter.addScenario(Status.FAILED, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); - counter.addStep(Status.AMBIGUOUS); - counter.addScenario(Status.AMBIGUOUS, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); - counter.addStep(Status.UNDEFINED); - counter.addScenario(Status.UNDEFINED, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); - counter.addStep(Status.PENDING); - counter.addScenario(Status.PENDING, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); - counter.printStats(new PrintStream(baos)); - - assertThat(baos.toString(), startsWith(String.format("" + - "Failed scenarios:%n" + - "path/file.feature:3 # Scenario: scenario_name%n" + - "%n" + - "Ambiguous scenarios:%n" + - "path/file.feature:3 # Scenario: scenario_name%n" + - "%n" + - "Pending scenarios:%n" + - "path/file.feature:3 # Scenario: scenario_name%n" + - "%n" + - "Undefined scenarios:%n" + - "path/file.feature:3 # Scenario: scenario_name%n" + - "%n" + - "4 Scenarios"))); - } - - @Test - void should_print_failed_ambiguous_pending_undefined_scenarios() { - Stats counter = createMonochromeSummaryCounter(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - counter.addStep(Status.FAILED); - counter.addScenario(Status.FAILED, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); - counter.addStep(Status.AMBIGUOUS); - counter.addScenario(Status.AMBIGUOUS, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); - counter.addStep(Status.UNDEFINED); - counter.addScenario(Status.UNDEFINED, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); - counter.addStep(Status.PENDING); - counter.addScenario(Status.PENDING, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); - counter.printStats(new PrintStream(baos)); - - assertThat(baos.toString(), startsWith(String.format("" + - "Failed scenarios:%n" + - "path/file.feature:3 # Scenario: scenario_name%n" + - "%n" + - "Ambiguous scenarios:%n" + - "path/file.feature:3 # Scenario: scenario_name%n" + - "%n" + - "Pending scenarios:%n" + - "path/file.feature:3 # Scenario: scenario_name%n" + - "%n" + - "Undefined scenarios:%n" + - "path/file.feature:3 # Scenario: scenario_name%n" + - "%n" + - "4 Scenarios"))); - } - - private static TestCase createTestCase(String uri, int line, String name) { - return new TestCase() { - @Override - public Integer getLine() { - return getLocation().getLine(); - } - - @Override - public Location getLocation() { - return new Location(line, -1); - } - - @Override - public String getKeyword() { - return "Scenario"; - } - - @Override - public String getName() { - return name; - } - - @Override - public String getScenarioDesignation() { - return null; - } - - @Override - public List getTags() { - return Collections.emptyList(); - } - - @Override - public List getTestSteps() { - return Collections.emptyList(); - } - - @Override - public URI getUri() { - return URI.create(uri); - } - - @Override - public UUID getId() { - return UUID.randomUUID(); - } - }; - } - -} +//package io.cucumber.core.plugin; +// +//import io.cucumber.plugin.event.Location; +//import io.cucumber.plugin.event.Status; +//import io.cucumber.plugin.event.TestCase; +//import io.cucumber.plugin.event.TestStep; +//import org.junit.jupiter.api.Test; +// +//import java.io.ByteArrayOutputStream; +//import java.io.PrintStream; +//import java.net.URI; +//import java.time.Instant; +//import java.util.Collections; +//import java.util.List; +//import java.util.Locale; +//import java.util.UUID; +// +//import static java.time.Duration.ofHours; +//import static java.time.Duration.ofMillis; +//import static java.time.Duration.ofMinutes; +//import static java.time.Duration.ofSeconds; +//import static org.hamcrest.CoreMatchers.containsString; +//import static org.hamcrest.CoreMatchers.endsWith; +//import static org.hamcrest.CoreMatchers.startsWith; +//import static org.hamcrest.MatcherAssert.assertThat; +// +//class StatsTest { +// +// private static final Instant ANY_TIME = Instant.ofEpochMilli(1234567890); +// +// @Test +// void should_print_zero_scenarios_zero_steps_if_nothing_has_executed() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), startsWith(String.format( +// "0 Scenarios%n" + +// "0 Steps%n"))); +// } +// +// private Stats createMonochromeSummaryCounter() { +// Stats stats = new Stats(query, Locale.US); +// stats.setMonochrome(true); +// return stats; +// } +// +// @Test +// void should_only_print_sub_counts_if_not_zero() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.addStep(Status.PASSED); +// counter.addStep(Status.PASSED); +// counter.addStep(Status.PASSED); +// counter.addScenario(Status.PASSED, createTestCase("classpath:com/example", 42, "scenario designation")); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), startsWith(String.format( +// "1 Scenarios (1 passed)%n" + +// "3 Steps (3 passed)%n"))); +// } +// +// @Test +// void should_print_sub_counts_in_order_failed_ambiguous_skipped_pending_undefined_passed() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// addOneStepScenario(counter, Status.PASSED); +// addOneStepScenario(counter, Status.FAILED); +// addOneStepScenario(counter, Status.AMBIGUOUS); +// addOneStepScenario(counter, Status.PENDING); +// addOneStepScenario(counter, Status.UNDEFINED); +// addOneStepScenario(counter, Status.SKIPPED); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), containsString(String.format("" + +// "6 Scenarios (1 failed, 1 ambiguous, 1 skipped, 1 pending, 1 undefined, 1 passed)%n" + +// "6 Steps (1 failed, 1 ambiguous, 1 skipped, 1 pending, 1 undefined, 1 passed)%n"))); +// } +// +// private void addOneStepScenario(Stats counter, Status status) { +// counter.addStep(status); +// counter.addScenario(status, createTestCase("classpath:com/example", 14, "scenario designation")); +// } +// +// @Test +// void should_print_sub_counts_in_order_failed_ambiguous_skipped_undefined_passed_in_color() { +// Stats counter = createColorSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// addOneStepScenario(counter, Status.PASSED); +// addOneStepScenario(counter, Status.FAILED); +// addOneStepScenario(counter, Status.AMBIGUOUS); +// addOneStepScenario(counter, Status.PENDING); +// addOneStepScenario(counter, Status.UNDEFINED); +// addOneStepScenario(counter, Status.SKIPPED); +// counter.printStats(new PrintStream(baos)); +// +// String colorSubCounts = "" + +// AnsiEscapes.RED + "1 failed" + AnsiEscapes.RESET + ", " + +// AnsiEscapes.RED + "1 ambiguous" + 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(), containsString(String.format("" + +// "6 Scenarios (" + colorSubCounts + ")%n" + +// "6 Steps (" + colorSubCounts + ")%n"))); +// } +// +// private Stats createColorSummaryCounter() { +// return new Stats(query, Locale.US); +// } +// +// @Test +// void should_print_zero_m_zero_s_if_nothing_has_executed() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), endsWith(String.format( +// "0m0.000s%n"))); +// } +// +// @Test +// void should_report_the_difference_between_finish_time_and_start_time() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.setStartTime(ANY_TIME); +// counter.setFinishTime(ANY_TIME.plus(ofMillis(4))); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), endsWith(String.format( +// "0m0.004s%n"))); +// } +// +// @Test +// void should_print_minutes_seconds_and_milliseconds() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.setStartTime(ANY_TIME); +// counter.setFinishTime(ANY_TIME.plus(ofMinutes(1)).plus(ofSeconds(1)).plus(ofMillis(1))); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), endsWith(String.format( +// "1m1.001s%n"))); +// } +// +// @Test +// void should_print_minutes_instead_of_hours() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.setStartTime(ANY_TIME); +// counter.setFinishTime(ANY_TIME.plus(ofHours(1)).plus(ofMinutes(1))); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), endsWith(String.format( +// "61m0.000s%n"))); +// } +// +// @Test +// void should_use_locale_for_decimal_separator() { +// Stats counter = new Stats(query, Locale.GERMANY); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.setStartTime(ANY_TIME); +// counter.setFinishTime(ANY_TIME.plus(ofMinutes(1)).plus(ofSeconds(1)).plus(ofMillis(1))); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), endsWith(String.format("1m1,001s%n"))); +// } +// +// @Test +// void should_print_failed_ambiguous_scenarios() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.addStep(Status.FAILED); +// counter.addScenario(Status.FAILED, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); +// counter.addStep(Status.AMBIGUOUS); +// counter.addScenario(Status.AMBIGUOUS, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); +// counter.addStep(Status.UNDEFINED); +// counter.addScenario(Status.UNDEFINED, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); +// counter.addStep(Status.PENDING); +// counter.addScenario(Status.PENDING, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), startsWith(String.format("" + +// "Failed scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "Ambiguous scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "Pending scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "Undefined scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "4 Scenarios"))); +// } +// +// @Test +// void should_print_failed_ambiguous_pending_undefined_scenarios() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.addStep(Status.FAILED); +// counter.addScenario(Status.FAILED, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); +// counter.addStep(Status.AMBIGUOUS); +// counter.addScenario(Status.AMBIGUOUS, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); +// counter.addStep(Status.UNDEFINED); +// counter.addScenario(Status.UNDEFINED, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); +// counter.addStep(Status.PENDING); +// counter.addScenario(Status.PENDING, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), startsWith(String.format("" + +// "Failed scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "Ambiguous scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "Pending scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "Undefined scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "4 Scenarios"))); +// } +// +// private static TestCase createTestCase(String uri, int line, String name) { +// return new TestCase() { +// @Override +// public Integer getLine() { +// return getLocation().getLine(); +// } +// +// @Override +// public Location getLocation() { +// return new Location(line, -1); +// } +// +// @Override +// public String getKeyword() { +// return "Scenario"; +// } +// +// @Override +// public String getName() { +// return name; +// } +// +// @Override +// public String getScenarioDesignation() { +// return null; +// } +// +// @Override +// public List getTags() { +// return Collections.emptyList(); +// } +// +// @Override +// public List getTestSteps() { +// return Collections.emptyList(); +// } +// +// @Override +// public URI getUri() { +// return URI.create(uri); +// } +// +// @Override +// public UUID getId() { +// return UUID.randomUUID(); +// } +// }; +// } +// +//} From efd845dc4a64bfabf1c70cb6c278388629d375d8 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 14 Sep 2025 18:06:24 +0200 Subject: [PATCH 4/9] Fixup --- .../java/io/cucumber/core/plugin/DefaultSummaryPrinter.java | 5 ++--- .../src/main/java/io/cucumber/core/plugin/Stats.java | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java index 9add727d30..8598853d06 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java @@ -6,7 +6,6 @@ import io.cucumber.plugin.ColorAware; import io.cucumber.plugin.ConcurrentEventListener; import io.cucumber.plugin.event.EventPublisher; -import io.cucumber.plugin.event.SnippetsSuggestedEvent; import io.cucumber.plugin.event.TestRunFinished; import io.cucumber.query.Query; import io.cucumber.query.Repository; @@ -21,13 +20,13 @@ import java.util.Set; import java.util.stream.Collectors; -import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_GHERKIN_DOCUMENT; +import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_GHERKIN_DOCUMENTS; import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_SUGGESTIONS; public final class DefaultSummaryPrinter implements ColorAware, ConcurrentEventListener { private final Repository repository = Repository.builder() - .feature(INCLUDE_GHERKIN_DOCUMENT, true) + .feature(INCLUDE_GHERKIN_DOCUMENTS, true) .feature(INCLUDE_SUGGESTIONS, true) .build(); private final Query query = new Query(repository); diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/Stats.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/Stats.java index 26a25c29f2..e9fcc882a3 100755 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/Stats.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/Stats.java @@ -28,7 +28,7 @@ class Stats implements ColorAware { private static final long ONE_SECOND = SECONDS.toNanos(1); private static final long ONE_MINUTE = 60 * ONE_SECOND; - private final Query query; + final Query query; private final Locale locale; private final List errors = new ArrayList<>(); private Formats formats = ansi(); From 7016a5ff5a5b9a3549211b3916663fbd02696b32 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 14 Sep 2025 18:40:46 +0200 Subject: [PATCH 5/9] Clean up --- .../core/plugin/DefaultSummaryPrinter.java | 166 ++++- .../java/io/cucumber/core/plugin/Stats.java | 152 ----- .../io/cucumber/core/plugin/StatsTest.java | 594 +++++++++--------- 3 files changed, 464 insertions(+), 448 deletions(-) delete mode 100755 cucumber-core/src/main/java/io/cucumber/core/plugin/Stats.java diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java index 8598853d06..e17b8b338b 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java @@ -1,8 +1,14 @@ package io.cucumber.core.plugin; import io.cucumber.messages.types.Envelope; +import io.cucumber.messages.types.Exception; +import io.cucumber.messages.types.Location; import io.cucumber.messages.types.Snippet; import io.cucumber.messages.types.Suggestion; +import io.cucumber.messages.types.TestCaseFinished; +import io.cucumber.messages.types.TestStepFinished; +import io.cucumber.messages.types.TestStepResult; +import io.cucumber.messages.types.TestStepResultStatus; import io.cucumber.plugin.ColorAware; import io.cucumber.plugin.ConcurrentEventListener; import io.cucumber.plugin.event.EventPublisher; @@ -12,18 +18,32 @@ import java.io.OutputStream; import java.io.PrintStream; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collector; import java.util.stream.Collectors; +import static io.cucumber.core.plugin.Formats.ansi; +import static io.cucumber.core.plugin.Formats.monochrome; import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_GHERKIN_DOCUMENTS; import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_SUGGESTIONS; +import static java.util.Collections.emptyList; +import static java.util.Locale.ROOT; +import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.stream.Collectors.counting; +import static java.util.stream.Collectors.groupingBy; public final class DefaultSummaryPrinter implements ColorAware, ConcurrentEventListener { + private static final long ONE_SECOND = SECONDS.toNanos(1); + private static final long ONE_MINUTE = 60 * ONE_SECOND; private final Repository repository = Repository.builder() .feature(INCLUDE_GHERKIN_DOCUMENTS, true) @@ -31,8 +51,9 @@ public final class DefaultSummaryPrinter implements ColorAware, ConcurrentEventL .build(); private final Query query = new Query(repository); - private final Stats stats; private final PrintStream out; + private final Locale locale; + private Formats formats = ansi(); public DefaultSummaryPrinter() { this(System.out, Locale.getDefault()); @@ -40,7 +61,7 @@ public DefaultSummaryPrinter() { DefaultSummaryPrinter(OutputStream out, Locale locale) { this.out = new PrintStream(out); - this.stats = new Stats(query, locale); + this.locale = locale; } @Override @@ -58,18 +79,125 @@ private void print() { } private void printStats() { - stats.printStats(out); + printNonPassingScenarios(); + printScenarioCounts(); + printStepCounts(); + printDuration(); out.println(); } + + private void printNonPassingScenarios() { + Map> testCaseFinishedByStatus = query + .findAllTestCaseFinished() + .stream() + .collect(groupingBy(this::getTestStepResultStatusBy)); + + printScenarios(testCaseFinishedByStatus, TestStepResultStatus.FAILED); + printScenarios(testCaseFinishedByStatus, TestStepResultStatus.AMBIGUOUS); + printScenarios(testCaseFinishedByStatus, TestStepResultStatus.PENDING); + printScenarios(testCaseFinishedByStatus, TestStepResultStatus.UNDEFINED); + } + + private void printScenarios( + Map> testCaseFinishedByStatus, + TestStepResultStatus type + ) { + List scenarios = testCaseFinishedByStatus.getOrDefault(type, emptyList()); + Format format = formats.get(type.name().toLowerCase(ROOT)); + if (!scenarios.isEmpty()) { + out.println(format.text(firstLetterCapitalizedName(type) + " scenarios:")); + } + for (TestCaseFinished testCaseFinished : scenarios) { + query.findPickleBy(testCaseFinished).ifPresent(pickle -> { + String location = pickle.getUri() + query.findLocationOf(pickle).map(Location::getLine).map(line -> ":" + line).orElse(""); + out.println(location + " # " + pickle.getName()); + }); + } + if (!scenarios.isEmpty()) { + out.println(); + } + } + + private void printScenarioCounts() { + List allTestCaseFinished = query.findAllTestCaseFinished(); + if (allTestCaseFinished.isEmpty()) { + out.println("0 Scenarios"); + return; + } + Map scenarioSubCounts = allTestCaseFinished + .stream() + .collect(countTestStepResultStatusByTestCaseFinished()); + + out.print(allTestCaseFinished.size()); + out.print(" Scenarios ("); + printSubCounts(scenarioSubCounts); + out.println(")"); + } + + + private void printStepCounts() { + List testStepsFinished = query.findAllTestStepFinished(); + if (testStepsFinished.isEmpty()) { + out.println("0 Steps"); + return; + } + + Map testStepResultStatus = testStepsFinished.stream() + .collect(countTestStepResultStatusByTestStepFinished()); + + out.print(testStepsFinished.size()); + out.print(" Steps ("); + printSubCounts(testStepResultStatus); + out.println(")"); + } + + private void printSubCounts(Map subCounts) { + boolean addComma = false; + addComma = printSubCount(out, subCounts, TestStepResultStatus.FAILED, addComma); + addComma = printSubCount(out, subCounts, TestStepResultStatus.AMBIGUOUS, addComma); + addComma = printSubCount(out, subCounts, TestStepResultStatus.SKIPPED, addComma); + addComma = printSubCount(out, subCounts, TestStepResultStatus.PENDING, addComma); + addComma = printSubCount(out, subCounts, TestStepResultStatus.UNDEFINED, addComma); + addComma = printSubCount(out, subCounts, TestStepResultStatus.PASSED, addComma); + } + + private boolean printSubCount( + PrintStream out, Map subCounts, TestStepResultStatus type, boolean addComma + ) { + long count = subCounts.getOrDefault(type, 0L); + if (count != 0) { + if (addComma) { + out.print(", "); + } + Format format = formats.get(type.name().toLowerCase(ROOT)); + out.print(format.text(count + " " + type.name().toLowerCase(ROOT))); + addComma = true; + } + return addComma; + } + + private void printDuration() { + query.findTestRunDuration().ifPresent(duration -> { + out.printf("%dm", (duration.toNanos() / ONE_MINUTE)); + DecimalFormat format = new DecimalFormat("0.000", new DecimalFormatSymbols(locale)); + out.println(format.format(((double) (duration.toNanos() % ONE_MINUTE) / ONE_SECOND)) + "s"); + }); + } private void printErrors() { - List errors = stats.getErrors(); + List errors = query.findAllTestStepFinished() + .stream() + .map(TestStepFinished::getTestStepResult) + .map(TestStepResult::getException) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); if (errors.isEmpty()) { return; } out.println(); - for (Throwable error : errors) { - error.printStackTrace(out); + for (Exception error : errors) { + out.println(error.getStackTrace()); out.println(); } } @@ -98,9 +226,33 @@ private void printSnippets() { } } + private Collector> countTestStepResultStatusByTestCaseFinished() { + return groupingBy(this::getTestStepResultStatusBy, counting()); + } + + private TestStepResultStatus getTestStepResultStatusBy(TestCaseFinished testCaseFinished) { + return query.findMostSevereTestStepResultBy(testCaseFinished) + .map(TestStepResult::getStatus) + // By definition + .orElse(TestStepResultStatus.PASSED); + } + + private static Collector> countTestStepResultStatusByTestStepFinished() { + return groupingBy(DefaultSummaryPrinter::getTestStepResultStatusBy, counting()); + } + + private static TestStepResultStatus getTestStepResultStatusBy(TestStepFinished testStepFinished) { + return testStepFinished.getTestStepResult().getStatus(); + } + + private String firstLetterCapitalizedName(TestStepResultStatus status) { + String name = status.name(); + return name.charAt(0) + name.substring(1).toLowerCase(ROOT); + } + @Override public void setMonochrome(boolean monochrome) { - stats.setMonochrome(monochrome); + formats = monochrome ? monochrome() : ansi(); } } diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/Stats.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/Stats.java deleted file mode 100755 index e9fcc882a3..0000000000 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/Stats.java +++ /dev/null @@ -1,152 +0,0 @@ -package io.cucumber.core.plugin; - -import io.cucumber.messages.types.Location; -import io.cucumber.messages.types.TestStepFinished; -import io.cucumber.messages.types.TestStepResult; -import io.cucumber.messages.types.TestStepResultStatus; -import io.cucumber.plugin.ColorAware; -import io.cucumber.query.Query; - -import java.io.PrintStream; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static io.cucumber.core.plugin.Formats.ansi; -import static io.cucumber.core.plugin.Formats.monochrome; -import static java.util.Locale.ROOT; -import static java.util.concurrent.TimeUnit.SECONDS; - -class Stats implements ColorAware { - - private static final long ONE_SECOND = SECONDS.toNanos(1); - private static final long ONE_MINUTE = 60 * ONE_SECOND; - final Query query; - private final Locale locale; - private final List errors = new ArrayList<>(); - private Formats formats = ansi(); - - Stats(Query query, Locale locale) { - this.query = query; - this.locale = locale; - } - - @Override - public void setMonochrome(boolean monochrome) { - formats = monochrome ? monochrome() : ansi(); - } - - public List getErrors() { - return errors; - } - - void printStats(PrintStream out) { - printNonZeroResultScenarios(out); - List testStepFinished = query.findAllTestStepFinished(); - if (testStepFinished.isEmpty()) { - out.println("0 Scenarios"); - out.println("0 Steps"); - } else { - printScenarioCounts(out); - printStepCounts(out); - } - printDuration(out); - } - - private void printStepCounts(PrintStream out) { - List testStepsFinished = query.findAllTestStepFinished(); - Map testStepResultStatus = query.countMostSevereTestStepResultStatus(); - - out.print(testStepsFinished.size()); - out.print(" Steps ("); - printSubCounts(out, testStepResultStatus); - out.println(")"); - } - - private void printScenarioCounts(PrintStream out) { - Map scenarioSubCounts = query.findAllTestCaseFinished() - .stream() - .map(query::findMostSevereTestStepResultBy) - .filter(Optional::isPresent) - .map(Optional::get) - .map(TestStepResult::getStatus) - .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); - - out.print(query.findAllTestCaseFinished().size()); - out.print(" Scenarios ("); - printSubCounts(out, scenarioSubCounts); - out.println(")"); - } - - private void printSubCounts(PrintStream out, Map subCounts) { - boolean addComma = false; - addComma = printSubCount(out, subCounts, TestStepResultStatus.FAILED, addComma); - addComma = printSubCount(out, subCounts, TestStepResultStatus.AMBIGUOUS, addComma); - addComma = printSubCount(out, subCounts, TestStepResultStatus.SKIPPED, addComma); - addComma = printSubCount(out, subCounts, TestStepResultStatus.PENDING, addComma); - addComma = printSubCount(out, subCounts, TestStepResultStatus.UNDEFINED, addComma); - addComma = printSubCount(out, subCounts, TestStepResultStatus.PASSED, addComma); - } - - private boolean printSubCount(PrintStream out, Map subCounts, TestStepResultStatus type, boolean addComma) { - long count = subCounts.getOrDefault(type, 0L); - if (count != 0) { - if (addComma) { - out.print(", "); - } - Format format = formats.get(type.name().toLowerCase(ROOT)); - out.print(format.text(count + " " + type.name().toLowerCase(ROOT))); - addComma = true; - } - return addComma; - } - - private void printDuration(PrintStream out) { - query.findTestRunDuration().ifPresent(duration -> { - out.printf("%dm", (duration.toNanos() / ONE_MINUTE)); - DecimalFormat format = new DecimalFormat("0.000", new DecimalFormatSymbols(locale)); - out.println(format.format(((double) (duration.toNanos() % ONE_MINUTE) / ONE_SECOND)) + "s"); - }); - } - - private void printNonZeroResultScenarios(PrintStream out) { - Map> testCaseFinishedByStatus = query.findAllTestCaseFinished() - .stream() - .collect(Collectors.groupingBy(testCaseFinished -> query.findMostSevereTestStepResultBy(testCaseFinished).map(TestStepResult::getStatus).orElse(TestStepResultStatus.UNKNOWN))); - - printScenarios(out, testCaseFinishedByStatus, TestStepResultStatus.FAILED); - printScenarios(out, testCaseFinishedByStatus, TestStepResultStatus.AMBIGUOUS); - printScenarios(out, testCaseFinishedByStatus, TestStepResultStatus.PENDING); - printScenarios(out, testCaseFinishedByStatus, TestStepResultStatus.UNDEFINED); - } - - private void printScenarios(PrintStream out, Map> testCaseFinishedByStatus, TestStepResultStatus type) { - List scenarios = testCaseFinishedByStatus.getOrDefault(type, Collections.emptyList()); - Format format = formats.get(type.name().toLowerCase(ROOT)); - if (!scenarios.isEmpty()) { - out.println(format.text(firstLetterCapitalizedName(type) + " scenarios:")); - } - for (io.cucumber.messages.types.TestCaseFinished testCaseFinished : scenarios) { - query.findPickleBy(testCaseFinished).ifPresent(pickle -> { - String location = pickle.getUri() + query.findLocationOf(pickle).map(Location::getLine).map(line -> ":" + line).orElse(""); - out.println(location + " # " + pickle.getName()); - }); - } - if (!scenarios.isEmpty()) { - out.println(); - } - } - - private String firstLetterCapitalizedName(TestStepResultStatus status) { - String name = status.name(); - return name.charAt(0) + name.substring(1).toLowerCase(ROOT); - } - -} diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/StatsTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/StatsTest.java index 4b10145a2e..0cdb80fb8c 100755 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/StatsTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/StatsTest.java @@ -1,289 +1,305 @@ -//package io.cucumber.core.plugin; -// -//import io.cucumber.plugin.event.Location; -//import io.cucumber.plugin.event.Status; -//import io.cucumber.plugin.event.TestCase; -//import io.cucumber.plugin.event.TestStep; -//import org.junit.jupiter.api.Test; -// -//import java.io.ByteArrayOutputStream; -//import java.io.PrintStream; -//import java.net.URI; -//import java.time.Instant; -//import java.util.Collections; -//import java.util.List; -//import java.util.Locale; -//import java.util.UUID; -// -//import static java.time.Duration.ofHours; -//import static java.time.Duration.ofMillis; -//import static java.time.Duration.ofMinutes; -//import static java.time.Duration.ofSeconds; -//import static org.hamcrest.CoreMatchers.containsString; -//import static org.hamcrest.CoreMatchers.endsWith; -//import static org.hamcrest.CoreMatchers.startsWith; -//import static org.hamcrest.MatcherAssert.assertThat; -// -//class StatsTest { -// -// private static final Instant ANY_TIME = Instant.ofEpochMilli(1234567890); -// -// @Test -// void should_print_zero_scenarios_zero_steps_if_nothing_has_executed() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), startsWith(String.format( -// "0 Scenarios%n" + -// "0 Steps%n"))); -// } -// -// private Stats createMonochromeSummaryCounter() { -// Stats stats = new Stats(query, Locale.US); -// stats.setMonochrome(true); -// return stats; -// } -// -// @Test -// void should_only_print_sub_counts_if_not_zero() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.addStep(Status.PASSED); -// counter.addStep(Status.PASSED); -// counter.addStep(Status.PASSED); -// counter.addScenario(Status.PASSED, createTestCase("classpath:com/example", 42, "scenario designation")); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), startsWith(String.format( -// "1 Scenarios (1 passed)%n" + -// "3 Steps (3 passed)%n"))); -// } -// -// @Test -// void should_print_sub_counts_in_order_failed_ambiguous_skipped_pending_undefined_passed() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// addOneStepScenario(counter, Status.PASSED); -// addOneStepScenario(counter, Status.FAILED); -// addOneStepScenario(counter, Status.AMBIGUOUS); -// addOneStepScenario(counter, Status.PENDING); -// addOneStepScenario(counter, Status.UNDEFINED); -// addOneStepScenario(counter, Status.SKIPPED); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), containsString(String.format("" + -// "6 Scenarios (1 failed, 1 ambiguous, 1 skipped, 1 pending, 1 undefined, 1 passed)%n" + -// "6 Steps (1 failed, 1 ambiguous, 1 skipped, 1 pending, 1 undefined, 1 passed)%n"))); -// } -// -// private void addOneStepScenario(Stats counter, Status status) { -// counter.addStep(status); -// counter.addScenario(status, createTestCase("classpath:com/example", 14, "scenario designation")); -// } -// -// @Test -// void should_print_sub_counts_in_order_failed_ambiguous_skipped_undefined_passed_in_color() { -// Stats counter = createColorSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// addOneStepScenario(counter, Status.PASSED); -// addOneStepScenario(counter, Status.FAILED); -// addOneStepScenario(counter, Status.AMBIGUOUS); -// addOneStepScenario(counter, Status.PENDING); -// addOneStepScenario(counter, Status.UNDEFINED); -// addOneStepScenario(counter, Status.SKIPPED); -// counter.printStats(new PrintStream(baos)); -// -// String colorSubCounts = "" + -// AnsiEscapes.RED + "1 failed" + AnsiEscapes.RESET + ", " + -// AnsiEscapes.RED + "1 ambiguous" + 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(), containsString(String.format("" + -// "6 Scenarios (" + colorSubCounts + ")%n" + -// "6 Steps (" + colorSubCounts + ")%n"))); -// } -// -// private Stats createColorSummaryCounter() { -// return new Stats(query, Locale.US); -// } -// -// @Test -// void should_print_zero_m_zero_s_if_nothing_has_executed() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), endsWith(String.format( -// "0m0.000s%n"))); -// } -// -// @Test -// void should_report_the_difference_between_finish_time_and_start_time() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.setStartTime(ANY_TIME); -// counter.setFinishTime(ANY_TIME.plus(ofMillis(4))); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), endsWith(String.format( -// "0m0.004s%n"))); -// } -// -// @Test -// void should_print_minutes_seconds_and_milliseconds() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.setStartTime(ANY_TIME); -// counter.setFinishTime(ANY_TIME.plus(ofMinutes(1)).plus(ofSeconds(1)).plus(ofMillis(1))); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), endsWith(String.format( -// "1m1.001s%n"))); -// } -// -// @Test -// void should_print_minutes_instead_of_hours() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.setStartTime(ANY_TIME); -// counter.setFinishTime(ANY_TIME.plus(ofHours(1)).plus(ofMinutes(1))); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), endsWith(String.format( -// "61m0.000s%n"))); -// } -// -// @Test -// void should_use_locale_for_decimal_separator() { -// Stats counter = new Stats(query, Locale.GERMANY); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.setStartTime(ANY_TIME); -// counter.setFinishTime(ANY_TIME.plus(ofMinutes(1)).plus(ofSeconds(1)).plus(ofMillis(1))); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), endsWith(String.format("1m1,001s%n"))); -// } -// -// @Test -// void should_print_failed_ambiguous_scenarios() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.addStep(Status.FAILED); -// counter.addScenario(Status.FAILED, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); -// counter.addStep(Status.AMBIGUOUS); -// counter.addScenario(Status.AMBIGUOUS, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); -// counter.addStep(Status.UNDEFINED); -// counter.addScenario(Status.UNDEFINED, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); -// counter.addStep(Status.PENDING); -// counter.addScenario(Status.PENDING, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), startsWith(String.format("" + -// "Failed scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "Ambiguous scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "Pending scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "Undefined scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "4 Scenarios"))); -// } -// -// @Test -// void should_print_failed_ambiguous_pending_undefined_scenarios() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.addStep(Status.FAILED); -// counter.addScenario(Status.FAILED, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); -// counter.addStep(Status.AMBIGUOUS); -// counter.addScenario(Status.AMBIGUOUS, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); -// counter.addStep(Status.UNDEFINED); -// counter.addScenario(Status.UNDEFINED, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); -// counter.addStep(Status.PENDING); -// counter.addScenario(Status.PENDING, createTestCase("path/file.feature", 3, "Scenario: scenario_name")); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), startsWith(String.format("" + -// "Failed scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "Ambiguous scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "Pending scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "Undefined scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "4 Scenarios"))); -// } -// -// private static TestCase createTestCase(String uri, int line, String name) { -// return new TestCase() { -// @Override -// public Integer getLine() { -// return getLocation().getLine(); -// } -// -// @Override -// public Location getLocation() { -// return new Location(line, -1); -// } -// -// @Override -// public String getKeyword() { -// return "Scenario"; -// } -// -// @Override -// public String getName() { -// return name; -// } -// -// @Override -// public String getScenarioDesignation() { -// return null; -// } -// -// @Override -// public List getTags() { -// return Collections.emptyList(); -// } -// -// @Override -// public List getTestSteps() { -// return Collections.emptyList(); -// } -// -// @Override -// public URI getUri() { -// return URI.create(uri); -// } -// -// @Override -// public UUID getId() { -// return UUID.randomUUID(); -// } -// }; -// } -// -//} +// package io.cucumber.core.plugin; +// +// import io.cucumber.plugin.event.Location; +// import io.cucumber.plugin.event.Status; +// import io.cucumber.plugin.event.TestCase; +// import io.cucumber.plugin.event.TestStep; +// import org.junit.jupiter.api.Test; +// +// import java.io.ByteArrayOutputStream; +// import java.io.PrintStream; +// import java.net.URI; +// import java.time.Instant; +// import java.util.Collections; +// import java.util.List; +// import java.util.Locale; +// import java.util.UUID; +// +// import static java.time.Duration.ofHours; +// import static java.time.Duration.ofMillis; +// import static java.time.Duration.ofMinutes; +// import static java.time.Duration.ofSeconds; +// import static org.hamcrest.CoreMatchers.containsString; +// import static org.hamcrest.CoreMatchers.endsWith; +// import static org.hamcrest.CoreMatchers.startsWith; +// import static org.hamcrest.MatcherAssert.assertThat; +// +// class StatsTest { +// +// private static final Instant ANY_TIME = Instant.ofEpochMilli(1234567890); +// +// @Test +// void should_print_zero_scenarios_zero_steps_if_nothing_has_executed() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), startsWith(String.format( +// "0 Scenarios%n" + +// "0 Steps%n"))); +// } +// +// private Stats createMonochromeSummaryCounter() { +// Stats stats = new Stats(query, Locale.US); +// stats.setMonochrome(true); +// return stats; +// } +// +// @Test +// void should_only_print_sub_counts_if_not_zero() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.addStep(Status.PASSED); +// counter.addStep(Status.PASSED); +// counter.addStep(Status.PASSED); +// counter.addScenario(Status.PASSED, createTestCase("classpath:com/example", +// 42, "scenario designation")); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), startsWith(String.format( +// "1 Scenarios (1 passed)%n" + +// "3 Steps (3 passed)%n"))); +// } +// +// @Test +// void +// should_print_sub_counts_in_order_failed_ambiguous_skipped_pending_undefined_passed() +// { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// addOneStepScenario(counter, Status.PASSED); +// addOneStepScenario(counter, Status.FAILED); +// addOneStepScenario(counter, Status.AMBIGUOUS); +// addOneStepScenario(counter, Status.PENDING); +// addOneStepScenario(counter, Status.UNDEFINED); +// addOneStepScenario(counter, Status.SKIPPED); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), containsString(String.format("" + +// "6 Scenarios (1 failed, 1 ambiguous, 1 skipped, 1 pending, 1 undefined, 1 +// passed)%n" + +// "6 Steps (1 failed, 1 ambiguous, 1 skipped, 1 pending, 1 undefined, 1 +// passed)%n"))); +// } +// +// private void addOneStepScenario(Stats counter, Status status) { +// counter.addStep(status); +// counter.addScenario(status, createTestCase("classpath:com/example", 14, +// "scenario designation")); +// } +// +// @Test +// void +// should_print_sub_counts_in_order_failed_ambiguous_skipped_undefined_passed_in_color() +// { +// Stats counter = createColorSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// addOneStepScenario(counter, Status.PASSED); +// addOneStepScenario(counter, Status.FAILED); +// addOneStepScenario(counter, Status.AMBIGUOUS); +// addOneStepScenario(counter, Status.PENDING); +// addOneStepScenario(counter, Status.UNDEFINED); +// addOneStepScenario(counter, Status.SKIPPED); +// counter.printStats(new PrintStream(baos)); +// +// String colorSubCounts = "" + +// AnsiEscapes.RED + "1 failed" + AnsiEscapes.RESET + ", " + +// AnsiEscapes.RED + "1 ambiguous" + 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(), containsString(String.format("" + +// "6 Scenarios (" + colorSubCounts + ")%n" + +// "6 Steps (" + colorSubCounts + ")%n"))); +// } +// +// private Stats createColorSummaryCounter() { +// return new Stats(query, Locale.US); +// } +// +// @Test +// void should_print_zero_m_zero_s_if_nothing_has_executed() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), endsWith(String.format( +// "0m0.000s%n"))); +// } +// +// @Test +// void should_report_the_difference_between_finish_time_and_start_time() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.setStartTime(ANY_TIME); +// counter.setFinishTime(ANY_TIME.plus(ofMillis(4))); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), endsWith(String.format( +// "0m0.004s%n"))); +// } +// +// @Test +// void should_print_minutes_seconds_and_milliseconds() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.setStartTime(ANY_TIME); +// counter.setFinishTime(ANY_TIME.plus(ofMinutes(1)).plus(ofSeconds(1)).plus(ofMillis(1))); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), endsWith(String.format( +// "1m1.001s%n"))); +// } +// +// @Test +// void should_print_minutes_instead_of_hours() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.setStartTime(ANY_TIME); +// counter.setFinishTime(ANY_TIME.plus(ofHours(1)).plus(ofMinutes(1))); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), endsWith(String.format( +// "61m0.000s%n"))); +// } +// +// @Test +// void should_use_locale_for_decimal_separator() { +// Stats counter = new Stats(query, Locale.GERMANY); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.setStartTime(ANY_TIME); +// counter.setFinishTime(ANY_TIME.plus(ofMinutes(1)).plus(ofSeconds(1)).plus(ofMillis(1))); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), endsWith(String.format("1m1,001s%n"))); +// } +// +// @Test +// void should_print_failed_ambiguous_scenarios() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.addStep(Status.FAILED); +// counter.addScenario(Status.FAILED, createTestCase("path/file.feature", 3, +// "Scenario: scenario_name")); +// counter.addStep(Status.AMBIGUOUS); +// counter.addScenario(Status.AMBIGUOUS, createTestCase("path/file.feature", 3, +// "Scenario: scenario_name")); +// counter.addStep(Status.UNDEFINED); +// counter.addScenario(Status.UNDEFINED, createTestCase("path/file.feature", 3, +// "Scenario: scenario_name")); +// counter.addStep(Status.PENDING); +// counter.addScenario(Status.PENDING, createTestCase("path/file.feature", 3, +// "Scenario: scenario_name")); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), startsWith(String.format("" + +// "Failed scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "Ambiguous scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "Pending scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "Undefined scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "4 Scenarios"))); +// } +// +// @Test +// void should_print_failed_ambiguous_pending_undefined_scenarios() { +// Stats counter = createMonochromeSummaryCounter(); +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// counter.addStep(Status.FAILED); +// counter.addScenario(Status.FAILED, createTestCase("path/file.feature", 3, +// "Scenario: scenario_name")); +// counter.addStep(Status.AMBIGUOUS); +// counter.addScenario(Status.AMBIGUOUS, createTestCase("path/file.feature", 3, +// "Scenario: scenario_name")); +// counter.addStep(Status.UNDEFINED); +// counter.addScenario(Status.UNDEFINED, createTestCase("path/file.feature", 3, +// "Scenario: scenario_name")); +// counter.addStep(Status.PENDING); +// counter.addScenario(Status.PENDING, createTestCase("path/file.feature", 3, +// "Scenario: scenario_name")); +// counter.printStats(new PrintStream(baos)); +// +// assertThat(baos.toString(), startsWith(String.format("" + +// "Failed scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "Ambiguous scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "Pending scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "Undefined scenarios:%n" + +// "path/file.feature:3 # Scenario: scenario_name%n" + +// "%n" + +// "4 Scenarios"))); +// } +// +// private static TestCase createTestCase(String uri, int line, String name) { +// return new TestCase() { +// @Override +// public Integer getLine() { +// return getLocation().getLine(); +// } +// +// @Override +// public Location getLocation() { +// return new Location(line, -1); +// } +// +// @Override +// public String getKeyword() { +// return "Scenario"; +// } +// +// @Override +// public String getName() { +// return name; +// } +// +// @Override +// public String getScenarioDesignation() { +// return null; +// } +// +// @Override +// public List getTags() { +// return Collections.emptyList(); +// } +// +// @Override +// public List getTestSteps() { +// return Collections.emptyList(); +// } +// +// @Override +// public URI getUri() { +// return URI.create(uri); +// } +// +// @Override +// public UUID getId() { +// return UUID.randomUUID(); +// } +// }; +// } +// +// } From 152bbf55617333f8ff32d1187749dc4207ef1819 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 14 Sep 2025 18:52:00 +0200 Subject: [PATCH 6/9] Minimal test coverage --- .../core/plugin/DefaultSummaryPrinter.java | 24 +- .../plugin/DefaultSummaryPrinterTest.java | 92 +++--- .../io/cucumber/core/plugin/StatsTest.java | 305 ------------------ 3 files changed, 49 insertions(+), 372 deletions(-) delete mode 100755 cucumber-core/src/test/java/io/cucumber/core/plugin/StatsTest.java diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java index e17b8b338b..a1b665652e 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java @@ -12,7 +12,6 @@ import io.cucumber.plugin.ColorAware; import io.cucumber.plugin.ConcurrentEventListener; import io.cucumber.plugin.event.EventPublisher; -import io.cucumber.plugin.event.TestRunFinished; import io.cucumber.query.Query; import io.cucumber.query.Repository; @@ -21,7 +20,6 @@ import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.Collection; -import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; @@ -66,8 +64,10 @@ public DefaultSummaryPrinter() { @Override public void setEventPublisher(EventPublisher publisher) { - publisher.registerHandlerFor(Envelope.class, repository::update); - publisher.registerHandlerFor(TestRunFinished.class, event -> print()); + publisher.registerHandlerFor(Envelope.class, envelope -> { + repository.update(envelope); + envelope.getTestRunFinished().ifPresent(testRunFinished -> print()); + }); } private void print() { @@ -75,7 +75,6 @@ private void print() { printStats(); printErrors(); printSnippets(); - out.println(); } private void printStats() { @@ -83,9 +82,8 @@ private void printStats() { printScenarioCounts(); printStepCounts(); printDuration(); - out.println(); } - + private void printNonPassingScenarios() { Map> testCaseFinishedByStatus = query .findAllTestCaseFinished() @@ -109,7 +107,8 @@ private void printScenarios( } for (TestCaseFinished testCaseFinished : scenarios) { query.findPickleBy(testCaseFinished).ifPresent(pickle -> { - String location = pickle.getUri() + query.findLocationOf(pickle).map(Location::getLine).map(line -> ":" + line).orElse(""); + String location = pickle.getUri() + + query.findLocationOf(pickle).map(Location::getLine).map(line -> ":" + line).orElse(""); out.println(location + " # " + pickle.getName()); }); } @@ -117,7 +116,7 @@ private void printScenarios( out.println(); } } - + private void printScenarioCounts() { List allTestCaseFinished = query.findAllTestCaseFinished(); if (allTestCaseFinished.isEmpty()) { @@ -134,14 +133,13 @@ private void printScenarioCounts() { out.println(")"); } - private void printStepCounts() { List testStepsFinished = query.findAllTestStepFinished(); if (testStepsFinished.isEmpty()) { out.println("0 Steps"); return; } - + Map testStepResultStatus = testStepsFinished.stream() .collect(countTestStepResultStatusByTestStepFinished()); @@ -229,14 +227,14 @@ private void printSnippets() { private Collector> countTestStepResultStatusByTestCaseFinished() { return groupingBy(this::getTestStepResultStatusBy, counting()); } - + private TestStepResultStatus getTestStepResultStatusBy(TestCaseFinished testCaseFinished) { return query.findMostSevereTestStepResultBy(testCaseFinished) .map(TestStepResult::getStatus) // By definition .orElse(TestStepResultStatus.PASSED); } - + private static Collector> countTestStepResultStatusByTestStepFinished() { return groupingBy(DefaultSummaryPrinter::getTestStepResultStatusBy, counting()); } diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/DefaultSummaryPrinterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/DefaultSummaryPrinterTest.java index 217bb3bf18..12d5aa61a5 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/DefaultSummaryPrinterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/DefaultSummaryPrinterTest.java @@ -1,74 +1,58 @@ package io.cucumber.core.plugin; -import io.cucumber.core.eventbus.EventBus; +import io.cucumber.core.backend.StubStepDefinition; +import io.cucumber.core.feature.TestFeatureParser; +import io.cucumber.core.gherkin.Feature; +import io.cucumber.core.options.RuntimeOptionsBuilder; +import io.cucumber.core.runner.StepDurationTimeService; +import io.cucumber.core.runtime.Runtime; +import io.cucumber.core.runtime.StubBackendSupplier; +import io.cucumber.core.runtime.StubFeatureSupplier; import io.cucumber.core.runtime.TimeServiceEventBus; -import io.cucumber.plugin.event.Location; -import io.cucumber.plugin.event.Result; -import io.cucumber.plugin.event.SnippetsSuggestedEvent; -import io.cucumber.plugin.event.SnippetsSuggestedEvent.Suggestion; -import io.cucumber.plugin.event.Status; -import io.cucumber.plugin.event.TestRunFinished; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; -import java.net.URI; -import java.time.Clock; import java.time.Duration; -import java.time.ZoneId; import java.util.Locale; import java.util.UUID; +import static io.cucumber.core.plugin.Bytes.bytes; import static io.cucumber.core.plugin.IsEqualCompressingLineSeparators.equalCompressingLineSeparators; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.time.Instant.ofEpochSecond; -import static java.util.Collections.singletonList; +import static io.cucumber.core.plugin.PrettyFormatterStepDefinition.oneReference; +import static io.cucumber.core.plugin.PrettyFormatterStepDefinition.threeReference; +import static io.cucumber.core.plugin.PrettyFormatterStepDefinition.twoReference; import static org.hamcrest.MatcherAssert.assertThat; class DefaultSummaryPrinterTest { - private final ByteArrayOutputStream out = new ByteArrayOutputStream(); - private final DefaultSummaryPrinter summaryPrinter = new DefaultSummaryPrinter(out, Locale.US); - private final EventBus bus = new TimeServiceEventBus( - Clock.fixed(ofEpochSecond(0), ZoneId.of("UTC")), - UUID::randomUUID); - - @BeforeEach - void setup() { - summaryPrinter.setEventPublisher(bus); - } - @Test - void does_not_print_duplicate_snippets() { - bus.send(new SnippetsSuggestedEvent( - bus.getInstant(), - URI.create("classpath:com/example.feature"), - new Location(12, -1), - new Location(13, -1), - new Suggestion("", singletonList("snippet")))); - - bus.send(new SnippetsSuggestedEvent( - bus.getInstant(), - URI.create("classpath:com/example.feature"), - new Location(12, -1), - new Location(14, -1), - new Suggestion("", singletonList("snippet")))); - - bus.send(new TestRunFinished(bus.getInstant(), new Result(Status.PASSED, Duration.ZERO, null))); - - assertThat(new String(out.toByteArray(), UTF_8), equalCompressingLineSeparators("" + + void writesSummary() { + Feature feature = TestFeatureParser.parse("path/test.feature", "" + + "Feature: feature name\n" + + " Scenario: scenario name\n" + + " Given first step\n" + + " When second step\n" + + " Then third step\n"); + + StepDurationTimeService timeService = new StepDurationTimeService(Duration.ofMillis(1128)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Runtime.builder() + .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) + .withFeatureSupplier(new StubFeatureSupplier(feature)) + .withAdditionalPlugins(timeService, new DefaultSummaryPrinter(out, Locale.US)) + .withRuntimeOptions(new RuntimeOptionsBuilder().setMonochrome().build()) + .withBackendSupplier(new StubBackendSupplier( + new StubStepDefinition("first step", oneReference()), + new StubStepDefinition("second step", twoReference()), + new StubStepDefinition("third step", threeReference()))) + .build() + .run(); + + assertThat(out, bytes(equalCompressingLineSeparators("" + "\n" + - "0 Scenarios\n" + - "0 Steps\n" + - "0m0.000s\n" + - "\n" + - "\n" + - "You can implement missing steps with the snippets below:\n" + - "\n" + - "snippet\n" + - "\n" + - "\n")); - + "1 Scenarios (1 passed)\n" + + "3 Steps (3 passed)\n" + + "0m3.384s\n"))); } } diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/StatsTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/StatsTest.java deleted file mode 100755 index 0cdb80fb8c..0000000000 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/StatsTest.java +++ /dev/null @@ -1,305 +0,0 @@ -// package io.cucumber.core.plugin; -// -// import io.cucumber.plugin.event.Location; -// import io.cucumber.plugin.event.Status; -// import io.cucumber.plugin.event.TestCase; -// import io.cucumber.plugin.event.TestStep; -// import org.junit.jupiter.api.Test; -// -// import java.io.ByteArrayOutputStream; -// import java.io.PrintStream; -// import java.net.URI; -// import java.time.Instant; -// import java.util.Collections; -// import java.util.List; -// import java.util.Locale; -// import java.util.UUID; -// -// import static java.time.Duration.ofHours; -// import static java.time.Duration.ofMillis; -// import static java.time.Duration.ofMinutes; -// import static java.time.Duration.ofSeconds; -// import static org.hamcrest.CoreMatchers.containsString; -// import static org.hamcrest.CoreMatchers.endsWith; -// import static org.hamcrest.CoreMatchers.startsWith; -// import static org.hamcrest.MatcherAssert.assertThat; -// -// class StatsTest { -// -// private static final Instant ANY_TIME = Instant.ofEpochMilli(1234567890); -// -// @Test -// void should_print_zero_scenarios_zero_steps_if_nothing_has_executed() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), startsWith(String.format( -// "0 Scenarios%n" + -// "0 Steps%n"))); -// } -// -// private Stats createMonochromeSummaryCounter() { -// Stats stats = new Stats(query, Locale.US); -// stats.setMonochrome(true); -// return stats; -// } -// -// @Test -// void should_only_print_sub_counts_if_not_zero() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.addStep(Status.PASSED); -// counter.addStep(Status.PASSED); -// counter.addStep(Status.PASSED); -// counter.addScenario(Status.PASSED, createTestCase("classpath:com/example", -// 42, "scenario designation")); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), startsWith(String.format( -// "1 Scenarios (1 passed)%n" + -// "3 Steps (3 passed)%n"))); -// } -// -// @Test -// void -// should_print_sub_counts_in_order_failed_ambiguous_skipped_pending_undefined_passed() -// { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// addOneStepScenario(counter, Status.PASSED); -// addOneStepScenario(counter, Status.FAILED); -// addOneStepScenario(counter, Status.AMBIGUOUS); -// addOneStepScenario(counter, Status.PENDING); -// addOneStepScenario(counter, Status.UNDEFINED); -// addOneStepScenario(counter, Status.SKIPPED); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), containsString(String.format("" + -// "6 Scenarios (1 failed, 1 ambiguous, 1 skipped, 1 pending, 1 undefined, 1 -// passed)%n" + -// "6 Steps (1 failed, 1 ambiguous, 1 skipped, 1 pending, 1 undefined, 1 -// passed)%n"))); -// } -// -// private void addOneStepScenario(Stats counter, Status status) { -// counter.addStep(status); -// counter.addScenario(status, createTestCase("classpath:com/example", 14, -// "scenario designation")); -// } -// -// @Test -// void -// should_print_sub_counts_in_order_failed_ambiguous_skipped_undefined_passed_in_color() -// { -// Stats counter = createColorSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// addOneStepScenario(counter, Status.PASSED); -// addOneStepScenario(counter, Status.FAILED); -// addOneStepScenario(counter, Status.AMBIGUOUS); -// addOneStepScenario(counter, Status.PENDING); -// addOneStepScenario(counter, Status.UNDEFINED); -// addOneStepScenario(counter, Status.SKIPPED); -// counter.printStats(new PrintStream(baos)); -// -// String colorSubCounts = "" + -// AnsiEscapes.RED + "1 failed" + AnsiEscapes.RESET + ", " + -// AnsiEscapes.RED + "1 ambiguous" + 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(), containsString(String.format("" + -// "6 Scenarios (" + colorSubCounts + ")%n" + -// "6 Steps (" + colorSubCounts + ")%n"))); -// } -// -// private Stats createColorSummaryCounter() { -// return new Stats(query, Locale.US); -// } -// -// @Test -// void should_print_zero_m_zero_s_if_nothing_has_executed() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), endsWith(String.format( -// "0m0.000s%n"))); -// } -// -// @Test -// void should_report_the_difference_between_finish_time_and_start_time() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.setStartTime(ANY_TIME); -// counter.setFinishTime(ANY_TIME.plus(ofMillis(4))); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), endsWith(String.format( -// "0m0.004s%n"))); -// } -// -// @Test -// void should_print_minutes_seconds_and_milliseconds() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.setStartTime(ANY_TIME); -// counter.setFinishTime(ANY_TIME.plus(ofMinutes(1)).plus(ofSeconds(1)).plus(ofMillis(1))); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), endsWith(String.format( -// "1m1.001s%n"))); -// } -// -// @Test -// void should_print_minutes_instead_of_hours() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.setStartTime(ANY_TIME); -// counter.setFinishTime(ANY_TIME.plus(ofHours(1)).plus(ofMinutes(1))); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), endsWith(String.format( -// "61m0.000s%n"))); -// } -// -// @Test -// void should_use_locale_for_decimal_separator() { -// Stats counter = new Stats(query, Locale.GERMANY); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.setStartTime(ANY_TIME); -// counter.setFinishTime(ANY_TIME.plus(ofMinutes(1)).plus(ofSeconds(1)).plus(ofMillis(1))); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), endsWith(String.format("1m1,001s%n"))); -// } -// -// @Test -// void should_print_failed_ambiguous_scenarios() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.addStep(Status.FAILED); -// counter.addScenario(Status.FAILED, createTestCase("path/file.feature", 3, -// "Scenario: scenario_name")); -// counter.addStep(Status.AMBIGUOUS); -// counter.addScenario(Status.AMBIGUOUS, createTestCase("path/file.feature", 3, -// "Scenario: scenario_name")); -// counter.addStep(Status.UNDEFINED); -// counter.addScenario(Status.UNDEFINED, createTestCase("path/file.feature", 3, -// "Scenario: scenario_name")); -// counter.addStep(Status.PENDING); -// counter.addScenario(Status.PENDING, createTestCase("path/file.feature", 3, -// "Scenario: scenario_name")); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), startsWith(String.format("" + -// "Failed scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "Ambiguous scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "Pending scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "Undefined scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "4 Scenarios"))); -// } -// -// @Test -// void should_print_failed_ambiguous_pending_undefined_scenarios() { -// Stats counter = createMonochromeSummaryCounter(); -// ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// -// counter.addStep(Status.FAILED); -// counter.addScenario(Status.FAILED, createTestCase("path/file.feature", 3, -// "Scenario: scenario_name")); -// counter.addStep(Status.AMBIGUOUS); -// counter.addScenario(Status.AMBIGUOUS, createTestCase("path/file.feature", 3, -// "Scenario: scenario_name")); -// counter.addStep(Status.UNDEFINED); -// counter.addScenario(Status.UNDEFINED, createTestCase("path/file.feature", 3, -// "Scenario: scenario_name")); -// counter.addStep(Status.PENDING); -// counter.addScenario(Status.PENDING, createTestCase("path/file.feature", 3, -// "Scenario: scenario_name")); -// counter.printStats(new PrintStream(baos)); -// -// assertThat(baos.toString(), startsWith(String.format("" + -// "Failed scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "Ambiguous scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "Pending scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "Undefined scenarios:%n" + -// "path/file.feature:3 # Scenario: scenario_name%n" + -// "%n" + -// "4 Scenarios"))); -// } -// -// private static TestCase createTestCase(String uri, int line, String name) { -// return new TestCase() { -// @Override -// public Integer getLine() { -// return getLocation().getLine(); -// } -// -// @Override -// public Location getLocation() { -// return new Location(line, -1); -// } -// -// @Override -// public String getKeyword() { -// return "Scenario"; -// } -// -// @Override -// public String getName() { -// return name; -// } -// -// @Override -// public String getScenarioDesignation() { -// return null; -// } -// -// @Override -// public List getTags() { -// return Collections.emptyList(); -// } -// -// @Override -// public List getTestSteps() { -// return Collections.emptyList(); -// } -// -// @Override -// public URI getUri() { -// return URI.create(uri); -// } -// -// @Override -// public UUID getId() { -// return UUID.randomUUID(); -// } -// }; -// } -// -// } From b28fd0f26bc4d0cd95aacb71a0d286e372a1edbe Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 14 Sep 2025 19:07:23 +0200 Subject: [PATCH 7/9] Clean up --- .../core/plugin/DefaultSummaryPrinter.java | 34 +++++++++---------- .../plugin/DefaultSummaryPrinterTest.java | 3 +- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java index a1b665652e..b90c03b822 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java @@ -17,15 +17,14 @@ import java.io.OutputStream; import java.io.PrintStream; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; +import java.time.Duration; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -35,31 +34,26 @@ import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_SUGGESTIONS; import static java.util.Collections.emptyList; import static java.util.Locale.ROOT; -import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.stream.Collectors.counting; import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toList; public final class DefaultSummaryPrinter implements ColorAware, ConcurrentEventListener { - private static final long ONE_SECOND = SECONDS.toNanos(1); - private static final long ONE_MINUTE = 60 * ONE_SECOND; private final Repository repository = Repository.builder() .feature(INCLUDE_GHERKIN_DOCUMENTS, true) .feature(INCLUDE_SUGGESTIONS, true) .build(); private final Query query = new Query(repository); - private final PrintStream out; - private final Locale locale; private Formats formats = ansi(); public DefaultSummaryPrinter() { - this(System.out, Locale.getDefault()); + this(System.out); } - DefaultSummaryPrinter(OutputStream out, Locale locale) { + DefaultSummaryPrinter(OutputStream out) { this.out = new PrintStream(out); - this.locale = locale; } @Override @@ -175,11 +169,16 @@ private boolean printSubCount( } private void printDuration() { - query.findTestRunDuration().ifPresent(duration -> { - out.printf("%dm", (duration.toNanos() / ONE_MINUTE)); - DecimalFormat format = new DecimalFormat("0.000", new DecimalFormatSymbols(locale)); - out.println(format.format(((double) (duration.toNanos() % ONE_MINUTE) / ONE_SECOND)) + "s"); - }); + query.findTestRunDuration() + .map(DefaultSummaryPrinter::formatDuration) + .ifPresent(out::println); + } + + private static String formatDuration(Duration duration) { + long minutes = duration.toMinutes(); + long seconds = duration.minusMinutes(minutes).getSeconds(); + long milliseconds = TimeUnit.NANOSECONDS.toMillis(duration.getNano()); + return String.format("%sm%s.%ss", minutes, seconds, milliseconds); } private void printErrors() { @@ -189,7 +188,8 @@ private void printErrors() { .map(TestStepResult::getException) .filter(Optional::isPresent) .map(Optional::get) - .collect(Collectors.toList()); + .collect(toList()); + if (errors.isEmpty()) { return; } diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/DefaultSummaryPrinterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/DefaultSummaryPrinterTest.java index 12d5aa61a5..3f22f77a58 100644 --- a/cucumber-core/src/test/java/io/cucumber/core/plugin/DefaultSummaryPrinterTest.java +++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/DefaultSummaryPrinterTest.java @@ -13,7 +13,6 @@ import java.io.ByteArrayOutputStream; import java.time.Duration; -import java.util.Locale; import java.util.UUID; import static io.cucumber.core.plugin.Bytes.bytes; @@ -39,7 +38,7 @@ void writesSummary() { Runtime.builder() .withEventBus(new TimeServiceEventBus(timeService, UUID::randomUUID)) .withFeatureSupplier(new StubFeatureSupplier(feature)) - .withAdditionalPlugins(timeService, new DefaultSummaryPrinter(out, Locale.US)) + .withAdditionalPlugins(timeService, new DefaultSummaryPrinter(out)) .withRuntimeOptions(new RuntimeOptionsBuilder().setMonochrome().build()) .withBackendSupplier(new StubBackendSupplier( new StubStepDefinition("first step", oneReference()), From bf626916768bd156b329e9371255c61292e88695 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 14 Sep 2025 19:24:34 +0200 Subject: [PATCH 8/9] Clean up --- .../core/plugin/DefaultSummaryPrinter.java | 71 ++++++------------- 1 file changed, 23 insertions(+), 48 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java index b90c03b822..ceeec203db 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.StringJoiner; import java.util.concurrent.TimeUnit; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -112,60 +113,34 @@ private void printScenarios( } private void printScenarioCounts() { - List allTestCaseFinished = query.findAllTestCaseFinished(); - if (allTestCaseFinished.isEmpty()) { - out.println("0 Scenarios"); - return; - } - Map scenarioSubCounts = allTestCaseFinished - .stream() - .collect(countTestStepResultStatusByTestCaseFinished()); + out.println(formatSubCounts( + "Scenarios", + query.findAllTestCaseFinished(), + countTestStepResultStatusByTestCaseFinished())); - out.print(allTestCaseFinished.size()); - out.print(" Scenarios ("); - printSubCounts(scenarioSubCounts); - out.println(")"); } private void printStepCounts() { - List testStepsFinished = query.findAllTestStepFinished(); - if (testStepsFinished.isEmpty()) { - out.println("0 Steps"); - return; - } - - Map testStepResultStatus = testStepsFinished.stream() - .collect(countTestStepResultStatusByTestStepFinished()); - - out.print(testStepsFinished.size()); - out.print(" Steps ("); - printSubCounts(testStepResultStatus); - out.println(")"); - } - - private void printSubCounts(Map subCounts) { - boolean addComma = false; - addComma = printSubCount(out, subCounts, TestStepResultStatus.FAILED, addComma); - addComma = printSubCount(out, subCounts, TestStepResultStatus.AMBIGUOUS, addComma); - addComma = printSubCount(out, subCounts, TestStepResultStatus.SKIPPED, addComma); - addComma = printSubCount(out, subCounts, TestStepResultStatus.PENDING, addComma); - addComma = printSubCount(out, subCounts, TestStepResultStatus.UNDEFINED, addComma); - addComma = printSubCount(out, subCounts, TestStepResultStatus.PASSED, addComma); - } - - private boolean printSubCount( - PrintStream out, Map subCounts, TestStepResultStatus type, boolean addComma - ) { - long count = subCounts.getOrDefault(type, 0L); - if (count != 0) { - if (addComma) { - out.print(", "); + out.println(formatSubCounts( + "Steps", + query.findAllTestStepFinished(), + countTestStepResultStatusByTestStepFinished())); + } + + private String formatSubCounts(String itemName, List finishedItems, Collector> countTestStepResultStatusByItem) { + String countAndName = finishedItems.size() + " " + itemName; + StringJoiner joiner = new StringJoiner(",", countAndName + " (",")"); + joiner.setEmptyValue(countAndName); + Map subCounts = finishedItems.stream() + .collect(countTestStepResultStatusByItem); + for (TestStepResultStatus value : TestStepResultStatus.values()) { + long count = subCounts.getOrDefault(value, 0L); + if (count != 0) { + Format format = formats.get(value.name().toLowerCase(ROOT)); + joiner.add(format.text(count + " " + value.name().toLowerCase(ROOT))); } - Format format = formats.get(type.name().toLowerCase(ROOT)); - out.print(format.text(count + " " + type.name().toLowerCase(ROOT))); - addComma = true; } - return addComma; + return joiner.toString(); } private void printDuration() { From 8b079296fe20a1462a32fe769f3ef94b4e5063b1 Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 14 Sep 2025 19:27:09 +0200 Subject: [PATCH 9/9] Clean up --- .../io/cucumber/core/plugin/DefaultSummaryPrinter.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java index ceeec203db..bbc12787c7 100644 --- a/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java +++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java @@ -3,6 +3,7 @@ import io.cucumber.messages.types.Envelope; import io.cucumber.messages.types.Exception; import io.cucumber.messages.types.Location; +import io.cucumber.messages.types.Pickle; import io.cucumber.messages.types.Snippet; import io.cucumber.messages.types.Suggestion; import io.cucumber.messages.types.TestCaseFinished; @@ -102,9 +103,7 @@ private void printScenarios( } for (TestCaseFinished testCaseFinished : scenarios) { query.findPickleBy(testCaseFinished).ifPresent(pickle -> { - String location = pickle.getUri() - + query.findLocationOf(pickle).map(Location::getLine).map(line -> ":" + line).orElse(""); - out.println(location + " # " + pickle.getName()); + out.println(formatLocation(pickle) + " # " + pickle.getName()); }); } if (!scenarios.isEmpty()) { @@ -112,6 +111,10 @@ private void printScenarios( } } + private String formatLocation(Pickle pickle) { + return pickle.getUri() + query.findLocationOf(pickle).map(Location::getLine).map(line -> ":" + line).orElse(""); + } + private void printScenarioCounts() { out.println(formatSubCounts( "Scenarios",