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