1 package org.apache.maven.surefire.booter.spi;
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.ConsoleLoggerUtils;
23 import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
24 import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
25 import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
26 import org.apache.maven.surefire.api.report.ReportEntry;
27 import org.apache.maven.surefire.api.report.RunMode;
28 import org.apache.maven.surefire.api.report.SafeThrowable;
29 import org.apache.maven.surefire.api.report.StackTraceWriter;
30 import org.apache.maven.surefire.shared.codec.binary.Base64;
31 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
32
33 import javax.annotation.Nonnull;
34 import java.io.IOException;
35 import java.nio.ByteBuffer;
36 import java.nio.channels.ClosedChannelException;
37 import java.nio.charset.Charset;
38 import java.util.Map;
39 import java.util.Map.Entry;
40 import java.util.concurrent.atomic.AtomicBoolean;
41
42 import static java.nio.charset.StandardCharsets.US_ASCII;
43 import static java.nio.charset.StandardCharsets.UTF_8;
44 import static java.util.Objects.requireNonNull;
45 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_BYE;
46 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG;
47 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_ERROR;
48 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_INFO;
49 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING;
50 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR;
51 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_NEXT_TEST;
52 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR;
53 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE;
54 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
55 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE;
56 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STOP_ON_NEXT_TEST;
57 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
58 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED;
59 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_STARTING;
60 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
61 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ERROR;
62 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_FAILED;
63 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED;
64 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
65 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
66 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.MAGIC_NUMBER;
67 import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
68 import static org.apache.maven.surefire.api.report.RunMode.RERUN_TEST_AFTER_FAILURE;
69
70
71
72
73
74
75
76
77 public class LegacyMasterProcessChannelEncoder implements MasterProcessChannelEncoder
78 {
79 private static final Base64 BASE64 = new Base64();
80 private static final Charset STREAM_ENCODING = US_ASCII;
81 private static final Charset STRING_ENCODING = UTF_8;
82
83 private final WritableBufferedByteChannel out;
84 private final RunMode runMode;
85 private final AtomicBoolean trouble = new AtomicBoolean();
86 private volatile boolean onExit;
87
88 public LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out )
89 {
90 this( out, NORMAL_RUN );
91 }
92
93 protected LegacyMasterProcessChannelEncoder( @Nonnull WritableBufferedByteChannel out, @Nonnull RunMode runMode )
94 {
95 this.out = requireNonNull( out );
96 this.runMode = requireNonNull( runMode );
97 }
98
99 @Override
100 public MasterProcessChannelEncoder asRerunMode()
101 {
102 return new LegacyMasterProcessChannelEncoder( out, RERUN_TEST_AFTER_FAILURE );
103 }
104
105 @Override
106 public MasterProcessChannelEncoder asNormalMode()
107 {
108 return new LegacyMasterProcessChannelEncoder( out, NORMAL_RUN );
109 }
110
111 @Override
112 public boolean checkError()
113 {
114 return trouble.get();
115 }
116
117 @Override
118 public void onJvmExit()
119 {
120 onExit = true;
121 encodeAndPrintEvent( new StringBuilder( "\n" ), true );
122 }
123
124 @Override
125 public void sendSystemProperties( Map<String, String> sysProps )
126 {
127 for ( Entry<String, String> entry : sysProps.entrySet() )
128 {
129 String key = entry.getKey();
130 String value = entry.getValue();
131 StringBuilder event = encode( BOOTERCODE_SYSPROPS, runMode, key, value );
132 encodeAndPrintEvent( event, false );
133 }
134 }
135
136 @Override
137 public void testSetStarting( ReportEntry reportEntry, boolean trimStackTraces )
138 {
139 encode( BOOTERCODE_TESTSET_STARTING, runMode, reportEntry, trimStackTraces, true );
140 }
141
142 @Override
143 public void testSetCompleted( ReportEntry reportEntry, boolean trimStackTraces )
144 {
145 encode( BOOTERCODE_TESTSET_COMPLETED, runMode, reportEntry, trimStackTraces, true );
146 }
147
148 @Override
149 public void testStarting( ReportEntry reportEntry, boolean trimStackTraces )
150 {
151 encode( BOOTERCODE_TEST_STARTING, runMode, reportEntry, trimStackTraces, true );
152 }
153
154 @Override
155 public void testSucceeded( ReportEntry reportEntry, boolean trimStackTraces )
156 {
157 encode( BOOTERCODE_TEST_SUCCEEDED, runMode, reportEntry, trimStackTraces, true );
158 }
159
160 @Override
161 public void testFailed( ReportEntry reportEntry, boolean trimStackTraces )
162 {
163 encode( BOOTERCODE_TEST_FAILED, runMode, reportEntry, trimStackTraces, true );
164 }
165
166 @Override
167 public void testSkipped( ReportEntry reportEntry, boolean trimStackTraces )
168 {
169 encode( BOOTERCODE_TEST_SKIPPED, runMode, reportEntry, trimStackTraces, true );
170 }
171
172 @Override
173 public void testError( ReportEntry reportEntry, boolean trimStackTraces )
174 {
175 encode( BOOTERCODE_TEST_ERROR, runMode, reportEntry, trimStackTraces, true );
176 }
177
178 @Override
179 public void testAssumptionFailure( ReportEntry reportEntry, boolean trimStackTraces )
180 {
181 encode( BOOTERCODE_TEST_ASSUMPTIONFAILURE, runMode, reportEntry, trimStackTraces, true );
182 }
183
184 @Override
185 public void stdOut( String msg, boolean newLine )
186 {
187 ForkedProcessEventType event = newLine ? BOOTERCODE_STDOUT_NEW_LINE : BOOTERCODE_STDOUT;
188 setOutErr( event.getOpcode(), msg );
189 }
190
191 @Override
192 public void stdErr( String msg, boolean newLine )
193 {
194 ForkedProcessEventType event = newLine ? BOOTERCODE_STDERR_NEW_LINE : BOOTERCODE_STDERR;
195 setOutErr( event.getOpcode(), msg );
196 }
197
198 private void setOutErr( String eventType, String message )
199 {
200 String base64Message = toBase64( message );
201 StringBuilder event = encodeMessage( eventType, runMode.geRunName(), base64Message );
202 encodeAndPrintEvent( event, false );
203 }
204
205 @Override
206 public void consoleInfoLog( String msg )
207 {
208 StringBuilder event = print( BOOTERCODE_CONSOLE_INFO.getOpcode(), msg );
209 encodeAndPrintEvent( event, true );
210 }
211
212 @Override
213 public void consoleErrorLog( String msg )
214 {
215 StringBuilder encoded = encodeHeader( BOOTERCODE_CONSOLE_ERROR.getOpcode(), null );
216 encode( encoded, msg, null, null );
217 encodeAndPrintEvent( encoded, true );
218 }
219
220 @Override
221 public void consoleErrorLog( Throwable t )
222 {
223 consoleErrorLog( t.getLocalizedMessage(), t );
224 }
225
226 @Override
227 public void consoleErrorLog( String msg, Throwable t )
228 {
229 StringBuilder encoded = encodeHeader( BOOTERCODE_CONSOLE_ERROR.getOpcode(), null );
230 encode( encoded, msg, null, ConsoleLoggerUtils.toString( t ) );
231 encodeAndPrintEvent( encoded, true );
232 }
233
234 @Override
235 public void consoleErrorLog( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
236 {
237 error( stackTraceWriter, trimStackTraces, BOOTERCODE_CONSOLE_ERROR, true );
238 }
239
240 @Override
241 public void consoleDebugLog( String msg )
242 {
243 StringBuilder event = print( BOOTERCODE_CONSOLE_DEBUG.getOpcode(), msg );
244 encodeAndPrintEvent( event, true );
245 }
246
247 @Override
248 public void consoleWarningLog( String msg )
249 {
250 StringBuilder event = print( BOOTERCODE_CONSOLE_WARNING.getOpcode(), msg );
251 encodeAndPrintEvent( event, true );
252 }
253
254 @Override
255 public void bye()
256 {
257 encodeOpcode( BOOTERCODE_BYE, true );
258 }
259
260 @Override
261 public void stopOnNextTest()
262 {
263 encodeOpcode( BOOTERCODE_STOP_ON_NEXT_TEST, true );
264 }
265
266 @Override
267 public void acquireNextTest()
268 {
269 encodeOpcode( BOOTERCODE_NEXT_TEST, true );
270 }
271
272 @Override
273 public void sendExitError( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
274 {
275 error( stackTraceWriter, trimStackTraces, BOOTERCODE_JVM_EXIT_ERROR, true );
276 }
277
278 private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEventType event,
279 @SuppressWarnings( "SameParameterValue" ) boolean sendImmediately )
280 {
281 StringBuilder encoded = encodeHeader( event.getOpcode(), null );
282 encode( encoded, stackTraceWriter, trimStackTraces );
283 encodeAndPrintEvent( encoded, sendImmediately );
284 }
285
286 private void encode( ForkedProcessEventType operation, RunMode runMode, ReportEntry reportEntry,
287 boolean trimStackTraces, @SuppressWarnings( "SameParameterValue" ) boolean sendImmediately )
288 {
289 StringBuilder event = encode( operation.getOpcode(), runMode.geRunName(), reportEntry, trimStackTraces );
290 encodeAndPrintEvent( event, sendImmediately );
291 }
292
293 private void encodeOpcode( ForkedProcessEventType operation, boolean sendImmediately )
294 {
295 StringBuilder event = encodeOpcode( operation.getOpcode(), null );
296 encodeAndPrintEvent( event, sendImmediately );
297 }
298
299 private void encodeAndPrintEvent( StringBuilder event, boolean sendImmediately )
300 {
301 try
302 {
303
304 Thread.interrupted();
305
306 byte[] array = event.append( '\n' )
307 .toString()
308 .getBytes( STREAM_ENCODING );
309
310 ByteBuffer bb = ByteBuffer.wrap( array );
311
312 if ( sendImmediately )
313 {
314 out.write( bb );
315 }
316 else
317 {
318 out.writeBuffered( bb );
319 }
320 }
321 catch ( ClosedChannelException e )
322 {
323 if ( !onExit )
324 {
325 DumpErrorSingleton.getSingleton()
326 .dumpException( e, "Channel closed while writing the event '" + event + "'." );
327 }
328 }
329 catch ( IOException e )
330 {
331 if ( trouble.compareAndSet( false, true ) )
332 {
333 DumpErrorSingleton.getSingleton()
334 .dumpException( e );
335 }
336 }
337 }
338
339 static StringBuilder encode( ForkedProcessEventType operation, RunMode runMode, String... args )
340 {
341 StringBuilder encodedTo = encodeHeader( operation.getOpcode(), runMode.geRunName() );
342
343 for ( int i = 0; i < args.length; )
344 {
345 String arg = args[i++];
346 encodedTo.append( toBase64( arg ) )
347 .append( ':' );
348 }
349 return encodedTo;
350 }
351
352 static void encode( StringBuilder encoded, StackTraceWriter stw, boolean trimStackTraces )
353 {
354 SafeThrowable throwable = stw == null ? null : stw.getThrowable();
355 String message = throwable == null ? null : throwable.getLocalizedMessage();
356 String smartStackTrace = stw == null ? null : stw.smartTrimmedStackTrace();
357 String stackTrace = stw == null ? null : toStackTrace( stw, trimStackTraces );
358 encode( encoded, message, smartStackTrace, stackTrace );
359 }
360
361 private static void encode( StringBuilder encoded, String message, String smartStackTrace, String stackTrace )
362 {
363 encoded.append( toBase64( message ) )
364 .append( ':' )
365 .append( toBase64( smartStackTrace ) )
366 .append( ':' )
367 .append( toBase64( stackTrace ) )
368 .append( ':' );
369 }
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384 static StringBuilder encode( String operation, String runMode, ReportEntry reportEntry, boolean trimStackTraces )
385 {
386 StringBuilder encodedTo = encodeHeader( operation, runMode )
387 .append( toBase64( reportEntry.getSourceName() ) )
388 .append( ':' )
389 .append( toBase64( reportEntry.getSourceText() ) )
390 .append( ':' )
391 .append( toBase64( reportEntry.getName() ) )
392 .append( ':' )
393 .append( toBase64( reportEntry.getNameText() ) )
394 .append( ':' )
395 .append( toBase64( reportEntry.getGroup() ) )
396 .append( ':' )
397 .append( toBase64( reportEntry.getMessage() ) )
398 .append( ':' )
399 .append( reportEntry.getElapsed() == null ? "-" : reportEntry.getElapsed().toString() )
400 .append( ':' );
401
402 encode( encodedTo, reportEntry.getStackTraceWriter(), trimStackTraces );
403
404 return encodedTo;
405 }
406
407
408
409
410
411 StringBuilder print( String operation, String... msgs )
412 {
413 String[] encodedMsgs = new String[msgs.length];
414 for ( int i = 0; i < encodedMsgs.length; i++ )
415 {
416 String msg = msgs[i];
417 encodedMsgs[i] = toBase64( msg );
418 }
419 return encodeMessage( operation, null, encodedMsgs );
420 }
421
422 static StringBuilder encodeMessage( String operation, String runMode, String... encodedMsgs )
423 {
424 StringBuilder builder = encodeHeader( operation, runMode );
425 for ( String encodedMsg : encodedMsgs )
426 {
427 builder.append( encodedMsg ).append( ':' );
428
429 }
430 return builder;
431 }
432
433 static StringBuilder encodeHeader( String operation, String runMode )
434 {
435 return encodeOpcode( operation, runMode )
436 .append( STRING_ENCODING.name() )
437 .append( ':' );
438 }
439
440
441
442
443
444
445
446
447
448 static StringBuilder encodeOpcode( String operation, String runMode )
449 {
450 StringBuilder s = new StringBuilder( 128 )
451 .append( ':' )
452 .append( MAGIC_NUMBER )
453 .append( ':' )
454 .append( operation )
455 .append( ':' );
456
457 return runMode == null ? s : s.append( runMode ).append( ':' );
458 }
459
460 private static String toStackTrace( StackTraceWriter stw, boolean trimStackTraces )
461 {
462 return trimStackTraces ? stw.writeTrimmedTraceToString() : stw.writeTraceToString();
463 }
464
465 static String toBase64( String msg )
466 {
467 return msg == null ? "-" : new String( BASE64.encode( msg.getBytes( STRING_ENCODING ) ), STREAM_ENCODING );
468 }
469 }