1 package org.apache.maven.surefire.booter;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
23 import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
24 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
25 import org.apache.maven.surefire.report.ConsoleStream;
26 import org.apache.maven.surefire.report.ReportEntry;
27 import org.apache.maven.surefire.report.RunListener;
28 import org.apache.maven.surefire.report.SafeThrowable;
29 import org.apache.maven.surefire.report.SimpleReportEntry;
30 import org.apache.maven.surefire.report.StackTraceWriter;
31 import org.apache.maven.surefire.report.TestSetReportEntry;
32
33 import java.io.PrintStream;
34 import java.util.Map.Entry;
35
36 import static java.lang.Integer.toHexString;
37 import static java.nio.charset.Charset.defaultCharset;
38 import static org.apache.maven.surefire.util.internal.ObjectUtils.systemProps;
39 import static org.apache.maven.surefire.util.internal.ObjectUtils.useNonNull;
40 import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
41 import static org.apache.maven.surefire.util.internal.StringUtils.escapeBytesToPrintable;
42 import static org.apache.maven.surefire.util.internal.StringUtils.escapeToPrintable;
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 public class ForkingRunListener
62 implements RunListener, ConsoleLogger, ConsoleOutputReceiver, ConsoleStream
63 {
64 public static final byte BOOTERCODE_TESTSET_STARTING = (byte) '1';
65
66 public static final byte BOOTERCODE_TESTSET_COMPLETED = (byte) '2';
67
68 public static final byte BOOTERCODE_STDOUT = (byte) '3';
69
70 public static final byte BOOTERCODE_STDERR = (byte) '4';
71
72 public static final byte BOOTERCODE_TEST_STARTING = (byte) '5';
73
74 public static final byte BOOTERCODE_TEST_SUCCEEDED = (byte) '6';
75
76 public static final byte BOOTERCODE_TEST_ERROR = (byte) '7';
77
78 public static final byte BOOTERCODE_TEST_FAILED = (byte) '8';
79
80 public static final byte BOOTERCODE_TEST_SKIPPED = (byte) '9';
81
82 public static final byte BOOTERCODE_TEST_ASSUMPTIONFAILURE = (byte) 'G';
83
84
85
86
87
88 public static final byte BOOTERCODE_CONSOLE = (byte) 'H';
89
90 public static final byte BOOTERCODE_SYSPROPS = (byte) 'I';
91
92 public static final byte BOOTERCODE_NEXT_TEST = (byte) 'N';
93
94 public static final byte BOOTERCODE_STOP_ON_NEXT_TEST = (byte) 'S';
95
96
97
98
99
100 public static final byte BOOTERCODE_ERROR = (byte) 'X';
101
102 public static final byte BOOTERCODE_BYE = (byte) 'Z';
103
104
105
106
107
108 public static final byte BOOTERCODE_DEBUG = (byte) 'D';
109
110
111
112
113
114 public static final byte BOOTERCODE_WARNING = (byte) 'W';
115
116
117 private final PrintStream target;
118
119 private final int testSetChannelId;
120
121 private final boolean trimStackTraces;
122
123 private final byte[] stdOutHeader;
124
125 private final byte[] stdErrHeader;
126
127 public ForkingRunListener( PrintStream target, int testSetChannelId, boolean trimStackTraces )
128 {
129 this.target = target;
130 this.testSetChannelId = testSetChannelId;
131 this.trimStackTraces = trimStackTraces;
132 stdOutHeader = createHeader( BOOTERCODE_STDOUT, testSetChannelId );
133 stdErrHeader = createHeader( BOOTERCODE_STDERR, testSetChannelId );
134 sendProps();
135 }
136
137 @Override
138 public void testSetStarting( TestSetReportEntry report )
139 {
140 encodeAndWriteToTarget( toString( BOOTERCODE_TESTSET_STARTING, report, testSetChannelId ) );
141 }
142
143 @Override
144 public void testSetCompleted( TestSetReportEntry report )
145 {
146 encodeAndWriteToTarget( toString( BOOTERCODE_TESTSET_COMPLETED, report, testSetChannelId ) );
147 }
148
149 @Override
150 public void testStarting( ReportEntry report )
151 {
152 encodeAndWriteToTarget( toString( BOOTERCODE_TEST_STARTING, report, testSetChannelId ) );
153 }
154
155 @Override
156 public void testSucceeded( ReportEntry report )
157 {
158 encodeAndWriteToTarget( toString( BOOTERCODE_TEST_SUCCEEDED, report, testSetChannelId ) );
159 }
160
161 @Override
162 public void testAssumptionFailure( ReportEntry report )
163 {
164 encodeAndWriteToTarget( toString( BOOTERCODE_TEST_ASSUMPTIONFAILURE, report, testSetChannelId ) );
165 }
166
167 @Override
168 public void testError( ReportEntry report )
169 {
170 encodeAndWriteToTarget( toString( BOOTERCODE_TEST_ERROR, report, testSetChannelId ) );
171 }
172
173 @Override
174 public void testFailed( ReportEntry report )
175 {
176 encodeAndWriteToTarget( toString( BOOTERCODE_TEST_FAILED, report, testSetChannelId ) );
177 }
178
179 @Override
180 public void testSkipped( ReportEntry report )
181 {
182 encodeAndWriteToTarget( toString( BOOTERCODE_TEST_SKIPPED, report, testSetChannelId ) );
183 }
184
185 @Override
186 public void testExecutionSkippedByUser()
187 {
188 encodeAndWriteToTarget( toString( BOOTERCODE_STOP_ON_NEXT_TEST, new SimpleReportEntry(), testSetChannelId ) );
189 }
190
191 void sendProps()
192 {
193 for ( Entry<String, String> entry : systemProps().entrySet() )
194 {
195 String value = entry.getValue();
196 encodeAndWriteToTarget( toPropertyString( entry.getKey(), useNonNull( value, "null" ) ) );
197 }
198 }
199
200 @Override
201 public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
202 {
203 byte[] header = stdout ? stdOutHeader : stdErrHeader;
204 byte[] content =
205 new byte[buf.length * 3 + 1];
206 int i = escapeBytesToPrintable( content, 0, buf, off, len );
207 content[i++] = (byte) '\n';
208 byte[] encodeBytes = new byte[header.length + i];
209 System.arraycopy( header, 0, encodeBytes, 0, header.length );
210 System.arraycopy( content, 0, encodeBytes, header.length, i );
211
212 synchronized ( target )
213 {
214 target.write( encodeBytes, 0, encodeBytes.length );
215 target.flush();
216 if ( target.checkError() )
217 {
218
219
220 DumpErrorSingleton.getSingleton()
221 .dumpStreamText( "Unexpected IOException with stream: " + new String( buf, off, len ) );
222 }
223 }
224 }
225
226 public static byte[] createHeader( byte booterCode, int testSetChannel )
227 {
228 return encodeStringForForkCommunication( String.valueOf( (char) booterCode )
229 + ','
230 + Integer.toString( testSetChannel, 16 )
231 + ',' + defaultCharset().name()
232 + ',' );
233 }
234
235 private void log( byte bootCode, String message )
236 {
237 if ( message != null )
238 {
239 StringBuilder sb = new StringBuilder( 7 + message.length() * 5 );
240 append( sb, bootCode ); comma( sb );
241 append( sb, toHexString( testSetChannelId ) ); comma( sb );
242 escapeToPrintable( sb, message );
243
244 sb.append( '\n' );
245 encodeAndWriteToTarget( sb.toString() );
246 }
247 }
248
249 @Override
250 public void debug( String message )
251 {
252 log( BOOTERCODE_DEBUG, message );
253 }
254
255 @Override
256 public void info( String message )
257 {
258 log( BOOTERCODE_CONSOLE, message );
259 }
260
261 @Override
262 public void warning( String message )
263 {
264 log( BOOTERCODE_WARNING, message );
265 }
266
267 @Override
268 public void error( String message )
269 {
270 log( BOOTERCODE_ERROR, message );
271 }
272
273 @Override
274 public void error( String message, Throwable t )
275 {
276 error( ConsoleLoggerUtils.toString( message, t ) );
277 }
278
279 @Override
280 public void error( Throwable t )
281 {
282 error( null, t );
283 }
284
285 private void encodeAndWriteToTarget( String string )
286 {
287 byte[] encodeBytes = encodeStringForForkCommunication( string );
288 synchronized ( target )
289 {
290 target.write( encodeBytes, 0, encodeBytes.length );
291 target.flush();
292 if ( target.checkError() )
293 {
294
295
296 DumpErrorSingleton.getSingleton().dumpStreamText( "Unexpected IOException: " + string );
297 }
298 }
299 }
300
301 private String toPropertyString( String key, String value )
302 {
303 StringBuilder stringBuilder = new StringBuilder();
304
305 append( stringBuilder, BOOTERCODE_SYSPROPS ); comma( stringBuilder );
306 append( stringBuilder, toHexString( testSetChannelId ) ); comma( stringBuilder );
307
308 escapeToPrintable( stringBuilder, key );
309 comma( stringBuilder );
310 escapeToPrintable( stringBuilder, value );
311 stringBuilder.append( "\n" );
312 return stringBuilder.toString();
313 }
314
315 private String toString( byte operationCode, ReportEntry reportEntry, int testSetChannelId )
316 {
317 StringBuilder stringBuilder = new StringBuilder();
318 append( stringBuilder, operationCode ); comma( stringBuilder );
319 append( stringBuilder, toHexString( testSetChannelId ) ); comma( stringBuilder );
320
321 nullableEncoding( stringBuilder, reportEntry.getSourceName() );
322 comma( stringBuilder );
323 nullableEncoding( stringBuilder, reportEntry.getName() );
324 comma( stringBuilder );
325 nullableEncoding( stringBuilder, reportEntry.getGroup() );
326 comma( stringBuilder );
327 nullableEncoding( stringBuilder, reportEntry.getMessage() );
328 comma( stringBuilder );
329 nullableEncoding( stringBuilder, reportEntry.getElapsed() );
330 encode( stringBuilder, reportEntry.getStackTraceWriter() );
331 stringBuilder.append( "\n" );
332 return stringBuilder.toString();
333 }
334
335 private static void comma( StringBuilder stringBuilder )
336 {
337 stringBuilder.append( "," );
338 }
339
340 private ForkingRunListener append( StringBuilder stringBuilder, String message )
341 {
342 stringBuilder.append( encode( message ) );
343 return this;
344 }
345
346 private ForkingRunListener append( StringBuilder stringBuilder, byte b )
347 {
348 stringBuilder.append( (char) b );
349 return this;
350 }
351
352 private void nullableEncoding( StringBuilder stringBuilder, Integer source )
353 {
354 if ( source == null )
355 {
356 stringBuilder.append( "null" );
357 }
358 else
359 {
360 stringBuilder.append( source.toString() );
361 }
362 }
363
364 private String encode( String source )
365 {
366 return source;
367 }
368
369
370 private static void nullableEncoding( StringBuilder stringBuilder, String source )
371 {
372 if ( source == null || source.isEmpty() )
373 {
374 stringBuilder.append( "null" );
375 }
376 else
377 {
378 escapeToPrintable( stringBuilder, source );
379 }
380 }
381
382 private void encode( StringBuilder stringBuilder, StackTraceWriter stackTraceWriter )
383 {
384 encode( stringBuilder, stackTraceWriter, trimStackTraces );
385 }
386
387 public static void encode( StringBuilder stringBuilder, StackTraceWriter stackTraceWriter, boolean trimStackTraces )
388 {
389 if ( stackTraceWriter != null )
390 {
391 comma( stringBuilder );
392
393 final SafeThrowable throwable = stackTraceWriter.getThrowable();
394 if ( throwable != null )
395 {
396 String message = throwable.getLocalizedMessage();
397 nullableEncoding( stringBuilder, message );
398 }
399 comma( stringBuilder );
400 nullableEncoding( stringBuilder, stackTraceWriter.smartTrimmedStackTrace() );
401 comma( stringBuilder );
402 nullableEncoding( stringBuilder, trimStackTraces
403 ? stackTraceWriter.writeTrimmedTraceToString()
404 : stackTraceWriter.writeTraceToString() );
405 }
406 }
407
408 @Override
409 public void println( String message )
410 {
411 byte[] buf = message.getBytes();
412 println( buf, 0, buf.length );
413 }
414
415 @Override
416 public void println( byte[] buf, int off, int len )
417 {
418 writeTestOutput( buf, off, len, true );
419 }
420 }