diff --git a/CHANGELOG.md b/CHANGELOG.md
index d69f7f8bc9..6a85974ca7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,13 +22,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- [Core] Use a message based `RerunFormatter` ([#3075](https://github.com/cucumber/cucumber-jvm/pull/3075) M.P. Korstanje)
- [Core] Use a message based `TeamCityPlugin` ([#3050](https://github.com/cucumber/cucumber-jvm/pull/3050) M.P. Korstanje)
+- [Core] Use a message based `DefaultSummaryPrinter` ([#3028](https://github.com/cucumber/cucumber-jvm/pull/3028) M.P. Korstanje)
+- [Core] Use a message based `ProgressFormatter` ([#3028](https://github.com/cucumber/cucumber-jvm/pull/3028) M.P. Korstanje)
- [Core] Update dependency io.cucumber:cucumber-json-formatter to v0.2.0
- [Core] Update dependency io.cucumber:gherkin to v35.0.0
- [Core] Update dependency io.cucumber:html-formatter to v21.15.0
- [Core] Update dependency io.cucumber:junit-xml-formatter to v0.9.0
- [Core] Update dependency io.cucumber:messages to v29.0.1
-- [Core] Update dependency io.cucumber:pretty-formatter to v2.2.0
-- [Core] Update dependency io.cucumber:query to v14.0.1
+- [Core] Update dependency io.cucumber:pretty-formatter to v2.3.0
+- [Core] Update dependency io.cucumber:query to v14.3.0
- [Core] Update dependency io.cucumber:testng-xml-formatter to v0.6.0
## [7.28.2] - 2025-09-09
diff --git a/cucumber-bom/pom.xml b/cucumber-bom/pom.xml
index 660380f5d3..26b5b7d757 100644
--- a/cucumber-bom/pom.xml
+++ b/cucumber-bom/pom.xml
@@ -20,7 +20,7 @@
21.15.1
0.9.0
29.0.1
- 2.2.0
+ 2.3.0
14.3.0
6.1.2
0.1.1
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..fb734f1b3b 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,86 +1,66 @@
package io.cucumber.core.plugin;
+import io.cucumber.messages.types.Envelope;
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.prettyformatter.MessagesToSummaryWriter;
+import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
+
+import static io.cucumber.prettyformatter.Theme.cucumber;
+import static io.cucumber.prettyformatter.Theme.plain;
public final class DefaultSummaryPrinter implements ColorAware, ConcurrentEventListener {
- private final Set snippets = new LinkedHashSet<>();
- private final Stats stats;
- private final PrintStream out;
+ private final OutputStream out;
+ private MessagesToSummaryWriter writer;
public DefaultSummaryPrinter() {
- this(System.out, Locale.getDefault());
- }
-
- DefaultSummaryPrinter(OutputStream out, Locale locale) {
- this.out = new PrintStream(out);
- this.stats = new Stats(locale);
+ this(new PrintStream(System.out) {
+ @Override
+ public void close() {
+ // Don't close System.out
+ }
+ });
}
- @Override
- public void setEventPublisher(EventPublisher publisher) {
- stats.setEventPublisher(publisher);
- publisher.registerHandlerFor(SnippetsSuggestedEvent.class, this::handleSnippetsSuggestedEvent);
- publisher.registerHandlerFor(TestRunFinished.class, event -> print());
+ DefaultSummaryPrinter(OutputStream out) {
+ this.out = out;
+ this.writer = createBuilder().build(out);
}
- private void handleSnippetsSuggestedEvent(SnippetsSuggestedEvent event) {
- this.snippets.addAll(event.getSuggestion().getSnippets());
+ private static MessagesToSummaryWriter.Builder createBuilder() {
+ return MessagesToSummaryWriter.builder()
+ .theme(cucumber());
}
- private void print() {
- out.println();
- printStats();
- printErrors();
- printSnippets();
- out.println();
- }
-
- private void printStats() {
- stats.printStats(out);
- out.println();
+ @Override
+ public void setMonochrome(boolean monochrome) {
+ if (monochrome) {
+ writer = createBuilder().theme(plain()).build(out);
+ }
}
- private void printErrors() {
- List errors = stats.getErrors();
- if (errors.isEmpty()) {
- return;
- }
- out.println();
- for (Throwable error : errors) {
- error.printStackTrace(out);
- out.println();
- }
+ @Override
+ public void setEventPublisher(EventPublisher publisher) {
+ publisher.registerHandlerFor(Envelope.class, this::write);
}
- private void printSnippets() {
- if (snippets.isEmpty()) {
- return;
+ private void write(Envelope event) {
+ try {
+ writer.write(event);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
}
- out.println();
- out.println("You can implement missing steps with the snippets below:");
- out.println();
- for (String snippet : snippets) {
- out.println(snippet);
- out.println();
+ // TODO: Plugins should implement the closable interface
+ // and be closed by Cucumber
+ if (event.getTestRunFinished().isPresent()) {
+ writer.close();
}
}
- @Override
- public void setMonochrome(boolean monochrome) {
- stats.setMonochrome(monochrome);
- }
-
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/ProgressFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/ProgressFormatter.java
index b5037cd50d..0a206c494c 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/ProgressFormatter.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/ProgressFormatter.java
@@ -1,33 +1,16 @@
package io.cucumber.core.plugin;
import io.cucumber.messages.types.Envelope;
-import io.cucumber.messages.types.TestRunFinished;
-import io.cucumber.messages.types.TestStepFinished;
-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.prettyformatter.MessagesToProgressWriter;
+import java.io.IOException;
import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.EnumMap;
-import java.util.Map;
-import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_CYAN;
-import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_DEFAULT;
-import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_GREEN;
-import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_RED;
-import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_YELLOW;
-import static io.cucumber.messages.types.TestStepResultStatus.AMBIGUOUS;
-import static io.cucumber.messages.types.TestStepResultStatus.FAILED;
-import static io.cucumber.messages.types.TestStepResultStatus.PASSED;
-import static io.cucumber.messages.types.TestStepResultStatus.PENDING;
-import static io.cucumber.messages.types.TestStepResultStatus.SKIPPED;
-import static io.cucumber.messages.types.TestStepResultStatus.UNDEFINED;
-import static java.lang.System.lineSeparator;
-import static java.util.Objects.requireNonNull;
+import static io.cucumber.prettyformatter.Theme.cucumber;
+import static io.cucumber.prettyformatter.Theme.plain;
/**
* Renders a rudimentary progress bar.
@@ -37,143 +20,42 @@
*/
public final class ProgressFormatter implements ConcurrentEventListener, ColorAware {
- private static final int MAX_WIDTH = 80;
- private static final Map SYMBOLS = new EnumMap<>(TestStepResultStatus.class);
- private static final Map ESCAPES = new EnumMap<>(TestStepResultStatus.class);
- private static final Ansi RESET = Ansi.with(FOREGROUND_DEFAULT);
- static {
- SYMBOLS.put(PASSED, ".");
- SYMBOLS.put(UNDEFINED, "U");
- SYMBOLS.put(PENDING, "P");
- SYMBOLS.put(SKIPPED, "-");
- SYMBOLS.put(FAILED, "F");
- SYMBOLS.put(AMBIGUOUS, "A");
-
- ESCAPES.put(PASSED, Ansi.with(FOREGROUND_GREEN));
- ESCAPES.put(UNDEFINED, Ansi.with(FOREGROUND_YELLOW));
- ESCAPES.put(PENDING, Ansi.with(FOREGROUND_YELLOW));
- ESCAPES.put(SKIPPED, Ansi.with(FOREGROUND_CYAN));
- ESCAPES.put(FAILED, Ansi.with(FOREGROUND_RED));
- ESCAPES.put(AMBIGUOUS, Ansi.with(FOREGROUND_RED));
- }
-
- private final PrintWriter writer;
- private boolean monochrome = false;
- private int width = 0;
+ private final OutputStream out;
+ private MessagesToProgressWriter writer;
public ProgressFormatter(OutputStream out) {
- this.writer = createPrintWriter(out);
+ this.out = out;
+ this.writer = createBuilder().build(out);
}
- private static PrintWriter createPrintWriter(OutputStream out) {
- return new PrintWriter(
- new OutputStreamWriter(
- requireNonNull(out),
- StandardCharsets.UTF_8));
+ private static MessagesToProgressWriter.Builder createBuilder() {
+ return MessagesToProgressWriter.builder()
+ .theme(cucumber());
}
@Override
public void setMonochrome(boolean monochrome) {
- this.monochrome = monochrome;
+ if (monochrome) {
+ writer = createBuilder().theme(plain()).build(out);
+ }
}
@Override
public void setEventPublisher(EventPublisher publisher) {
- publisher.registerHandlerFor(Envelope.class, event -> {
- event.getTestStepFinished().ifPresent(this::handleTestStepFinished);
- event.getTestRunFinished().ifPresent(this::handleTestRunFinished);
- });
+ publisher.registerHandlerFor(Envelope.class, this::write);
}
- private void handleTestStepFinished(TestStepFinished event) {
- TestStepResultStatus status = event.getTestStepResult().getStatus();
- // Prevent tearing in output when multiple threads write to System.out
- StringBuilder buffer = new StringBuilder();
- if (!monochrome) {
- buffer.append(ESCAPES.get(status));
- }
- buffer.append(SYMBOLS.get(status));
- if (!monochrome) {
- buffer.append(RESET);
- }
- // Start a new line if at the end of this one
- if (++width % MAX_WIDTH == 0) {
- width = 0;
- buffer.append(lineSeparator());
- }
- writer.append(buffer);
- // Flush to provide immediate feedback.
- writer.flush();
- }
-
- private void handleTestRunFinished(TestRunFinished testRunFinished) {
- writer.println();
- writer.close();
- }
-
- /**
- * Represents an
- * ANSI escape
- * code in the format {@code CSI n m}.
- */
- static final class Ansi {
-
- private static final char FIRST_ESCAPE = 27;
- private static final char SECOND_ESCAPE = '[';
- private static final String END_SEQUENCE = "m";
- private final String controlSequence;
-
- /**
- * Constructs an ANSI escape code with the given attributes.
- *
- * @param attributes to include.
- * @return an ANSI escape code with the given attributes
- */
- public static Ansi with(Ansi.Attributes... attributes) {
- return new Ansi(requireNonNull(attributes));
+ private void write(Envelope event) {
+ try {
+ writer.write(event);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
}
- private Ansi(Ansi.Attributes... attributes) {
- this.controlSequence = createControlSequence(attributes);
- }
-
- private String createControlSequence(Ansi.Attributes... attributes) {
- StringBuilder a = new StringBuilder(attributes.length * 5);
-
- for (Ansi.Attributes attribute : attributes) {
- a.append(FIRST_ESCAPE).append(SECOND_ESCAPE);
- a.append(attribute.value);
- a.append(END_SEQUENCE);
- }
-
- return a.toString();
- }
-
- @Override
- public String toString() {
- return controlSequence;
- }
-
- /**
- * A select number of attributes from all the available Select
- * Graphic Rendition attributes.
- */
- enum Attributes {
-
- // https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
- FOREGROUND_RED(31),
- FOREGROUND_GREEN(32),
- FOREGROUND_YELLOW(33),
- FOREGROUND_CYAN(36),
- FOREGROUND_DEFAULT(39);
-
- private final int value;
-
- Attributes(int index) {
- this.value = index;
- }
+ // TODO: Plugins should implement the closable interface
+ // and be closed by Cucumber
+ if (event.getTestRunFinished().isPresent()) {
+ writer.close();
}
}
-
}
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 6cecc731b3..0000000000
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/Stats.java
+++ /dev/null
@@ -1,242 +0,0 @@
-package io.cucumber.core.plugin;
-
-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 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.List;
-import java.util.Locale;
-
-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 {
-
- 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 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) {
- this.locale = locale;
- }
-
- @Override
- 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) {
- out.println("0 Scenarios");
- out.println("0 Steps");
- } else {
- printScenarioCounts(out);
- printStepCounts(out);
- }
- printDuration(out);
- }
-
- private void printStepCounts(PrintStream out) {
- out.print(stepSubCounts.getTotal());
- out.print(" Steps (");
- printSubCounts(out, stepSubCounts);
- out.println(")");
- }
-
- private void printScenarioCounts(PrintStream out) {
- out.print(scenarioSubCounts.getTotal());
- out.print(" Scenarios (");
- printSubCounts(out, scenarioSubCounts);
- out.println(")");
- }
-
- private void printSubCounts(PrintStream out, SubCounts subCounts) {
- boolean addComma = false;
- addComma = printSubCount(out, subCounts.failed, 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);
- }
-
- private boolean printSubCount(PrintStream out, int count, Status type, boolean addComma) {
- 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) {
- 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");
- }
-
- 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);
- }
-
- private void printScenarios(PrintStream out, List scenarios, Status type) {
- 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());
- }
- if (!scenarios.isEmpty()) {
- out.println();
- }
- }
-
- private String firstLetterCapitalizedName(Status 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;
- }
-
- }
-
-}
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..d5aadd8142 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,57 @@
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))
+ .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" +
+ "0m 3.384s\n")));
}
}
diff --git a/cucumber-core/src/test/java/io/cucumber/core/plugin/ProgressFormatterTest.java b/cucumber-core/src/test/java/io/cucumber/core/plugin/ProgressFormatterTest.java
index 506e464c11..a2fe5a8107 100644
--- a/cucumber-core/src/test/java/io/cucumber/core/plugin/ProgressFormatterTest.java
+++ b/cucumber-core/src/test/java/io/cucumber/core/plugin/ProgressFormatterTest.java
@@ -1,54 +1,24 @@
package io.cucumber.core.plugin;
-import io.cucumber.core.backend.StubHookDefinition;
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.plugin.ProgressFormatter.Ansi;
import io.cucumber.core.runtime.Runtime;
import io.cucumber.core.runtime.StubBackendSupplier;
import io.cucumber.core.runtime.StubFeatureSupplier;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
-import java.util.Arrays;
import static io.cucumber.core.plugin.Bytes.bytes;
import static io.cucumber.core.plugin.IsEqualCompressingLineSeparators.equalCompressingLineSeparators;
-import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_CYAN;
-import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_DEFAULT;
-import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_GREEN;
-import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_RED;
-import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_YELLOW;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
import static org.hamcrest.MatcherAssert.assertThat;
class ProgressFormatterTest {
- private final Ansi GREEN = Ansi.with(FOREGROUND_GREEN);
- private final Ansi YELLOW = Ansi.with(FOREGROUND_YELLOW);
- private final Ansi RED = Ansi.with(FOREGROUND_RED);
- private final Ansi RESET = Ansi.with(FOREGROUND_DEFAULT);
- private final Ansi CYAN = Ansi.with(FOREGROUND_CYAN);
-
@Test
- void prints_empty_line_for_empty_test_run() {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- Runtime.builder()
- .withFeatureSupplier(new StubFeatureSupplier())
- .withAdditionalPlugins(new ProgressFormatter(out))
- .withBackendSupplier(new StubBackendSupplier(
- new StubStepDefinition("passed step")))
- .build()
- .run();
-
- assertThat(out, bytes(equalCompressingLineSeparators("\n")));
- }
-
- @Test
- void prints_green_dot_for_passed_step() {
+ void prints_dot_for_passed_step() {
Feature feature = TestFeatureParser.parse("classpath:path/test.feature", "" +
"Feature: feature name\n" +
" Scenario: passed scenario\n" +
@@ -58,26 +28,7 @@ void prints_green_dot_for_passed_step() {
Runtime.builder()
.withFeatureSupplier(new StubFeatureSupplier(feature))
.withAdditionalPlugins(new ProgressFormatter(out))
- .withBackendSupplier(new StubBackendSupplier(
- new StubStepDefinition("passed step")))
- .build()
- .run();
-
- assertThat(out, bytes(equalCompressingLineSeparators(GREEN + "." + RESET + "\n")));
- }
-
- @Test
- void prints_dot_for_passed_step_without_color() {
- Feature feature = TestFeatureParser.parse("classpath:path/test.feature", "" +
- "Feature: feature name\n" +
- " Scenario: passed scenario\n" +
- " Given passed step\n");
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- Runtime.builder()
.withRuntimeOptions(new RuntimeOptionsBuilder().setMonochrome().build())
- .withFeatureSupplier(new StubFeatureSupplier(feature))
- .withAdditionalPlugins(new ProgressFormatter(out))
.withBackendSupplier(new StubBackendSupplier(
new StubStepDefinition("passed step")))
.build()
@@ -86,114 +37,4 @@ void prints_dot_for_passed_step_without_color() {
assertThat(out, bytes(equalCompressingLineSeparators(".\n")));
}
- @Test
- void prints_at_most_80_characters_per_line() {
- Feature[] features = new Feature[81];
- Arrays.fill(features,
- TestFeatureParser.parse("classpath:path/test.feature", "" +
- "Feature: feature name\n" +
- " Scenario: passed scenario\n" +
- " Given passed step\n"));
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- Runtime.builder()
- .withFeatureSupplier(new StubFeatureSupplier(features))
- .withAdditionalPlugins(new ProgressFormatter(out))
- .withBackendSupplier(new StubBackendSupplier(
- new StubStepDefinition("passed step")))
- .build()
- .run();
-
- StringBuilder expected = new StringBuilder();
- for (int i = 0; i < 80; i++) {
- expected.append(GREEN).append(".").append(RESET);
- }
- expected.append("\n");
- expected.append(GREEN).append(".").append(RESET);
- expected.append("\n");
-
- assertThat(out, bytes(equalCompressingLineSeparators(expected.toString())));
- }
-
- @Test
- void print_yellow_U_for_undefined_step() {
- Feature feature = TestFeatureParser.parse("classpath:path/test.feature", "" +
- "Feature: feature name\n" +
- " Scenario: undefined scenario\n" +
- " Given undefined step\n");
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- Runtime.builder()
- .withFeatureSupplier(new StubFeatureSupplier(feature))
- .withAdditionalPlugins(new ProgressFormatter(out))
- .withBackendSupplier(new StubBackendSupplier())
- .build()
- .run();
-
- assertThat(out, bytes(equalCompressingLineSeparators(YELLOW + "U" + RESET + "\n")));
- }
-
- @Test
- void prints_green_dot_for_passed_hook() {
- Feature feature = TestFeatureParser.parse("classpath:path/test.feature", "" +
- "Feature: feature name\n" +
- " Scenario: passed scenario\n" +
- " Given passed step\n");
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- Runtime.builder()
- .withFeatureSupplier(new StubFeatureSupplier(feature))
- .withAdditionalPlugins(new ProgressFormatter(out))
- .withBackendSupplier(new StubBackendSupplier(
- singletonList(new StubHookDefinition()),
- singletonList(new StubStepDefinition("passed step")),
- emptyList()))
- .build()
- .run();
-
- assertThat(out, bytes(equalCompressingLineSeparators(GREEN + "." + RESET + GREEN + "." + RESET + "\n")));
- }
-
- @Test
- void print_red_F_for_failed_step() {
-
- Feature feature = TestFeatureParser.parse("classpath:path/test.feature", "" +
- "Feature: feature name\n" +
- " Scenario: failing scenario\n" +
- " Given failed step\n");
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- Runtime.builder()
- .withFeatureSupplier(new StubFeatureSupplier(feature))
- .withAdditionalPlugins(new ProgressFormatter(out))
- .withBackendSupplier(new StubBackendSupplier(
- new StubStepDefinition("failed step", new Exception("Boom"))))
- .build()
- .run();
-
- assertThat(out, bytes(equalCompressingLineSeparators(RED + "F" + RESET + "\n")));
- }
-
- @Test
- void print_red_F_for_failed_hook() {
- Feature feature = TestFeatureParser.parse("classpath:path/test.feature", "" +
- "Feature: feature name\n" +
- " Scenario: failed hook\n" +
- " Given passed step\n");
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- Runtime.builder()
- .withFeatureSupplier(new StubFeatureSupplier(feature))
- .withAdditionalPlugins(new ProgressFormatter(out))
- .withBackendSupplier(new StubBackendSupplier(
- singletonList(new StubHookDefinition(new RuntimeException("Boom"))),
- singletonList(new StubStepDefinition("passed step")),
- emptyList()))
- .build()
- .run();
-
- assertThat(out, bytes(
- equalCompressingLineSeparators(RED + "F" + RESET + CYAN + "-" + RESET + "\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 fe180747ba..0000000000
--- a/cucumber-core/src/test/java/io/cucumber/core/plugin/StatsTest.java
+++ /dev/null
@@ -1,289 +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(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();
- }
- };
- }
-
-}