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
78
79
80 public class EventDecoder extends AbstractStreamDecoder<Event, ForkedProcessEventType, SegmentType> {
81 private static final int DEBUG_SINK_BUFFER_SIZE = 64 * 1024;
82
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
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
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(
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
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
407
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
433 if (debugSink != null) {
434 debugSink.close();
435 }
436 }
437 }