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);
169 }
170
171 private void setOutErr(ForkedProcessEventType eventType, RunMode runMode, Long testRunId, String message) {
172 ByteBuffer result = encodeMessage(eventType, runMode, testRunId, message);
173 write(result, false);
174 }
175
176 @Override
177 public void consoleInfoLog(String message) {
178 ByteBuffer result = encodeMessage(BOOTERCODE_CONSOLE_INFO, message);
179 write(result, true);
180 }
181
182 @Override
183 public void consoleErrorLog(String message) {
184 consoleErrorLog(message, null);
185 }
186
187 @Override
188 public void consoleErrorLog(Throwable t) {
189 consoleErrorLog(t.getLocalizedMessage(), t);
190 }
191
192 @Override
193 public void consoleErrorLog(String message, Throwable t) {
194 CharsetEncoder encoder = newCharsetEncoder();
195 String stackTrace = t == null ? null : ConsoleLoggerUtils.toString(t);
196 int bufferMaxLength = estimateBufferLength(
197 BOOTERCODE_CONSOLE_ERROR.getOpcode().length(), null, encoder, 0, 0, message, null, stackTrace);
198 ByteBuffer result = ByteBuffer.allocate(bufferMaxLength);
199 encodeHeader(result, BOOTERCODE_CONSOLE_ERROR);
200 encodeCharset(result);
201 encode(encoder, result, message, null, stackTrace);
202 write(result, true);
203 }
204
205 @Override
206 public void consoleErrorLog(StackTraceWriter stackTraceWriter, boolean trimStackTraces) {
207 error(stackTraceWriter, trimStackTraces, BOOTERCODE_CONSOLE_ERROR, true);
208 }
209
210 @Override
211 public void consoleDebugLog(String message) {
212 ByteBuffer result = encodeMessage(BOOTERCODE_CONSOLE_DEBUG, message);
213 write(result, true);
214 }
215
216 @Override
217 public void consoleWarningLog(String message) {
218 ByteBuffer result = encodeMessage(BOOTERCODE_CONSOLE_WARNING, message);
219 write(result, true);
220 }
221
222 @Override
223 public void bye() {
224 encodeOpcode(BOOTERCODE_BYE, true);
225 }
226
227 @Override
228 public void stopOnNextTest() {
229 encodeOpcode(BOOTERCODE_STOP_ON_NEXT_TEST, true);
230 }
231
232 @Override
233 public void acquireNextTest() {
234 encodeOpcode(BOOTERCODE_NEXT_TEST, true);
235 }
236
237 @Override
238 public void sendExitError(StackTraceWriter stackTraceWriter, boolean trimStackTraces) {
239 error(stackTraceWriter, trimStackTraces, BOOTERCODE_JVM_EXIT_ERROR, true);
240 }
241
242 private void error(
243 StackTraceWriter stackTraceWriter,
244 boolean trimStackTraces,
245 ForkedProcessEventType eventType,
246 @SuppressWarnings("SameParameterValue") boolean sync) {
247 CharsetEncoder encoder = newCharsetEncoder();
248 StackTrace stackTraceWrapper = new StackTrace(stackTraceWriter, trimStackTraces);
249 int bufferMaxLength = estimateBufferLength(
250 eventType.getOpcode().length(),
251 null,
252 encoder,
253 0,
254 0,
255 stackTraceWrapper.message,
256 stackTraceWrapper.smartTrimmedStackTrace,
257 stackTraceWrapper.stackTrace);
258 ByteBuffer result = ByteBuffer.allocate(bufferMaxLength);
259
260 encodeHeader(result, eventType);
261 encodeCharset(result);
262 encode(encoder, result, stackTraceWrapper);
263 write(result, sync);
264 }
265
266
267
268 private void encode(
269 ForkedProcessEventType operation,
270 ReportEntry reportEntry,
271 boolean trimStackTraces,
272 @SuppressWarnings("SameParameterValue") boolean sync) {
273 ByteBuffer result = encode(operation, reportEntry, trimStackTraces);
274 write(result, sync);
275 }
276
277 private void encodeOpcode(ForkedProcessEventType eventType, boolean sync) {
278 int bufferMaxLength = estimateBufferLength(eventType.getOpcode().length(), null, null, 0, 0);
279 ByteBuffer result = ByteBuffer.allocate(bufferMaxLength);
280 encodeHeader(result, eventType);
281 write(result, sync);
282 }
283
284 @Override
285 protected void write(ByteBuffer frame, boolean sync) {
286 final boolean wasInterrupted = Thread.interrupted();
287 try {
288 super.write(frame, sync);
289 } catch (ClosedChannelException e) {
290 if (!onExit) {
291 String event = new String(
292 frame.array(),
293 frame.arrayOffset() + ((Buffer) frame).position(),
294 frame.remaining(),
295 getCharset());
296
297 DumpErrorSingleton.getSingleton()
298 .dumpException(e, "Channel closed while writing the event '" + event + "'.");
299 }
300 } catch (IOException e) {
301 if (trouble.compareAndSet(false, true)) {
302 DumpErrorSingleton.getSingleton().dumpException(e);
303 }
304 } finally {
305 if (wasInterrupted) {
306 Thread.currentThread().interrupt();
307 }
308 }
309 }
310
311 private void encode(CharsetEncoder encoder, ByteBuffer result, StackTrace stw) {
312 encode(encoder, result, stw.message, stw.smartTrimmedStackTrace, stw.stackTrace);
313 }
314
315 private void encode(
316 CharsetEncoder encoder, ByteBuffer result, String message, String smartStackTrace, String stackTrace) {
317 encodeString(encoder, result, message);
318 encodeString(encoder, result, smartStackTrace);
319 encodeString(encoder, result, stackTrace);
320 }
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335 ByteBuffer encode(ForkedProcessEventType operation, ReportEntry reportEntry, boolean trimStackTraces) {
336 StackTrace stackTraceWrapper = new StackTrace(reportEntry.getStackTraceWriter(), trimStackTraces);
337
338 CharsetEncoder encoder = newCharsetEncoder();
339
340 int bufferMaxLength = estimateBufferLength(
341 operation.getOpcode().length(),
342 reportEntry.getRunMode(),
343 encoder,
344 1,
345 1,
346 reportEntry.getSourceName(),
347 reportEntry.getSourceText(),
348 reportEntry.getName(),
349 reportEntry.getNameText(),
350 reportEntry.getGroup(),
351 reportEntry.getMessage(),
352 stackTraceWrapper.message,
353 stackTraceWrapper.smartTrimmedStackTrace,
354 stackTraceWrapper.stackTrace);
355
356 ByteBuffer result = ByteBuffer.allocate(bufferMaxLength);
357
358 encodeHeader(result, operation, reportEntry.getRunMode(), reportEntry.getTestRunId());
359 encodeCharset(result);
360
361 encodeString(encoder, result, reportEntry.getSourceName());
362 encodeString(encoder, result, reportEntry.getSourceText());
363 encodeString(encoder, result, reportEntry.getName());
364 encodeString(encoder, result, reportEntry.getNameText());
365 encodeString(encoder, result, reportEntry.getGroup());
366 encodeString(encoder, result, reportEntry.getMessage());
367 encodeInteger(result, reportEntry.getElapsed());
368
369 encode(encoder, result, stackTraceWrapper);
370
371 return result;
372 }
373
374 ByteBuffer encodeMessage(ForkedProcessEventType eventType, RunMode runMode, Long testRunId, String message) {
375 CharsetEncoder encoder = newCharsetEncoder();
376 int bufferMaxLength = estimateBufferLength(eventType.getOpcode().length(), runMode, encoder, 0, 1, message);
377 ByteBuffer result = ByteBuffer.allocate(bufferMaxLength);
378 encode(encoder, result, eventType, runMode, testRunId, message);
379 return result;
380 }
381
382 ByteBuffer encodeMessage(ForkedProcessEventType eventType, String message) {
383 CharsetEncoder encoder = newCharsetEncoder();
384 int bufferMaxLength = estimateBufferLength(eventType.getOpcode().length(), null, encoder, 0, 0, message);
385 ByteBuffer result = ByteBuffer.allocate(bufferMaxLength);
386 encode(encoder, result, eventType, message);
387 return result;
388 }
389
390 private static String toStackTrace(StackTraceWriter stw, boolean trimStackTraces) {
391 if (stw == null) {
392 return null;
393 }
394
395 return trimStackTraces ? stw.writeTrimmedTraceToString() : stw.writeTraceToString();
396 }
397
398 private static final class StackTrace {
399 final String message;
400 final String smartTrimmedStackTrace;
401 final String stackTrace;
402
403 StackTrace(StackTraceWriter stackTraceWriter, boolean trimStackTraces) {
404 SafeThrowable throwable = stackTraceWriter == null ? null : stackTraceWriter.getThrowable();
405 message = throwable == null ? null : throwable.getLocalizedMessage();
406 smartTrimmedStackTrace = stackTraceWriter == null ? null : stackTraceWriter.smartTrimmedStackTrace();
407 stackTrace = stackTraceWriter == null ? null : toStackTrace(stackTraceWriter, trimStackTraces);
408 }
409 }
410 }