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