1
0
Fork 0

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 calls
pull/10637/head
Jordi Boggiano 2022-03-17 14:52:14 +01:00 committed by GitHub
parent ca3b874414
commit b0665981c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 69 additions and 18 deletions

View File

@ -21,6 +21,8 @@ use Composer\Util\Git as GitUtil;
use Composer\Util\HttpDownloader; use Composer\Util\HttpDownloader;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\Svn as SvnUtil; 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. * Try to guess the current version number based on different VCS configuration.
@ -307,6 +309,8 @@ class VersionGuesser
return strnatcasecmp($b, $a); return strnatcasecmp($b, $a);
}); });
$promises = [];
$this->process->setMaxJobs(30);
foreach ($branches as $candidate) { foreach ($branches as $candidate) {
$candidateVersion = Preg::replace('{^remotes/\S+/}', '', $candidate); $candidateVersion = Preg::replace('{^remotes/\S+/}', '', $candidate);
@ -316,20 +320,30 @@ class VersionGuesser
} }
$cmdLine = str_replace(array('%candidate%', '%branch%'), array($candidate, $branch), $scmCmdline); $cmdLine = str_replace(array('%candidate%', '%branch%'), array($candidate, $branch), $scmCmdline);
if (0 !== $this->process->execute($cmdLine, $output, $path)) { $promises[] = $this->process->executeAsync($cmdLine, $path)->then(function (Process $process) use (&$length, &$version, &$prettyVersion, $candidateVersion, &$promises): void {
continue; if (!$process->isSuccessful()) {
return;
} }
$output = $process->getOutput();
if (strlen($output) < $length) { if (strlen($output) < $length) {
$length = strlen($output); $length = strlen($output);
$version = $this->versionParser->normalizeBranch($candidateVersion); $version = $this->versionParser->normalizeBranch($candidateVersion);
$prettyVersion = 'dev-' . $candidateVersion; $prettyVersion = 'dev-' . $candidateVersion;
if ($length === 0) { if ($length === 0) {
break; foreach ($promises as $promise) {
if ($promise instanceof CancellablePromiseInterface) {
$promise->cancel();
} }
} }
} }
} }
});
}
$this->process->wait();
$this->process->resetMaxJobs();
}
return array('version' => $version, 'pretty_version' => $prettyVersion); return array('version' => $version, 'pretty_version' => $prettyVersion);
} }

View File

@ -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 * @param ?int $index job id
* @return void * @return void
@ -278,7 +288,7 @@ class ProcessExecutor
public function wait($index = null): void public function wait($index = null): void
{ {
while (true) { while (true) {
if (!$this->countActiveJobs($index)) { if (0 === $this->countActiveJobs($index)) {
return; return;
} }

View File

@ -12,6 +12,8 @@
namespace Composer\Test\Mock; namespace Composer\Test\Mock;
use Composer\Test\TestCase;
use PHPUnit\Framework\MockObject\MockBuilder;
use React\Promise\PromiseInterface; use React\Promise\PromiseInterface;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\Platform; use Composer\Util\Platform;
@ -41,6 +43,19 @@ class ProcessExecutorMock extends ProcessExecutor
* @var string[] * @var string[]
*/ */
private $log = array(); 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 * @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 public function executeAsync($command, ?string $cwd = null): PromiseInterface
{ {
$resolver = function ($resolve, $reject): void { $cwd = $cwd ?? Platform::getCwd();
// TODO strictly speaking this should resolve with a mock Process instance here
$resolve(); $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 { $canceler = function (): void {

View File

@ -20,6 +20,7 @@ use Composer\Package\RootPackage;
use Composer\Package\Version\VersionGuesser; use Composer\Package\Version\VersionGuesser;
use Composer\Semver\VersionParser; use Composer\Semver\VersionParser;
use Composer\Test\TestCase; use Composer\Test\TestCase;
use Composer\Util\ProcessExecutor;
class RootPackageLoaderTest extends TestCase class RootPackageLoaderTest extends TestCase
{ {
@ -36,8 +37,11 @@ class RootPackageLoaderTest extends TestCase
$config = new Config; $config = new Config;
$config->merge(array('repositories' => array('packagist' => false))); $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); return $loader->load($data);
} }

View File

@ -320,7 +320,7 @@ class GitHubDriverTest extends TestCase
->setConstructorArgs(array($io, $this->config)) ->setConstructorArgs(array($io, $this->config))
->getMock(); ->getMock();
$gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $httpDownloader, new ProcessExecutorMock); $gitHubDriver = new GitHubDriver($repoConfig, $io, $this->config, $httpDownloader, $this->getProcessExecutorMock());
$gitHubDriver->initialize(); $gitHubDriver->initialize();
} }

View File

@ -32,6 +32,7 @@ use Composer\Package\RootAliasPackage;
use Composer\Package\CompletePackage; use Composer\Package\CompletePackage;
use Composer\Package\CompleteAliasPackage; use Composer\Package\CompleteAliasPackage;
use Composer\Package\Package; use Composer\Package\Package;
use Symfony\Component\Process\Process;
abstract class TestCase extends \PHPUnit\Framework\TestCase abstract class TestCase extends \PHPUnit\Framework\TestCase
{ {
@ -256,7 +257,7 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
protected function getProcessExecutorMock(): ProcessExecutorMock protected function getProcessExecutorMock(): ProcessExecutorMock
{ {
$this->processExecutorMocks[] = $mock = new ProcessExecutorMock(); $this->processExecutorMocks[] = $mock = new ProcessExecutorMock($this->getMockBuilder(Process::class));
return $mock; return $mock;
} }