Parallellize the branch comparisons to speed up bootstrapping/version guessing when on a feature branch (#10632)
* Parallellize the branch comparisons to speed up bootstrapping/version guessing when on a feature branch, fixes #10568 * Allow ProcessExecutorMock to function with async callspull/10637/head
parent
ca3b874414
commit
b0665981c2
|
@ -21,6 +21,8 @@ use Composer\Util\Git as GitUtil;
|
|||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\Svn as SvnUtil;
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
/**
|
||||
* Try to guess the current version number based on different VCS configuration.
|
||||
|
@ -307,6 +309,8 @@ class VersionGuesser
|
|||
return strnatcasecmp($b, $a);
|
||||
});
|
||||
|
||||
$promises = [];
|
||||
$this->process->setMaxJobs(30);
|
||||
foreach ($branches as $candidate) {
|
||||
$candidateVersion = Preg::replace('{^remotes/\S+/}', '', $candidate);
|
||||
|
||||
|
@ -316,19 +320,29 @@ class VersionGuesser
|
|||
}
|
||||
|
||||
$cmdLine = str_replace(array('%candidate%', '%branch%'), array($candidate, $branch), $scmCmdline);
|
||||
if (0 !== $this->process->execute($cmdLine, $output, $path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strlen($output) < $length) {
|
||||
$length = strlen($output);
|
||||
$version = $this->versionParser->normalizeBranch($candidateVersion);
|
||||
$prettyVersion = 'dev-' . $candidateVersion;
|
||||
if ($length === 0) {
|
||||
break;
|
||||
$promises[] = $this->process->executeAsync($cmdLine, $path)->then(function (Process $process) use (&$length, &$version, &$prettyVersion, $candidateVersion, &$promises): void {
|
||||
if (!$process->isSuccessful()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$output = $process->getOutput();
|
||||
if (strlen($output) < $length) {
|
||||
$length = strlen($output);
|
||||
$version = $this->versionParser->normalizeBranch($candidateVersion);
|
||||
$prettyVersion = 'dev-' . $candidateVersion;
|
||||
if ($length === 0) {
|
||||
foreach ($promises as $promise) {
|
||||
if ($promise instanceof CancellablePromiseInterface) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$this->process->wait();
|
||||
$this->process->resetMaxJobs();
|
||||
}
|
||||
|
||||
return array('version' => $version, 'pretty_version' => $prettyVersion);
|
||||
|
|
|
@ -271,6 +271,16 @@ class ProcessExecutor
|
|||
}
|
||||
}
|
||||
|
||||
public function setMaxJobs(int $maxJobs): void
|
||||
{
|
||||
$this->maxJobs = $maxJobs;
|
||||
}
|
||||
|
||||
public function resetMaxJobs(): void
|
||||
{
|
||||
$this->maxJobs = 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?int $index job id
|
||||
* @return void
|
||||
|
@ -278,7 +288,7 @@ class ProcessExecutor
|
|||
public function wait($index = null): void
|
||||
{
|
||||
while (true) {
|
||||
if (!$this->countActiveJobs($index)) {
|
||||
if (0 === $this->countActiveJobs($index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
namespace Composer\Test\Mock;
|
||||
|
||||
use Composer\Test\TestCase;
|
||||
use PHPUnit\Framework\MockObject\MockBuilder;
|
||||
use React\Promise\PromiseInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Composer\Util\Platform;
|
||||
|
@ -41,6 +43,19 @@ class ProcessExecutorMock extends ProcessExecutor
|
|||
* @var string[]
|
||||
*/
|
||||
private $log = array();
|
||||
/**
|
||||
* @var MockBuilder<Process>
|
||||
*/
|
||||
private $processMockBuilder;
|
||||
|
||||
/**
|
||||
* @param MockBuilder<Process> $processMockBuilder
|
||||
*/
|
||||
public function __construct(MockBuilder $processMockBuilder)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->processMockBuilder = $processMockBuilder->disableOriginalConstructor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|array{cmd: string|list<string>, return?: int, stdout?: string, stderr?: string, callback?: callable}> $expectations
|
||||
|
@ -180,9 +195,16 @@ class ProcessExecutorMock extends ProcessExecutor
|
|||
|
||||
public function executeAsync($command, ?string $cwd = null): PromiseInterface
|
||||
{
|
||||
$resolver = function ($resolve, $reject): void {
|
||||
// TODO strictly speaking this should resolve with a mock Process instance here
|
||||
$resolve();
|
||||
$cwd = $cwd ?? Platform::getCwd();
|
||||
|
||||
$resolver = function ($resolve, $reject) use ($command, $cwd): void {
|
||||
$result = $this->doExecute($command, $cwd, false, $output);
|
||||
$procMock = $this->processMockBuilder->getMock();
|
||||
$procMock->method('getOutput')->willReturn($output);
|
||||
$procMock->method('isSuccessful')->willReturn($result === 0);
|
||||
$procMock->method('getExitCode')->willReturn($result);
|
||||
|
||||
$resolve($procMock);
|
||||
};
|
||||
|
||||
$canceler = function (): void {
|
||||
|
|
|
@ -20,6 +20,7 @@ use Composer\Package\RootPackage;
|
|||
use Composer\Package\Version\VersionGuesser;
|
||||
use Composer\Semver\VersionParser;
|
||||
use Composer\Test\TestCase;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
|
||||
class RootPackageLoaderTest extends TestCase
|
||||
{
|
||||
|
@ -36,8 +37,11 @@ class RootPackageLoaderTest extends TestCase
|
|||
|
||||
$config = new Config;
|
||||
$config->merge(array('repositories' => array('packagist' => false)));
|
||||
$processExecutor = new ProcessExecutor();
|
||||
$processExecutor->enableAsync();
|
||||
$guesser = new VersionGuesser($config, $processExecutor, new VersionParser());
|
||||
|
||||
$loader = new RootPackageLoader($manager, $config);
|
||||
$loader = new RootPackageLoader($manager, $config, null, $guesser);
|
||||
|
||||
return $loader->load($data);
|
||||
}
|
||||
|
|
|
@ -320,7 +320,7 @@ class GitHubDriverTest extends TestCase
|
|||
->setConstructorArgs(array($io, $this->config))
|
||||
->getMock();
|
||||
|
||||
$gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $httpDownloader, new ProcessExecutorMock);
|
||||
$gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $httpDownloader, $this->getProcessExecutorMock());
|
||||
$gitHubDriver->initialize();
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ use Composer\Package\RootAliasPackage;
|
|||
use Composer\Package\CompletePackage;
|
||||
use Composer\Package\CompleteAliasPackage;
|
||||
use Composer\Package\Package;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
abstract class TestCase extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
|
@ -256,7 +257,7 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
|
|||
|
||||
protected function getProcessExecutorMock(): ProcessExecutorMock
|
||||
{
|
||||
$this->processExecutorMocks[] = $mock = new ProcessExecutorMock();
|
||||
$this->processExecutorMocks[] = $mock = new ProcessExecutorMock($this->getMockBuilder(Process::class));
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue