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