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