View Javadoc

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