Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,11 @@ public class IntegrationTestMojo extends AbstractSurefireMojo {
* Failed first will run tests that failed on previous run first, as well as new tests for this run.
* <br>
* <br>
* Balanced is only relevant with parallel=classes, and will try to optimize the run-order of the tests reducing the
* overall execution time. Initially a statistics file is created and every next test run will reorder classes.
* Balanced will try to optimize the run-order of the tests reducing the overall execution time by distributing the
* classes across the concurrent test consumers based on their recorded run time. The number of consumers is
* {@code forkCount} multiplied by {@code threadCount} (when {@code parallel} is used), so balanced is effective
* with {@code forkCount > 1} even without {@code parallel=classes}. Initially a statistics file is created and
* every next test run will reorder classes.
* <br>
* <br>
* Note that the statistics are stored in a file named <b>.surefire-XXXXXXXXX</b> beside <i>pom.xml</i> and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1825,6 +1825,10 @@ private ProviderConfiguration createProviderConfiguration(RunOrderParameters run

getProperties().setProperty(ProviderParameterNames.STACK_TRACE_MAX_FRAMES, String.valueOf(stackTraceMaxFrames));

getProperties()
.setProperty(
ProviderParameterNames.FORKCOUNT_PROP, Integer.toString(Math.max(getEffectiveForkCount(), 1)));

Map<String, String> providerProperties = toStringProperties(getProperties());

return new ProviderConfiguration(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,11 @@ public class SurefireMojo extends AbstractSurefireMojo implements SurefireReport
* Failed first will run tests that failed on previous run first, as well as new tests for this run.
* <br>
* <br>
* Balanced is only relevant with parallel=classes, and will try to optimize the run-order of the tests reducing the
* overall execution time. Initially a statistics file is created and every next test run will reorder classes.
* Balanced will try to optimize the run-order of the tests reducing the overall execution time by distributing the
* classes across the concurrent test consumers based on their recorded run time. The number of consumers is
* {@code forkCount} multiplied by {@code threadCount} (when {@code parallel} is used), so balanced is effective
* with {@code forkCount > 1} even without {@code parallel=classes}. Initially a statistics file is created and
* every next test run will reorder classes.
* <br>
* <br>
* Note that the statistics are stored in a file named <b>.surefire-XXXXXXXXX</b> beside <i>pom.xml</i> and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,14 @@ private int getThreadCount() {
return threadcount == null ? 1 : Math.max(Integer.parseInt(threadcount), 1);
}

private int getForkCount() {
final String forkcount = providerProperties.get(ProviderParameterNames.FORKCOUNT_PROP);
return forkcount == null ? 1 : Math.max(Integer.parseInt(forkcount), 1);
}

@Override
public RunOrderCalculator getRunOrderCalculator() {
return new DefaultRunOrderCalculator(runOrderParameters, getThreadCount());
return new DefaultRunOrderCalculator(runOrderParameters, getThreadCount() * getForkCount());
}

public void setReporterFactory(ReporterFactory reporterFactory) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ public class ProviderParameterNames {

public static final String THREADCOUNT_PROP = "threadcount";

/**
* Number of forked JVMs executing tests concurrently, used together with {@link #THREADCOUNT_PROP} to size the
* distribution of the {@code balanced} run order.
* @since 3.6.0
*/
public static final String FORKCOUNT_PROP = "forkcount";

public static final String PARALLEL_PROP = "parallel";

public static final String THREADCOUNTSUITES_PROP = "threadcountsuites";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ public class DefaultRunOrderCalculator implements RunOrderCalculator {

private final RunOrderParameters runOrderParameters;

private final int threadCount;
private final int distributedParallelism;

private final Random random;

public DefaultRunOrderCalculator(RunOrderParameters runOrderParameters, int threadCount) {
public DefaultRunOrderCalculator(RunOrderParameters runOrderParameters, int distributedParallelism) {
this.runOrderParameters = runOrderParameters;
this.threadCount = threadCount;
this.distributedParallelism = distributedParallelism;
this.runOrder = runOrderParameters.getRunOrder();
this.sortOrder = this.runOrder.length > 0 ? getSortOrderComparator(this.runOrder[0]) : null;
Long runOrderRandomSeed = runOrderParameters.getRunOrderRandomSeed();
Expand Down Expand Up @@ -77,7 +77,7 @@ private void orderTestClasses(List<Class<?>> testClasses, RunOrder runOrder) {

} else if (RunOrder.BALANCED.equals(runOrder)) {
RunEntryStatisticsMap stat = RunEntryStatisticsMap.fromFile(runOrderParameters.getRunStatisticsFile());
List<Class<?>> prioritized = stat.getPrioritizedTestsClassRunTime(testClasses, threadCount);
List<Class<?>> prioritized = stat.getPrioritizedTestsClassRunTime(testClasses, distributedParallelism);
testClasses.clear();
testClasses.addAll(prioritized);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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.
*/
package org.apache.maven.surefire.api.booter;

import java.io.File;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import org.apache.maven.surefire.api.testset.RunOrderParameters;
import org.apache.maven.surefire.api.util.DefaultRunOrderCalculator;
import org.apache.maven.surefire.api.util.RunOrder;
import org.apache.maven.surefire.api.util.RunOrderCalculator;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Verifies that the {@code balanced} run order distribution width is derived from both the parallel
* {@code threadcount} and the {@code forkcount}.
*/
public class BaseProviderFactoryTest {

@Test
public void distributionWidthDefaultsToForkCountWhenNoThreadCount() {
assertThat(distributedParallelism(properties(null, "3"))).isEqualTo(3);
}

@Test
public void distributionWidthEqualsThreadCountWhenSingleFork() {
assertThat(distributedParallelism(properties("4", "1"))).isEqualTo(4);
}

@Test
public void distributionWidthMultipliesThreadCountAndForkCount() {
assertThat(distributedParallelism(properties("4", "3"))).isEqualTo(12);
}

@Test
public void distributionWidthDefaultsToOneWhenNoPropertiesSet() {
assertThat(distributedParallelism(properties(null, null))).isEqualTo(1);
}

@Test
public void distributionWidthClampsZeroForkCountToOne() {
assertThat(distributedParallelism(properties("4", "0"))).isEqualTo(4);
}

private static Map<String, String> properties(String threadCount, String forkCount) {
Map<String, String> providerProperties = new HashMap<>();
if (threadCount != null) {
providerProperties.put(ProviderParameterNames.THREADCOUNT_PROP, threadCount);
}
if (forkCount != null) {
providerProperties.put(ProviderParameterNames.FORKCOUNT_PROP, forkCount);
}
return providerProperties;
}

private static int distributedParallelism(Map<String, String> providerProperties) {
BaseProviderFactory factory = new BaseProviderFactory(true);
factory.setProviderProperties(providerProperties);
factory.setRunOrderParameters(new RunOrderParameters(new RunOrder[] {RunOrder.BALANCED}, new File(".")));
RunOrderCalculator calculator = factory.getRunOrderCalculator();
assertThat(calculator).isInstanceOf(DefaultRunOrderCalculator.class);
return getInternalState(calculator, "distributedParallelism");
}

private static int getInternalState(Object target, String fieldName) {
try {
Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.getInt(target);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ public void testReverseAlphabeticalJUnit4() throws VerificationException {
assertTestnamesAppearInSpecificOrder(validator, TESTS_IN_REVERSE_ALPHABETICAL_ORDER);
}

@Test
public void testBalancedWithMultipleForksWithoutParallel() {
executeBalancedWithForkCount(2);
}

@Test
public void testBalancedWithoutForkingClampsDistributionWidth() {
executeBalancedWithForkCount(0);
}

@Test
public void testNonExistingRunOrderJUnit4() {
unpack().forkCount(1)
Expand Down Expand Up @@ -141,6 +151,14 @@ private OutputValidator executeWithRunOrder(String runOrder, String sourceName,
.verifyErrorFree(3);
}

private OutputValidator executeBalancedWithForkCount(int forkCount) {
return unpack().forkCount(forkCount)
.reuseForks(reuseForks())
.runOrder("balanced")
.executeTest()
.verifyErrorFree(3);
}

private OutputValidator executeWithRandomOrder(long seed) {
return unpack().forkCount(1)
.reuseForks(reuseForks())
Expand Down