1
1
package io .cucumber .core .plugin ;
2
2
3
+ 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 ;
3
7
import io .cucumber .plugin .ColorAware ;
4
8
import io .cucumber .plugin .ConcurrentEventListener ;
5
9
import io .cucumber .plugin .event .EventPublisher ;
6
- import io .cucumber .plugin .event .PickleStepTestStep ;
7
- import io .cucumber .plugin .event .Status ;
8
- import io .cucumber .plugin .event .TestRunFinished ;
9
- import io .cucumber .plugin .event .TestStepFinished ;
10
10
11
11
import java .io .OutputStream ;
12
- import java .util .HashMap ;
12
+ import java .io .OutputStreamWriter ;
13
+ import java .io .PrintWriter ;
14
+ import java .nio .charset .StandardCharsets ;
15
+ import java .util .EnumMap ;
13
16
import java .util .Map ;
14
17
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 ;
31
+
32
+ /**
33
+ * Renders a rudimentary progress bar.
34
+ * <p>
35
+ * Each character in the bar represents either a step or hook. The status of
36
+ * that step or hook is indicated by the character and its color.
37
+ */
15
38
public final class ProgressFormatter implements ConcurrentEventListener , ColorAware {
16
39
17
- private static final Map <Status , Character > CHARS = new HashMap <Status , Character >() {
18
- {
19
- put (Status .PASSED , '.' );
20
- put (Status .UNDEFINED , 'U' );
21
- put (Status .PENDING , 'P' );
22
- put (Status .SKIPPED , '-' );
23
- put (Status .FAILED , 'F' );
24
- put (Status .AMBIGUOUS , 'A' );
25
- }
26
- };
27
- private static final Map <Status , AnsiEscapes > ANSI_ESCAPES = new HashMap <Status , AnsiEscapes >() {
28
- {
29
- put (Status .PASSED , AnsiEscapes .GREEN );
30
- put (Status .UNDEFINED , AnsiEscapes .YELLOW );
31
- put (Status .PENDING , AnsiEscapes .YELLOW );
32
- put (Status .SKIPPED , AnsiEscapes .CYAN );
33
- put (Status .FAILED , AnsiEscapes .RED );
34
- put (Status .AMBIGUOUS , AnsiEscapes .RED );
35
- }
36
- };
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" );
37
51
38
- private final UTF8PrintWriter out ;
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 ;
39
61
private boolean monochrome = false ;
62
+ private int width = 0 ;
40
63
41
64
public ProgressFormatter (OutputStream out ) {
42
- this .out = new UTF8PrintWriter (out );
65
+ this .writer = createPrintWriter (out );
66
+ }
67
+
68
+ private static PrintWriter createPrintWriter (OutputStream out ) {
69
+ return new PrintWriter (
70
+ new OutputStreamWriter (
71
+ requireNonNull (out ),
72
+ StandardCharsets .UTF_8 ));
43
73
}
44
74
45
75
@ Override
@@ -49,32 +79,101 @@ public void setMonochrome(boolean monochrome) {
49
79
50
80
@ Override
51
81
public void setEventPublisher (EventPublisher publisher ) {
52
- publisher .registerHandlerFor (TestStepFinished .class , this ::handleTestStepFinished );
53
- publisher .registerHandlerFor (TestRunFinished .class , this ::handleTestRunFinished );
82
+ publisher .registerHandlerFor (Envelope .class , event -> {
83
+ event .getTestStepFinished ().ifPresent (this ::handleTestStepFinished );
84
+ event .getTestRunFinished ().ifPresent (this ::handleTestRunFinished );
85
+ });
54
86
}
55
87
56
88
private void handleTestStepFinished (TestStepFinished event ) {
57
- boolean isTestStep = event .getTestStep () instanceof PickleStepTestStep ;
58
- boolean isFailedHookOrTestStep = event .getResult ().getStatus ().is (Status .FAILED );
59
- if (!(isTestStep || isFailedHookOrTestStep )) {
60
- return ;
61
- }
89
+ TestStepResultStatus status = event .getTestStepResult ().getStatus ();
62
90
// Prevent tearing in output when multiple threads write to System.out
63
91
StringBuilder buffer = new StringBuilder ();
64
92
if (!monochrome ) {
65
- ANSI_ESCAPES . get ( event . getResult (). getStatus ()). appendTo ( buffer );
93
+ buffer . append ( ESCAPES . get ( status ) );
66
94
}
67
- buffer .append (CHARS .get (event . getResult (). getStatus () ));
95
+ buffer .append (SYMBOLS .get (status ));
68
96
if (!monochrome ) {
69
- AnsiEscapes .RESET .appendTo (buffer );
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 ());
70
103
}
71
- out .append (buffer );
72
- out .flush ();
104
+ writer .append (buffer );
105
+ // Flush to provide immediate feedback.
106
+ writer .flush ();
73
107
}
74
108
75
109
private void handleTestRunFinished (TestRunFinished testRunFinished ) {
76
- out .println ();
77
- out .close ();
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 ));
134
+ }
135
+
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
+ }
176
+ }
78
177
}
79
178
80
179
}
0 commit comments