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, 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     // example
268     // :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>:
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      * Used operations:<br>
325      * <ul>
326      * <li>{@link ForkedProcessEventType#BOOTERCODE_TESTSET_STARTING},</li>
327      * <li>{@link ForkedProcessEventType#BOOTERCODE_TESTSET_COMPLETED},</li>
328      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_STARTING},</li>
329      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_SUCCEEDED},</li>
330      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_FAILED},</li>
331      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_ERROR},</li>
332      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_SKIPPED},</li>
333      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_ASSUMPTIONFAILURE}.</li>
334      * </ul>
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 }