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 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  // todo move to the same package with ForkStarter
51  
52  /**
53   * Knows how to reconstruct *all* the state transmitted over stdout by the forked process.
54   *
55   * @author Kristian Rosenvold
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       * <em>testSetStartedAt</em> is set to non-zero after received
73       * {@link MasterProcessChannelEncoder#testSetStarting(ReportEntry, boolean)}.
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       * Written by one Thread and read by another: Main Thread and ForkStarter's Thread.
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      * Overridden by a subclass, see {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter}.
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      * Called in concurrent Thread.
334      * Will shutdown if timeout was reached.
335      *
336      * @param currentTimeMillis    current time in millis seconds
337      * @param forkedProcessTimeoutInSeconds timeout in seconds given by MOJO
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 ) // JIT can optimize <= no JNI call
367         {
368             // Not necessary to call JNI library library #currentTimeMillis
369             // which may waste 10 - 30 machine cycles in callback. Callbacks should be fast.
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      * Used when getting reporters on the plugin side of a fork.
408      * Used by testing purposes only. May not be volatile variable.
409      *
410      * @return A mock provider reporter
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         // no op
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 }