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 org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
23 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
24 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
25 import org.apache.maven.surefire.api.event.Event;
26 import org.apache.maven.surefire.extensions.EventHandler;
27 import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
28 import org.apache.maven.surefire.api.report.ConsoleOutputReceiver;
29 import org.apache.maven.surefire.api.report.ReportEntry;
30 import org.apache.maven.surefire.api.report.RunListener;
31 import org.apache.maven.surefire.api.report.RunMode;
32 import org.apache.maven.surefire.api.report.StackTraceWriter;
33 import org.apache.maven.surefire.api.report.TestSetReportEntry;
34
35 import javax.annotation.Nonnull;
36 import java.io.File;
37 import java.util.Map;
38 import java.util.Queue;
39 import java.util.Set;
40 import java.util.TreeSet;
41 import java.util.concurrent.ConcurrentHashMap;
42 import java.util.concurrent.ConcurrentLinkedQueue;
43 import java.util.concurrent.atomic.AtomicLong;
44
45 import static java.lang.System.currentTimeMillis;
46 import static java.util.Collections.unmodifiableMap;
47 import static org.apache.maven.surefire.api.booter.Shutdown.KILL;
48 import static org.apache.maven.surefire.api.report.CategorizedReportEntry.reportEntry;
49
50
51
52
53
54
55
56
57 public class ForkClient
58 implements EventHandler<Event>
59 {
60 private static final long START_TIME_ZERO = 0L;
61 private static final long START_TIME_NEGATIVE_TIMEOUT = -1L;
62
63 private final DefaultReporterFactory defaultReporterFactory;
64
65 private final Map<String, String> testVmSystemProperties = new ConcurrentHashMap<>();
66
67 private final NotifiableTestStream notifiableTestStream;
68
69 private final Queue<String> testsInProgress = new ConcurrentLinkedQueue<>();
70
71
72
73
74
75 private final AtomicLong testSetStartedAt = new AtomicLong( START_TIME_ZERO );
76
77 private final ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
78
79 private final int forkNumber;
80
81 private RunListener testSetReporter;
82
83
84
85
86 private volatile boolean saidGoodBye;
87
88 private volatile StackTraceWriter errorInFork;
89
90 public ForkClient( DefaultReporterFactory defaultReporterFactory, NotifiableTestStream notifiableTestStream,
91 int forkNumber )
92 {
93 this.defaultReporterFactory = defaultReporterFactory;
94 this.notifiableTestStream = notifiableTestStream;
95 this.forkNumber = forkNumber;
96 notifier.setTestSetStartingListener( new TestSetStartingListener() );
97 notifier.setTestSetCompletedListener( new TestSetCompletedListener() );
98 notifier.setTestStartingListener( new TestStartingListener() );
99 notifier.setTestSucceededListener( new TestSucceededListener() );
100 notifier.setTestFailedListener( new TestFailedListener() );
101 notifier.setTestSkippedListener( new TestSkippedListener() );
102 notifier.setTestErrorListener( new TestErrorListener() );
103 notifier.setTestAssumptionFailureListener( new TestAssumptionFailureListener() );
104 notifier.setSystemPropertiesListener( new SystemPropertiesListener() );
105 notifier.setStdOutListener( new StdOutListener() );
106 notifier.setStdErrListener( new StdErrListener() );
107 notifier.setConsoleInfoListener( new ConsoleListener() );
108 notifier.setAcquireNextTestListener( new AcquireNextTestListener() );
109 notifier.setConsoleErrorListener( new ErrorListener() );
110 notifier.setByeListener( new ByeListener() );
111 notifier.setStopOnNextTestListener( new StopOnNextTestListener() );
112 notifier.setConsoleDebugListener( new DebugListener() );
113 notifier.setConsoleWarningListener( new WarningListener() );
114 notifier.setExitErrorEventListener( new ExitErrorEventListener() );
115 }
116
117 private final class TestSetStartingListener
118 implements ForkedProcessReportEventListener<TestSetReportEntry>
119 {
120 @Override
121 public void handle( RunMode runMode, TestSetReportEntry reportEntry )
122 {
123 getTestSetReporter().testSetStarting( reportEntry );
124 setCurrentStartTime();
125 }
126 }
127
128 private final class TestSetCompletedListener
129 implements ForkedProcessReportEventListener<TestSetReportEntry>
130 {
131 @Override
132 public void handle( RunMode runMode, TestSetReportEntry reportEntry )
133 {
134 testsInProgress.clear();
135 TestSetReportEntry entry = reportEntry( reportEntry.getSourceName(), reportEntry.getSourceText(),
136 reportEntry.getName(), reportEntry.getNameText(),
137 reportEntry.getGroup(), reportEntry.getStackTraceWriter(), reportEntry.getElapsed(),
138 reportEntry.getMessage(), getTestVmSystemProperties() );
139 getTestSetReporter().testSetCompleted( entry );
140 }
141 }
142
143 private final class TestStartingListener implements ForkedProcessReportEventListener<ReportEntry>
144 {
145 @Override
146 public void handle( RunMode runMode, ReportEntry reportEntry )
147 {
148 testsInProgress.offer( reportEntry.getSourceName() );
149 getTestSetReporter().testStarting( reportEntry );
150 }
151 }
152
153 private final class TestSucceededListener implements ForkedProcessReportEventListener<ReportEntry>
154 {
155 @Override
156 public void handle( RunMode runMode, ReportEntry reportEntry )
157 {
158 testsInProgress.remove( reportEntry.getSourceName() );
159 getTestSetReporter().testSucceeded( reportEntry );
160 }
161 }
162
163 private final class TestFailedListener implements ForkedProcessReportEventListener<ReportEntry>
164 {
165 @Override
166 public void handle( RunMode runMode, ReportEntry reportEntry )
167 {
168 testsInProgress.remove( reportEntry.getSourceName() );
169 getTestSetReporter().testFailed( reportEntry );
170 }
171 }
172
173 private final class TestSkippedListener implements ForkedProcessReportEventListener<ReportEntry>
174 {
175 @Override
176 public void handle( RunMode runMode, ReportEntry reportEntry )
177 {
178 testsInProgress.remove( reportEntry.getSourceName() );
179 getTestSetReporter().testSkipped( reportEntry );
180 }
181 }
182
183 private final class TestErrorListener implements ForkedProcessReportEventListener<ReportEntry>
184 {
185 @Override
186 public void handle( RunMode runMode, ReportEntry reportEntry )
187 {
188 testsInProgress.remove( reportEntry.getSourceName() );
189 getTestSetReporter().testError( reportEntry );
190 }
191 }
192
193 private final class TestAssumptionFailureListener implements ForkedProcessReportEventListener<ReportEntry>
194 {
195 @Override
196 public void handle( RunMode runMode, ReportEntry reportEntry )
197 {
198 testsInProgress.remove( reportEntry.getSourceName() );
199 getTestSetReporter().testAssumptionFailure( reportEntry );
200 }
201 }
202
203 private final class SystemPropertiesListener implements ForkedProcessPropertyEventListener
204 {
205 @Override
206 public void handle( RunMode runMode, String key, String value )
207 {
208 testVmSystemProperties.put( key, value );
209 }
210 }
211
212 private final class StdOutListener implements ForkedProcessStandardOutErrEventListener
213 {
214 @Override
215 public void handle( RunMode runMode, String output, boolean newLine )
216 {
217 writeTestOutput( output, newLine, true );
218 }
219 }
220
221 private final class StdErrListener implements ForkedProcessStandardOutErrEventListener
222 {
223 @Override
224 public void handle( RunMode runMode, String output, boolean newLine )
225 {
226 writeTestOutput( output, newLine, false );
227 }
228 }
229
230 private final class ConsoleListener implements ForkedProcessStringEventListener
231 {
232 @Override
233 public void handle( String msg )
234 {
235 getOrCreateConsoleLogger()
236 .info( msg );
237 }
238 }
239
240 private final class AcquireNextTestListener implements ForkedProcessEventListener
241 {
242 @Override
243 public void handle()
244 {
245 notifiableTestStream.provideNewTest();
246 }
247 }
248
249 private class ErrorListener implements ForkedProcessStackTraceEventListener
250 {
251 @Override
252 public void handle( @Nonnull StackTraceWriter stackTrace )
253 {
254 String msg = stackTrace.getThrowable().getMessage();
255 if ( errorInFork == null )
256 {
257 errorInFork = stackTrace.writeTraceToString() != null ? stackTrace : null;
258 if ( msg != null )
259 {
260 getOrCreateConsoleLogger()
261 .error( msg );
262 }
263 }
264 dumpToLoFile( msg );
265 }
266 }
267
268 private final class ByeListener implements ForkedProcessEventListener
269 {
270 @Override
271 public void handle()
272 {
273 saidGoodBye = true;
274 notifiableTestStream.acknowledgeByeEventReceived();
275 }
276 }
277
278 private final class StopOnNextTestListener implements ForkedProcessEventListener
279 {
280 @Override
281 public void handle()
282 {
283 stopOnNextTest();
284 }
285 }
286
287 private final class DebugListener implements ForkedProcessStringEventListener
288 {
289 @Override
290 public void handle( String msg )
291 {
292 getOrCreateConsoleLogger()
293 .debug( msg );
294 }
295 }
296
297 private final class WarningListener implements ForkedProcessStringEventListener
298 {
299 @Override
300 public void handle( String msg )
301 {
302 getOrCreateConsoleLogger()
303 .warning( msg );
304 }
305 }
306
307 private final class ExitErrorEventListener implements ForkedProcessExitErrorListener
308 {
309 @Override
310 public void handle( StackTraceWriter stackTrace )
311 {
312 getOrCreateConsoleLogger()
313 .error( "System Exit has timed out in the forked process " + forkNumber );
314 }
315 }
316
317
318
319
320 protected void stopOnNextTest()
321 {
322 }
323
324 public void kill()
325 {
326 if ( !saidGoodBye )
327 {
328 notifiableTestStream.shutdown( KILL );
329 }
330 }
331
332
333
334
335
336
337
338
339 public final void tryToTimeout( long currentTimeMillis, int forkedProcessTimeoutInSeconds )
340 {
341 if ( forkedProcessTimeoutInSeconds > 0 )
342 {
343 final long forkedProcessTimeoutInMillis = 1000 * forkedProcessTimeoutInSeconds;
344 final long startedAt = testSetStartedAt.get();
345 if ( startedAt > START_TIME_ZERO && currentTimeMillis - startedAt >= forkedProcessTimeoutInMillis )
346 {
347 testSetStartedAt.set( START_TIME_NEGATIVE_TIMEOUT );
348 notifiableTestStream.shutdown( KILL );
349 }
350 }
351 }
352
353 public final DefaultReporterFactory getDefaultReporterFactory()
354 {
355 return defaultReporterFactory;
356 }
357
358 @Override
359 public final void handleEvent( @Nonnull Event event )
360 {
361 notifier.notifyEvent( event );
362 }
363
364 private void setCurrentStartTime()
365 {
366 if ( testSetStartedAt.get() == START_TIME_ZERO )
367 {
368
369
370 testSetStartedAt.compareAndSet( START_TIME_ZERO, currentTimeMillis() );
371 }
372 }
373
374 public final boolean hadTimeout()
375 {
376 return testSetStartedAt.get() == START_TIME_NEGATIVE_TIMEOUT;
377 }
378
379 private RunListener getTestSetReporter()
380 {
381 if ( testSetReporter == null )
382 {
383 testSetReporter = defaultReporterFactory.createReporter();
384 }
385 return testSetReporter;
386 }
387
388 void dumpToLoFile( String msg )
389 {
390 File reportsDir = defaultReporterFactory.getReportsDirectory();
391 InPluginProcessDumpSingleton util = InPluginProcessDumpSingleton.getSingleton();
392 util.dumpStreamText( msg, reportsDir, forkNumber );
393 }
394
395 private void writeTestOutput( String output, boolean newLine, boolean isStdout )
396 {
397 getOrCreateConsoleOutputReceiver()
398 .writeTestOutput( output, newLine, isStdout );
399 }
400
401 public final Map<String, String> getTestVmSystemProperties()
402 {
403 return unmodifiableMap( testVmSystemProperties );
404 }
405
406
407
408
409
410
411
412 public final RunListener getReporter()
413 {
414 return getTestSetReporter();
415 }
416
417 private ConsoleOutputReceiver getOrCreateConsoleOutputReceiver()
418 {
419 return (ConsoleOutputReceiver) getTestSetReporter();
420 }
421
422 private ConsoleLogger getOrCreateConsoleLogger()
423 {
424 return (ConsoleLogger) getTestSetReporter();
425 }
426
427 public void close( boolean hadTimeout )
428 {
429
430 }
431
432 public final boolean isSaidGoodBye()
433 {
434 return saidGoodBye;
435 }
436
437 public final StackTraceWriter getErrorInFork()
438 {
439 return errorInFork;
440 }
441
442 public final boolean isErrorInFork()
443 {
444 return errorInFork != null;
445 }
446
447 public Set<String> testsInProgress()
448 {
449 return new TreeSet<>( testsInProgress );
450 }
451
452 public boolean hasTestsInProgress()
453 {
454 return !testsInProgress.isEmpty();
455 }
456 }