package org.apache.maven.surefire.junitcore.pc; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import org.apache.maven.surefire.junitcore.JUnitCoreParameters; import org.apache.maven.surefire.testset.TestSetFailedException; import org.junit.runner.Description; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; /** * An algorithm which configures {@link ParallelComputer} with allocated thread resources by given * {@link org.apache.maven.surefire.junitcore.JUnitCoreParameters}. * The {@code AbstractSurefireMojo} has to provide correct combinations of thread-counts and * configuration parameter {@code parallel}. * * @author Tibor Digana (tibor17) * @see org.apache.maven.surefire.junitcore.pc.ParallelComputerBuilder * @since 2.16 */ final class ParallelComputerUtil { private static final Collection<Description> UNUSED_DESCRIPTIONS = Arrays.asList( null, Description.createSuiteDescription( "null" ), Description.TEST_MECHANISM, Description.EMPTY ); private static int availableProcessors = Runtime.getRuntime().availableProcessors(); private ParallelComputerUtil() { throw new IllegalStateException( "Suppresses calling constructor, ensuring non-instantiability." ); } /* * For testing purposes. */ static void overrideAvailableProcessors( int availableProcessors ) { ParallelComputerUtil.availableProcessors = availableProcessors; } /* * For testing purposes. */ static void setDefaultAvailableProcessors() { ParallelComputerUtil.availableProcessors = Runtime.getRuntime().availableProcessors(); } static Concurrency resolveConcurrency( JUnitCoreParameters params, RunnerCounter counts ) throws TestSetFailedException { if ( !params.isParallelismSelected() ) { throw new TestSetFailedException( "Unspecified parameter '" + JUnitCoreParameters.PARALLEL_KEY + "'." ); } if ( !params.isUseUnlimitedThreads() && !hasThreadCount( params ) && !hasThreadCounts( params ) ) { throw new TestSetFailedException( "Unspecified thread-count(s). " + "See the parameters " + JUnitCoreParameters.USEUNLIMITEDTHREADS_KEY + ", " + JUnitCoreParameters.THREADCOUNT_KEY + ", " + JUnitCoreParameters.THREADCOUNTSUITES_KEY + ", " + JUnitCoreParameters.THREADCOUNTCLASSES_KEY + ", " + JUnitCoreParameters.THREADCOUNTMETHODS_KEY + "." ); } if ( params.isUseUnlimitedThreads() ) { return concurrencyForUnlimitedThreads( params ); } else if ( hasThreadCount( params ) ) { if ( hasThreadCounts( params ) ) { return isLeafUnspecified( params ) ? concurrencyFromAllThreadCountsButUnspecifiedLeafCount( params, counts ) : concurrencyFromAllThreadCounts( params ); } else { return estimateConcurrency( params, counts ); } } else { return concurrencyFromThreadCounts( params ); } } static boolean isUnusedDescription( Description examined ) { if ( UNUSED_DESCRIPTIONS.contains( examined ) ) { return true; } else { // UNUSED_DESCRIPTIONS ensures that "examined" cannot be null for ( Description unused : UNUSED_DESCRIPTIONS ) { if ( unused != null && unused.getDisplayName().equals( examined.getDisplayName() ) ) { return true; } } return false; } } static void removeUnusedDescriptions( Collection<Description> examined ) { for ( Iterator<Description> it = examined.iterator(); it.hasNext(); ) { if ( isUnusedDescription( it.next() ) ) { it.remove(); } } } private static Concurrency concurrencyForUnlimitedThreads( JUnitCoreParameters params ) { Concurrency concurrency = new Concurrency(); concurrency.suites = params.isParallelSuites() ? threadCountSuites( params ) : 0; concurrency.classes = params.isParallelClasses() ? threadCountClasses( params ) : 0; concurrency.methods = params.isParallelMethods() ? threadCountMethods( params ) : 0; concurrency.capacity = Integer.MAX_VALUE; return concurrency; } private static Concurrency estimateConcurrency( JUnitCoreParameters params, RunnerCounter counts ) { final Concurrency concurrency = new Concurrency(); final int parallelEntities = countParallelEntities( params ); concurrency.capacity = multiplyByCoreCount( params, params.getThreadCount() ); if ( parallelEntities == 1 || counts == null || counts.classes == 0 ) { // Estimate parallel thread counts. double ratio = 1d / parallelEntities; int threads = multiplyByCoreCount( params, ratio * params.getThreadCount() ); concurrency.suites = params.isParallelSuites() ? minSuites( threads, counts ) : 0; concurrency.classes = params.isParallelClasses() ? minClasses( threads, counts ) : 0; concurrency.methods = params.isParallelMethods() ? minMethods( threads, counts ) : 0; if ( parallelEntities == 1 ) { concurrency.capacity = 0; } else { adjustLeaf( params, concurrency ); } } else { // Try to allocate suites+classes+methods within threadCount, concurrency.suites = params.isParallelSuites() ? toNonNegative( counts.suites ) : 0; concurrency.classes = params.isParallelClasses() ? toNonNegative( counts.classes ) : 0; concurrency.methods = params.isParallelMethods() ? toNonNegative( Math.ceil( counts.methods / (double) counts.classes ) ) : 0; double sum = toNonNegative( concurrency.suites + concurrency.classes + concurrency.methods ); if ( concurrency.capacity < sum && sum != 0 ) { // otherwise allocate them using the weighting factor < 1. double weight = concurrency.capacity / sum; concurrency.suites *= weight; concurrency.classes *= weight; concurrency.methods *= weight; } adjustLeaf( params, concurrency ); } return concurrency; } private static Concurrency concurrencyFromAllThreadCountsButUnspecifiedLeafCount( JUnitCoreParameters params, RunnerCounter counts ) { Concurrency concurrency = new Concurrency(); concurrency.suites = params.isParallelSuites() ? params.getThreadCountSuites() : 0; concurrency.suites = params.isParallelSuites() ? multiplyByCoreCount( params, concurrency.suites ) : 0; concurrency.classes = params.isParallelClasses() ? params.getThreadCountClasses() : 0; concurrency.classes = params.isParallelClasses() ? multiplyByCoreCount( params, concurrency.classes ) : 0; concurrency.methods = params.isParallelMethods() ? params.getThreadCountMethods() : 0; concurrency.methods = params.isParallelMethods() ? multiplyByCoreCount( params, concurrency.methods ) : 0; concurrency.capacity = multiplyByCoreCount( params, params.getThreadCount() ); if ( counts != null ) { concurrency.suites = toNonNegative( Math.min( concurrency.suites, counts.suites ) ); concurrency.classes = toNonNegative( Math.min( concurrency.classes, counts.classes ) ); } setLeafInfinite( params, concurrency ); return concurrency; } private static Concurrency concurrencyFromAllThreadCounts( JUnitCoreParameters params ) { Concurrency concurrency = new Concurrency(); concurrency.suites = params.isParallelSuites() ? params.getThreadCountSuites() : 0; concurrency.classes = params.isParallelClasses() ? params.getThreadCountClasses() : 0; concurrency.methods = params.isParallelMethods() ? params.getThreadCountMethods() : 0; concurrency.capacity = params.getThreadCount(); double all = sumThreadCounts( concurrency ); concurrency.suites = params.isParallelSuites() ? multiplyByCoreCount( params, concurrency.capacity * ( concurrency.suites / all ) ) : 0; concurrency.classes = params.isParallelClasses() ? multiplyByCoreCount( params, concurrency.capacity * ( concurrency.classes / all ) ) : 0; concurrency.methods = params.isParallelMethods() ? multiplyByCoreCount( params, concurrency.capacity * ( concurrency.methods / all ) ) : 0; concurrency.capacity = multiplyByCoreCount( params, concurrency.capacity ); adjustPrecisionInLeaf( params, concurrency ); return concurrency; } private static Concurrency concurrencyFromThreadCounts( JUnitCoreParameters params ) { Concurrency concurrency = new Concurrency(); concurrency.suites = params.isParallelSuites() ? threadCountSuites( params ) : 0; concurrency.classes = params.isParallelClasses() ? threadCountClasses( params ) : 0; concurrency.methods = params.isParallelMethods() ? threadCountMethods( params ) : 0; concurrency.capacity = toNonNegative( sumThreadCounts( concurrency ) ); return concurrency; } private static int countParallelEntities( JUnitCoreParameters params ) { int count = 0; if ( params.isParallelSuites() ) { count++; } if ( params.isParallelClasses() ) { count++; } if ( params.isParallelMethods() ) { count++; } return count; } private static void adjustPrecisionInLeaf( JUnitCoreParameters params, Concurrency concurrency ) { if ( params.isParallelMethods() ) { concurrency.methods = concurrency.capacity - concurrency.suites - concurrency.classes; } else if ( params.isParallelClasses() ) { concurrency.classes = concurrency.capacity - concurrency.suites; } } private static void adjustLeaf( JUnitCoreParameters params, Concurrency concurrency ) { if ( params.isParallelMethods() ) { concurrency.methods = Integer.MAX_VALUE; } else if ( params.isParallelClasses() ) { concurrency.classes = Integer.MAX_VALUE; } } private static void setLeafInfinite( JUnitCoreParameters params, Concurrency concurrency ) { if ( params.isParallelMethods() ) { concurrency.methods = Integer.MAX_VALUE; } else if ( params.isParallelClasses() ) { concurrency.classes = Integer.MAX_VALUE; } else if ( params.isParallelSuites() ) { concurrency.suites = Integer.MAX_VALUE; } } private static boolean isLeafUnspecified( JUnitCoreParameters params ) { int maskOfParallel = params.isParallelSuites() ? 4 : 0; maskOfParallel |= params.isParallelClasses() ? 2 : 0; maskOfParallel |= params.isParallelMethods() ? 1 : 0; int maskOfConcurrency = params.getThreadCountSuites() > 0 ? 4 : 0; maskOfConcurrency |= params.getThreadCountClasses() > 0 ? 2 : 0; maskOfConcurrency |= params.getThreadCountMethods() > 0 ? 1 : 0; maskOfConcurrency &= maskOfParallel; int leaf = Integer.lowestOneBit( maskOfParallel ); return maskOfConcurrency == maskOfParallel - leaf; } private static double sumThreadCounts( Concurrency concurrency ) { double sum = concurrency.suites; sum += concurrency.classes; sum += concurrency.methods; return sum; } private static boolean hasThreadCounts( JUnitCoreParameters jUnitCoreParameters ) { return ( jUnitCoreParameters.isParallelSuites() && jUnitCoreParameters.getThreadCountSuites() > 0 ) || ( jUnitCoreParameters.isParallelClasses() && jUnitCoreParameters.getThreadCountClasses() > 0 ) || ( jUnitCoreParameters.isParallelMethods() && jUnitCoreParameters.getThreadCountMethods() > 0 ); } private static boolean hasThreadCount( JUnitCoreParameters jUnitCoreParameters ) { return jUnitCoreParameters.getThreadCount() > 0; } private static int threadCountMethods( JUnitCoreParameters jUnitCoreParameters ) { return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountMethods() ); } private static int threadCountClasses( JUnitCoreParameters jUnitCoreParameters ) { return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountClasses() ); } private static int threadCountSuites( JUnitCoreParameters jUnitCoreParameters ) { return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountSuites() ); } private static int multiplyByCoreCount( JUnitCoreParameters jUnitCoreParameters, double threadsPerCore ) { double numberOfThreads = jUnitCoreParameters.isPerCoreThreadCount() ? threadsPerCore * (double) availableProcessors : threadsPerCore; return numberOfThreads > 0 ? toNonNegative( numberOfThreads ) : Integer.MAX_VALUE; } private static int minSuites( int threads, RunnerCounter counts ) { long count = counts == null ? Integer.MAX_VALUE : counts.suites; return Math.min( threads, toNonNegative( count ) ); } private static int minClasses( int threads, RunnerCounter counts ) { long count = counts == null ? Integer.MAX_VALUE : counts.classes; return Math.min( threads, toNonNegative( count ) ); } private static int minMethods( int threads, RunnerCounter counts ) { long count = counts == null ? Integer.MAX_VALUE : counts.methods; return Math.min( threads, toNonNegative( count ) ); } private static int toNonNegative( long num ) { return (int) Math.min( num > 0 ? num : 0, Integer.MAX_VALUE ); } private static int toNonNegative( double num ) { return (int) Math.min( num > 0 ? num : 0, Integer.MAX_VALUE ); } }