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