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_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
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
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(
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
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
405
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
431 if (debugSink != null) {
432 debugSink.close();
433 }
434 }
435 }