1 package org.apache.maven.plugin.surefire.booterclient.output;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.BufferedReader;
23 import java.io.IOException;
24 import java.io.StringReader;
25 import java.nio.ByteBuffer;
26 import java.util.Map;
27 import java.util.NoSuchElementException;
28 import java.util.Properties;
29 import java.util.StringTokenizer;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.atomic.AtomicLong;
32
33 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
34 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
35 import org.apache.maven.shared.utils.cli.StreamConsumer;
36 import org.apache.maven.surefire.booter.ForkingRunListener;
37 import org.apache.maven.surefire.report.CategorizedReportEntry;
38 import org.apache.maven.surefire.report.ConsoleLogger;
39 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
40 import org.apache.maven.surefire.report.ReportEntry;
41 import org.apache.maven.surefire.report.ReporterException;
42 import org.apache.maven.surefire.report.RunListener;
43 import org.apache.maven.surefire.report.StackTraceWriter;
44 import org.apache.maven.surefire.util.internal.StringUtils;
45
46 import static org.apache.maven.surefire.booter.Shutdown.KILL;
47
48
49
50
51
52
53 public class ForkClient
54 implements StreamConsumer
55 {
56 private static final long START_TIME_ZERO = 0L;
57 private static final long START_TIME_NEGATIVE_TIMEOUT = -1L;
58
59 private final DefaultReporterFactory defaultReporterFactory;
60
61 private final NotifiableTestStream notifiableTestStream;
62
63 private final Map<Integer, RunListener> testSetReporters = new ConcurrentHashMap<Integer, RunListener>();
64
65 private final Properties testVmSystemProperties;
66
67
68
69
70
71 private final AtomicLong testSetStartedAt = new AtomicLong( START_TIME_ZERO );
72
73 private volatile boolean saidGoodBye;
74
75 private volatile StackTraceWriter errorInFork;
76
77 public ForkClient( DefaultReporterFactory defaultReporterFactory, Properties testVmSystemProperties,
78 NotifiableTestStream notifiableTestStream )
79 {
80 this.defaultReporterFactory = defaultReporterFactory;
81 this.testVmSystemProperties = testVmSystemProperties;
82 this.notifiableTestStream = notifiableTestStream;
83 }
84
85 protected void stopOnNextTest()
86 {
87 }
88
89
90
91
92 public final void tryToTimeout( long currentTimeMillis, int forkedProcessTimeoutInSeconds )
93 {
94 if ( forkedProcessTimeoutInSeconds > 0 )
95 {
96 final long forkedProcessTimeoutInMillis = 1000 * forkedProcessTimeoutInSeconds;
97 final long startedAt = testSetStartedAt.get();
98 if ( startedAt > START_TIME_ZERO && currentTimeMillis - startedAt >= forkedProcessTimeoutInMillis )
99 {
100 testSetStartedAt.set( START_TIME_NEGATIVE_TIMEOUT );
101 notifiableTestStream.shutdown( KILL );
102 }
103 }
104 }
105
106 public final DefaultReporterFactory getDefaultReporterFactory()
107 {
108 return defaultReporterFactory;
109 }
110
111 public final void consumeLine( String s )
112 {
113 if ( StringUtils.isNotBlank( s ) )
114 {
115 processLine( s );
116 }
117 }
118
119 private void setCurrentStartTime()
120 {
121 if ( testSetStartedAt.get() == START_TIME_ZERO )
122 {
123
124
125 testSetStartedAt.compareAndSet( START_TIME_ZERO, System.currentTimeMillis() );
126 }
127 }
128
129 public final boolean hadTimeout()
130 {
131 return testSetStartedAt.get() == START_TIME_NEGATIVE_TIMEOUT;
132 }
133
134 private void processLine( String s )
135 {
136 try
137 {
138 final byte operationId = (byte) s.charAt( 0 );
139 int commma = s.indexOf( ",", 3 );
140 if ( commma < 0 )
141 {
142 System.out.println( s );
143 return;
144 }
145 final int channelNumber = Integer.parseInt( s.substring( 2, commma ), 16 );
146 int rest = s.indexOf( ",", commma );
147 final String remaining = s.substring( rest + 1 );
148
149 switch ( operationId )
150 {
151 case ForkingRunListener.BOOTERCODE_TESTSET_STARTING:
152 getOrCreateReporter( channelNumber ).testSetStarting( createReportEntry( remaining ) );
153 setCurrentStartTime();
154 break;
155 case ForkingRunListener.BOOTERCODE_TESTSET_COMPLETED:
156 getOrCreateReporter( channelNumber ).testSetCompleted( createReportEntry( remaining ) );
157 break;
158 case ForkingRunListener.BOOTERCODE_TEST_STARTING:
159 getOrCreateReporter( channelNumber ).testStarting( createReportEntry( remaining ) );
160 break;
161 case ForkingRunListener.BOOTERCODE_TEST_SUCCEEDED:
162 getOrCreateReporter( channelNumber ).testSucceeded( createReportEntry( remaining ) );
163 break;
164 case ForkingRunListener.BOOTERCODE_TEST_FAILED:
165 getOrCreateReporter( channelNumber ).testFailed( createReportEntry( remaining ) );
166 break;
167 case ForkingRunListener.BOOTERCODE_TEST_SKIPPED:
168 getOrCreateReporter( channelNumber ).testSkipped( createReportEntry( remaining ) );
169 break;
170 case ForkingRunListener.BOOTERCODE_TEST_ERROR:
171 getOrCreateReporter( channelNumber ).testError( createReportEntry( remaining ) );
172 break;
173 case ForkingRunListener.BOOTERCODE_TEST_ASSUMPTIONFAILURE:
174 getOrCreateReporter( channelNumber ).testAssumptionFailure( createReportEntry( remaining ) );
175 break;
176 case ForkingRunListener.BOOTERCODE_SYSPROPS:
177 int keyEnd = remaining.indexOf( "," );
178 StringBuilder key = new StringBuilder();
179 StringBuilder value = new StringBuilder();
180 StringUtils.unescapeString( key, remaining.substring( 0, keyEnd ) );
181 StringUtils.unescapeString( value, remaining.substring( keyEnd + 1 ) );
182
183 synchronized ( testVmSystemProperties )
184 {
185 testVmSystemProperties.put( key.toString(), value.toString() );
186 }
187 break;
188 case ForkingRunListener.BOOTERCODE_STDOUT:
189 writeTestOutput( channelNumber, remaining, true );
190 break;
191 case ForkingRunListener.BOOTERCODE_STDERR:
192 writeTestOutput( channelNumber, remaining, false );
193 break;
194 case ForkingRunListener.BOOTERCODE_CONSOLE:
195 getOrCreateConsoleLogger( channelNumber ).info( createConsoleMessage( remaining ) );
196 break;
197 case ForkingRunListener.BOOTERCODE_NEXT_TEST:
198 notifiableTestStream.provideNewTest();
199 break;
200 case ForkingRunListener.BOOTERCODE_ERROR:
201 errorInFork = deserializeStackTraceWriter( new StringTokenizer( remaining, "," ) );
202 break;
203 case ForkingRunListener.BOOTERCODE_BYE:
204 saidGoodBye = true;
205 break;
206 case ForkingRunListener.BOOTERCODE_STOP_ON_NEXT_TEST:
207 stopOnNextTest();
208 break;
209 default:
210 System.out.println( s );
211 }
212 }
213 catch ( NumberFormatException e )
214 {
215
216 System.out.println( s );
217 }
218 catch ( NoSuchElementException e )
219 {
220
221 System.out.println( s );
222 }
223 catch ( ReporterException e )
224 {
225 throw new RuntimeException( e );
226 }
227 }
228
229 private void writeTestOutput( final int channelNumber, final String remaining, boolean isStdout )
230 {
231 int csNameEnd = remaining.indexOf( ',' );
232 String charsetName = remaining.substring( 0, csNameEnd );
233 String byteEncoded = remaining.substring( csNameEnd + 1 );
234 ByteBuffer unescaped = StringUtils.unescapeBytes( byteEncoded, charsetName );
235
236 if ( unescaped.hasArray() )
237 {
238 byte[] convertedBytes = unescaped.array();
239
240 getOrCreateConsoleOutputReceiver( channelNumber )
241 .writeTestOutput( convertedBytes, unescaped.position(), unescaped.remaining(), isStdout );
242 }
243 else
244 {
245 byte[] convertedBytes = new byte[unescaped.remaining()];
246 unescaped.get( convertedBytes, 0, unescaped.remaining() );
247
248 getOrCreateConsoleOutputReceiver( channelNumber )
249 .writeTestOutput( convertedBytes, 0, convertedBytes.length, isStdout );
250 }
251 }
252
253 public final void consumeMultiLineContent( String s )
254 throws IOException
255 {
256 BufferedReader stringReader = new BufferedReader( new StringReader( s ) );
257 for ( String s1 = stringReader.readLine(); s1 != null; s1 = stringReader.readLine() )
258 {
259 consumeLine( s1 );
260 }
261 }
262
263 private String createConsoleMessage( String remaining )
264 {
265 return unescape( remaining );
266 }
267
268 private ReportEntry createReportEntry( String untokenized )
269 {
270 StringTokenizer tokens = new StringTokenizer( untokenized, "," );
271 try
272 {
273 String source = nullableCsv( tokens.nextToken() );
274 String name = nullableCsv( tokens.nextToken() );
275 String group = nullableCsv( tokens.nextToken() );
276 String message = nullableCsv( tokens.nextToken() );
277 String elapsedStr = tokens.nextToken();
278 Integer elapsed = "null".equals( elapsedStr ) ? null : Integer.decode( elapsedStr );
279 final StackTraceWriter stackTraceWriter =
280 tokens.hasMoreTokens() ? deserializeStackTraceWriter( tokens ) : null;
281
282 return CategorizedReportEntry.reportEntry( source, name, group, stackTraceWriter, elapsed, message );
283 }
284 catch ( RuntimeException e )
285 {
286 throw new RuntimeException( untokenized, e );
287 }
288 }
289
290 private StackTraceWriter deserializeStackTraceWriter( StringTokenizer tokens )
291 {
292 String stackTraceMessage = nullableCsv( tokens.nextToken() );
293 String smartStackTrace = nullableCsv( tokens.nextToken() );
294 String stackTrace = tokens.hasMoreTokens() ? nullableCsv( tokens.nextToken() ) : null;
295 return stackTrace != null
296 ? new DeserializedStacktraceWriter( stackTraceMessage, smartStackTrace, stackTrace )
297 : null;
298 }
299
300 private String nullableCsv( String source )
301 {
302 return "null".equals( source ) ? null : unescape( source );
303 }
304
305 private String unescape( String source )
306 {
307 StringBuilder stringBuffer = new StringBuilder( source.length() );
308 StringUtils.unescapeString( stringBuffer, source );
309 return stringBuffer.toString();
310 }
311
312
313
314
315
316
317
318 public final RunListener getReporter( int channelNumber )
319 {
320 return testSetReporters.get( channelNumber );
321 }
322
323 private RunListener getOrCreateReporter( int channelNumber )
324 {
325 RunListener reporter = testSetReporters.get( channelNumber );
326 if ( reporter == null )
327 {
328 reporter = defaultReporterFactory.createReporter();
329 testSetReporters.put( channelNumber, reporter );
330 }
331 return reporter;
332 }
333
334 private ConsoleOutputReceiver getOrCreateConsoleOutputReceiver( int channelNumber )
335 {
336 return (ConsoleOutputReceiver) getOrCreateReporter( channelNumber );
337 }
338
339 private ConsoleLogger getOrCreateConsoleLogger( int channelNumber )
340 {
341 return (ConsoleLogger) getOrCreateReporter( channelNumber );
342 }
343
344 public void close( boolean hadTimeout )
345 {
346 }
347
348 public final boolean isSaidGoodBye()
349 {
350 return saidGoodBye;
351 }
352
353 public final StackTraceWriter getErrorInFork()
354 {
355 return errorInFork;
356 }
357
358 public final boolean isErrorInFork()
359 {
360 return errorInFork != null;
361 }
362 }