View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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   * magic number : opcode : run mode [: opcode specific data]*
70   * <br>
71   *
72   * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
73   * @since 3.0.0-M4
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       * The encoder for events.
82       *
83       * @param out     the channel available for writing the events
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             // :maven-surefire-event:sys-prop:<runMode>:<testRunId>:UTF-8:<integer>:<key>:<integer>:<value>:
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     // example
267     // :maven-surefire-event:testset-starting:rerun-test-after-failure:1:5:UTF-8:<integer>:SourceName:<integer>:SourceText:<integer>:Name:<integer>:NameText:<integer>:Group:<integer>:Message:<integer>:ElapsedTime:<integer>:LocalizedMessage:<integer>:SmartTrimmedStackTrace:<integer>:toStackTrace( stw, trimStackTraces ):<integer>:
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      * Used operations:<br>
324      * <ul>
325      * <li>{@link ForkedProcessEventType#BOOTERCODE_TESTSET_STARTING},</li>
326      * <li>{@link ForkedProcessEventType#BOOTERCODE_TESTSET_COMPLETED},</li>
327      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_STARTING},</li>
328      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_SUCCEEDED},</li>
329      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_FAILED},</li>
330      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_ERROR},</li>
331      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_SKIPPED},</li>
332      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_ASSUMPTIONFAILURE}.</li>
333      * </ul>
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 }