View Javadoc
1   package org.apache.maven.surefire.booter.spi;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.plugin.surefire.log.api.ConsoleLoggerUtils;
23  import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
24  import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
25  import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
26  import org.apache.maven.surefire.api.report.ReportEntry;
27  import org.apache.maven.surefire.api.report.RunMode;
28  import org.apache.maven.surefire.api.report.SafeThrowable;
29  import org.apache.maven.surefire.api.report.StackTraceWriter;
30  import org.apache.maven.surefire.api.report.TestOutputReportEntry;
31  import org.apache.maven.surefire.api.report.TestSetReportEntry;
32  import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
33  import org.apache.maven.surefire.booter.stream.EventEncoder;
34  
35  import javax.annotation.Nonnull;
36  import java.io.IOException;
37  import java.nio.Buffer;
38  import java.nio.ByteBuffer;
39  import java.nio.channels.ClosedChannelException;
40  import java.nio.charset.CharsetEncoder;
41  import java.util.Iterator;
42  import java.util.Map;
43  import java.util.Map.Entry;
44  import java.util.concurrent.atomic.AtomicBoolean;
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  {
78      private final AtomicBoolean trouble = new AtomicBoolean();
79      private volatile boolean onExit;
80  
81      /**
82       * The encoder for events.
83       *
84       * @param out     the channel available for writing the events
85       */
86      public EventChannelEncoder( @Nonnull WritableBufferedByteChannel out )
87      {
88          super( out );
89      }
90  
91      @Override
92      public boolean checkError()
93      {
94          return trouble.get();
95      }
96  
97      @Override
98      public void onJvmExit()
99      {
100         onExit = true;
101         write( ByteBuffer.wrap( new byte[] {'\n'} ), true );
102     }
103 
104     void encodeSystemProperties( Map<String, String> sysProps, RunMode runMode, Long testRunId )
105     {
106         CharsetEncoder encoder = newCharsetEncoder();
107         ByteBuffer result = null;
108         for ( Iterator<Entry<String, String>> it = sysProps.entrySet().iterator(); it.hasNext(); )
109         {
110             Entry<String, String> entry = it.next();
111             String key = entry.getKey();
112             String value = entry.getValue();
113 
114             int bufferLength =
115                 estimateBufferLength( BOOTERCODE_SYSPROPS.getOpcode().length(), runMode, encoder, 0, 1, key, value );
116             result = result != null && result.capacity() >= bufferLength ? result : ByteBuffer.allocate( bufferLength );
117             ( (Buffer) result ).clear();
118             // :maven-surefire-event:sys-prop:<runMode>:<testRunId>:UTF-8:<integer>:<key>:<integer>:<value>:
119             encode( encoder, result, BOOTERCODE_SYSPROPS, runMode, testRunId, key, value );
120             boolean sync = !it.hasNext();
121             write( result, sync );
122         }
123     }
124 
125     @Override
126     public void testSetStarting( TestSetReportEntry reportEntry, boolean trimStackTraces )
127     {
128         encode( BOOTERCODE_TESTSET_STARTING, reportEntry, trimStackTraces, true );
129     }
130 
131     @Override
132     public void testSetCompleted( TestSetReportEntry reportEntry, boolean trimStackTraces )
133     {
134         encodeSystemProperties( reportEntry.getSystemProperties(), reportEntry.getRunMode(), reportEntry.getTestRunId() );
135         encode( BOOTERCODE_TESTSET_COMPLETED, reportEntry, trimStackTraces, true );
136     }
137 
138     @Override
139     public void testStarting( ReportEntry reportEntry, boolean trimStackTraces )
140     {
141         encode( BOOTERCODE_TEST_STARTING, reportEntry, trimStackTraces, true );
142     }
143 
144     @Override
145     public void testSucceeded( ReportEntry reportEntry, boolean trimStackTraces )
146     {
147         encode( BOOTERCODE_TEST_SUCCEEDED, reportEntry, trimStackTraces, true );
148     }
149 
150     @Override
151     public void testFailed( ReportEntry reportEntry, boolean trimStackTraces )
152     {
153         encode( BOOTERCODE_TEST_FAILED, reportEntry, trimStackTraces, true );
154     }
155 
156     @Override
157     public void testSkipped( ReportEntry reportEntry, boolean trimStackTraces )
158     {
159         encode( BOOTERCODE_TEST_SKIPPED, reportEntry, trimStackTraces, true );
160     }
161 
162     @Override
163     public void testError( ReportEntry reportEntry, boolean trimStackTraces )
164     {
165         encode( BOOTERCODE_TEST_ERROR, reportEntry, trimStackTraces, true );
166     }
167 
168     @Override
169     public void testAssumptionFailure( ReportEntry reportEntry, boolean trimStackTraces )
170     {
171         encode( BOOTERCODE_TEST_ASSUMPTIONFAILURE, reportEntry, trimStackTraces, true );
172     }
173 
174     @Override
175     public void testOutput( TestOutputReportEntry reportEntry )
176     {
177         boolean stdout = reportEntry.isStdOut();
178         boolean newLine = reportEntry.isNewLine();
179         String msg = reportEntry.getLog();
180         ForkedProcessEventType event =
181             stdout ? ( newLine ? BOOTERCODE_STDOUT_NEW_LINE : BOOTERCODE_STDOUT )
182                 : ( newLine ? BOOTERCODE_STDERR_NEW_LINE : BOOTERCODE_STDERR );
183         setOutErr( event, reportEntry.getRunMode(), reportEntry.getTestRunId(), msg );
184     }
185 
186     private void setOutErr( ForkedProcessEventType eventType, RunMode runMode, Long testRunId, String message )
187     {
188         ByteBuffer result = encodeMessage( eventType, runMode, testRunId, message );
189         write( result, false );
190     }
191 
192     @Override
193     public void consoleInfoLog( String message )
194     {
195         ByteBuffer result = encodeMessage( BOOTERCODE_CONSOLE_INFO, message );
196         write( result, true );
197     }
198 
199     @Override
200     public void consoleErrorLog( String message )
201     {
202         consoleErrorLog( message, null );
203     }
204 
205     @Override
206     public void consoleErrorLog( Throwable t )
207     {
208         consoleErrorLog( t.getLocalizedMessage(), t );
209     }
210 
211     @Override
212     public void consoleErrorLog( String message, Throwable t )
213     {
214         CharsetEncoder encoder = newCharsetEncoder();
215         String stackTrace = t == null ? null : ConsoleLoggerUtils.toString( t );
216         int bufferMaxLength = estimateBufferLength( BOOTERCODE_CONSOLE_ERROR.getOpcode().length(), null, encoder, 0, 0,
217             message, stackTrace );
218         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
219         encodeHeader( result, BOOTERCODE_CONSOLE_ERROR );
220         encodeCharset( result );
221         encode( encoder, result, message, null, stackTrace );
222         write( result, true );
223     }
224 
225     @Override
226     public void consoleErrorLog( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
227     {
228         error( stackTraceWriter, trimStackTraces, BOOTERCODE_CONSOLE_ERROR, true );
229     }
230 
231     @Override
232     public void consoleDebugLog( String message )
233     {
234         ByteBuffer result = encodeMessage( BOOTERCODE_CONSOLE_DEBUG, message );
235         write( result, true );
236     }
237 
238     @Override
239     public void consoleWarningLog( String message )
240     {
241         ByteBuffer result = encodeMessage( BOOTERCODE_CONSOLE_WARNING, message );
242         write( result, true );
243     }
244 
245     @Override
246     public void bye()
247     {
248         encodeOpcode( BOOTERCODE_BYE, true );
249     }
250 
251     @Override
252     public void stopOnNextTest()
253     {
254         encodeOpcode( BOOTERCODE_STOP_ON_NEXT_TEST, true );
255     }
256 
257     @Override
258     public void acquireNextTest()
259     {
260         encodeOpcode( BOOTERCODE_NEXT_TEST, true );
261     }
262 
263     @Override
264     public void sendExitError( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
265     {
266         error( stackTraceWriter, trimStackTraces, BOOTERCODE_JVM_EXIT_ERROR, true );
267     }
268 
269     private void error( StackTraceWriter stackTraceWriter, boolean trimStackTraces, ForkedProcessEventType eventType,
270                         @SuppressWarnings( "SameParameterValue" ) boolean sync )
271     {
272         CharsetEncoder encoder = newCharsetEncoder();
273         StackTrace stackTraceWrapper = new StackTrace( stackTraceWriter, trimStackTraces );
274         int bufferMaxLength = estimateBufferLength( eventType.getOpcode().length(), null, encoder, 0, 0,
275             stackTraceWrapper.message, stackTraceWrapper.smartTrimmedStackTrace, stackTraceWrapper.stackTrace );
276         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
277 
278         encodeHeader( result, eventType );
279         encodeCharset( result );
280         encode( encoder, result, stackTraceWrapper );
281         write( result, sync );
282     }
283 
284     // example
285     // :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>:
286     private void encode( ForkedProcessEventType operation, ReportEntry reportEntry,
287                          boolean trimStackTraces, @SuppressWarnings( "SameParameterValue" ) boolean sync )
288     {
289         ByteBuffer result = encode( operation, reportEntry, trimStackTraces );
290         write( result, sync );
291     }
292 
293     private void encodeOpcode( ForkedProcessEventType eventType, boolean sync )
294     {
295         int bufferMaxLength = estimateBufferLength( eventType.getOpcode().length(), null, null, 0, 0 );
296         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
297         encodeHeader( result, eventType );
298         write( result, sync );
299     }
300 
301     @Override
302     protected void write( ByteBuffer frame, boolean sync )
303     {
304         final boolean wasInterrupted = Thread.interrupted();
305         try
306         {
307             super.write( frame, sync );
308         }
309         catch ( ClosedChannelException e )
310         {
311             if ( !onExit )
312             {
313                 String event = new String( frame.array(), frame.arrayOffset() + ( (Buffer) frame ).position(), frame.remaining(),
314                     getCharset() );
315 
316                 DumpErrorSingleton.getSingleton()
317                     .dumpException( e, "Channel closed while writing the event '" + event + "'." );
318             }
319         }
320         catch ( IOException e )
321         {
322             if ( trouble.compareAndSet( false, true ) )
323             {
324                 DumpErrorSingleton.getSingleton()
325                     .dumpException( e );
326             }
327         }
328         finally
329         {
330             if ( wasInterrupted )
331             {
332                 Thread.currentThread().interrupt();
333             }
334         }
335     }
336 
337     private void encode( CharsetEncoder encoder, ByteBuffer result, StackTrace stw )
338     {
339         encode( encoder, result, stw.message, stw.smartTrimmedStackTrace, stw.stackTrace );
340     }
341 
342     private void encode( CharsetEncoder encoder, ByteBuffer result,
343                          String message, String smartStackTrace, String stackTrace )
344     {
345         encodeString( encoder, result, message );
346         encodeString( encoder, result, smartStackTrace );
347         encodeString( encoder, result, stackTrace );
348     }
349 
350     /**
351      * Used operations:<br>
352      * <ul>
353      * <li>{@link ForkedProcessEventType#BOOTERCODE_TESTSET_STARTING},</li>
354      * <li>{@link ForkedProcessEventType#BOOTERCODE_TESTSET_COMPLETED},</li>
355      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_STARTING},</li>
356      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_SUCCEEDED},</li>
357      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_FAILED},</li>
358      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_ERROR},</li>
359      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_SKIPPED},</li>
360      * <li>{@link ForkedProcessEventType#BOOTERCODE_TEST_ASSUMPTIONFAILURE}.</li>
361      * </ul>
362      */
363     ByteBuffer encode( ForkedProcessEventType operation, ReportEntry reportEntry, boolean trimStackTraces )
364     {
365         StackTrace stackTraceWrapper = new StackTrace( reportEntry.getStackTraceWriter(), trimStackTraces );
366 
367         CharsetEncoder encoder = newCharsetEncoder();
368 
369         int bufferMaxLength = estimateBufferLength( operation.getOpcode().length(), reportEntry.getRunMode(), encoder,
370             1, 1, reportEntry.getSourceName(), reportEntry.getSourceText(), reportEntry.getName(),
371             reportEntry.getNameText(), reportEntry.getGroup(), reportEntry.getMessage(), stackTraceWrapper.message,
372             stackTraceWrapper.smartTrimmedStackTrace, stackTraceWrapper.stackTrace );
373 
374         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
375 
376         encodeHeader( result, operation, reportEntry.getRunMode(), reportEntry.getTestRunId() );
377         encodeCharset( result );
378 
379         encodeString( encoder, result, reportEntry.getSourceName() );
380         encodeString( encoder, result, reportEntry.getSourceText() );
381         encodeString( encoder, result, reportEntry.getName() );
382         encodeString( encoder, result, reportEntry.getNameText() );
383         encodeString( encoder, result, reportEntry.getGroup() );
384         encodeString( encoder, result, reportEntry.getMessage() );
385         encodeInteger( result, reportEntry.getElapsed() );
386 
387         encode( encoder, result, stackTraceWrapper );
388 
389         return result;
390     }
391 
392     ByteBuffer encodeMessage( ForkedProcessEventType eventType, RunMode runMode, Long testRunId, String message )
393     {
394         CharsetEncoder encoder = newCharsetEncoder();
395         int bufferMaxLength = estimateBufferLength( eventType.getOpcode().length(), runMode, encoder, 0,
396             testRunId == null ? 0 : 1, message );
397         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
398         encode( encoder, result, eventType, runMode, testRunId, message );
399         return result;
400     }
401 
402     ByteBuffer encodeMessage( ForkedProcessEventType eventType, String message )
403     {
404         CharsetEncoder encoder = newCharsetEncoder();
405         int bufferMaxLength = estimateBufferLength( eventType.getOpcode().length(), null, encoder, 0, 0, message );
406         ByteBuffer result = ByteBuffer.allocate( bufferMaxLength );
407         encode( encoder, result, eventType, message );
408         return result;
409     }
410 
411     private static String toStackTrace( StackTraceWriter stw, boolean trimStackTraces )
412     {
413         if ( stw == null )
414         {
415             return null;
416         }
417 
418         return trimStackTraces ? stw.writeTrimmedTraceToString() : stw.writeTraceToString();
419     }
420 
421     private static final class StackTrace
422     {
423         final String message;
424         final String smartTrimmedStackTrace;
425         final String stackTrace;
426 
427         StackTrace( StackTraceWriter stackTraceWriter, boolean trimStackTraces )
428         {
429             SafeThrowable throwable = stackTraceWriter == null ? null : stackTraceWriter.getThrowable();
430             message = throwable == null ? null : throwable.getLocalizedMessage();
431             smartTrimmedStackTrace = stackTraceWriter == null ? null : stackTraceWriter.smartTrimmedStackTrace();
432             stackTrace = stackTraceWriter == null ? null : toStackTrace( stackTraceWriter, trimStackTraces );
433         }
434     }
435 }