View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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  public class EventDecoder extends AbstractStreamDecoder<Event, ForkedProcessEventType, SegmentType> {
78      private static final int DEBUG_SINK_BUFFER_SIZE = 64 * 1024;
79      // due to have fast and thread-safe Map
80      private static final Map<Segment, ForkedProcessEventType> EVENT_TYPES = segmentsToEvents();
81      private static final Map<Segment, RunMode> RUN_MODES = segmentsToRunModes();
82  
83      private static final SegmentType[] EVENT_WITHOUT_DATA = new SegmentType[] {END_OF_FRAME};
84  
85      private static final SegmentType[] EVENT_WITH_ERROR_TRACE =
86              new SegmentType[] {STRING_ENCODING, DATA_STRING, DATA_STRING, DATA_STRING, END_OF_FRAME};
87  
88      private static final SegmentType[] EVENT_WITH_ONE_STRING =
89              new SegmentType[] {STRING_ENCODING, DATA_STRING, END_OF_FRAME};
90  
91      private static final SegmentType[] EVENT_WITH_RUNMODE_TID_AND_ONE_STRING =
92              new SegmentType[] {RUN_MODE, TEST_RUN_ID, STRING_ENCODING, DATA_STRING, END_OF_FRAME};
93  
94      private static final SegmentType[] EVENT_WITH_RUNMODE_TID_AND_TWO_STRINGS =
95              new SegmentType[] {RUN_MODE, TEST_RUN_ID, STRING_ENCODING, DATA_STRING, DATA_STRING, END_OF_FRAME};
96  
97      private static final SegmentType[] EVENT_TEST_CONTROL = new SegmentType[] {
98          RUN_MODE,
99          TEST_RUN_ID,
100         STRING_ENCODING,
101         DATA_STRING,
102         DATA_STRING,
103         DATA_STRING,
104         DATA_STRING,
105         DATA_STRING,
106         DATA_STRING,
107         DATA_INTEGER,
108         DATA_STRING,
109         DATA_STRING,
110         DATA_STRING,
111         END_OF_FRAME
112     };
113 
114     private static final int NO_POSITION = -1;
115 
116     private final OutputStream debugSink;
117 
118     public EventDecoder(@Nonnull ReadableByteChannel channel, @Nonnull ForkNodeArguments arguments) {
119         super(channel, arguments, EVENT_TYPES);
120         debugSink = newDebugSink(arguments);
121     }
122 
123     @Override
124     public Event decode(@Nonnull Memento memento) throws IOException {
125         try {
126             ForkedProcessEventType eventType = readMessageType(memento);
127             if (eventType == null) {
128                 throw new MalformedFrameException(
129                         memento.getLine().getPositionByteBuffer(),
130                         memento.getByteBuffer().position());
131             }
132 
133             for (SegmentType segmentType : nextSegmentType(eventType)) {
134                 switch (segmentType) {
135                     case RUN_MODE:
136                         memento.getData().add(RUN_MODES.get(readSegment(memento)));
137                         break;
138                     case TEST_RUN_ID:
139                         memento.getData().add(readLong(memento));
140                         break;
141                     case STRING_ENCODING:
142                         memento.setCharset(readCharset(memento));
143                         break;
144                     case DATA_STRING:
145                         memento.getData().add(readString(memento));
146                         break;
147                     case DATA_INTEGER:
148                         memento.getData().add(readInteger(memento));
149                         break;
150                     case END_OF_FRAME:
151                         memento.getLine()
152                                 .setPositionByteBuffer(memento.getByteBuffer().position());
153                         memento.getLine().clear();
154                         return toMessage(eventType, memento);
155                     default:
156                         memento.getLine().setPositionByteBuffer(NO_POSITION);
157                         getArguments()
158                                 .dumpStreamText(
159                                         "Unknown enum (" + SegmentType.class.getSimpleName() + ") " + segmentType);
160                 }
161             }
162         } catch (MalformedFrameException e) {
163             if (e.hasValidPositions()) {
164                 int length = e.readTo() - e.readFrom();
165                 memento.getLine().write(memento.getByteBuffer(), e.readFrom(), length);
166             }
167             return null;
168         } catch (RuntimeException e) {
169             getArguments().dumpStreamException(e);
170             return null;
171         } catch (IOException e) {
172             if (!(e.getCause() instanceof InterruptedException)) {
173                 printRemainingStream(memento);
174             }
175             throw e;
176         } finally {
177             memento.reset();
178         }
179 
180         throw new IOException("unreachable statement");
181     }
182 
183     @Nonnull
184     @Override
185     protected final byte[] getEncodedMagicNumber() {
186         return MAGIC_NUMBER_FOR_EVENTS_BYTES;
187     }
188 
189     @Override
190     @Nonnull
191     protected final SegmentType[] nextSegmentType(@Nonnull ForkedProcessEventType eventType) {
192         switch (eventType) {
193             case BOOTERCODE_BYE:
194             case BOOTERCODE_STOP_ON_NEXT_TEST:
195             case BOOTERCODE_NEXT_TEST:
196                 return EVENT_WITHOUT_DATA;
197             case BOOTERCODE_CONSOLE_ERROR:
198             case BOOTERCODE_JVM_EXIT_ERROR:
199                 return EVENT_WITH_ERROR_TRACE;
200             case BOOTERCODE_CONSOLE_INFO:
201             case BOOTERCODE_CONSOLE_DEBUG:
202             case BOOTERCODE_CONSOLE_WARNING:
203                 return EVENT_WITH_ONE_STRING;
204             case BOOTERCODE_STDOUT:
205             case BOOTERCODE_STDOUT_NEW_LINE:
206             case BOOTERCODE_STDERR:
207             case BOOTERCODE_STDERR_NEW_LINE:
208                 return EVENT_WITH_RUNMODE_TID_AND_TWO_STRINGS;
209             case BOOTERCODE_SYSPROPS:
210                 return EVENT_WITH_RUNMODE_TID_AND_TWO_STRINGS;
211             case BOOTERCODE_TESTSET_STARTING:
212             case BOOTERCODE_TESTSET_COMPLETED:
213             case BOOTERCODE_TEST_STARTING:
214             case BOOTERCODE_TEST_SUCCEEDED:
215             case BOOTERCODE_TEST_FAILED:
216             case BOOTERCODE_TEST_SKIPPED:
217             case BOOTERCODE_TEST_ERROR:
218             case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
219                 return EVENT_TEST_CONTROL;
220             default:
221                 throw new IllegalArgumentException("Unknown enum " + eventType);
222         }
223     }
224 
225     @Override
226     @Nonnull
227     protected final Event toMessage(@Nonnull ForkedProcessEventType eventType, @Nonnull Memento memento)
228             throws MalformedFrameException {
229         switch (eventType) {
230             case BOOTERCODE_BYE:
231                 checkArguments(memento, 0);
232                 return new ControlByeEvent();
233             case BOOTERCODE_STOP_ON_NEXT_TEST:
234                 checkArguments(memento, 0);
235                 return new ControlStopOnNextTestEvent();
236             case BOOTERCODE_NEXT_TEST:
237                 checkArguments(memento, 0);
238                 return new ControlNextTestEvent();
239             case BOOTERCODE_JVM_EXIT_ERROR:
240                 checkArguments(memento, 3);
241                 return new JvmExitErrorEvent(toStackTraceWriter(memento.getData()));
242             case BOOTERCODE_CONSOLE_ERROR:
243                 checkArguments(memento, 3);
244                 return new ConsoleErrorEvent(toStackTraceWriter(memento.getData()));
245             case BOOTERCODE_CONSOLE_INFO:
246                 checkArguments(memento, 1);
247                 return new ConsoleInfoEvent((String) memento.getData().get(0));
248             case BOOTERCODE_CONSOLE_DEBUG:
249                 checkArguments(memento, 1);
250                 return new ConsoleDebugEvent((String) memento.getData().get(0));
251             case BOOTERCODE_CONSOLE_WARNING:
252                 checkArguments(memento, 1);
253                 return new ConsoleWarningEvent((String) memento.getData().get(0));
254             case BOOTERCODE_STDOUT:
255                 checkArguments(memento, 4);
256                 return new StandardStreamOutEvent(
257                         memento.ofDataAt(0), memento.ofDataAt(1), memento.ofDataAt(2), memento.ofDataAt(3));
258             case BOOTERCODE_STDOUT_NEW_LINE:
259                 checkArguments(memento, 4);
260                 return new StandardStreamOutWithNewLineEvent(
261                         memento.ofDataAt(0), memento.ofDataAt(1), memento.ofDataAt(2), memento.ofDataAt(3));
262             case BOOTERCODE_STDERR:
263                 checkArguments(memento, 4);
264                 return new StandardStreamErrEvent(
265                         memento.ofDataAt(0), memento.ofDataAt(1), memento.ofDataAt(2), memento.ofDataAt(3));
266             case BOOTERCODE_STDERR_NEW_LINE:
267                 checkArguments(memento, 4);
268                 return new StandardStreamErrWithNewLineEvent(
269                         memento.ofDataAt(0), memento.ofDataAt(1), memento.ofDataAt(2), memento.ofDataAt(3));
270             case BOOTERCODE_SYSPROPS:
271                 checkArguments(memento, 4);
272                 return new SystemPropertyEvent(
273                         memento.ofDataAt(0), memento.ofDataAt(1), memento.ofDataAt(2), memento.ofDataAt(3));
274             case BOOTERCODE_TESTSET_STARTING:
275                 checkArguments(memento, 12);
276                 return new TestsetStartingEvent(toReportEntry(memento.getData()));
277             case BOOTERCODE_TESTSET_COMPLETED:
278                 checkArguments(memento, 12);
279                 return new TestsetCompletedEvent(toReportEntry(memento.getData()));
280             case BOOTERCODE_TEST_STARTING:
281                 checkArguments(memento, 12);
282                 return new TestStartingEvent(toReportEntry(memento.getData()));
283             case BOOTERCODE_TEST_SUCCEEDED:
284                 checkArguments(memento, 12);
285                 return new TestSucceededEvent(toReportEntry(memento.getData()));
286             case BOOTERCODE_TEST_FAILED:
287                 checkArguments(memento, 12);
288                 return new TestFailedEvent(toReportEntry(memento.getData()));
289             case BOOTERCODE_TEST_SKIPPED:
290                 checkArguments(memento, 12);
291                 return new TestSkippedEvent(toReportEntry(memento.getData()));
292             case BOOTERCODE_TEST_ERROR:
293                 checkArguments(memento, 12);
294                 return new TestErrorEvent(toReportEntry(memento.getData()));
295             case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
296                 checkArguments(memento, 12);
297                 return new TestAssumptionFailureEvent(toReportEntry(memento.getData()));
298             default:
299                 throw new IllegalArgumentException("Missing a branch for the event type " + eventType);
300         }
301     }
302 
303     @Nonnull
304     private static TestSetReportEntry toReportEntry(List<Object> args) {
305         RunMode runMode = (RunMode) args.get(0);
306         long testRunId = (long) args.get(1);
307         // ReportEntry:
308         String source = (String) args.get(2);
309         String sourceText = (String) args.get(3);
310         String name = (String) args.get(4);
311         String nameText = (String) args.get(5);
312         String group = (String) args.get(6);
313         String message = (String) args.get(7);
314         Integer timeElapsed = (Integer) args.get(8);
315         // StackTraceWriter:
316         String traceMessage = (String) args.get(9);
317         String smartTrimmedStackTrace = (String) args.get(10);
318         String stackTrace = (String) args.get(11);
319         return newReportEntry(
320                 runMode,
321                 testRunId,
322                 source,
323                 sourceText,
324                 name,
325                 nameText,
326                 group,
327                 message,
328                 timeElapsed,
329                 traceMessage,
330                 smartTrimmedStackTrace,
331                 stackTrace);
332     }
333 
334     private static StackTraceWriter toStackTraceWriter(List<Object> args) {
335         String traceMessage = (String) args.get(0);
336         String smartTrimmedStackTrace = (String) args.get(1);
337         String stackTrace = (String) args.get(2);
338         return toTrace(traceMessage, smartTrimmedStackTrace, stackTrace);
339     }
340 
341     private static StackTraceWriter toTrace(String traceMessage, String smartTrimmedStackTrace, String stackTrace) {
342         boolean exists = traceMessage != null || stackTrace != null || smartTrimmedStackTrace != null;
343         return exists ? new DeserializedStacktraceWriter(traceMessage, smartTrimmedStackTrace, stackTrace) : null;
344     }
345 
346     static TestSetReportEntry newReportEntry( // ReportEntry:
347             RunMode runMode,
348             long testRunId,
349             String source,
350             String sourceText,
351             String name,
352             String nameText,
353             String group,
354             String message,
355             Integer timeElapsed,
356             // StackTraceWriter:
357             String traceMessage,
358             String smartTrimmedStackTrace,
359             String stackTrace)
360             throws NumberFormatException {
361         StackTraceWriter stackTraceWriter = toTrace(traceMessage, smartTrimmedStackTrace, stackTrace);
362         return reportEntry(
363                 runMode,
364                 testRunId,
365                 source,
366                 sourceText,
367                 name,
368                 nameText,
369                 group,
370                 stackTraceWriter,
371                 timeElapsed,
372                 message,
373                 emptyMap());
374     }
375 
376     private static Map<Segment, ForkedProcessEventType> segmentsToEvents() {
377         Map<Segment, ForkedProcessEventType> events = new HashMap<>();
378         for (ForkedProcessEventType event : ForkedProcessEventType.values()) {
379             byte[] array = event.getOpcodeBinary();
380             events.put(new Segment(array, 0, array.length), event);
381         }
382         return events;
383     }
384 
385     private static Map<Segment, RunMode> segmentsToRunModes() {
386         Map<Segment, RunMode> runModes = new HashMap<>();
387         for (RunMode runMode : RunMode.values()) {
388             byte[] array = runMode.getRunmodeBinary();
389             runModes.put(new Segment(array, 0, array.length), runMode);
390         }
391         return runModes;
392     }
393 
394     @Override
395     protected void debugStream(byte[] array, int position, int remaining) {
396         if (debugSink == null) {
397             return;
398         }
399 
400         try {
401             debugSink.write(array, position, remaining);
402         } catch (IOException e) {
403             // logger file was deleted
404             // System.out is already used by the stream in this decoder
405         }
406     }
407 
408     private OutputStream newDebugSink(ForkNodeArguments arguments) {
409         final File sink = arguments.getEventStreamBinaryFile();
410         if (sink == null) {
411             return null;
412         }
413 
414         try {
415             OutputStream fos = new FileOutputStream(sink, true);
416             final OutputStream os = new BufferedOutputStream(fos, DEBUG_SINK_BUFFER_SIZE);
417             addShutDownHook(new Thread(new FutureTask<>(() -> {
418                 os.close();
419                 return null;
420             })));
421             return os;
422         } catch (FileNotFoundException e) {
423             return null;
424         }
425     }
426 
427     @Override
428     public void close() throws IOException {
429         // do NOT close the channel, it's std/out.
430         if (debugSink != null) {
431             debugSink.close();
432         }
433     }
434 }