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.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  // todo move to the same package with ForkStarter
53  
54  /**
55   * Knows how to reconstruct *all* the state transmitted over stdout by the forked process.
56   *
57   * @author Kristian Rosenvold
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       * <em>testSetStartedAt</em> is set to non-zero after received
75       * {@link MasterProcessChannelEncoder#testSetStarting(TestSetReportEntry, boolean)}.
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       * Written by one Thread and read by another: Main Thread and ForkStarter's Thread.
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      * Called in concurrent Thread.
326      * Will shutdown if timeout was reached.
327      *
328      * @param currentTimeMillis    current time in millis seconds
329      * @param forkedProcessTimeoutInSeconds timeout in seconds given by MOJO
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 ) // JIT can optimize <= no JNI call
359         {
360             // Not necessary to call JNI library library #currentTimeMillis
361             // which may waste 10 - 30 machine cycles in callback. Callbacks should be fast.
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      * Only {@link #getConsoleOutputReceiver()} may call this method in another Thread.
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      * Used when getting reporters on the plugin side of a fork.
409      * Used by testing purposes only. May not be volatile variable.
410      *
411      * @return A mock provider reporter
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         // no op
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 }