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_ONE_STRING;
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, 3);
256                 return new StandardStreamOutEvent(memento.ofDataAt(0), memento.ofDataAt(1), memento.ofDataAt(2));
257             case BOOTERCODE_STDOUT_NEW_LINE:
258                 checkArguments(memento, 3);
259                 return new StandardStreamOutWithNewLineEvent(
260                         memento.ofDataAt(0), memento.ofDataAt(1), memento.ofDataAt(2));
261             case BOOTERCODE_STDERR:
262                 checkArguments(memento, 3);
263                 return new StandardStreamErrEvent(memento.ofDataAt(0), memento.ofDataAt(1), memento.ofDataAt(2));
264             case BOOTERCODE_STDERR_NEW_LINE:
265                 checkArguments(memento, 3);
266                 return new StandardStreamErrWithNewLineEvent(
267                         memento.ofDataAt(0), memento.ofDataAt(1), memento.ofDataAt(2));
268             case BOOTERCODE_SYSPROPS:
269                 checkArguments(memento, 4);
270                 return new SystemPropertyEvent(
271                         memento.ofDataAt(0), memento.ofDataAt(1), memento.ofDataAt(2), memento.ofDataAt(3));
272             case BOOTERCODE_TESTSET_STARTING:
273                 checkArguments(memento, 12);
274                 return new TestsetStartingEvent(toReportEntry(memento.getData()));
275             case BOOTERCODE_TESTSET_COMPLETED:
276                 checkArguments(memento, 12);
277                 return new TestsetCompletedEvent(toReportEntry(memento.getData()));
278             case BOOTERCODE_TEST_STARTING:
279                 checkArguments(memento, 12);
280                 return new TestStartingEvent(toReportEntry(memento.getData()));
281             case BOOTERCODE_TEST_SUCCEEDED:
282                 checkArguments(memento, 12);
283                 return new TestSucceededEvent(toReportEntry(memento.getData()));
284             case BOOTERCODE_TEST_FAILED:
285                 checkArguments(memento, 12);
286                 return new TestFailedEvent(toReportEntry(memento.getData()));
287             case BOOTERCODE_TEST_SKIPPED:
288                 checkArguments(memento, 12);
289                 return new TestSkippedEvent(toReportEntry(memento.getData()));
290             case BOOTERCODE_TEST_ERROR:
291                 checkArguments(memento, 12);
292                 return new TestErrorEvent(toReportEntry(memento.getData()));
293             case BOOTERCODE_TEST_ASSUMPTIONFAILURE:
294                 checkArguments(memento, 12);
295                 return new TestAssumptionFailureEvent(toReportEntry(memento.getData()));
296             default:
297                 throw new IllegalArgumentException("Missing a branch for the event type " + eventType);
298         }
299     }
300 
301     @Nonnull
302     private static TestSetReportEntry toReportEntry(List<Object> args) {
303         RunMode runMode = (RunMode) args.get(0);
304         long testRunId = (long) args.get(1);
305         // ReportEntry:
306         String source = (String) args.get(2);
307         String sourceText = (String) args.get(3);
308         String name = (String) args.get(4);
309         String nameText = (String) args.get(5);
310         String group = (String) args.get(6);
311         String message = (String) args.get(7);
312         Integer timeElapsed = (Integer) args.get(8);
313         // StackTraceWriter:
314         String traceMessage = (String) args.get(9);
315         String smartTrimmedStackTrace = (String) args.get(10);
316         String stackTrace = (String) args.get(11);
317         return newReportEntry(
318                 runMode,
319                 testRunId,
320                 source,
321                 sourceText,
322                 name,
323                 nameText,
324                 group,
325                 message,
326                 timeElapsed,
327                 traceMessage,
328                 smartTrimmedStackTrace,
329                 stackTrace);
330     }
331 
332     private static StackTraceWriter toStackTraceWriter(List<Object> args) {
333         String traceMessage = (String) args.get(0);
334         String smartTrimmedStackTrace = (String) args.get(1);
335         String stackTrace = (String) args.get(2);
336         return toTrace(traceMessage, smartTrimmedStackTrace, stackTrace);
337     }
338 
339     private static StackTraceWriter toTrace(String traceMessage, String smartTrimmedStackTrace, String stackTrace) {
340         boolean exists = traceMessage != null || stackTrace != null || smartTrimmedStackTrace != null;
341         return exists ? new DeserializedStacktraceWriter(traceMessage, smartTrimmedStackTrace, stackTrace) : null;
342     }
343 
344     static TestSetReportEntry newReportEntry( // ReportEntry:
345             RunMode runMode,
346             long testRunId,
347             String source,
348             String sourceText,
349             String name,
350             String nameText,
351             String group,
352             String message,
353             Integer timeElapsed,
354             // StackTraceWriter:
355             String traceMessage,
356             String smartTrimmedStackTrace,
357             String stackTrace)
358             throws NumberFormatException {
359         StackTraceWriter stackTraceWriter = toTrace(traceMessage, smartTrimmedStackTrace, stackTrace);
360         return reportEntry(
361                 runMode,
362                 testRunId,
363                 source,
364                 sourceText,
365                 name,
366                 nameText,
367                 group,
368                 stackTraceWriter,
369                 timeElapsed,
370                 message,
371                 emptyMap());
372     }
373 
374     private static Map<Segment, ForkedProcessEventType> segmentsToEvents() {
375         Map<Segment, ForkedProcessEventType> events = new HashMap<>();
376         for (ForkedProcessEventType event : ForkedProcessEventType.values()) {
377             byte[] array = event.getOpcodeBinary();
378             events.put(new Segment(array, 0, array.length), event);
379         }
380         return events;
381     }
382 
383     private static Map<Segment, RunMode> segmentsToRunModes() {
384         Map<Segment, RunMode> runModes = new HashMap<>();
385         for (RunMode runMode : RunMode.values()) {
386             byte[] array = runMode.getRunmodeBinary();
387             runModes.put(new Segment(array, 0, array.length), runMode);
388         }
389         return runModes;
390     }
391 
392     @Override
393     protected void debugStream(byte[] array, int position, int remaining) {
394         if (debugSink == null) {
395             return;
396         }
397 
398         try {
399             debugSink.write(array, position, remaining);
400         } catch (IOException e) {
401             // logger file was deleted
402             // System.out is already used by the stream in this decoder
403         }
404     }
405 
406     private OutputStream newDebugSink(ForkNodeArguments arguments) {
407         final File sink = arguments.getEventStreamBinaryFile();
408         if (sink == null) {
409             return null;
410         }
411 
412         try {
413             OutputStream fos = new FileOutputStream(sink, true);
414             final OutputStream os = new BufferedOutputStream(fos, DEBUG_SINK_BUFFER_SIZE);
415             addShutDownHook(new Thread(new FutureTask<>(() -> {
416                 os.close();
417                 return null;
418             })));
419             return os;
420         } catch (FileNotFoundException e) {
421             return null;
422         }
423     }
424 
425     @Override
426     public void close() throws IOException {
427         // do NOT close the channel, it's std/out.
428         if (debugSink != null) {
429             debugSink.close();
430         }
431     }
432 }