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