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