1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
|
<?php
declare( strict_types = 1 );
namespace MediaWiki\Composer\PhpUnitSplitter;
/**
* @license GPL-2.0-or-later
*/
class TestSuiteBuilder {
private static function sortByTimeDescending( TestDescriptor $a, TestDescriptor $b ): int {
if ( $a->getDuration() === $b->getDuration() ) {
return 0;
}
return ( $a->getDuration() > $b->getDuration() ? -1 : 1 );
}
private static function smallestGroup( array $suites ): int {
$min = 10000;
$minIndex = 0;
$groups = count( $suites );
for ( $i = 0; $i < $groups; $i++ ) {
if ( $suites[$i]["time"] < $min ) {
$min = $suites[$i]["time"];
$minIndex = $i;
}
}
return $minIndex;
}
public function buildSuites( array $testDescriptors, int $groups ): array {
$suites = array_fill( 0, $groups, [ "list" => [], "time" => 0 ] );
$roundRobin = 0;
usort( $testDescriptors, [ self::class, "sortByTimeDescending" ] );
foreach ( $testDescriptors as $testDescriptor ) {
if ( !$testDescriptor->getFilename() ) {
// We didn't resolve a matching file for this test, so we skip it
// from the suite here. This only happens for "known" missing test
// classes (see PhpUnitXmlManager::EXPECTED_MISSING_CLASSES) - in
// all other cases a missing test file will throw an exception during
// suite building.
continue;
}
if ( $testDescriptor->getDuration() === 0 ) {
// If no explicit timing information is available for a test, we just
// drop it round-robin into the next bucket.
$nextSuite = $roundRobin;
$roundRobin = ( $roundRobin + 1 ) % $groups;
} else {
// If we have information about the test duration, we try and balance
// out the tests suites by having an even amount of time spent on
// each suite.
$nextSuite = self::smallestGroup( $suites );
}
$suites[$nextSuite]["list"][] = $testDescriptor->getFilename();
$suites[$nextSuite]["time"] += $testDescriptor->getDuration();
}
return $suites;
}
}
|