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