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 public class EventDecoder extends AbstractStreamDecoder<Event, ForkedProcessEventType, SegmentType> {
78 private static final int DEBUG_SINK_BUFFER_SIZE = 64 * 1024;
79
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
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
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(
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
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
402
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
428 if (debugSink != null) {
429 debugSink.close();
430 }
431 }
432 }