View Javadoc
1   package org.apache.maven.plugin.surefire.booterclient.output;
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 java.io.BufferedReader;
23  import java.io.IOException;
24  import java.io.StringReader;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.NoSuchElementException;
29  import java.util.Properties;
30  import java.util.StringTokenizer;
31  
32  import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStream;
33  import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
34  import org.apache.maven.shared.utils.cli.StreamConsumer;
35  import org.apache.maven.surefire.booter.ForkingRunListener;
36  import org.apache.maven.surefire.report.CategorizedReportEntry;
37  import org.apache.maven.surefire.report.ConsoleLogger;
38  import org.apache.maven.surefire.report.ConsoleOutputReceiver;
39  import org.apache.maven.surefire.report.ReportEntry;
40  import org.apache.maven.surefire.report.ReporterException;
41  import org.apache.maven.surefire.report.RunListener;
42  import org.apache.maven.surefire.report.StackTraceWriter;
43  import org.apache.maven.surefire.util.internal.StringUtils;
44  
45  /**
46   * Knows how to reconstruct *all* the state transmitted over stdout by the forked process.
47   *
48   * @author Kristian Rosenvold
49   */
50  public class ForkClient
51      implements StreamConsumer
52  {
53  
54      private final DefaultReporterFactory defaultReporterFactory;
55  
56      private final TestProvidingInputStream testProvidingInputStream;
57  
58      private final Map<Integer, RunListener> testSetReporters =
59          Collections.synchronizedMap( new HashMap<Integer, RunListener>() );
60  
61      private final Properties testVmSystemProperties;
62  
63      private volatile boolean saidGoodBye = false;
64  
65      private volatile StackTraceWriter errorInFork = null;
66  
67      public ForkClient( DefaultReporterFactory defaultReporterFactory, Properties testVmSystemProperties )
68      {
69          this( defaultReporterFactory, testVmSystemProperties, null );
70      }
71  
72      public ForkClient( DefaultReporterFactory defaultReporterFactory, Properties testVmSystemProperties,
73                         TestProvidingInputStream testProvidingInputStream )
74      {
75          this.defaultReporterFactory = defaultReporterFactory;
76          this.testVmSystemProperties = testVmSystemProperties;
77          this.testProvidingInputStream = testProvidingInputStream;
78      }
79  
80      public DefaultReporterFactory getDefaultReporterFactory()
81      {
82          return defaultReporterFactory;
83      }
84  
85      public void consumeLine( String s )
86      {
87          try
88          {
89              if ( s.length() == 0 )
90              {
91                  return;
92              }
93              final byte operationId = (byte) s.charAt( 0 );
94              int commma = s.indexOf( ",", 3 );
95              if ( commma < 0 )
96              {
97                  System.out.println( s );
98                  return;
99              }
100             final Integer channelNumber = Integer.parseInt( s.substring( 2, commma ), 16 );
101             int rest = s.indexOf( ",", commma );
102             final String remaining = s.substring( rest + 1 );
103 
104             switch ( operationId )
105             {
106                 case ForkingRunListener.BOOTERCODE_TESTSET_STARTING:
107                     getOrCreateReporter( channelNumber ).testSetStarting( createReportEntry( remaining ) );
108                     break;
109                 case ForkingRunListener.BOOTERCODE_TESTSET_COMPLETED:
110                     getOrCreateReporter( channelNumber ).testSetCompleted( createReportEntry( remaining ) );
111                     break;
112                 case ForkingRunListener.BOOTERCODE_TEST_STARTING:
113                     getOrCreateReporter( channelNumber ).testStarting( createReportEntry( remaining ) );
114                     break;
115                 case ForkingRunListener.BOOTERCODE_TEST_SUCCEEDED:
116                     getOrCreateReporter( channelNumber ).testSucceeded( createReportEntry( remaining ) );
117                     break;
118                 case ForkingRunListener.BOOTERCODE_TEST_FAILED:
119                     getOrCreateReporter( channelNumber ).testFailed( createReportEntry( remaining ) );
120                     break;
121                 case ForkingRunListener.BOOTERCODE_TEST_SKIPPED:
122                     getOrCreateReporter( channelNumber ).testSkipped( createReportEntry( remaining ) );
123                     break;
124                 case ForkingRunListener.BOOTERCODE_TEST_ERROR:
125                     getOrCreateReporter( channelNumber ).testError( createReportEntry( remaining ) );
126                     break;
127                 case ForkingRunListener.BOOTERCODE_TEST_ASSUMPTIONFAILURE:
128                     getOrCreateReporter( channelNumber ).testAssumptionFailure( createReportEntry( remaining ) );
129                     break;
130                 case ForkingRunListener.BOOTERCODE_SYSPROPS:
131                     int keyEnd = remaining.indexOf( "," );
132                     StringBuilder key = new StringBuilder();
133                     StringBuilder value = new StringBuilder();
134                     StringUtils.unescapeString( key, remaining.substring( 0, keyEnd ) );
135                     StringUtils.unescapeString( value, remaining.substring( keyEnd + 1 ) );
136 
137                     synchronized ( testVmSystemProperties )
138                     {
139                         testVmSystemProperties.put( key.toString(), value.toString() );
140                     }
141                     break;
142                 case ForkingRunListener.BOOTERCODE_STDOUT:
143                     byte[] bytes = new byte[remaining.length()];
144                     int len = StringUtils.unescapeBytes( bytes, remaining );
145                     getOrCreateConsoleOutputReceiver( channelNumber ).writeTestOutput( bytes, 0, len, true );
146                     break;
147                 case ForkingRunListener.BOOTERCODE_STDERR:
148                     bytes = new byte[remaining.length()];
149                     len = StringUtils.unescapeBytes( bytes, remaining );
150                     getOrCreateConsoleOutputReceiver( channelNumber ).writeTestOutput( bytes, 0, len, false );
151                     break;
152                 case ForkingRunListener.BOOTERCODE_CONSOLE:
153                     getOrCreateConsoleLogger( channelNumber ).info( createConsoleMessage( remaining ) );
154                     break;
155                 case ForkingRunListener.BOOTERCODE_NEXT_TEST:
156                     if ( null != testProvidingInputStream )
157                     {
158                         testProvidingInputStream.provideNewTest();
159                     }
160                     break;
161                 case ForkingRunListener.BOOTERCODE_ERROR:
162                     errorInFork = deserializeStackStraceWriter( new StringTokenizer( remaining, "," ) );
163                     break;
164                 case ForkingRunListener.BOOTERCODE_BYE:
165                     saidGoodBye = true;
166                     break;
167                 default:
168                     System.out.println( s );
169             }
170         }
171         catch ( NumberFormatException e )
172         {
173             System.out.println( "SUREFIRE-859: " + s );
174         }
175         catch ( NoSuchElementException e )
176         {
177             System.out.println( "SUREFIRE-859: " + s );
178         }
179         catch ( ReporterException e )
180         {
181             throw new RuntimeException( e );
182         }
183     }
184 
185     public void consumeMultiLineContent( String s )
186         throws IOException
187     {
188         BufferedReader stringReader = new BufferedReader( new StringReader( s ) );
189         String s1;
190         while ( ( s1 = stringReader.readLine() ) != null )
191         {
192             consumeLine( s1 );
193         }
194     }
195 
196     private String createConsoleMessage( String remaining )
197     {
198         return unescape( remaining );
199     }
200 
201     private ReportEntry createReportEntry( String untokenized )
202     {
203         StringTokenizer tokens = new StringTokenizer( untokenized, "," );
204         try
205         {
206             String source = nullableCsv( tokens.nextToken() );
207             String name = nullableCsv( tokens.nextToken() );
208             String group = nullableCsv( tokens.nextToken() );
209             String message = nullableCsv( tokens.nextToken() );
210             String elapsedStr = tokens.nextToken();
211             Integer elapsed = "null".equals( elapsedStr ) ? null : Integer.decode( elapsedStr );
212             final StackTraceWriter stackTraceWriter =
213                 tokens.hasMoreTokens() ? deserializeStackStraceWriter( tokens ) : null;
214 
215             return CategorizedReportEntry.reportEntry( source, name, group, stackTraceWriter, elapsed, message );
216         }
217         catch ( RuntimeException e )
218         {
219             throw new RuntimeException( untokenized, e );
220         }
221     }
222 
223     private StackTraceWriter deserializeStackStraceWriter( StringTokenizer tokens )
224     {
225         StackTraceWriter stackTraceWriter;
226         String stackTraceMessage = nullableCsv( tokens.nextToken() );
227         String smartStackTrace = nullableCsv( tokens.nextToken() );
228         String stackTrace = tokens.hasMoreTokens() ? nullableCsv( tokens.nextToken() ) : null;
229         stackTraceWriter =
230             stackTrace != null ? new DeserializedStacktraceWriter( stackTraceMessage, smartStackTrace, stackTrace )
231                             : null;
232         return stackTraceWriter;
233     }
234 
235     private String nullableCsv( String source )
236     {
237         if ( "null".equals( source ) )
238         {
239             return null;
240         }
241         return unescape( source );
242     }
243 
244     private String unescape( String source )
245     {
246         StringBuilder stringBuffer = new StringBuilder( source.length() );
247 
248         StringUtils.unescapeString( stringBuffer, source );
249         return stringBuffer.toString();
250     }
251 
252     /**
253      * Used when getting reporters on the plugin side of a fork.
254      *
255      * @param channelNumber The logical channel number
256      * @return A mock provider reporter
257      */
258     public RunListener getReporter( Integer channelNumber )
259     {
260         return testSetReporters.get( channelNumber );
261     }
262 
263     private RunListener getOrCreateReporter( Integer channelNumber )
264     {
265         RunListener reporter = testSetReporters.get( channelNumber );
266         if ( reporter == null )
267         {
268             reporter = defaultReporterFactory.createReporter();
269             testSetReporters.put( channelNumber, reporter );
270         }
271         return reporter;
272     }
273 
274     private ConsoleOutputReceiver getOrCreateConsoleOutputReceiver( Integer channelNumber )
275     {
276         return (ConsoleOutputReceiver) getOrCreateReporter( channelNumber );
277     }
278 
279     private ConsoleLogger getOrCreateConsoleLogger( Integer channelNumber )
280     {
281         return (ConsoleLogger) getOrCreateReporter( channelNumber );
282     }
283 
284     public void close( boolean hadTimeout )
285     {
286     }
287 
288     public boolean isSaidGoodBye()
289     {
290         return saidGoodBye;
291     }
292 
293     public StackTraceWriter getErrorInFork()
294     {
295         return errorInFork;
296     }
297 
298     public boolean isErrorInFork()
299     {
300         return errorInFork != null;
301     }
302 }