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.junitcore.JUnitCoreParameters;
23  import org.apache.maven.surefire.api.testset.TestSetFailedException;
24  import org.junit.runner.Description;
25  
26  import java.util.Arrays;
27  import java.util.Collection;
28  import java.util.Iterator;
29  
30  /**
31   * An algorithm which configures {@link ParallelComputer} with allocated thread resources by given
32   * {@link org.apache.maven.surefire.junitcore.JUnitCoreParameters}.
33   * The {@code AbstractSurefireMojo} has to provide correct combinations of thread-counts and
34   * configuration parameter {@code parallel}.
35   *
36   * @author Tibor Digana (tibor17)
37   * @see org.apache.maven.surefire.junitcore.pc.ParallelComputerBuilder
38   * @since 2.16
39   */
40  final class ParallelComputerUtil
41  {
42      private static final Collection<Description> UNUSED_DESCRIPTIONS =
43              Arrays.asList( null, Description.createSuiteDescription( "null" ), Description.TEST_MECHANISM,
44                      Description.EMPTY );
45  
46      private static int availableProcessors = Runtime.getRuntime().availableProcessors();
47  
48      private ParallelComputerUtil()
49      {
50          throw new IllegalStateException( "Suppresses calling constructor, ensuring non-instantiability." );
51      }
52  
53      /*
54      * For testing purposes.
55      */
56      static void overrideAvailableProcessors( int availableProcessors )
57      {
58          ParallelComputerUtil.availableProcessors = availableProcessors;
59      }
60  
61      /*
62      * For testing purposes.
63      */
64      static void setDefaultAvailableProcessors()
65      {
66          ParallelComputerUtil.availableProcessors = Runtime.getRuntime().availableProcessors();
67      }
68  
69      static Concurrency resolveConcurrency( JUnitCoreParameters params, RunnerCounter counts )
70          throws TestSetFailedException
71      {
72          if ( !params.isParallelismSelected() )
73          {
74              throw new TestSetFailedException( "Unspecified parameter '" + JUnitCoreParameters.PARALLEL_KEY + "'." );
75          }
76  
77          if ( !params.isUseUnlimitedThreads() && !hasThreadCount( params ) && !hasThreadCounts( params ) )
78          {
79              throw new TestSetFailedException( "Unspecified thread-count(s). "
80                                                    + "See the parameters "
81                                                    + JUnitCoreParameters.USEUNLIMITEDTHREADS_KEY + ", "
82                                                    + JUnitCoreParameters.THREADCOUNT_KEY + ", "
83                                                    + JUnitCoreParameters.THREADCOUNTSUITES_KEY + ", "
84                                                    + JUnitCoreParameters.THREADCOUNTCLASSES_KEY + ", "
85                                                    + JUnitCoreParameters.THREADCOUNTMETHODS_KEY + "." );
86          }
87  
88          if ( params.isUseUnlimitedThreads() )
89          {
90              return concurrencyForUnlimitedThreads( params );
91          }
92          else if ( hasThreadCount( params ) )
93          {
94              if ( hasThreadCounts( params ) )
95              {
96                  return isLeafUnspecified( params )
97                      ? concurrencyFromAllThreadCountsButUnspecifiedLeafCount( params, counts )
98                      : concurrencyFromAllThreadCounts( params );
99              }
100             else
101             {
102                 return estimateConcurrency( params, counts );
103             }
104         }
105         else
106         {
107             return concurrencyFromThreadCounts( params );
108         }
109     }
110 
111     static boolean isUnusedDescription( Description examined )
112     {
113         if ( UNUSED_DESCRIPTIONS.contains( examined ) )
114         {
115             return true;
116         }
117         else
118         {
119             // UNUSED_DESCRIPTIONS ensures that "examined" cannot be null
120             for ( Description unused : UNUSED_DESCRIPTIONS )
121             {
122                 if ( unused != null && unused.getDisplayName().equals( examined.getDisplayName() ) )
123                 {
124                     return true;
125                 }
126             }
127             return false;
128         }
129     }
130 
131     static void removeUnusedDescriptions( Collection<Description> examined )
132     {
133         for ( Iterator<Description> it = examined.iterator(); it.hasNext(); )
134         {
135             if ( isUnusedDescription( it.next() ) )
136             {
137                 it.remove();
138             }
139         }
140     }
141 
142     private static Concurrency concurrencyForUnlimitedThreads( JUnitCoreParameters params )
143     {
144         Concurrency concurrency = new Concurrency();
145         concurrency.suites = params.isParallelSuites() ? threadCountSuites( params ) : 0;
146         concurrency.classes = params.isParallelClasses() ? threadCountClasses( params ) : 0;
147         concurrency.methods = params.isParallelMethods() ? threadCountMethods( params ) : 0;
148         concurrency.capacity = Integer.MAX_VALUE;
149         return concurrency;
150     }
151 
152     private static Concurrency estimateConcurrency( JUnitCoreParameters params, RunnerCounter counts )
153     {
154         final Concurrency concurrency = new Concurrency();
155         final int parallelEntities = countParallelEntities( params );
156         concurrency.capacity = multiplyByCoreCount( params, params.getThreadCount() );
157         if ( parallelEntities == 1 || counts == null || counts.classes == 0 )
158         {
159             // Estimate parallel thread counts.
160             double ratio = 1d / parallelEntities;
161             int threads = multiplyByCoreCount( params, ratio * params.getThreadCount() );
162             concurrency.suites = params.isParallelSuites() ? minSuites( threads, counts ) : 0;
163             concurrency.classes = params.isParallelClasses() ? minClasses( threads, counts ) : 0;
164             concurrency.methods = params.isParallelMethods() ? minMethods( threads, counts ) : 0;
165             if ( parallelEntities == 1 )
166             {
167                 concurrency.capacity = 0;
168             }
169             else
170             {
171                 adjustLeaf( params, concurrency );
172             }
173         }
174         else
175         {
176             // Try to allocate suites+classes+methods within threadCount,
177             concurrency.suites = params.isParallelSuites() ? toNonNegative( counts.suites ) : 0;
178             concurrency.classes = params.isParallelClasses() ? toNonNegative( counts.classes ) : 0;
179             concurrency.methods =
180                 params.isParallelMethods() ? toNonNegative( Math.ceil( counts.methods / (double) counts.classes ) ) : 0;
181             double sum = toNonNegative( concurrency.suites + concurrency.classes + concurrency.methods );
182             if ( concurrency.capacity < sum && sum != 0 )
183             {
184                 // otherwise allocate them using the weighting factor < 1.
185                 double weight = concurrency.capacity / sum;
186                 concurrency.suites *= weight;
187                 concurrency.classes *= weight;
188                 concurrency.methods *= weight;
189             }
190             adjustLeaf( params, concurrency );
191         }
192         return concurrency;
193     }
194 
195     private static Concurrency concurrencyFromAllThreadCountsButUnspecifiedLeafCount( JUnitCoreParameters params,
196                                                                                       RunnerCounter counts )
197     {
198         Concurrency concurrency = new Concurrency();
199         concurrency.suites = params.isParallelSuites() ? params.getThreadCountSuites() : 0;
200         concurrency.suites = params.isParallelSuites() ? multiplyByCoreCount( params, concurrency.suites ) : 0;
201         concurrency.classes = params.isParallelClasses() ? params.getThreadCountClasses() : 0;
202         concurrency.classes = params.isParallelClasses() ? multiplyByCoreCount( params, concurrency.classes ) : 0;
203         concurrency.methods = params.isParallelMethods() ? params.getThreadCountMethods() : 0;
204         concurrency.methods = params.isParallelMethods() ? multiplyByCoreCount( params, concurrency.methods ) : 0;
205         concurrency.capacity = multiplyByCoreCount( params, params.getThreadCount() );
206 
207         if ( counts != null )
208         {
209             concurrency.suites = toNonNegative( Math.min( concurrency.suites, counts.suites ) );
210             concurrency.classes = toNonNegative( Math.min( concurrency.classes, counts.classes ) );
211         }
212 
213         setLeafInfinite( params, concurrency );
214 
215         return concurrency;
216     }
217 
218     private static Concurrency concurrencyFromAllThreadCounts( JUnitCoreParameters params )
219     {
220         Concurrency concurrency = new Concurrency();
221         concurrency.suites = params.isParallelSuites() ? params.getThreadCountSuites() : 0;
222         concurrency.classes = params.isParallelClasses() ? params.getThreadCountClasses() : 0;
223         concurrency.methods = params.isParallelMethods() ? params.getThreadCountMethods() : 0;
224         concurrency.capacity = params.getThreadCount();
225         double all = sumThreadCounts( concurrency );
226 
227         concurrency.suites = params.isParallelSuites() ? multiplyByCoreCount( params, concurrency.capacity * (
228             concurrency.suites / all ) ) : 0;
229 
230         concurrency.classes = params.isParallelClasses() ? multiplyByCoreCount( params, concurrency.capacity * (
231             concurrency.classes / all ) ) : 0;
232 
233         concurrency.methods = params.isParallelMethods() ? multiplyByCoreCount( params, concurrency.capacity * (
234             concurrency.methods / all ) ) : 0;
235 
236         concurrency.capacity = multiplyByCoreCount( params, concurrency.capacity );
237         adjustPrecisionInLeaf( params, concurrency );
238         return concurrency;
239     }
240 
241     private static Concurrency concurrencyFromThreadCounts( JUnitCoreParameters params )
242     {
243         Concurrency concurrency = new Concurrency();
244         concurrency.suites = params.isParallelSuites() ? threadCountSuites( params ) : 0;
245         concurrency.classes = params.isParallelClasses() ? threadCountClasses( params ) : 0;
246         concurrency.methods = params.isParallelMethods() ? threadCountMethods( params ) : 0;
247         concurrency.capacity = toNonNegative( sumThreadCounts( concurrency ) );
248         return concurrency;
249     }
250 
251     private static int countParallelEntities( JUnitCoreParameters params )
252     {
253         int count = 0;
254         if ( params.isParallelSuites() )
255         {
256             count++;
257         }
258 
259         if ( params.isParallelClasses() )
260         {
261             count++;
262         }
263 
264         if ( params.isParallelMethods() )
265         {
266             count++;
267         }
268         return count;
269     }
270 
271     private static void adjustPrecisionInLeaf( JUnitCoreParameters params, Concurrency concurrency )
272     {
273         if ( params.isParallelMethods() )
274         {
275             concurrency.methods = concurrency.capacity - concurrency.suites - concurrency.classes;
276         }
277         else if ( params.isParallelClasses() )
278         {
279             concurrency.classes = concurrency.capacity - concurrency.suites;
280         }
281     }
282 
283     private static void adjustLeaf( JUnitCoreParameters params, Concurrency concurrency )
284     {
285         if ( params.isParallelMethods() )
286         {
287             concurrency.methods = Integer.MAX_VALUE;
288         }
289         else if ( params.isParallelClasses() )
290         {
291             concurrency.classes = Integer.MAX_VALUE;
292         }
293     }
294 
295     private static void setLeafInfinite( JUnitCoreParameters params, Concurrency concurrency )
296     {
297         if ( params.isParallelMethods() )
298         {
299             concurrency.methods = Integer.MAX_VALUE;
300         }
301         else if ( params.isParallelClasses() )
302         {
303             concurrency.classes = Integer.MAX_VALUE;
304         }
305         else if ( params.isParallelSuites() )
306         {
307             concurrency.suites = Integer.MAX_VALUE;
308         }
309     }
310 
311     private static boolean isLeafUnspecified( JUnitCoreParameters params )
312     {
313         int maskOfParallel = params.isParallelSuites() ? 4 : 0;
314         maskOfParallel |= params.isParallelClasses() ? 2 : 0;
315         maskOfParallel |= params.isParallelMethods() ? 1 : 0;
316 
317         int maskOfConcurrency = params.getThreadCountSuites() > 0 ? 4 : 0;
318         maskOfConcurrency |= params.getThreadCountClasses() > 0 ? 2 : 0;
319         maskOfConcurrency |= params.getThreadCountMethods() > 0 ? 1 : 0;
320 
321         maskOfConcurrency &= maskOfParallel;
322 
323         int leaf = Integer.lowestOneBit( maskOfParallel );
324         return maskOfConcurrency == maskOfParallel - leaf;
325     }
326 
327     private static double sumThreadCounts( Concurrency concurrency )
328     {
329         double sum = concurrency.suites;
330         sum += concurrency.classes;
331         sum += concurrency.methods;
332         return sum;
333     }
334 
335     private static boolean hasThreadCounts( JUnitCoreParameters jUnitCoreParameters )
336     {
337         return ( jUnitCoreParameters.isParallelSuites() && jUnitCoreParameters.getThreadCountSuites() > 0 )
338             || ( jUnitCoreParameters.isParallelClasses() && jUnitCoreParameters.getThreadCountClasses() > 0 )
339             || ( jUnitCoreParameters.isParallelMethods() && jUnitCoreParameters.getThreadCountMethods() > 0 );
340     }
341 
342     private static boolean hasThreadCount( JUnitCoreParameters jUnitCoreParameters )
343     {
344         return jUnitCoreParameters.getThreadCount() > 0;
345     }
346 
347     private static int threadCountMethods( JUnitCoreParameters jUnitCoreParameters )
348     {
349         return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountMethods() );
350     }
351 
352     private static int threadCountClasses( JUnitCoreParameters jUnitCoreParameters )
353     {
354         return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountClasses() );
355     }
356 
357     private static int threadCountSuites( JUnitCoreParameters jUnitCoreParameters )
358     {
359         return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountSuites() );
360     }
361 
362     private static int multiplyByCoreCount( JUnitCoreParameters jUnitCoreParameters, double threadsPerCore )
363     {
364         double numberOfThreads =
365             jUnitCoreParameters.isPerCoreThreadCount() ? threadsPerCore * (double) availableProcessors : threadsPerCore;
366 
367         return numberOfThreads > 0 ? toNonNegative( numberOfThreads ) : Integer.MAX_VALUE;
368     }
369 
370     private static int minSuites( int threads, RunnerCounter counts )
371     {
372         long count = counts == null ? Integer.MAX_VALUE : counts.suites;
373         return Math.min( threads, toNonNegative( count ) );
374     }
375 
376     private static int minClasses( int threads, RunnerCounter counts )
377     {
378         long count = counts == null ? Integer.MAX_VALUE : counts.classes;
379         return Math.min( threads, toNonNegative( count ) );
380     }
381 
382     private static int minMethods( int threads, RunnerCounter counts )
383     {
384         long count = counts == null ? Integer.MAX_VALUE : counts.methods;
385         return Math.min( threads, toNonNegative( count ) );
386     }
387 
388     private static int toNonNegative( long num )
389     {
390         return (int) Math.min( num > 0 ? num : 0, Integer.MAX_VALUE );
391     }
392 
393     private static int toNonNegative( double num )
394     {
395         return (int) Math.min( num > 0 ? num : 0, Integer.MAX_VALUE );
396     }
397 }