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_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         // ReportEntry:
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         // StackTraceWriter:
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( // ReportEntry:
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             // StackTraceWriter:
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             // logger file was deleted
405             // System.out is already used by the stream in this decoder
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         // do NOT close the channel, it's std/out.
431         if (debugSink != null) {
432             debugSink.close();
433         }
434     }
435 }