Skip to content

Commit 63ad555

Browse files
authored
Use messages in summary formatter (#3028)
Switch the summary and progress plugins to messages and extract to the pretty-formatter module.
1 parent 628c022 commit 63ad555

File tree

8 files changed

+105
-948
lines changed

8 files changed

+105
-948
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
### Changed
2323
- [Core] Use a message based `RerunFormatter` ([#3075](https://github.com/cucumber/cucumber-jvm/pull/3075) M.P. Korstanje)
2424
- [Core] Use a message based `TeamCityPlugin` ([#3050](https://github.com/cucumber/cucumber-jvm/pull/3050) M.P. Korstanje)
25+
- [Core] Use a message based `DefaultSummaryPrinter` ([#3028](https://github.com/cucumber/cucumber-jvm/pull/3028) M.P. Korstanje)
26+
- [Core] Use a message based `ProgressFormatter` ([#3028](https://github.com/cucumber/cucumber-jvm/pull/3028) M.P. Korstanje)
2527
- [Core] Update dependency io.cucumber:cucumber-json-formatter to v0.2.0
2628
- [Core] Update dependency io.cucumber:gherkin to v35.0.0
2729
- [Core] Update dependency io.cucumber:html-formatter to v21.15.0
2830
- [Core] Update dependency io.cucumber:junit-xml-formatter to v0.9.0
2931
- [Core] Update dependency io.cucumber:messages to v29.0.1
30-
- [Core] Update dependency io.cucumber:pretty-formatter to v2.2.0
31-
- [Core] Update dependency io.cucumber:query to v14.0.1
32+
- [Core] Update dependency io.cucumber:pretty-formatter to v2.3.0
33+
- [Core] Update dependency io.cucumber:query to v14.3.0
3234
- [Core] Update dependency io.cucumber:testng-xml-formatter to v0.6.0
3335

3436
## [7.28.2] - 2025-09-09

cucumber-bom/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<html-formatter.version>21.15.1</html-formatter.version>
2121
<junit-xml-formatter.version>0.9.0</junit-xml-formatter.version>
2222
<messages.version>29.0.1</messages.version>
23-
<pretty-formatter.version>2.2.0</pretty-formatter.version>
23+
<pretty-formatter.version>2.3.0</pretty-formatter.version>
2424
<query.version>14.3.0</query.version>
2525
<tag-expressions.version>6.1.2</tag-expressions.version>
2626
<teamcity-formatter.version>0.1.1</teamcity-formatter.version>
Lines changed: 37 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,66 @@
11
package io.cucumber.core.plugin;
22

3+
import io.cucumber.messages.types.Envelope;
34
import io.cucumber.plugin.ColorAware;
45
import io.cucumber.plugin.ConcurrentEventListener;
56
import io.cucumber.plugin.event.EventPublisher;
6-
import io.cucumber.plugin.event.SnippetsSuggestedEvent;
7-
import io.cucumber.plugin.event.TestRunFinished;
7+
import io.cucumber.prettyformatter.MessagesToSummaryWriter;
88

9+
import java.io.IOException;
910
import java.io.OutputStream;
1011
import java.io.PrintStream;
11-
import java.util.LinkedHashSet;
12-
import java.util.List;
13-
import java.util.Locale;
14-
import java.util.Set;
12+
13+
import static io.cucumber.prettyformatter.Theme.cucumber;
14+
import static io.cucumber.prettyformatter.Theme.plain;
1515

1616
public final class DefaultSummaryPrinter implements ColorAware, ConcurrentEventListener {
1717

18-
private final Set<String> snippets = new LinkedHashSet<>();
19-
private final Stats stats;
20-
private final PrintStream out;
18+
private final OutputStream out;
19+
private MessagesToSummaryWriter writer;
2120

2221
public DefaultSummaryPrinter() {
23-
this(System.out, Locale.getDefault());
24-
}
25-
26-
DefaultSummaryPrinter(OutputStream out, Locale locale) {
27-
this.out = new PrintStream(out);
28-
this.stats = new Stats(locale);
22+
this(new PrintStream(System.out) {
23+
@Override
24+
public void close() {
25+
// Don't close System.out
26+
}
27+
});
2928
}
3029

31-
@Override
32-
public void setEventPublisher(EventPublisher publisher) {
33-
stats.setEventPublisher(publisher);
34-
publisher.registerHandlerFor(SnippetsSuggestedEvent.class, this::handleSnippetsSuggestedEvent);
35-
publisher.registerHandlerFor(TestRunFinished.class, event -> print());
30+
DefaultSummaryPrinter(OutputStream out) {
31+
this.out = out;
32+
this.writer = createBuilder().build(out);
3633
}
3734

38-
private void handleSnippetsSuggestedEvent(SnippetsSuggestedEvent event) {
39-
this.snippets.addAll(event.getSuggestion().getSnippets());
35+
private static MessagesToSummaryWriter.Builder createBuilder() {
36+
return MessagesToSummaryWriter.builder()
37+
.theme(cucumber());
4038
}
4139

42-
private void print() {
43-
out.println();
44-
printStats();
45-
printErrors();
46-
printSnippets();
47-
out.println();
48-
}
49-
50-
private void printStats() {
51-
stats.printStats(out);
52-
out.println();
40+
@Override
41+
public void setMonochrome(boolean monochrome) {
42+
if (monochrome) {
43+
writer = createBuilder().theme(plain()).build(out);
44+
}
5345
}
5446

55-
private void printErrors() {
56-
List<Throwable> errors = stats.getErrors();
57-
if (errors.isEmpty()) {
58-
return;
59-
}
60-
out.println();
61-
for (Throwable error : errors) {
62-
error.printStackTrace(out);
63-
out.println();
64-
}
47+
@Override
48+
public void setEventPublisher(EventPublisher publisher) {
49+
publisher.registerHandlerFor(Envelope.class, this::write);
6550
}
6651

67-
private void printSnippets() {
68-
if (snippets.isEmpty()) {
69-
return;
52+
private void write(Envelope event) {
53+
try {
54+
writer.write(event);
55+
} catch (IOException e) {
56+
throw new IllegalStateException(e);
7057
}
7158

72-
out.println();
73-
out.println("You can implement missing steps with the snippets below:");
74-
out.println();
75-
for (String snippet : snippets) {
76-
out.println(snippet);
77-
out.println();
59+
// TODO: Plugins should implement the closable interface
60+
// and be closed by Cucumber
61+
if (event.getTestRunFinished().isPresent()) {
62+
writer.close();
7863
}
7964
}
8065

81-
@Override
82-
public void setMonochrome(boolean monochrome) {
83-
stats.setMonochrome(monochrome);
84-
}
85-
8666
}
Lines changed: 24 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,16 @@
11
package io.cucumber.core.plugin;
22

33
import io.cucumber.messages.types.Envelope;
4-
import io.cucumber.messages.types.TestRunFinished;
5-
import io.cucumber.messages.types.TestStepFinished;
6-
import io.cucumber.messages.types.TestStepResultStatus;
74
import io.cucumber.plugin.ColorAware;
85
import io.cucumber.plugin.ConcurrentEventListener;
96
import io.cucumber.plugin.event.EventPublisher;
7+
import io.cucumber.prettyformatter.MessagesToProgressWriter;
108

9+
import java.io.IOException;
1110
import java.io.OutputStream;
12-
import java.io.OutputStreamWriter;
13-
import java.io.PrintWriter;
14-
import java.nio.charset.StandardCharsets;
15-
import java.util.EnumMap;
16-
import java.util.Map;
1711

18-
import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_CYAN;
19-
import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_DEFAULT;
20-
import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_GREEN;
21-
import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_RED;
22-
import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_YELLOW;
23-
import static io.cucumber.messages.types.TestStepResultStatus.AMBIGUOUS;
24-
import static io.cucumber.messages.types.TestStepResultStatus.FAILED;
25-
import static io.cucumber.messages.types.TestStepResultStatus.PASSED;
26-
import static io.cucumber.messages.types.TestStepResultStatus.PENDING;
27-
import static io.cucumber.messages.types.TestStepResultStatus.SKIPPED;
28-
import static io.cucumber.messages.types.TestStepResultStatus.UNDEFINED;
29-
import static java.lang.System.lineSeparator;
30-
import static java.util.Objects.requireNonNull;
12+
import static io.cucumber.prettyformatter.Theme.cucumber;
13+
import static io.cucumber.prettyformatter.Theme.plain;
3114

3215
/**
3316
* Renders a rudimentary progress bar.
@@ -37,143 +20,42 @@
3720
*/
3821
public final class ProgressFormatter implements ConcurrentEventListener, ColorAware {
3922

40-
private static final int MAX_WIDTH = 80;
41-
private static final Map<TestStepResultStatus, String> SYMBOLS = new EnumMap<>(TestStepResultStatus.class);
42-
private static final Map<TestStepResultStatus, Ansi> ESCAPES = new EnumMap<>(TestStepResultStatus.class);
43-
private static final Ansi RESET = Ansi.with(FOREGROUND_DEFAULT);
44-
static {
45-
SYMBOLS.put(PASSED, ".");
46-
SYMBOLS.put(UNDEFINED, "U");
47-
SYMBOLS.put(PENDING, "P");
48-
SYMBOLS.put(SKIPPED, "-");
49-
SYMBOLS.put(FAILED, "F");
50-
SYMBOLS.put(AMBIGUOUS, "A");
51-
52-
ESCAPES.put(PASSED, Ansi.with(FOREGROUND_GREEN));
53-
ESCAPES.put(UNDEFINED, Ansi.with(FOREGROUND_YELLOW));
54-
ESCAPES.put(PENDING, Ansi.with(FOREGROUND_YELLOW));
55-
ESCAPES.put(SKIPPED, Ansi.with(FOREGROUND_CYAN));
56-
ESCAPES.put(FAILED, Ansi.with(FOREGROUND_RED));
57-
ESCAPES.put(AMBIGUOUS, Ansi.with(FOREGROUND_RED));
58-
}
59-
60-
private final PrintWriter writer;
61-
private boolean monochrome = false;
62-
private int width = 0;
23+
private final OutputStream out;
24+
private MessagesToProgressWriter writer;
6325

6426
public ProgressFormatter(OutputStream out) {
65-
this.writer = createPrintWriter(out);
27+
this.out = out;
28+
this.writer = createBuilder().build(out);
6629
}
6730

68-
private static PrintWriter createPrintWriter(OutputStream out) {
69-
return new PrintWriter(
70-
new OutputStreamWriter(
71-
requireNonNull(out),
72-
StandardCharsets.UTF_8));
31+
private static MessagesToProgressWriter.Builder createBuilder() {
32+
return MessagesToProgressWriter.builder()
33+
.theme(cucumber());
7334
}
7435

7536
@Override
7637
public void setMonochrome(boolean monochrome) {
77-
this.monochrome = monochrome;
38+
if (monochrome) {
39+
writer = createBuilder().theme(plain()).build(out);
40+
}
7841
}
7942

8043
@Override
8144
public void setEventPublisher(EventPublisher publisher) {
82-
publisher.registerHandlerFor(Envelope.class, event -> {
83-
event.getTestStepFinished().ifPresent(this::handleTestStepFinished);
84-
event.getTestRunFinished().ifPresent(this::handleTestRunFinished);
85-
});
45+
publisher.registerHandlerFor(Envelope.class, this::write);
8646
}
8747

88-
private void handleTestStepFinished(TestStepFinished event) {
89-
TestStepResultStatus status = event.getTestStepResult().getStatus();
90-
// Prevent tearing in output when multiple threads write to System.out
91-
StringBuilder buffer = new StringBuilder();
92-
if (!monochrome) {
93-
buffer.append(ESCAPES.get(status));
94-
}
95-
buffer.append(SYMBOLS.get(status));
96-
if (!monochrome) {
97-
buffer.append(RESET);
98-
}
99-
// Start a new line if at the end of this one
100-
if (++width % MAX_WIDTH == 0) {
101-
width = 0;
102-
buffer.append(lineSeparator());
103-
}
104-
writer.append(buffer);
105-
// Flush to provide immediate feedback.
106-
writer.flush();
107-
}
108-
109-
private void handleTestRunFinished(TestRunFinished testRunFinished) {
110-
writer.println();
111-
writer.close();
112-
}
113-
114-
/**
115-
* Represents an
116-
* <a href="https://en.wikipedia.org/wiki/ANSI_escape_code">ANSI escape
117-
* code</a> in the format {@code CSI n m}.
118-
*/
119-
static final class Ansi {
120-
121-
private static final char FIRST_ESCAPE = 27;
122-
private static final char SECOND_ESCAPE = '[';
123-
private static final String END_SEQUENCE = "m";
124-
private final String controlSequence;
125-
126-
/**
127-
* Constructs an ANSI escape code with the given attributes.
128-
*
129-
* @param attributes to include.
130-
* @return an ANSI escape code with the given attributes
131-
*/
132-
public static Ansi with(Ansi.Attributes... attributes) {
133-
return new Ansi(requireNonNull(attributes));
48+
private void write(Envelope event) {
49+
try {
50+
writer.write(event);
51+
} catch (IOException e) {
52+
throw new IllegalStateException(e);
13453
}
13554

136-
private Ansi(Ansi.Attributes... attributes) {
137-
this.controlSequence = createControlSequence(attributes);
138-
}
139-
140-
private String createControlSequence(Ansi.Attributes... attributes) {
141-
StringBuilder a = new StringBuilder(attributes.length * 5);
142-
143-
for (Ansi.Attributes attribute : attributes) {
144-
a.append(FIRST_ESCAPE).append(SECOND_ESCAPE);
145-
a.append(attribute.value);
146-
a.append(END_SEQUENCE);
147-
}
148-
149-
return a.toString();
150-
}
151-
152-
@Override
153-
public String toString() {
154-
return controlSequence;
155-
}
156-
157-
/**
158-
* A select number of attributes from all the available <a
159-
* href=https://en.wikipedia.org/wiki/ANSI_escape_code#Select_Graphic_Rendition_parameters>Select
160-
* Graphic Rendition attributes</a>.
161-
*/
162-
enum Attributes {
163-
164-
// https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
165-
FOREGROUND_RED(31),
166-
FOREGROUND_GREEN(32),
167-
FOREGROUND_YELLOW(33),
168-
FOREGROUND_CYAN(36),
169-
FOREGROUND_DEFAULT(39);
170-
171-
private final int value;
172-
173-
Attributes(int index) {
174-
this.value = index;
175-
}
55+
// TODO: Plugins should implement the closable interface
56+
// and be closed by Cucumber
57+
if (event.getTestRunFinished().isPresent()) {
58+
writer.close();
17659
}
17760
}
178-
17961
}

0 commit comments

Comments
 (0)