1 package org.apache.maven.surefire.stream;
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.booterclient.output.DeserializedStacktraceWriter;
23 import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
24 import org.apache.maven.surefire.api.event.ConsoleDebugEvent;
25 import org.apache.maven.surefire.api.event.ConsoleErrorEvent;
26 import org.apache.maven.surefire.api.event.ConsoleInfoEvent;
27 import org.apache.maven.surefire.api.event.ConsoleWarningEvent;
28 import org.apache.maven.surefire.api.event.ControlByeEvent;
29 import org.apache.maven.surefire.api.event.ControlNextTestEvent;
30 import org.apache.maven.surefire.api.event.ControlStopOnNextTestEvent;
31 import org.apache.maven.surefire.api.event.Event;
32 import org.apache.maven.surefire.api.event.JvmExitErrorEvent;
33 import org.apache.maven.surefire.api.event.StandardStreamErrEvent;
34 import org.apache.maven.surefire.api.event.StandardStreamErrWithNewLineEvent;
35 import org.apache.maven.surefire.api.event.StandardStreamOutEvent;
36 import org.apache.maven.surefire.api.event.StandardStreamOutWithNewLineEvent;
37 import org.apache.maven.surefire.api.event.SystemPropertyEvent;
38 import org.apache.maven.surefire.api.event.TestAssumptionFailureEvent;
39 import org.apache.maven.surefire.api.event.TestErrorEvent;
40 import org.apache.maven.surefire.api.event.TestFailedEvent;
41 import org.apache.maven.surefire.api.event.TestSkippedEvent;
42 import org.apache.maven.surefire.api.event.TestStartingEvent;
43 import org.apache.maven.surefire.api.event.TestSucceededEvent;
44 import org.apache.maven.surefire.api.event.TestsetCompletedEvent;
45 import org.apache.maven.surefire.api.event.TestsetStartingEvent;
46 import org.apache.maven.surefire.api.fork.ForkNodeArguments;
47 import org.apache.maven.surefire.api.report.RunMode;
48 import org.apache.maven.surefire.api.report.StackTraceWriter;
49 import org.apache.maven.surefire.api.report.TestSetReportEntry;
50 import org.apache.maven.surefire.api.stream.AbstractStreamDecoder;
51 import org.apache.maven.surefire.api.stream.SegmentType;
52
53 import javax.annotation.Nonnull;
54 import java.io.BufferedOutputStream;
55 import java.io.File;
56 import java.io.FileNotFoundException;
57 import java.io.FileOutputStream;
58 import java.io.IOException;
59 import java.io.OutputStream;
60 import java.nio.channels.ReadableByteChannel;
61 import java.util.HashMap;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.concurrent.FutureTask;
65
66 import static java.util.Collections.emptyMap;
67 import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_FOR_EVENTS_BYTES;
68 import static org.apache.maven.surefire.api.report.CategorizedReportEntry.reportEntry;
69 import static org.apache.maven.surefire.api.stream.SegmentType.DATA_INTEGER;
70 import static org.apache.maven.surefire.api.stream.SegmentType.DATA_STRING;
71 import static org.apache.maven.surefire.api.stream.SegmentType.END_OF_FRAME;
72 import static org.apache.maven.surefire.api.stream.SegmentType.RUN_MODE;
73 import static org.apache.maven.surefire.api.stream.SegmentType.STRING_ENCODING;
74 import static org.apache.maven.surefire.api.stream.SegmentType.TEST_RUN_ID;
75 import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.addShutDownHook;
76
77
78
79
80 public class EventDecoder extends AbstractStreamDecoder<Event, ForkedProcessEventType, SegmentType>
81 {
82 private static final int DEBUG_SINK_BUFFER_SIZE = 64 * 1024;
83
84 private static final Map<Segment, ForkedProcessEventType> EVENT_TYPES = segmentsToEvents();
85 private static final Map<Segment, RunMode> RUN_MODES = segmentsToRunModes();
86
87 private static final SegmentType[] EVENT_WITHOUT_DATA = new SegmentType[] {
88 END_OF_FRAME
89 };
90
91 private static final SegmentType[] EVENT_WITH_ERROR_TRACE = new SegmentType[] {
92 STRING_ENCODING,
93 DATA_STRING,
94 DATA_STRING,
95 DATA_STRING,
96 END_OF_FRAME
97 };
98
99 private static final SegmentType[] EVENT_WITH_ONE_STRING = new SegmentType[] {
100 STRING_ENCODING,
101 DATA_STRING,
102 END_OF_FRAME
103 };
104
105 private static final SegmentType[] EVENT_WITH_RUNMODE_TID_AND_ONE_STRING = new SegmentType[] {
106 RUN_MODE,
107 TEST_RUN_ID,
108 STRING_ENCODING,
109 DATA_STRING,
110 END_OF_FRAME
111 };
112
113 private static final SegmentType[] EVENT_WITH_RUNMODE_TID_AND_TWO_STRINGS = new SegmentType[] {
114 RUN_MODE,
115 TEST_RUN_ID,
116 STRING_ENCODING,
117 DATA_STRING,
118 DATA_STRING,
119 END_OF_FRAME
120 };
121
122 private static final SegmentType[] EVENT_TEST_CONTROL = new SegmentType[] {
123 RUN_MODE,
124 TEST_RUN_ID,
125 STRING_ENCODING,
126 DATA_STRING,
127 DATA_STRING,
128 DATA_STRING,
129 DATA_STRING,
130 DATA_STRING,
131 DATA_STRING,
132 DATA_INTEGER,
133 DATA_STRING,
134 DATA_STRING,
135 DATA_STRING,
136 END_OF_FRAME
137 };
138
139 private static final int NO_POSITION = -1;
140
141 private final OutputStream debugSink;
142
143 public EventDecoder( @Nonnull ReadableByteChannel channel,
144 @Nonnull ForkNodeArguments arguments )
145 {
146 super( channel, arguments, EVENT_TYPES );
147 debugSink = newDebugSink( arguments );
148 }
149
150 @Override
151 public Event decode( @Nonnull Memento memento ) throws IOException
152 {
153 try
154 {
155 ForkedProcessEventType eventType = readMessageType( memento );
156 if ( eventType == null )
157 {
158 throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(),
159 memento.getByteBuffer().position() );
160 }
161
162 for ( SegmentType segmentType : nextSegmentType( eventType ) )
163 {
164 switch ( segmentType )
165 {
166 case RUN_MODE:
167 memento.getData().add( RUN_MODES.get( readSegment( memento ) ) );
168 break;
169 case TEST_RUN_ID:
170 memento.getData().add( readLong( memento ) );
171 break;
172 case STRING_ENCODING:
173 memento.setCharset( readCharset( memento ) );
174 break;
175 case DATA_STRING:
176 memento.getData().add( readString( memento ) );
177 break;
178 case DATA_INTEGER:
179 memento.getData().add( readInteger( memento ) );
180 break;
181 case END_OF_FRAME:
182 memento.getLine().setPositionByteBuffer( memento.getByteBuffer().position() );
183 memento.getLine().clear();
184 return toMessage( eventType, memento );
185 default:
186 memento.getLine().setPositionByteBuffer( NO_POSITION );
187 getArguments()
188 .dumpStreamText( "Unknown enum ("
189 + SegmentType.class.getSimpleName()
190 + ") "
191 + segmentType );
192 }
193 }
194 }
195 catch ( MalformedFrameException e )
196 {
197 if ( e.hasValidPositions() )
198 {
199 int length = e.readTo() - e.readFrom();
200 memento.getLine().write( memento.getByteBuffer(), e.readFrom(), length );
201 }
202 return null;
203 }
204 catch ( RuntimeException e )
205 {
206 getArguments().dumpStreamException( e );
207 return null;
208 }
209 catch ( IOException e )
210 {
211 if ( !( e.getCause() instanceof InterruptedException ) )
212 {
213 printRemainingStream( memento );
214 }
215 throw e;
216 }
217 finally
218 {
219 memento.reset();
220 }
221
222 throw new IOException( "unreachable statement" );
223 }
224
225 @Nonnull
226 @Override
227 protected final byte[] getEncodedMagicNumber()
228 {
229 return MAGIC_NUMBER_FOR_EVENTS_BYTES;
230 }
231
232 @Override
233 @Nonnull
234 protected final SegmentType[] nextSegmentType( @Nonnull ForkedProcessEventType eventType )
235 {
236 switch ( eventType )
237 {
238 case BOOTERCODE_BYE:
239 case BOOTERCODE_STOP_ON_NEXT_TEST:
240 case BOOTERCODE_NEXT_TEST:
241 return EVENT_WITHOUT_DATA;
242 case BOOTERCODE_CONSOLE_ERROR:
243 case BOOTERCODE_JVM_EXIT_ERROR:
244 return EVENT_WITH_ERROR_TRACE;
245 case BOOTERCODE_CONSOLE_INFO:
246 case BOOTERCODE_CONSOLE_DEBUG:
247 case BOOTERCODE_CONSOLE_WARNING:
248 return EVENT_WITH_ONE_STRING;
249 case BOOTERCODE_STDOUT:
250 case BOOTERCODE_STDOUT_NEW_LINE:
251 case BOOTERCODE_STDERR:
252 case BOOTERCODE_STDERR_NEW_LINE:
253 return EVENT_WITH_RUNMODE_TID_AND_ONE_STRING;
254 case BOOTERCODE_SYSPROPS:
255 return EVENT_WITH_RUNMODE_TID_AND_TWO_STRINGS;
256 case BOOTERCODE_TESTSET_STARTING:
257 case BOOTERCODE_TESTSET_COMPLETED:
258 case BOOTERCODE_TEST_STARTING:
259 case BOOTERCODE_TEST_SUCCEEDED:
260 case BOOTERCODE_TEST_FAILED:
261 case BOOTERCODE_TEST_SKIPPED:
262 case BOOTERCODE_TEST_ERROR:
263 case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
264 return EVENT_TEST_CONTROL;
265 default:
266 throw new IllegalArgumentException( "Unknown enum " + eventType );
267 }
268 }
269
270 @Override
271 @Nonnull
272 protected final Event toMessage( @Nonnull ForkedProcessEventType eventType, @Nonnull Memento memento )
273 throws MalformedFrameException
274 {
275 switch ( eventType )
276 {
277 case BOOTERCODE_BYE:
278 checkArguments( memento, 0 );
279 return new ControlByeEvent();
280 case BOOTERCODE_STOP_ON_NEXT_TEST:
281 checkArguments( memento, 0 );
282 return new ControlStopOnNextTestEvent();
283 case BOOTERCODE_NEXT_TEST:
284 checkArguments( memento, 0 );
285 return new ControlNextTestEvent();
286 case BOOTERCODE_JVM_EXIT_ERROR:
287 checkArguments( memento, 3 );
288 return new JvmExitErrorEvent( toStackTraceWriter( memento.getData() ) );
289 case BOOTERCODE_CONSOLE_ERROR:
290 checkArguments( memento, 3 );
291 return new ConsoleErrorEvent( toStackTraceWriter( memento.getData() ) );
292 case BOOTERCODE_CONSOLE_INFO:
293 checkArguments( memento, 1 );
294 return new ConsoleInfoEvent( (String) memento.getData().get( 0 ) );
295 case BOOTERCODE_CONSOLE_DEBUG:
296 checkArguments( memento, 1 );
297 return new ConsoleDebugEvent( (String) memento.getData().get( 0 ) );
298 case BOOTERCODE_CONSOLE_WARNING:
299 checkArguments( memento, 1 );
300 return new ConsoleWarningEvent( (String) memento.getData().get( 0 ) );
301 case BOOTERCODE_STDOUT:
302 checkArguments( memento, 3 );
303 return new StandardStreamOutEvent( memento.ofDataAt( 0 ), memento.ofDataAt( 1 ),
304 memento.ofDataAt( 2 ) );
305 case BOOTERCODE_STDOUT_NEW_LINE:
306 checkArguments( memento, 3 );
307 return new StandardStreamOutWithNewLineEvent( memento.ofDataAt( 0 ), memento.ofDataAt( 1 ),
308 memento.ofDataAt( 2 ) );
309 case BOOTERCODE_STDERR:
310 checkArguments( memento, 3 );
311 return new StandardStreamErrEvent( memento.ofDataAt( 0 ), memento.ofDataAt( 1 ),
312 memento.ofDataAt( 2 ) );
313 case BOOTERCODE_STDERR_NEW_LINE:
314 checkArguments( memento, 3 );
315 return new StandardStreamErrWithNewLineEvent( memento.ofDataAt( 0 ), memento.ofDataAt( 1 ),
316 memento.ofDataAt( 2 ) );
317 case BOOTERCODE_SYSPROPS:
318 checkArguments( memento, 4 );
319 return new SystemPropertyEvent( memento.ofDataAt( 0 ), memento.ofDataAt( 1 ),
320 memento.ofDataAt( 2 ), memento.ofDataAt( 3 ) );
321 case BOOTERCODE_TESTSET_STARTING:
322 checkArguments( memento, 12 );
323 return new TestsetStartingEvent( toReportEntry( memento.getData() ) );
324 case BOOTERCODE_TESTSET_COMPLETED:
325 checkArguments( memento, 12 );
326 return new TestsetCompletedEvent( toReportEntry( memento.getData() ) );
327 case BOOTERCODE_TEST_STARTING:
328 checkArguments( memento, 12 );
329 return new TestStartingEvent( toReportEntry( memento.getData() ) );
330 case BOOTERCODE_TEST_SUCCEEDED:
331 checkArguments( memento, 12 );
332 return new TestSucceededEvent( toReportEntry( memento.getData() ) );
333 case BOOTERCODE_TEST_FAILED:
334 checkArguments( memento, 12 );
335 return new TestFailedEvent( toReportEntry( memento.getData() ) );
336 case BOOTERCODE_TEST_SKIPPED:
337 checkArguments( memento, 12 );
338 return new TestSkippedEvent( toReportEntry( memento.getData() ) );
339 case BOOTERCODE_TEST_ERROR:
340 checkArguments( memento, 12 );
341 return new TestErrorEvent( toReportEntry( memento.getData() ) );
342 case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
343 checkArguments( memento, 12 );
344 return new TestAssumptionFailureEvent( toReportEntry( memento.getData() ) );
345 default:
346 throw new IllegalArgumentException( "Missing a branch for the event type " + eventType );
347 }
348 }
349
350 @Nonnull
351 private static TestSetReportEntry toReportEntry( List<Object> args )
352 {
353 RunMode runMode = (RunMode) args.get( 0 );
354 long testRunId = (long) args.get( 1 );
355
356 String source = (String) args.get( 2 );
357 String sourceText = (String) args.get( 3 );
358 String name = (String) args.get( 4 );
359 String nameText = (String) args.get( 5 );
360 String group = (String) args.get( 6 );
361 String message = (String) args.get( 7 );
362 Integer timeElapsed = (Integer) args.get( 8 );
363
364 String traceMessage = (String) args.get( 9 );
365 String smartTrimmedStackTrace = (String) args.get( 10 );
366 String stackTrace = (String) args.get( 11 );
367 return newReportEntry( runMode, testRunId, source, sourceText, name, nameText, group, message, timeElapsed,
368 traceMessage, smartTrimmedStackTrace, stackTrace );
369 }
370
371 private static StackTraceWriter toStackTraceWriter( List<Object> args )
372 {
373 String traceMessage = (String) args.get( 0 );
374 String smartTrimmedStackTrace = (String) args.get( 1 );
375 String stackTrace = (String) args.get( 2 );
376 return toTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
377 }
378
379 private static StackTraceWriter toTrace( String traceMessage, String smartTrimmedStackTrace, String stackTrace )
380 {
381 boolean exists = traceMessage != null || stackTrace != null || smartTrimmedStackTrace != null;
382 return exists ? new DeserializedStacktraceWriter( traceMessage, smartTrimmedStackTrace, stackTrace ) : null;
383 }
384
385 static TestSetReportEntry newReportEntry(
386 RunMode runMode, long testRunId, String source, String sourceText,
387 String name, String nameText, String group, String message,
388 Integer timeElapsed,
389
390 String traceMessage,
391 String smartTrimmedStackTrace, String stackTrace )
392 throws NumberFormatException
393 {
394 StackTraceWriter stackTraceWriter = toTrace( traceMessage, smartTrimmedStackTrace, stackTrace );
395 return reportEntry( runMode, testRunId, source, sourceText, name, nameText, group, stackTraceWriter,
396 timeElapsed, message, emptyMap() );
397 }
398
399 private static Map<Segment, ForkedProcessEventType> segmentsToEvents()
400 {
401 Map<Segment, ForkedProcessEventType> events = new HashMap<>();
402 for ( ForkedProcessEventType event : ForkedProcessEventType.values() )
403 {
404 byte[] array = event.getOpcodeBinary();
405 events.put( new Segment( array, 0, array.length ), event );
406 }
407 return events;
408 }
409
410 private static Map<Segment, RunMode> segmentsToRunModes()
411 {
412 Map<Segment, RunMode> runModes = new HashMap<>();
413 for ( RunMode runMode : RunMode.values() )
414 {
415 byte[] array = runMode.getRunmodeBinary();
416 runModes.put( new Segment( array, 0, array.length ), runMode );
417 }
418 return runModes;
419 }
420
421 @Override
422 protected void debugStream( byte[] array, int position, int remaining )
423 {
424 if ( debugSink == null )
425 {
426 return;
427 }
428
429 try
430 {
431 debugSink.write( array, position, remaining );
432 }
433 catch ( IOException e )
434 {
435
436
437 }
438 }
439
440 private OutputStream newDebugSink( ForkNodeArguments arguments )
441 {
442 final File sink = arguments.getEventStreamBinaryFile();
443 if ( sink == null )
444 {
445 return null;
446 }
447
448 try
449 {
450 OutputStream fos = new FileOutputStream( sink, true );
451 final OutputStream os = new BufferedOutputStream( fos, DEBUG_SINK_BUFFER_SIZE );
452 addShutDownHook( new Thread( new FutureTask<>( () ->
453 {
454 os.close();
455 return null;
456 } ) ) );
457 return os;
458 }
459 catch ( FileNotFoundException e )
460 {
461 return null;
462 }
463 }
464
465 @Override
466 public void close() throws IOException
467 {
468
469 if ( debugSink != null )
470 {
471 debugSink.close();
472 }
473 }
474 }