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