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