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(); - } - }; - } - -}