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