1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.surefire.booter.spi;
20
21 import javax.annotation.Nonnull;
22
23 import java.io.IOException;
24 import java.nio.Buffer;
25 import java.nio.ByteBuffer;
26 import java.nio.channels.ClosedChannelException;
27 import java.nio.charset.CharsetEncoder;
28 import java.util.Iterator;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 import java.util.concurrent.atomic.AtomicBoolean;
32
33 import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
34 import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
35 import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
36 import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
37 import org.apache.maven.surefire.api.report.ReportEntry;
38 import org.apache.maven.surefire.api.report.RunMode;
39 import org.apache.maven.surefire.api.report.SafeThrowable;
40 import org.apache.maven.surefire.api.report.StackTraceWriter;
41 import org.apache.maven.surefire.api.report.TestOutputReportEntry;
42 import org.apache.maven.surefire.api.report.TestSetReportEntry;
43 import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
44 import org.apache.maven.surefire.booter.stream.EventEncoder;
45
46 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_BYE;
47 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_DEBUG;
48 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_ERROR;
49 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_INFO;
50 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_CONSOLE_WARNING;
51 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_JVM_EXIT_ERROR;
52 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_NEXT_TEST;
53 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR;
54 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDERR_NEW_LINE;
55 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
56 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT_NEW_LINE;
57 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STOP_ON_NEXT_TEST;
58 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_SYSPROPS;
59 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_COMPLETED;
60 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TESTSET_STARTING;
61 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ASSUMPTIONFAILURE;
62 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_ERROR;
63 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_FAILED;
64 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SKIPPED;
65 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_STARTING;
66 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_TEST_SUCCEEDED;
67
68
69
70
71
72
73
74
75 @SuppressWarnings("checkstyle:linelength")
76 public class EventChannelEncoder extends EventEncoder implements MasterProcessChannelEncoder {
77 private final AtomicBoolean trouble = new AtomicBoolean();
78 private volatile boolean onExit;
79
80
81
82
83
84
85 public EventChannelEncoder(@Nonnull WritableBufferedByteChannel out) {
86 super(out);
87 }
88
89 @Override
90 public boolean checkError() {
91 return trouble.get();
92 }
93
94 @Override
95 public void onJvmExit() {
96 onExit = true;
97 write(ByteBuffer.wrap(new byte[] {'\n'}), true);
98 }
99
100 void encodeSystemProperties(Map<String, String> sysProps, RunMode runMode, Long testRunId) {
101 CharsetEncoder encoder = newCharsetEncoder();
102 ByteBuffer result = null;
103 for (Iterator<Entry<String, String>> it = sysProps.entrySet().iterator(); it.hasNext(); ) {
104 Entry<String, String> entry = it.next();
105 String key = entry.getKey();
106 String value = entry.getValue();
107
108 int bufferLength =
109 estimateBufferLength(BOOTERCODE_SYSPROPS.getOpcode().length(), runMode, encoder, 0, 1, key, value);
110 result = result != null && result.capacity() >= bufferLength ? result : ByteBuffer.allocate(bufferLength);
111 ((Buffer) result).clear();
112
113 encode(encoder, result, BOOTERCODE_SYSPROPS, runMode, testRunId, key, value);
114 boolean sync = !it.hasNext();
115 write(result, sync);
116 }
117 }
118
119 @Override
120 public void testSetStarting(TestSetReportEntry reportEntry, boolean trimStackTraces) {
121 encode(BOOTERCODE_TESTSET_STARTING, reportEntry, trimStackTraces, true);
122 }
123
124 @Override
125 public void testSetCompleted(TestSetReportEntry reportEntry, boolean trimStackTraces) {
126 encodeSystemProperties(reportEntry.getSystemProperties(), reportEntry.getRunMode(), reportEntry.getTestRunId());
127 encode(BOOTERCODE_TESTSET_COMPLETED, reportEntry, trimStackTraces, true);
128 }
129
130 @Override
131 public void testStarting(ReportEntry reportEntry, boolean trimStackTraces) {
132 encode(BOOTERCODE_TEST_STARTING, reportEntry, trimStackTraces, true);
133 }
134
135 @Override
136 public void testSucceeded(ReportEntry reportEntry, boolean trimStackTraces) {
137 encode(BOOTERCODE_TEST_SUCCEEDED, reportEntry, trimStackTraces, true);
138 }
139
140 @Override
141 public void testFailed(ReportEntry reportEntry, boolean trimStackTraces) {
142 encode(BOOTERCODE_TEST_FAILED, reportEntry, trimStackTraces, true);
143 }
144
145 @Override
146 public void testSkipped(ReportEntry reportEntry, boolean trimStackTraces) {
147 encode(BOOTERCODE_TEST_SKIPPED, reportEntry, trimStackTraces, true);
148 }
149
150 @Override
151 public void testError(ReportEntry reportEntry, boolean trimStackTraces) {
152 encode(BOOTERCODE_TEST_ERROR, reportEntry, trimStackTraces, true);
153 }
154
155 @Override
156 public void testAssumptionFailure(ReportEntry reportEntry, boolean trimStackTraces) {
157 encode(BOOTERCODE_TEST_ASSUMPTIONFAILURE, reportEntry, trimStackTraces, true);
158 }
159
160 @Override
161 public void testOutput(TestOutputReportEntry reportEntry) {
162 boolean stdout = reportEntry.isStdOut();
163 boolean newLine = reportEntry.isNewLine();
164 String msg = reportEntry.getLog();
165 ForkedProcessEventType event = stdout
166 ? (newLine ? BOOTERCODE_STDOUT_NEW_LINE : BOOTERCODE_STDOUT)
167 : (newLine ? BOOTERCODE_STDERR_NEW_LINE : BOOTERCODE_STDERR);
168 setOutErr(event, reportEntry.getRunMode(), reportEntry.getTestRunId(), msg, reportEntry.getStack());
169 }
170
171 private void setOutErr(
172 ForkedProcessEventType eventType, RunMode runMode, Long testRunId, String message, String stackTrace) {
173 ByteBuffer result = encodeMessage(eventType, runMode, testRunId, message, stackTrace);
174 write(result, false);
175 }
176
177 @Override
178 public void consoleInfoLog(String message) {
179 ByteBuffer result = encodeMessage(BOOTERCODE_CONSOLE_INFO, message);
180 write(result, true);
181 }
182
183 @Override
184 public void consoleErrorLog(String message) {
185 consoleErrorLog(message, null);
186 }
187
188 @Override
189 public void consoleErrorLog(Throwable t) {
190 consoleErrorLog(t.getLocalizedMessage(), t);
191 }
192
193 @Override
194 public void consoleErrorLog(String message, Throwable t) {
195 CharsetEncoder encoder = newCharsetEncoder();
196 String stackTrace = t == null ? null : ConsoleLoggerUtils.toString(t);
197 int bufferMaxLength = estimateBufferLength(
198 BOOTERCODE_CONSOLE_ERROR.getOpcode().length(), null, encoder, 0, 0, message, null, stackTrace);
199 ByteBuffer result = ByteBuffer.allocate(bufferMaxLength);
200 encodeHeader(result, BOOTERCODE_CONSOLE_ERROR);
201 encodeCharset(result);
202 encode(encoder, result, message, null, stackTrace);
203 write(result, true);
204 }
205
206 @Override
207 public void consoleErrorLog(StackTraceWriter stackTraceWriter, boolean trimStackTraces) {
208 error(stackTraceWriter, trimStackTraces, BOOTERCODE_CONSOLE_ERROR, true);
209 }
210
211 @Override
212 public void consoleDebugLog(String message) {
213 ByteBuffer result = encodeMessage(BOOTERCODE_CONSOLE_DEBUG, message);
214 write(result, true);
215 }
216
217 @Override
218 public void consoleWarningLog(String message) {
219 ByteBuffer result = encodeMessage(BOOTERCODE_CONSOLE_WARNING, message);
220 write(result, true);
221 }
222
223 @Override
224 public void bye() {
225 encodeOpcode(BOOTERCODE_BYE, true);
226 }
227
228 @Override
229 public void stopOnNextTest() {
230 encodeOpcode(BOOTERCODE_STOP_ON_NEXT_TEST, true);
231 }
232
233 @Override
234 public void acquireNextTest() {
235 encodeOpcode(BOOTERCODE_NEXT_TEST, true);
236 }
237
238 @Override
239 public void sendExitError(StackTraceWriter stackTraceWriter, boolean trimStackTraces) {
240 error(stackTraceWriter, trimStackTraces, BOOTERCODE_JVM_EXIT_ERROR, true);
241 }
242
243 private void error(
244 StackTraceWriter stackTraceWriter,
245 boolean trimStackTraces,
246 ForkedProcessEventType eventType,
247 @SuppressWarnings("SameParameterValue") boolean sync) {
248 CharsetEncoder encoder = newCharsetEncoder();
249 StackTrace stackTraceWrapper = new StackTrace(stackTraceWriter, trimStackTraces);
250 int bufferMaxLength = estimateBufferLength(
251 eventType.getOpcode().length(),
252 null,
253 encoder,
254 0,
255 0,
256 stackTraceWrapper.message,
257 stackTraceWrapper.smartTrimmedStackTrace,
258 stackTraceWrapper.stackTrace);
259 ByteBuffer result = ByteBuffer.allocate(bufferMaxLength);
260
261 encodeHeader(result, eventType);
262 encodeCharset(result);
263 encode(encoder, result, stackTraceWrapper);
264 write(result, sync);
265 }
266
267
268
269 private void encode(
270 ForkedProcessEventType operation,
271 ReportEntry reportEntry,
272 boolean trimStackTraces,
273 @SuppressWarnings("SameParameterValue") boolean sync) {
274 ByteBuffer result = encode(operation, reportEntry, trimStackTraces);
275 write(result, sync);
276 }
277
278 private void encodeOpcode(ForkedProcessEventType eventType, boolean sync) {
279 int bufferMaxLength = estimateBufferLength(eventType.getOpcode().length(), null, null, 0, 0);
280 ByteBuffer result = ByteBuffer.allocate(bufferMaxLength);
281 encodeHeader(result, eventType);
282 write(result, sync);
283 }
284
285 @Override
286 protected void write(ByteBuffer frame, boolean sync) {
287 final boolean wasInterrupted = Thread.interrupted();
288 try {
289 super.write(frame, sync);
290 } catch (ClosedChannelException e) {
291 if (!onExit) {
292 String event = new String(
293 frame.array(),
294 frame.arrayOffset() + ((Buffer) frame).position(),
295 frame.remaining(),
296 getCharset());
297
298 DumpErrorSingleton.getSingleton()
299 .dumpException(e, "Channel closed while writing the event '" + event + "'.");
300 }
301 } catch (IOException e) {
302 if (trouble.compareAndSet(false, true)) {
303 DumpErrorSingleton.getSingleton().dumpException(e);
304 }
305 } finally {
306 if (wasInterrupted) {
307 Thread.currentThread().interrupt();
308 }
309 }
310 }
311
312 private void encode(CharsetEncoder encoder, ByteBuffer result, StackTrace stw) {
313 encode(encoder, result, stw.message, stw.smartTrimmedStackTrace, stw.stackTrace);
314 }
315
316 private void encode(
317 CharsetEncoder encoder, ByteBuffer result, String message, String smartStackTrace, String stackTrace) {
318 encodeString(encoder, result, message);
319 encodeString(encoder, result, smartStackTrace);
320 encodeString(encoder, result, stackTrace);
321 }
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336 ByteBuffer encode(ForkedProcessEventType operation, ReportEntry reportEntry, boolean trimStackTraces) {
337 StackTrace stackTraceWrapper = new StackTrace(reportEntry.getStackTraceWriter(), trimStackTraces);
338
339 CharsetEncoder encoder = newCharsetEncoder();
340
341 int bufferMaxLength = estimateBufferLength(
342 operation.getOpcode().length(),
343 reportEntry.getRunMode(),
344 encoder,
345 1,
346 1,
347 reportEntry.getSourceName(),
348 reportEntry.getSourceText(),
349 reportEntry.getName(),
350 reportEntry.getNameText(),
351 reportEntry.getGroup(),
352 reportEntry.getMessage(),
353 stackTraceWrapper.message,
354 stackTraceWrapper.smartTrimmedStackTrace,
355 stackTraceWrapper.stackTrace);
356
357 ByteBuffer result = ByteBuffer.allocate(bufferMaxLength);
358
359 encodeHeader(result, operation, reportEntry.getRunMode(), reportEntry.getTestRunId());
360 encodeCharset(result);
361
362 encodeString(encoder, result, reportEntry.getSourceName());
363 encodeString(encoder, result, reportEntry.getSourceText());
364 encodeString(encoder, result, reportEntry.getName());
365 encodeString(encoder, result, reportEntry.getNameText());
366 encodeString(encoder, result, reportEntry.getGroup());
367 encodeString(encoder, result, reportEntry.getMessage());
368 encodeInteger(result, reportEntry.getElapsed());
369
370 encode(encoder, result, stackTraceWrapper);
371
372 return result;
373 }
374
375 ByteBuffer encodeMessage(
376 ForkedProcessEventType eventType, RunMode runMode, Long testRunId, String message, String stackCall) {
377 CharsetEncoder encoder = newCharsetEncoder();
378 int bufferMaxLength =
379 estimateBufferLength(eventType.getOpcode().length(), runMode, encoder, 0, 1, message, stackCall);
380 ByteBuffer result = ByteBuffer.allocate(bufferMaxLength);
381 encode(encoder, result, eventType, runMode, testRunId, message, stackCall);
382 return result;
383 }
384
385 ByteBuffer encodeMessage(ForkedProcessEventType eventType, String message) {
386 CharsetEncoder encoder = newCharsetEncoder();
387 int bufferMaxLength = estimateBufferLength(eventType.getOpcode().length(), null, encoder, 0, 0, message);
388 ByteBuffer result = ByteBuffer.allocate(bufferMaxLength);
389 encode(encoder, result, eventType, message);
390 return result;
391 }
392
393 private static String toStackTrace(StackTraceWriter stw, boolean trimStackTraces) {
394 if (stw == null) {
395 return null;
396 }
397
398 return trimStackTraces ? stw.writeTrimmedTraceToString() : stw.writeTraceToString();
399 }
400
401 private static final class StackTrace {
402 final String message;
403 final String smartTrimmedStackTrace;
404 final String stackTrace;
405
406 StackTrace(StackTraceWriter stackTraceWriter, boolean trimStackTraces) {
407 SafeThrowable throwable = stackTraceWriter == null ? null : stackTraceWriter.getThrowable();
408 message = throwable == null ? null : throwable.getLocalizedMessage();
409 smartTrimmedStackTrace = stackTraceWriter == null ? null : stackTraceWriter.smartTrimmedStackTrace();
410 stackTrace = stackTraceWriter == null ? null : toStackTrace(stackTraceWriter, trimStackTraces);
411 }
412 }
413 }