View Javadoc
1   package org.apache.maven.surefire.junitcore.pc;
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.surefire.testset.TestSetFailedException;
23  import org.apache.maven.surefire.util.internal.DaemonThreadFactory;
24  import org.junit.runner.Computer;
25  import org.junit.runner.Description;
26  
27  import java.util.Collection;
28  import java.util.TreeSet;
29  import java.util.concurrent.Callable;
30  import java.util.concurrent.ExecutionException;
31  import java.util.concurrent.Executors;
32  import java.util.concurrent.Future;
33  import java.util.concurrent.ScheduledExecutorService;
34  import java.util.concurrent.ThreadFactory;
35  
36  import static java.util.concurrent.TimeUnit.NANOSECONDS;
37  
38  /**
39   * ParallelComputer extends JUnit {@link Computer} and has a shutdown functionality.
40   *
41   * @author Tibor Digana (tibor17)
42   * @see ParallelComputerBuilder
43   * @since 2.16
44   */
45  public abstract class ParallelComputer
46      extends Computer
47  {
48      private static final ThreadFactory DAEMON_THREAD_FACTORY = DaemonThreadFactory.newDaemonThreadFactory();
49  
50      private static final double NANOS_IN_A_SECOND = 1E9;
51  
52      private final ShutdownStatus shutdownStatus = new ShutdownStatus();
53  
54      private final ShutdownStatus forcedShutdownStatus = new ShutdownStatus();
55  
56      private final long timeoutNanos;
57  
58      private final long timeoutForcedNanos;
59  
60      private ScheduledExecutorService shutdownScheduler;
61  
62      public ParallelComputer( double timeoutInSeconds, double timeoutForcedInSeconds )
63      {
64          this.timeoutNanos = secondsToNanos( timeoutInSeconds );
65          this.timeoutForcedNanos = secondsToNanos( timeoutForcedInSeconds );
66      }
67  
68      protected abstract ShutdownResult describeStopped( boolean shutdownNow );
69  
70      protected abstract boolean shutdownThreadPoolsAwaitingKilled();
71  
72      protected final void beforeRunQuietly()
73      {
74          shutdownStatus.setDescriptionsBeforeShutdown( hasTimeout() ? scheduleShutdown() : null );
75          forcedShutdownStatus.setDescriptionsBeforeShutdown( hasTimeoutForced() ? scheduleForcedShutdown() : null );
76      }
77  
78      protected final boolean afterRunQuietly()
79      {
80          shutdownStatus.tryFinish();
81          forcedShutdownStatus.tryFinish();
82          boolean notInterrupted = true;
83          if ( shutdownScheduler != null )
84          {
85              shutdownScheduler.shutdownNow();
86              /**
87               * Clear <i>interrupted status</i> of the (main) Thread.
88               * Could be previously interrupted by {@link InvokerStrategy} after triggering immediate shutdown.
89               */
90              Thread.interrupted();
91              try
92              {
93                  shutdownScheduler.awaitTermination( Long.MAX_VALUE, NANOSECONDS );
94              }
95              catch ( InterruptedException e )
96              {
97                  notInterrupted = false;
98              }
99          }
100         notInterrupted &= shutdownThreadPoolsAwaitingKilled();
101         return notInterrupted;
102     }
103 
104     public String describeElapsedTimeout()
105         throws TestSetFailedException
106     {
107         final StringBuilder msg = new StringBuilder();
108         final boolean isShutdownTimeout = shutdownStatus.isTimeoutElapsed();
109         final boolean isForcedShutdownTimeout = forcedShutdownStatus.isTimeoutElapsed();
110         if ( isShutdownTimeout || isForcedShutdownTimeout )
111         {
112             msg.append( "The test run has finished abruptly after timeout of " );
113             msg.append( nanosToSeconds( minTimeout( timeoutNanos, timeoutForcedNanos ) ) );
114             msg.append( " seconds.\n" );
115 
116             try
117             {
118                 final TreeSet<String> executedTests = new TreeSet<String>();
119                 final TreeSet<String> incompleteTests = new TreeSet<String>();
120 
121                 if ( isShutdownTimeout )
122                 {
123                     printShutdownHook( executedTests, incompleteTests, shutdownStatus.getDescriptionsBeforeShutdown() );
124                 }
125 
126                 if ( isForcedShutdownTimeout )
127                 {
128                     printShutdownHook( executedTests, incompleteTests,
129                                        forcedShutdownStatus.getDescriptionsBeforeShutdown() );
130                 }
131 
132                 if ( !executedTests.isEmpty() )
133                 {
134                     msg.append( "These tests were executed in prior to the shutdown operation:\n" );
135                     for ( String executedTest : executedTests )
136                     {
137                         msg.append( executedTest ).append( '\n' );
138                     }
139                 }
140 
141                 if ( !incompleteTests.isEmpty() )
142                 {
143                     msg.append( "These tests are incomplete:\n" );
144                     for ( String incompleteTest : incompleteTests )
145                     {
146                         msg.append( incompleteTest ).append( '\n' );
147                     }
148                 }
149             }
150             catch ( InterruptedException e )
151             {
152                 throw new TestSetFailedException( "Timed termination was interrupted.", e );
153             }
154             catch ( ExecutionException e )
155             {
156                 throw new TestSetFailedException( e.getLocalizedMessage(), e.getCause() );
157             }
158         }
159         return msg.toString();
160     }
161 
162     private Future<ShutdownResult> scheduleShutdown()
163     {
164         return getShutdownScheduler().schedule( createShutdownTask(), timeoutNanos, NANOSECONDS );
165     }
166 
167     private Future<ShutdownResult> scheduleForcedShutdown()
168     {
169         return getShutdownScheduler().schedule( createForcedShutdownTask(), timeoutForcedNanos, NANOSECONDS );
170     }
171 
172     private ScheduledExecutorService getShutdownScheduler()
173     {
174         if ( shutdownScheduler == null )
175         {
176             shutdownScheduler = Executors.newScheduledThreadPool( 2, DAEMON_THREAD_FACTORY );
177         }
178         return shutdownScheduler;
179     }
180 
181     private Callable<ShutdownResult> createShutdownTask()
182     {
183         return new Callable<ShutdownResult>()
184         {
185             @Override
186             public ShutdownResult call()
187                 throws Exception
188             {
189                 boolean stampedStatusWithTimeout = ParallelComputer.this.shutdownStatus.tryTimeout();
190                 return stampedStatusWithTimeout ? ParallelComputer.this.describeStopped( false ) : null;
191             }
192         };
193     }
194 
195     private Callable<ShutdownResult> createForcedShutdownTask()
196     {
197         return new Callable<ShutdownResult>()
198         {
199             @Override
200             public ShutdownResult call()
201                 throws Exception
202             {
203                 boolean stampedStatusWithTimeout = ParallelComputer.this.forcedShutdownStatus.tryTimeout();
204                 return stampedStatusWithTimeout ? ParallelComputer.this.describeStopped( true ) : null;
205             }
206         };
207     }
208 
209     private double nanosToSeconds( long nanos )
210     {
211         return (double) nanos / NANOS_IN_A_SECOND;
212     }
213 
214     private boolean hasTimeout()
215     {
216         return timeoutNanos > 0;
217     }
218 
219     private boolean hasTimeoutForced()
220     {
221         return timeoutForcedNanos > 0;
222     }
223 
224     private static long secondsToNanos( double seconds )
225     {
226         double nanos = seconds > 0 ? seconds * NANOS_IN_A_SECOND : 0;
227         return Double.isInfinite( nanos ) || nanos >= Long.MAX_VALUE ? 0 : (long) nanos;
228     }
229 
230     private static long minTimeout( long timeout1, long timeout2 )
231     {
232         if ( timeout1 == 0 )
233         {
234             return timeout2;
235         }
236         else if ( timeout2 == 0 )
237         {
238             return timeout1;
239         }
240         else
241         {
242             return Math.min( timeout1, timeout2 );
243         }
244     }
245 
246     private static void printShutdownHook( Collection<String> executedTests, Collection<String> incompleteTests,
247                                            Future<ShutdownResult> testsBeforeShutdown )
248         throws ExecutionException, InterruptedException
249     {
250         if ( testsBeforeShutdown != null )
251         {
252             final ShutdownResult shutdownResult = testsBeforeShutdown.get();
253             if ( shutdownResult != null )
254             {
255                 for ( final Description test : shutdownResult.getTriggeredTests() )
256                 {
257                     if ( test != null && test.getDisplayName() != null )
258                     {
259                         executedTests.add( test.getDisplayName() );
260                     }
261                 }
262 
263                 for ( final Description test : shutdownResult.getIncompleteTests() )
264                 {
265                     if ( test != null && test.getDisplayName() != null )
266                     {
267                         incompleteTests.add( test.getDisplayName() );
268                     }
269                 }
270             }
271         }
272     }
273 }