1
0
Fork 0

Add parallel download capability to FileDownloader and derivatives

pull/7904/head
Jordi Boggiano 2019-01-17 17:12:33 +01:00
parent 0f2f950cb6
commit 3dfcae99a9
50 changed files with 803 additions and 492 deletions

View File

@ -22,6 +22,7 @@ use Composer\Script\ScriptEvents;
use Composer\Plugin\CommandEvent; use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use Composer\Util\Loop;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
@ -104,8 +105,9 @@ EOT
$archiveManager = $composer->getArchiveManager(); $archiveManager = $composer->getArchiveManager();
} else { } else {
$factory = new Factory; $factory = new Factory;
$downloadManager = $factory->createDownloadManager($io, $config, $factory->createHttpDownloader($io, $config)); $httpDownloader = $factory->createHttpDownloader($io, $config);
$archiveManager = $factory->createArchiveManager($config, $downloadManager); $downloadManager = $factory->createDownloadManager($io, $config, $httpDownloader);
$archiveManager = $factory->createArchiveManager($config, $downloadManager, new Loop($httpDownloader));
} }
if ($packageName) { if ($packageName) {

View File

@ -38,6 +38,7 @@ use Symfony\Component\Finder\Finder;
use Composer\Json\JsonFile; use Composer\Json\JsonFile;
use Composer\Config\JsonConfigSource; use Composer\Config\JsonConfigSource;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use Composer\Util\Loop;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
/** /**
@ -345,15 +346,18 @@ EOT
$package = $package->getAliasOf(); $package = $package->getAliasOf();
} }
$dm = $this->createDownloadManager($io, $config); $factory = new Factory();
$httpDownloader = $factory->createHttpDownloader($io, $config);
$dm = $factory->createDownloadManager($io, $config, $httpDownloader);
$dm->setPreferSource($preferSource) $dm->setPreferSource($preferSource)
->setPreferDist($preferDist) ->setPreferDist($preferDist)
->setOutputProgress(!$noProgress); ->setOutputProgress(!$noProgress);
$projectInstaller = new ProjectInstaller($directory, $dm); $projectInstaller = new ProjectInstaller($directory, $dm);
$im = $this->createInstallationManager(); $im = $factory->createInstallationManager(new Loop($httpDownloader));
$im->addInstaller($projectInstaller); $im->addInstaller($projectInstaller);
$im->install(new InstalledFilesystemRepository(new JsonFile('php://memory')), new InstallOperation($package)); $im->execute(new InstalledFilesystemRepository(new JsonFile('php://memory')), new InstallOperation($package));
$im->notifyInstalls($io); $im->notifyInstalls($io);
// collect suggestions // collect suggestions
@ -369,16 +373,4 @@ EOT
return $installedFromVcs; return $installedFromVcs;
} }
protected function createDownloadManager(IOInterface $io, Config $config)
{
$factory = new Factory();
return $factory->createDownloadManager($io, $config, $factory->createHttpDownloader($io, $config));
}
protected function createInstallationManager()
{
return new InstallationManager();
}
} }

View File

@ -89,7 +89,7 @@ EOT
// list packages // list packages
foreach ($installedRepo->getCanonicalPackages() as $package) { foreach ($installedRepo->getCanonicalPackages() as $package) {
$downloader = $dm->getDownloaderForInstalledPackage($package); $downloader = $dm->getDownloaderForPackage($package);
$targetDir = $im->getInstallPath($package); $targetDir = $im->getInstallPath($package);
if ($downloader instanceof ChangeReportInterface) { if ($downloader instanceof ChangeReportInterface) {

View File

@ -30,33 +30,50 @@ abstract class ArchiveDownloader extends FileDownloader
* @throws \RuntimeException * @throws \RuntimeException
* @throws \UnexpectedValueException * @throws \UnexpectedValueException
*/ */
public function download(PackageInterface $package, $path, $output = true) public function install(PackageInterface $package, $path, $output = true)
{ {
$temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8); if ($output) {
$retries = 3; $this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
while ($retries--) { }
$fileName = parent::download($package, $path, $output);
if ($output) { $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8);
$this->io->writeError(' Extracting archive', false, IOInterface::VERBOSE); $fileName = $this->getFileName($package, $path);
if ($output) {
$this->io->writeError(' Extracting archive', true, IOInterface::VERBOSE);
}
try {
$this->filesystem->ensureDirectoryExists($temporaryDir);
try {
$this->extract($package, $fileName, $temporaryDir);
} catch (\Exception $e) {
// remove cache if the file was corrupted
parent::clearLastCacheWrite($package);
throw $e;
} }
try { $this->filesystem->unlink($fileName);
$this->filesystem->ensureDirectoryExists($temporaryDir);
try { $renameAsOne = false;
$this->extract($fileName, $temporaryDir); if (!file_exists($path) || ($this->filesystem->isDirEmpty($path) && $this->filesystem->removeDirectory($path))) {
} catch (\Exception $e) { $renameAsOne = true;
// remove cache if the file was corrupted }
parent::clearLastCacheWrite($package);
throw $e; $contentDir = $this->getFolderContent($temporaryDir);
$singleDirAtTopLevel = 1 === count($contentDir) && is_dir(reset($contentDir));
if ($renameAsOne) {
// if the target $path is clear, we can rename the whole package in one go instead of looping over the contents
if ($singleDirAtTopLevel) {
$extractedDir = (string) reset($contentDir);
} else {
$extractedDir = $temporaryDir;
} }
$this->filesystem->rename($extractedDir, $path);
$this->filesystem->unlink($fileName); } else {
$contentDir = $this->getFolderContent($temporaryDir);
// only one dir in the archive, extract its contents out of it // only one dir in the archive, extract its contents out of it
if (1 === count($contentDir) && is_dir(reset($contentDir))) { if ($singleDirAtTopLevel) {
$contentDir = $this->getFolderContent((string) reset($contentDir)); $contentDir = $this->getFolderContent((string) reset($contentDir));
} }
@ -65,35 +82,24 @@ abstract class ArchiveDownloader extends FileDownloader
$file = (string) $file; $file = (string) $file;
$this->filesystem->rename($file, $path . '/' . basename($file)); $this->filesystem->rename($file, $path . '/' . basename($file));
} }
$this->filesystem->removeDirectory($temporaryDir);
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) {
$this->filesystem->removeDirectory($this->config->get('vendor-dir').'/composer/');
}
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir'))) {
$this->filesystem->removeDirectory($this->config->get('vendor-dir'));
}
} catch (\Exception $e) {
// clean up
$this->filesystem->removeDirectory($path);
$this->filesystem->removeDirectory($temporaryDir);
// retry downloading if we have an invalid zip file
if ($retries && $e instanceof \UnexpectedValueException && class_exists('ZipArchive') && $e->getCode() === \ZipArchive::ER_NOZIP) {
$this->io->writeError('');
if ($this->io->isDebug()) {
$this->io->writeError(' Invalid zip file ('.$e->getMessage().'), retrying...');
} else {
$this->io->writeError(' Invalid zip file, retrying...');
}
usleep(500000);
continue;
}
throw $e;
} }
break; $this->filesystem->removeDirectory($temporaryDir);
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) {
$this->filesystem->removeDirectory($this->config->get('vendor-dir').'/composer/');
}
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir'))) {
$this->filesystem->removeDirectory($this->config->get('vendor-dir'));
}
} catch (\Exception $e) {
// clean up
$this->filesystem->removeDirectory($path);
$this->filesystem->removeDirectory($temporaryDir);
if (file_exists($fileName)) {
$this->filesystem->unlink($fileName);
}
throw $e;
} }
} }
@ -102,7 +108,7 @@ abstract class ArchiveDownloader extends FileDownloader
*/ */
protected function getFileName(PackageInterface $package, $path) protected function getFileName(PackageInterface $package, $path)
{ {
return rtrim($path.'/'.md5($path.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.'); return rtrim($path.'_'.md5($path.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.');
} }
/** /**
@ -113,7 +119,7 @@ abstract class ArchiveDownloader extends FileDownloader
* *
* @throws \UnexpectedValueException If can not extract downloaded file to path * @throws \UnexpectedValueException If can not extract downloaded file to path
*/ */
abstract protected function extract($file, $path); abstract protected function extract(PackageInterface $package, $file, $path);
/** /**
* Returns the folder content, excluding dotfiles * Returns the folder content, excluding dotfiles

View File

@ -15,6 +15,7 @@ namespace Composer\Downloader;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use React\Promise\PromiseInterface;
/** /**
* Downloaders manager. * Downloaders manager.
@ -24,6 +25,7 @@ use Composer\Util\Filesystem;
class DownloadManager class DownloadManager
{ {
private $io; private $io;
private $httpDownloader;
private $preferDist = false; private $preferDist = false;
private $preferSource = false; private $preferSource = false;
private $packagePreferences = array(); private $packagePreferences = array();
@ -33,9 +35,9 @@ class DownloadManager
/** /**
* Initializes download manager. * Initializes download manager.
* *
* @param IOInterface $io The Input Output Interface * @param IOInterface $io The Input Output Interface
* @param bool $preferSource prefer downloading from source * @param bool $preferSource prefer downloading from source
* @param Filesystem|null $filesystem custom Filesystem object * @param Filesystem|null $filesystem custom Filesystem object
*/ */
public function __construct(IOInterface $io, $preferSource = false, Filesystem $filesystem = null) public function __construct(IOInterface $io, $preferSource = false, Filesystem $filesystem = null)
{ {
@ -140,7 +142,7 @@ class DownloadManager
* wrong type * wrong type
* @return DownloaderInterface|null * @return DownloaderInterface|null
*/ */
public function getDownloaderForInstalledPackage(PackageInterface $package) public function getDownloaderForPackage(PackageInterface $package)
{ {
$installationSource = $package->getInstallationSource(); $installationSource = $package->getInstallationSource();
@ -154,7 +156,7 @@ class DownloadManager
$downloader = $this->getDownloader($package->getSourceType()); $downloader = $this->getDownloader($package->getSourceType());
} else { } else {
throw new \InvalidArgumentException( throw new \InvalidArgumentException(
'Package '.$package.' seems not been installed properly' 'Package '.$package.' does not have an installation source set'
); );
} }
@ -171,63 +173,95 @@ class DownloadManager
return $downloader; return $downloader;
} }
public function getDownloaderType(DownloaderInterface $downloader)
{
return array_search($downloader, $this->downloaders);
}
/** /**
* Downloads package into target dir. * Downloads package into target dir.
* *
* @param PackageInterface $package package instance * @param PackageInterface $package package instance
* @param string $targetDir target dir * @param string $targetDir target dir
* @param bool $preferSource prefer installation from source * @param PackageInterface $prevPackage previous package instance in case of updates
*
* @return PromiseInterface
* @throws \InvalidArgumentException if package have no urls to download from
* @throws \RuntimeException
*/
public function download(PackageInterface $package, $targetDir, PackageInterface $prevPackage = null)
{
$this->filesystem->ensureDirectoryExists(dirname($targetDir));
$sources = $this->getAvailableSources($package, $prevPackage);
$io = $this->io;
$self = $this;
$download = function ($retry = false) use (&$sources, $io, $package, $self, $targetDir, &$download) {
$source = array_shift($sources);
if ($retry) {
$io->writeError(' <warning>Now trying to download from ' . $source . '</warning>');
}
$package->setInstallationSource($source);
$downloader = $self->getDownloaderForPackage($package);
if (!$downloader) {
return \React\Promise\resolve();
}
$handleError = function ($e) use ($sources, $source, $package, $io, $download) {
if ($e instanceof \RuntimeException) {
if (!$sources) {
throw $e;
}
$io->writeError(
' <warning>Failed to download '.
$package->getPrettyName().
' from ' . $source . ': '.
$e->getMessage().'</warning>'
);
return $download(true);
}
throw $e;
};
try {
$result = $downloader->download($package, $targetDir);
} catch (\Exception $e) {
return $handleError($e);
}
if (!$result instanceof PromiseInterface) {
return \React\Promise\resolve($result);
}
$res = $result->then(function ($res) {
return $res;
}, $handleError);
return $res;
};
return $download();
}
/**
* Installs package into target dir.
*
* @param PackageInterface $package package instance
* @param string $targetDir target dir
* *
* @throws \InvalidArgumentException if package have no urls to download from * @throws \InvalidArgumentException if package have no urls to download from
* @throws \RuntimeException * @throws \RuntimeException
*/ */
public function download(PackageInterface $package, $targetDir, $preferSource = null) public function install(PackageInterface $package, $targetDir)
{ {
$preferSource = null !== $preferSource ? $preferSource : $this->preferSource; $downloader = $this->getDownloaderForPackage($package);
$sourceType = $package->getSourceType(); if ($downloader) {
$distType = $package->getDistType(); $downloader->install($package, $targetDir);
$sources = array();
if ($sourceType) {
$sources[] = 'source';
}
if ($distType) {
$sources[] = 'dist';
}
if (empty($sources)) {
throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
}
if (!$preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) {
$sources = array_reverse($sources);
}
$this->filesystem->ensureDirectoryExists($targetDir);
foreach ($sources as $i => $source) {
if (isset($e)) {
$this->io->writeError(' <warning>Now trying to download from ' . $source . '</warning>');
}
$package->setInstallationSource($source);
try {
$downloader = $this->getDownloaderForInstalledPackage($package);
if ($downloader) {
$downloader->download($package, $targetDir);
}
break;
} catch (\RuntimeException $e) {
if ($i === count($sources) - 1) {
throw $e;
}
$this->io->writeError(
' <warning>Failed to download '.
$package->getPrettyName().
' from ' . $source . ': '.
$e->getMessage().'</warning>'
);
}
} }
} }
@ -242,31 +276,23 @@ class DownloadManager
*/ */
public function update(PackageInterface $initial, PackageInterface $target, $targetDir) public function update(PackageInterface $initial, PackageInterface $target, $targetDir)
{ {
$downloader = $this->getDownloaderForInstalledPackage($initial); $downloader = $this->getDownloaderForPackage($target);
$initialDownloader = $this->getDownloaderForPackage($initial);
// no downloaders present means update from metapackage to metapackage, nothing to do
if (!$initialDownloader && !$downloader) {
return;
}
// if we have a downloader present before, but not after, the package became a metapackage and its files should be removed
if (!$downloader) { if (!$downloader) {
$initialDownloader->remove($initial, $targetDir);
return; return;
} }
$installationSource = $initial->getInstallationSource(); $initialType = $this->getDownloaderType($initialDownloader);
$targetType = $this->getDownloaderType($downloader);
if ('dist' === $installationSource) {
$initialType = $initial->getDistType();
$targetType = $target->getDistType();
} else {
$initialType = $initial->getSourceType();
$targetType = $target->getSourceType();
}
// upgrading from a dist stable package to a dev package, force source reinstall
if ($target->isDev() && 'dist' === $installationSource) {
$downloader->remove($initial, $targetDir);
$this->download($target, $targetDir);
return;
}
if ($initialType === $targetType) { if ($initialType === $targetType) {
$target->setInstallationSource($installationSource);
try { try {
$downloader->update($initial, $target, $targetDir); $downloader->update($initial, $target, $targetDir);
@ -282,8 +308,12 @@ class DownloadManager
} }
} }
$downloader->remove($initial, $targetDir); // if downloader type changed, or update failed and user asks for reinstall,
$this->download($target, $targetDir, 'source' === $installationSource); // we wipe the dir and do a new install instead of updating it
if ($initialDownloader) {
$initialDownloader->remove($initial, $targetDir);
}
$this->install($target, $targetDir);
} }
/** /**
@ -294,7 +324,7 @@ class DownloadManager
*/ */
public function remove(PackageInterface $package, $targetDir) public function remove(PackageInterface $package, $targetDir)
{ {
$downloader = $this->getDownloaderForInstalledPackage($package); $downloader = $this->getDownloaderForPackage($package);
if ($downloader) { if ($downloader) {
$downloader->remove($package, $targetDir); $downloader->remove($package, $targetDir);
} }
@ -322,4 +352,48 @@ class DownloadManager
return $package->isDev() ? 'source' : 'dist'; return $package->isDev() ? 'source' : 'dist';
} }
/**
* @return string[]
*/
private function getAvailableSources(PackageInterface $package, PackageInterface $prevPackage = null)
{
$sourceType = $package->getSourceType();
$distType = $package->getDistType();
// add source before dist by default
$sources = array();
if ($sourceType) {
$sources[] = 'source';
}
if ($distType) {
$sources[] = 'dist';
}
if (empty($sources)) {
throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
}
if (
$prevPackage
// if we are updating, we want to keep the same source as the previously installed package (if available in the new one)
&& in_array($prevPackage->getInstallationSource(), $sources, true)
// unless the previous package was stable dist (by default) and the new package is dev, then we allow the new default to take over
&& !(!$prevPackage->isDev() && $prevPackage->getInstallationSource() === 'dist' && $package->isDev())
) {
$prevSource = $prevPackage->getInstallationSource();
usort($sources, function ($a, $b) use ($prevSource) {
return $a === $prevSource ? -1 : 1;
});
return $sources;
}
// reverse sources in case dist is the preferred source for this package
if (!$this->preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) {
$sources = array_reverse($sources);
}
return $sources;
}
} }

View File

@ -13,6 +13,7 @@
namespace Composer\Downloader; namespace Composer\Downloader;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use React\Promise\PromiseInterface;
/** /**
* Downloader interface. * Downloader interface.
@ -29,13 +30,20 @@ interface DownloaderInterface
*/ */
public function getInstallationSource(); public function getInstallationSource();
/**
* This should do any network-related tasks to prepare for install/update
*
* @return PromiseInterface|null
*/
public function download(PackageInterface $package, $path);
/** /**
* Downloads specific package into specific folder. * Downloads specific package into specific folder.
* *
* @param PackageInterface $package package instance * @param PackageInterface $package package instance
* @param string $path download path * @param string $path download path
*/ */
public function download(PackageInterface $package, $path); public function install(PackageInterface $package, $path);
/** /**
* Updates specific package in specific folder from initial to target version. * Updates specific package in specific folder from initial to target version.

View File

@ -26,6 +26,7 @@ use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use Composer\Util\HttpDownloader; use Composer\Util\HttpDownloader;
use Composer\Util\Url as UrlUtil; use Composer\Util\Url as UrlUtil;
use Composer\Downloader\TransportException;
/** /**
* Base downloader for files * Base downloader for files
@ -43,7 +44,10 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
protected $filesystem; protected $filesystem;
protected $cache; protected $cache;
protected $outputProgress = true; protected $outputProgress = true;
private $lastCacheWrites = array(); /**
* @private this is only public for php 5.3 support in closures
*/
public $lastCacheWrites = array();
private $eventDispatcher; private $eventDispatcher;
/** /**
@ -87,108 +91,149 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
throw new \InvalidArgumentException('The given package is missing url information'); throw new \InvalidArgumentException('The given package is missing url information');
} }
if ($output) { $retries = 3;
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): ", false);
}
$urls = $package->getDistUrls(); $urls = $package->getDistUrls();
while ($url = array_shift($urls)) { foreach ($urls as $index => $url) {
try { $processedUrl = $this->processUrl($package, $url);
$fileName = $this->doDownload($package, $path, $url); $urls[$index] = array(
break; 'base' => $url,
} catch (\Exception $e) { 'processed' => $processedUrl,
if ($this->io->isDebug()) { 'cacheKey' => $this->getCacheKey($package, $processedUrl)
$this->io->writeError(''); );
$this->io->writeError('Failed: ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
} elseif (count($urls)) {
$this->io->writeError('');
$this->io->writeError(' Failed, trying the next URL ('.$e->getCode().': '.$e->getMessage().')', false);
}
if (!count($urls)) {
throw $e;
}
}
} }
if ($output) {
$this->io->writeError('');
}
return $fileName;
}
protected function doDownload(PackageInterface $package, $path, $url)
{
$this->filesystem->emptyDirectory($path); $this->filesystem->emptyDirectory($path);
$fileName = $this->getFileName($package, $path); $fileName = $this->getFileName($package, $path);
$processedUrl = $this->processUrl($package, $url); $io = $this->io;
$cache = $this->cache;
$originalHttpDownloader = $this->httpDownloader;
$eventDispatcher = $this->eventDispatcher;
$filesystem = $this->filesystem;
$self = $this;
$preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $processedUrl); $accept = null;
if ($this->eventDispatcher) { $reject = null;
$this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); $download = function () use ($io, $output, $originalHttpDownloader, $cache, $eventDispatcher, $package, $fileName, $path, &$urls, &$accept, &$reject) {
} $url = reset($urls);
$httpDownloader = $preFileDownloadEvent->getHttpDownloader();
$httpDownloader = $originalHttpDownloader;
if ($eventDispatcher) {
$preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $httpDownloader, $url['processed']);
$eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
$httpDownloader = $preFileDownloadEvent->getHttpDownloader();
}
try {
$checksum = $package->getDistSha1Checksum(); $checksum = $package->getDistSha1Checksum();
$cacheKey = $this->getCacheKey($package, $processedUrl); $cacheKey = $url['cacheKey'];
// use from cache if it is present and has a valid checksum or we have no checksum to check against // use from cache if it is present and has a valid checksum or we have no checksum to check against
if ($this->cache && (!$checksum || $checksum === $this->cache->sha1($cacheKey)) && $this->cache->copyTo($cacheKey, $fileName)) { if ($cache && (!$checksum || $checksum === $cache->sha1($cacheKey)) && $cache->copyTo($cacheKey, $fileName)) {
$this->io->writeError('Loading from cache', false); if ($output) {
$io->writeError(" - Loading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>) from cache");
}
$result = \React\Promise\resolve($fileName);
} else { } else {
// download if cache restore failed if ($output) {
if (!$this->outputProgress) { $io->writeError(" - Downloading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
$this->io->writeError('Downloading', false);
} }
// try to download 3 times then fail hard $result = $httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions())
$retries = 3; ->then($accept, $reject);
while ($retries--) {
try {
// TODO handle this->outputProgress
$httpDownloader->copy($processedUrl, $fileName, $package->getTransportOptions());
break;
} catch (TransportException $e) {
// if we got an http response with a proper code, then requesting again will probably not help, abort
if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) {
throw $e;
}
$this->io->writeError('');
$this->io->writeError(' Download failed, retrying...', true, IOInterface::VERBOSE);
usleep(500000);
}
}
if (!$this->outputProgress) {
$this->io->writeError(' (<comment>100%</comment>)', false);
}
if ($this->cache) {
$this->lastCacheWrites[$package->getName()] = $cacheKey;
$this->cache->copyFrom($cacheKey, $fileName);
}
} }
if (!file_exists($fileName)) { return $result->then(function ($result) use ($fileName, $checksum, $url) {
throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the' // in case of retry, the first call's Promise chain finally calls this twice at the end,
.' directory is writable and you have internet connectivity'); // once with $result being the returned $fileName from $accept, and then once for every
// failed request with a null result, which can be skipped.
if (null === $result) {
return $fileName;
}
if (!file_exists($fileName)) {
throw new \UnexpectedValueException($url['base'].' could not be saved to '.$fileName.', make sure the'
.' directory is writable and you have internet connectivity');
}
if ($checksum && hash_file('sha1', $fileName) !== $checksum) {
throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url['base'].')');
}
return $fileName;
});
};
$accept = function ($response) use ($io, $cache, $package, $fileName, $path, $self, &$urls) {
$url = reset($urls);
$cacheKey = $url['cacheKey'];
if ($cache) {
$self->lastCacheWrites[$package->getName()] = $cacheKey;
$cache->copyFrom($cacheKey, $fileName);
} }
if ($checksum && hash_file('sha1', $fileName) !== $checksum) { $response->collect();
throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url.')');
} return $fileName;
} catch (\Exception $e) { };
$reject = function ($e) use ($io, &$urls, $download, $fileName, $path, $package, &$retries, $filesystem, $self) {
// clean up // clean up
$this->filesystem->removeDirectory($path); $filesystem->removeDirectory($path);
$this->clearLastCacheWrite($package); $self->clearLastCacheWrite($package);
if ($e instanceof TransportException) {
// if we got an http response with a proper code, then requesting again will probably not help, abort
if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) {
$retries = 0;
}
}
// special error code returned when network is being artificially disabled
if ($e instanceof TransportException && $e->getStatusCode() === 499) {
$retries = 0;
$urls = array();
}
if ($retries) {
usleep(500000);
$retries--;
return $download();
}
array_shift($urls);
if ($urls) {
if ($io->isDebug()) {
$io->writeError(' Failed downloading '.$package->getName().': ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
$io->writeError(' Trying the next URL for '.$package->getName());
} elseif (count($urls)) {
$io->writeError(' Failed downloading '.$package->getName().', trying the next URL ('.$e->getCode().': '.$e->getMessage().')');
}
$retries = 3;
usleep(100000);
return $download();
}
throw $e; throw $e;
};
return $download();
}
/**
* {@inheritDoc}
*/
public function install(PackageInterface $package, $path, $output = true)
{
if ($output) {
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
} }
return $fileName; $this->filesystem->ensureDirectoryExists($path);
$this->filesystem->rename($this->getFileName($package, $path), $path . pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME));
} }
/** /**
@ -201,7 +246,11 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
return $this; return $this;
} }
protected function clearLastCacheWrite(PackageInterface $package) /**
* TODO mark private in v3
* @protected This is public due to PHP 5.3
*/
public function clearLastCacheWrite(PackageInterface $package)
{ {
if ($this->cache && isset($this->lastCacheWrites[$package->getName()])) { if ($this->cache && isset($this->lastCacheWrites[$package->getName()])) {
$this->cache->remove($this->lastCacheWrites[$package->getName()]); $this->cache->remove($this->lastCacheWrites[$package->getName()]);
@ -222,7 +271,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
$this->io->writeError(" - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false); $this->io->writeError(" - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false);
$this->remove($initial, $path, false); $this->remove($initial, $path, false);
$this->download($target, $path, false); $this->install($target, $path, false);
$this->io->writeError(''); $this->io->writeError('');
} }
@ -249,7 +298,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
*/ */
protected function getFileName(PackageInterface $package, $path) protected function getFileName(PackageInterface $package, $path)
{ {
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME); return $path.'_'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
} }
/** /**
@ -299,7 +348,9 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
$e = null; $e = null;
try { try {
$this->download($package, $targetDir.'_compare', false); $res = $this->download($package, $targetDir.'_compare', false);
$this->httpDownloader->wait();
$res = $this->install($package, $targetDir.'_compare', false);
$comparer = new Comparer(); $comparer = new Comparer();
$comparer->setSource($targetDir.'_compare'); $comparer->setSource($targetDir.'_compare');

View File

@ -23,7 +23,7 @@ class FossilDownloader extends VcsDownloader
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function doDownload(PackageInterface $package, $path, $url) public function doInstall(PackageInterface $package, $path, $url)
{ {
// Ensure we are allowed to use this URL by config // Ensure we are allowed to use this URL by config
$this->config->prohibitUrlByConfig($url, $this->io); $this->config->prohibitUrlByConfig($url, $this->io);

View File

@ -38,7 +38,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function doDownload(PackageInterface $package, $path, $url) public function doInstall(PackageInterface $package, $path, $url)
{ {
GitUtil::cleanEnv(); GitUtil::cleanEnv();
$path = $this->normalizePath($path); $path = $this->normalizePath($path);

View File

@ -36,9 +36,10 @@ class GzipDownloader extends ArchiveDownloader
parent::__construct($io, $config, $downloader, $eventDispatcher, $cache); parent::__construct($io, $config, $downloader, $eventDispatcher, $cache);
} }
protected function extract($file, $path) protected function extract(PackageInterface $package, $file, $path)
{ {
$targetFilepath = $path . DIRECTORY_SEPARATOR . basename(substr($file, 0, -3)); $filename = pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_FILENAME);
$targetFilepath = $path . DIRECTORY_SEPARATOR . $filename;
// Try to use gunzip on *nix // Try to use gunzip on *nix
if (!Platform::isWindows()) { if (!Platform::isWindows()) {
@ -63,14 +64,6 @@ class GzipDownloader extends ArchiveDownloader
$this->extractUsingExt($file, $targetFilepath); $this->extractUsingExt($file, $targetFilepath);
} }
/**
* {@inheritdoc}
*/
protected function getFileName(PackageInterface $package, $path)
{
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
}
private function extractUsingExt($file, $targetFilepath) private function extractUsingExt($file, $targetFilepath)
{ {
$archiveFile = gzopen($file, 'rb'); $archiveFile = gzopen($file, 'rb');

View File

@ -24,7 +24,7 @@ class HgDownloader extends VcsDownloader
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function doDownload(PackageInterface $package, $path, $url) public function doInstall(PackageInterface $package, $path, $url)
{ {
$hgUtils = new HgUtils($this->io, $this->config, $this->process); $hgUtils = new HgUtils($this->io, $this->config, $this->process);

View File

@ -61,6 +61,15 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
$realUrl $realUrl
)); ));
} }
}
/**
* {@inheritdoc}
*/
public function install(PackageInterface $package, $path, $output = true)
{
$url = $package->getDistUrl();
$realUrl = realpath($url);
// Get the transport options with default values // Get the transport options with default values
$transportOptions = $package->getTransportOptions() + array('symlink' => null); $transportOptions = $package->getTransportOptions() + array('symlink' => null);

View File

@ -27,7 +27,7 @@ class PerforceDownloader extends VcsDownloader
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function doDownload(PackageInterface $package, $path, $url) public function doInstall(PackageInterface $package, $path, $url)
{ {
$ref = $package->getSourceReference(); $ref = $package->getSourceReference();
$label = $this->getLabelFromSourceReference($ref); $label = $this->getLabelFromSourceReference($ref);

View File

@ -12,6 +12,8 @@
namespace Composer\Downloader; namespace Composer\Downloader;
use Composer\Package\PackageInterface;
/** /**
* Downloader for phar files * Downloader for phar files
* *
@ -22,7 +24,7 @@ class PharDownloader extends ArchiveDownloader
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function extract($file, $path) protected function extract(PackageInterface $package, $file, $path)
{ {
// Can throw an UnexpectedValueException // Can throw an UnexpectedValueException
$archive = new \Phar($file); $archive = new \Phar($file);

View File

@ -20,6 +20,7 @@ use Composer\Util\Platform;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\HttpDownloader; use Composer\Util\HttpDownloader;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use RarArchive; use RarArchive;
/** /**
@ -39,7 +40,7 @@ class RarDownloader extends ArchiveDownloader
parent::__construct($io, $config, $downloader, $eventDispatcher, $cache); parent::__construct($io, $config, $downloader, $eventDispatcher, $cache);
} }
protected function extract($file, $path) protected function extract(PackageInterface $package, $file, $path)
{ {
$processError = null; $processError = null;

View File

@ -28,7 +28,7 @@ class SvnDownloader extends VcsDownloader
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function doDownload(PackageInterface $package, $path, $url) public function doInstall(PackageInterface $package, $path, $url)
{ {
SvnUtil::cleanEnv(); SvnUtil::cleanEnv();
$ref = $package->getSourceReference(); $ref = $package->getSourceReference();

View File

@ -12,6 +12,8 @@
namespace Composer\Downloader; namespace Composer\Downloader;
use Composer\Package\PackageInterface;
/** /**
* Downloader for tar files: tar, tar.gz or tar.bz2 * Downloader for tar files: tar, tar.gz or tar.bz2
* *
@ -22,7 +24,7 @@ class TarDownloader extends ArchiveDownloader
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function extract($file, $path) protected function extract(PackageInterface $package, $file, $path)
{ {
// Can throw an UnexpectedValueException // Can throw an UnexpectedValueException
$archive = new \PharData($file); $archive = new \PharData($file);

View File

@ -55,6 +55,14 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
* {@inheritDoc} * {@inheritDoc}
*/ */
public function download(PackageInterface $package, $path) public function download(PackageInterface $package, $path)
{
// noop for now, ideally we would do a git fetch already here, or make sure the cached git repo is synced, etc.
}
/**
* {@inheritDoc}
*/
public function install(PackageInterface $package, $path)
{ {
if (!$package->getSourceReference()) { if (!$package->getSourceReference()) {
throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information'); throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
@ -87,7 +95,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
$url = $needle . $url; $url = $needle . $url;
} }
} }
$this->doDownload($package, $path, $url); $this->doInstall($package, $path, $url);
break; break;
} catch (\Exception $e) { } catch (\Exception $e) {
// rethrow phpunit exceptions to avoid hard to debug bug failures // rethrow phpunit exceptions to avoid hard to debug bug failures
@ -260,7 +268,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
* @param string $path download path * @param string $path download path
* @param string $url package url * @param string $url package url
*/ */
abstract protected function doDownload(PackageInterface $package, $path, $url); abstract protected function doInstall(PackageInterface $package, $path, $url);
/** /**
* Updates specific package in specific folder from initial to target version. * Updates specific package in specific folder from initial to target version.

View File

@ -37,7 +37,7 @@ class XzDownloader extends ArchiveDownloader
parent::__construct($io, $config, $downloader, $eventDispatcher, $cache); parent::__construct($io, $config, $downloader, $eventDispatcher, $cache);
} }
protected function extract($file, $path) protected function extract(PackageInterface $package, $file, $path)
{ {
$command = 'tar -xJf ' . ProcessExecutor::escape($file) . ' -C ' . ProcessExecutor::escape($path); $command = 'tar -xJf ' . ProcessExecutor::escape($file) . ' -C ' . ProcessExecutor::escape($path);
@ -49,12 +49,4 @@ class XzDownloader extends ArchiveDownloader
throw new \RuntimeException($processError); throw new \RuntimeException($processError);
} }
/**
* {@inheritdoc}
*/
protected function getFileName(PackageInterface $package, $path)
{
return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
}
} }

View File

@ -185,7 +185,7 @@ class ZipDownloader extends ArchiveDownloader
* @param string $file File to extract * @param string $file File to extract
* @param string $path Path where to extract file * @param string $path Path where to extract file
*/ */
public function extract($file, $path) public function extract(PackageInterface $package, $file, $path)
{ {
// Each extract calls its alternative if not available or fails // Each extract calls its alternative if not available or fails
if (self::$isWindows) { if (self::$isWindows) {

View File

@ -24,6 +24,7 @@ use Composer\Util\Filesystem;
use Composer\Util\Platform; use Composer\Util\Platform;
use Composer\Util\ProcessExecutor; use Composer\Util\ProcessExecutor;
use Composer\Util\HttpDownloader; use Composer\Util\HttpDownloader;
use Composer\Util\Loop;
use Composer\Util\Silencer; use Composer\Util\Silencer;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Composer\EventDispatcher\Event; use Composer\EventDispatcher\Event;
@ -326,6 +327,7 @@ class Factory
} }
$httpDownloader = self::createHttpDownloader($io, $config); $httpDownloader = self::createHttpDownloader($io, $config);
$loop = new Loop($httpDownloader);
// initialize event dispatcher // initialize event dispatcher
$dispatcher = new EventDispatcher($composer, $io); $dispatcher = new EventDispatcher($composer, $io);
@ -352,7 +354,7 @@ class Factory
$composer->setPackage($package); $composer->setPackage($package);
// initialize installation manager // initialize installation manager
$im = $this->createInstallationManager(); $im = $this->createInstallationManager($loop);
$composer->setInstallationManager($im); $composer->setInstallationManager($im);
if ($fullLoad) { if ($fullLoad) {
@ -365,7 +367,7 @@ class Factory
$composer->setAutoloadGenerator($generator); $composer->setAutoloadGenerator($generator);
// initialize archive manager // initialize archive manager
$am = $this->createArchiveManager($config, $dm); $am = $this->createArchiveManager($config, $dm, $loop);
$composer->setArchiveManager($am); $composer->setArchiveManager($am);
} }
@ -501,9 +503,9 @@ class Factory
* @param Downloader\DownloadManager $dm Manager use to download sources * @param Downloader\DownloadManager $dm Manager use to download sources
* @return Archiver\ArchiveManager * @return Archiver\ArchiveManager
*/ */
public function createArchiveManager(Config $config, Downloader\DownloadManager $dm) public function createArchiveManager(Config $config, Downloader\DownloadManager $dm, Loop $loop)
{ {
$am = new Archiver\ArchiveManager($dm); $am = new Archiver\ArchiveManager($dm, $loop);
$am->addArchiver(new Archiver\ZipArchiver); $am->addArchiver(new Archiver\ZipArchiver);
$am->addArchiver(new Archiver\PharArchiver); $am->addArchiver(new Archiver\PharArchiver);
@ -525,9 +527,9 @@ class Factory
/** /**
* @return Installer\InstallationManager * @return Installer\InstallationManager
*/ */
protected function createInstallationManager() public function createInstallationManager(Loop $loop)
{ {
return new Installer\InstallationManager(); return new Installer\InstallationManager($loop);
} }
/** /**

View File

@ -24,6 +24,7 @@ use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation; use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation;
use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation; use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation;
use Composer\Util\StreamContextFactory; use Composer\Util\StreamContextFactory;
use Composer\Util\Loop;
/** /**
* Package operation manager. * Package operation manager.
@ -37,6 +38,12 @@ class InstallationManager
private $installers = array(); private $installers = array();
private $cache = array(); private $cache = array();
private $notifiablePackages = array(); private $notifiablePackages = array();
private $loop;
public function __construct(Loop $loop)
{
$this->loop = $loop;
}
public function reset() public function reset()
{ {
@ -156,7 +163,24 @@ class InstallationManager
*/ */
public function execute(RepositoryInterface $repo, OperationInterface $operation) public function execute(RepositoryInterface $repo, OperationInterface $operation)
{ {
// TODO this should take all operations in one go
$method = $operation->getJobType(); $method = $operation->getJobType();
if ($method === 'install') {
$package = $operation->getPackage();
$installer = $this->getInstaller($package->getType());
$promise = $installer->download($package);
} elseif ($method === 'update') {
$target = $operation->getTargetPackage();
$targetType = $target->getType();
$installer = $this->getInstaller($targetType);
$promise = $installer->download($target, $operation->getInitialPackage());
}
if (isset($promise)) {
$this->loop->wait(array($promise));
}
$this->$method($repo, $operation); $this->$method($repo, $operation);
} }
@ -194,7 +218,8 @@ class InstallationManager
$this->markForNotification($target); $this->markForNotification($target);
} else { } else {
$this->getInstaller($initialType)->uninstall($repo, $initial); $this->getInstaller($initialType)->uninstall($repo, $initial);
$this->getInstaller($targetType)->install($repo, $target); $installer = $this->getInstaller($targetType);
$installer->install($repo, $target);
} }
} }

View File

@ -15,6 +15,7 @@ namespace Composer\Installer;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\InstalledRepositoryInterface;
use InvalidArgumentException; use InvalidArgumentException;
use React\Promise\PromiseInterface;
/** /**
* Interface for the package installation manager. * Interface for the package installation manager.
@ -42,6 +43,15 @@ interface InstallerInterface
*/ */
public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package); public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package);
/**
* Downloads the files needed to later install the given package.
*
* @param PackageInterface $package package instance
* @param PackageInterface $prevPackage previous package instance in case of an update
* @return PromiseInterface
*/
public function download(PackageInterface $package, PackageInterface $prevPackage = null);
/** /**
* Installs specific package. * Installs specific package.
* *

View File

@ -85,6 +85,14 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
return (Platform::isWindows() && $this->filesystem->isJunction($installPath)) || is_link($installPath); return (Platform::isWindows() && $this->filesystem->isJunction($installPath)) || is_link($installPath);
} }
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
{
$this->initializeVendorDir();
$downloadPath = $this->getInstallPath($package);
return $this->downloadManager->download($package, $downloadPath, $prevPackage);
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -194,7 +202,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
protected function installCode(PackageInterface $package) protected function installCode(PackageInterface $package)
{ {
$downloadPath = $this->getInstallPath($package); $downloadPath = $this->getInstallPath($package);
$this->downloadManager->download($package, $downloadPath); $this->downloadManager->install($package, $downloadPath);
} }
protected function updateCode(PackageInterface $initial, PackageInterface $target) protected function updateCode(PackageInterface $initial, PackageInterface $target)

View File

@ -38,6 +38,14 @@ class MetapackageInstaller implements InstallerInterface
return $repo->hasPackage($package); return $repo->hasPackage($package);
} }
/**
* {@inheritDoc}
*/
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
{
// noop
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */

View File

@ -40,6 +40,13 @@ class NoopInstaller implements InstallerInterface
return $repo->hasPackage($package); return $repo->hasPackage($package);
} }
/**
* {@inheritDoc}
*/
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
{
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */

View File

@ -50,13 +50,21 @@ class PluginInstaller extends LibraryInstaller
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function install(InstalledRepositoryInterface $repo, PackageInterface $package) public function download(PackageInterface $package, PackageInterface $prevPackage = null)
{ {
$extra = $package->getExtra(); $extra = $package->getExtra();
if (empty($extra['class'])) { if (empty($extra['class'])) {
throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
} }
return parent::download($package, $prevPackage);
}
/**
* {@inheritDoc}
*/
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
{
parent::install($repo, $package); parent::install($repo, $package);
try { try {
$this->composer->getPluginManager()->registerPackage($package, true); $this->composer->getPluginManager()->registerPackage($package, true);

View File

@ -58,7 +58,7 @@ class ProjectInstaller implements InstallerInterface
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function install(InstalledRepositoryInterface $repo, PackageInterface $package) public function download(PackageInterface $package, PackageInterface $prevPackage = null)
{ {
$installPath = $this->installPath; $installPath = $this->installPath;
if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) { if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) {
@ -67,7 +67,16 @@ class ProjectInstaller implements InstallerInterface
if (!is_dir($installPath)) { if (!is_dir($installPath)) {
mkdir($installPath, 0777, true); mkdir($installPath, 0777, true);
} }
$this->downloadManager->download($package, $installPath);
return $this->downloadManager->download($package, $installPath, $prevPackage);
}
/**
* {@inheritDoc}
*/
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
{
$this->downloadManager->install($package, $this->installPath);
} }
/** /**

View File

@ -16,6 +16,7 @@ use Composer\Downloader\DownloadManager;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface; use Composer\Package\RootPackageInterface;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use Composer\Util\Loop;
use Composer\Json\JsonFile; use Composer\Json\JsonFile;
/** /**
@ -25,6 +26,7 @@ use Composer\Json\JsonFile;
class ArchiveManager class ArchiveManager
{ {
protected $downloadManager; protected $downloadManager;
protected $loop;
protected $archivers = array(); protected $archivers = array();
@ -36,9 +38,10 @@ class ArchiveManager
/** /**
* @param DownloadManager $downloadManager A manager used to download package sources * @param DownloadManager $downloadManager A manager used to download package sources
*/ */
public function __construct(DownloadManager $downloadManager) public function __construct(DownloadManager $downloadManager, Loop $loop)
{ {
$this->downloadManager = $downloadManager; $this->downloadManager = $downloadManager;
$this->loop = $loop;
} }
/** /**
@ -148,7 +151,9 @@ class ArchiveManager
$filesystem->ensureDirectoryExists($sourcePath); $filesystem->ensureDirectoryExists($sourcePath);
// Download sources // Download sources
$this->downloadManager->download($package, $sourcePath); $promise = $this->downloadManager->download($package, $sourcePath);
$this->loop->wait(array($promise));
$this->downloadManager->install($package, $sourcePath);
// Check exclude from downloaded composer.json // Check exclude from downloaded composer.json
if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) { if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) {

View File

@ -22,6 +22,7 @@ use Composer\Config;
use Composer\Factory; use Composer\Factory;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Util\HttpDownloader; use Composer\Util\HttpDownloader;
use Composer\Util\Loop;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Composer\Plugin\PreFileDownloadEvent; use Composer\Plugin\PreFileDownloadEvent;
use Composer\EventDispatcher\EventDispatcher; use Composer\EventDispatcher\EventDispatcher;
@ -42,6 +43,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
private $baseUrl; private $baseUrl;
private $io; private $io;
private $httpDownloader; private $httpDownloader;
private $loop;
protected $cache; protected $cache;
protected $notifyUrl; protected $notifyUrl;
protected $searchUrl; protected $searchUrl;
@ -107,6 +109,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$this->httpDownloader = $httpDownloader; $this->httpDownloader = $httpDownloader;
$this->eventDispatcher = $eventDispatcher; $this->eventDispatcher = $eventDispatcher;
$this->repoConfig = $repoConfig; $this->repoConfig = $repoConfig;
$this->loop = new Loop($this->httpDownloader);
} }
public function getRepoConfig() public function getRepoConfig()
@ -569,6 +572,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$this->loadRootServerFile(); $this->loadRootServerFile();
$packages = array(); $packages = array();
$promises = array();
$repo = $this; $repo = $this;
if (!$this->lazyProvidersUrl) { if (!$this->lazyProvidersUrl) {
@ -592,7 +596,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$lastModified = isset($contents['last-modified']) ? $contents['last-modified'] : null; $lastModified = isset($contents['last-modified']) ? $contents['last-modified'] : null;
} }
$this->asyncFetchFile($url, $cacheKey, $lastModified) $promises[] = $this->asyncFetchFile($url, $cacheKey, $lastModified)
->then(function ($response) use (&$packages, $contents, $name, $constraint, $repo, $isPackageAcceptableCallable) { ->then(function ($response) use (&$packages, $contents, $name, $constraint, $repo, $isPackageAcceptableCallable) {
static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time'); static $uniqKeys = array('version', 'version_normalized', 'source', 'dist', 'time');
@ -637,13 +641,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf(); $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
} }
} }
}, function ($e) {
// TODO use ->done() above instead with react/promise 2.0
throw $e;
}); });
} }
$this->httpDownloader->wait(); $this->loop->wait($promises);
return $packages; return $packages;
// RepositorySet should call loadMetadata, getMetadata when all promises resolved, then metadataComplete when done so we can GC the loaded json and whatnot then as needed // RepositorySet should call loadMetadata, getMetadata when all promises resolved, then metadataComplete when done so we can GC the loaded json and whatnot then as needed
@ -1119,7 +1120,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
} }
$degradedMode = true; $degradedMode = true;
return true; throw $e;
}; };
return $httpDownloader->add($filename, $options)->then($accept, $reject); return $httpDownloader->add($filename, $options)->then($accept, $reject);

View File

@ -295,7 +295,7 @@ class CurlDownloader
// resolve promise // resolve promise
if ($job['filename']) { if ($job['filename']) {
rename($job['filename'].'~', $job['filename']); rename($job['filename'].'~', $job['filename']);
call_user_func($job['resolve'], true); call_user_func($job['resolve'], $response);
} else { } else {
call_user_func($job['resolve'], $response); call_user_func($job['resolve'], $response);
} }

View File

@ -160,7 +160,10 @@ class HttpDownloader
if ($job['request']['copyTo']) { if ($job['request']['copyTo']) {
$result = $rfs->copy($job['origin'], $url, $job['request']['copyTo'], false /* TODO progress */, $options); $result = $rfs->copy($job['origin'], $url, $job['request']['copyTo'], false /* TODO progress */, $options);
$resolve($result); $headers = $rfs->getLastHeaders();
$response = new Http\Response($job['request'], $rfs->findStatusCode($headers), $headers, $job['request']['copyTo'].'~');
$resolve($response);
} else { } else {
$body = $rfs->getContents($job['origin'], $url, false /* TODO progress */, $options); $body = $rfs->getContents($job['origin'], $url, false /* TODO progress */, $options);
$headers = $rfs->getLastHeaders(); $headers = $rfs->getLastHeaders();
@ -191,6 +194,7 @@ class HttpDownloader
$job['exception'] = $e; $job['exception'] = $e;
$downloader->markJobDone(); $downloader->markJobDone();
$downloader->scheduleNextJob();
throw $e; throw $e;
}); });

View File

@ -0,0 +1,47 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Util;
use Composer\Util\HttpDownloader;
use React\Promise\Promise;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class Loop
{
private $io;
public function __construct(HttpDownloader $httpDownloader)
{
$this->httpDownloader = $httpDownloader;
}
public function wait(array $promises)
{
$uncaught = null;
\React\Promise\all($promises)->then(
function () { },
function ($e) use (&$uncaught) {
$uncaught = $e;
}
);
$this->httpDownloader->wait();
if ($uncaught) {
throw $uncaught;
}
}
}

View File

@ -57,7 +57,7 @@ class ComposerTest extends TestCase
public function testSetGetInstallationManager() public function testSetGetInstallationManager()
{ {
$composer = new Composer(); $composer = new Composer();
$manager = $this->getMockBuilder('Composer\Installer\InstallationManager')->getMock(); $manager = $this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock();
$composer->setInstallationManager($manager); $composer->setInstallationManager($manager);
$this->assertSame($manager, $composer->getInstallationManager()); $this->assertSame($manager, $composer->getInstallationManager());

View File

@ -29,7 +29,7 @@ class ArchiveDownloaderTest extends TestCase
$method->setAccessible(true); $method->setAccessible(true);
$first = $method->invoke($downloader, $packageMock, '/path'); $first = $method->invoke($downloader, $packageMock, '/path');
$this->assertRegExp('#/path/[a-z0-9]+\.js#', $first); $this->assertRegExp('#/path_[a-z0-9]+\.js#', $first);
$this->assertSame($first, $method->invoke($downloader, $packageMock, '/path')); $this->assertSame($first, $method->invoke($downloader, $packageMock, '/path'));
} }

View File

@ -50,7 +50,7 @@ class DownloadManagerTest extends TestCase
$this->setExpectedException('InvalidArgumentException'); $this->setExpectedException('InvalidArgumentException');
$manager->getDownloaderForInstalledPackage($package); $manager->getDownloaderForPackage($package);
} }
public function testGetDownloaderForCorrectlyInstalledDistPackage() public function testGetDownloaderForCorrectlyInstalledDistPackage()
@ -82,7 +82,7 @@ class DownloadManagerTest extends TestCase
->with('pear') ->with('pear')
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
$this->assertSame($downloader, $manager->getDownloaderForInstalledPackage($package)); $this->assertSame($downloader, $manager->getDownloaderForPackage($package));
} }
public function testGetDownloaderForIncorrectlyInstalledDistPackage() public function testGetDownloaderForIncorrectlyInstalledDistPackage()
@ -116,7 +116,7 @@ class DownloadManagerTest extends TestCase
$this->setExpectedException('LogicException'); $this->setExpectedException('LogicException');
$manager->getDownloaderForInstalledPackage($package); $manager->getDownloaderForPackage($package);
} }
public function testGetDownloaderForCorrectlyInstalledSourcePackage() public function testGetDownloaderForCorrectlyInstalledSourcePackage()
@ -148,7 +148,7 @@ class DownloadManagerTest extends TestCase
->with('git') ->with('git')
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
$this->assertSame($downloader, $manager->getDownloaderForInstalledPackage($package)); $this->assertSame($downloader, $manager->getDownloaderForPackage($package));
} }
public function testGetDownloaderForIncorrectlyInstalledSourcePackage() public function testGetDownloaderForIncorrectlyInstalledSourcePackage()
@ -182,7 +182,7 @@ class DownloadManagerTest extends TestCase
$this->setExpectedException('LogicException'); $this->setExpectedException('LogicException');
$manager->getDownloaderForInstalledPackage($package); $manager->getDownloaderForPackage($package);
} }
public function testGetDownloaderForMetapackage() public function testGetDownloaderForMetapackage()
@ -195,7 +195,7 @@ class DownloadManagerTest extends TestCase
$manager = new DownloadManager($this->io, false, $this->filesystem); $manager = new DownloadManager($this->io, false, $this->filesystem);
$this->assertNull($manager->getDownloaderForInstalledPackage($package)); $this->assertNull($manager->getDownloaderForPackage($package));
} }
public function testFullPackageDownload() public function testFullPackageDownload()
@ -223,11 +223,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
@ -274,16 +274,16 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->at(0)) ->expects($this->at(0))
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloaderFail)); ->will($this->returnValue($downloaderFail));
$manager $manager
->expects($this->at(1)) ->expects($this->at(1))
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloaderSuccess)); ->will($this->returnValue($downloaderSuccess));
@ -333,11 +333,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
@ -369,11 +369,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
@ -399,11 +399,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue(null)); // There is no downloader for Metapackages. ->will($this->returnValue(null)); // There is no downloader for Metapackages.
@ -435,11 +435,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
@ -472,11 +472,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
@ -509,11 +509,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
@ -550,33 +550,30 @@ class DownloadManagerTest extends TestCase
$initial $initial
->expects($this->once()) ->expects($this->once())
->method('getDistType') ->method('getDistType')
->will($this->returnValue('pear')); ->will($this->returnValue('zip'));
$target = $this->createPackageMock(); $target = $this->createPackageMock();
$target $target
->expects($this->once()) ->expects($this->once())
->method('getDistType') ->method('getInstallationSource')
->will($this->returnValue('pear')); ->will($this->returnValue('dist'));
$target $target
->expects($this->once()) ->expects($this->once())
->method('setInstallationSource') ->method('getDistType')
->with('dist'); ->will($this->returnValue('zip'));
$pearDownloader = $this->createDownloaderMock(); $zipDownloader = $this->createDownloaderMock();
$pearDownloader $zipDownloader
->expects($this->once()) ->expects($this->once())
->method('update') ->method('update')
->with($initial, $target, 'vendor/bundles/FOS/UserBundle'); ->with($initial, $target, 'vendor/bundles/FOS/UserBundle');
$zipDownloader
->expects($this->any())
->method('getInstallationSource')
->will($this->returnValue('dist'));
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = new DownloadManager($this->io, false, $this->filesystem);
->setConstructorArgs(array($this->io, false, $this->filesystem)) $manager->setDownloader('zip', $zipDownloader);
->setMethods(array('getDownloaderForInstalledPackage'))
->getMock();
$manager
->expects($this->once())
->method('getDownloaderForInstalledPackage')
->with($initial)
->will($this->returnValue($pearDownloader));
$manager->update($initial, $target, 'vendor/bundles/FOS/UserBundle'); $manager->update($initial, $target, 'vendor/bundles/FOS/UserBundle');
} }
@ -591,113 +588,89 @@ class DownloadManagerTest extends TestCase
$initial $initial
->expects($this->once()) ->expects($this->once())
->method('getDistType') ->method('getDistType')
->will($this->returnValue('pear')); ->will($this->returnValue('xz'));
$target = $this->createPackageMock(); $target = $this->createPackageMock();
$target $target
->expects($this->once()) ->expects($this->any())
->method('getInstallationSource')
->will($this->returnValue('dist'));
$target
->expects($this->any())
->method('getDistType') ->method('getDistType')
->will($this->returnValue('composer')); ->will($this->returnValue('zip'));
$pearDownloader = $this->createDownloaderMock(); $xzDownloader = $this->createDownloaderMock();
$pearDownloader $xzDownloader
->expects($this->once()) ->expects($this->once())
->method('remove') ->method('remove')
->with($initial, 'vendor/bundles/FOS/UserBundle'); ->with($initial, 'vendor/bundles/FOS/UserBundle');
$xzDownloader
->expects($this->any())
->method('getInstallationSource')
->will($this->returnValue('dist'));
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $zipDownloader = $this->createDownloaderMock();
->setConstructorArgs(array($this->io, false, $this->filesystem)) $zipDownloader
->setMethods(array('getDownloaderForInstalledPackage', 'download'))
->getMock();
$manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('install')
->with($initial) ->with($target, 'vendor/bundles/FOS/UserBundle');
->will($this->returnValue($pearDownloader)); $zipDownloader
$manager ->expects($this->any())
->expects($this->once()) ->method('getInstallationSource')
->method('download') ->will($this->returnValue('dist'));
->with($target, 'vendor/bundles/FOS/UserBundle', false);
$manager = new DownloadManager($this->io, false, $this->filesystem);
$manager->setDownloader('xz', $xzDownloader);
$manager->setDownloader('zip', $zipDownloader);
$manager->update($initial, $target, 'vendor/bundles/FOS/UserBundle'); $manager->update($initial, $target, 'vendor/bundles/FOS/UserBundle');
} }
public function testUpdateSourceWithEqualTypes() /**
* @dataProvider updatesProvider
*/
public function testGetAvailableSourcesUpdateSticksToSameSource($prevPkgSource, $prevPkgIsDev, $targetAvailable, $targetIsDev, $expected)
{ {
$initial = $this->createPackageMock(); $initial = null;
$initial if ($prevPkgSource) {
->expects($this->once()) $initial = $this->prophesize('Composer\Package\PackageInterface');
->method('getInstallationSource') $initial->getInstallationSource()->willReturn($prevPkgSource);
->will($this->returnValue('source')); $initial->isDev()->willReturn($prevPkgIsDev);
$initial }
->expects($this->once())
->method('getSourceType')
->will($this->returnValue('svn'));
$target = $this->createPackageMock(); $target = $this->prophesize('Composer\Package\PackageInterface');
$target $target->getSourceType()->willReturn(in_array('source', $targetAvailable, true) ? 'git' : null);
->expects($this->once()) $target->getDistType()->willReturn(in_array('dist', $targetAvailable, true) ? 'zip' : null);
->method('getSourceType') $target->isDev()->willReturn($targetIsDev);
->will($this->returnValue('svn'));
$svnDownloader = $this->createDownloaderMock(); $manager = new DownloadManager($this->io, false, $this->filesystem);
$svnDownloader $method = new \ReflectionMethod($manager, 'getAvailableSources');
->expects($this->once()) $method->setAccessible(true);
->method('update') $this->assertEquals($expected, $method->invoke($manager, $target->reveal(), $initial ? $initial->reveal() : null));
->with($initial, $target, 'vendor/pkg');
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage', 'download'))
->getMock();
$manager
->expects($this->once())
->method('getDownloaderForInstalledPackage')
->with($initial)
->will($this->returnValue($svnDownloader));
$manager->update($initial, $target, 'vendor/pkg');
} }
public function testUpdateSourceWithNotEqualTypes() public static function updatesProvider()
{ {
$initial = $this->createPackageMock(); return array(
$initial // prevPkg source, prevPkg isDev, pkg available, pkg isDev, expected
->expects($this->once()) // updates keep previous source as preference
->method('getInstallationSource') array('source', false, array('source', 'dist'), false, array('source', 'dist')),
->will($this->returnValue('source')); array('dist', false, array('source', 'dist'), false, array('dist', 'source')),
$initial // updates do not keep previous source if target package does not have it
->expects($this->once()) array('source', false, array('dist'), false, array('dist')),
->method('getSourceType') array('dist', false, array('source'), false, array('source')),
->will($this->returnValue('svn')); // updates do not keep previous source if target is dev and prev wasn't dev and installed from dist
array('source', false, array('source', 'dist'), true, array('source', 'dist')),
$target = $this->createPackageMock(); array('dist', false, array('source', 'dist'), true, array('source', 'dist')),
$target // install picks the right default
->expects($this->once()) array(null, null, array('source', 'dist'), true, array('source', 'dist')),
->method('getSourceType') array(null, null, array('dist'), true, array('dist')),
->will($this->returnValue('git')); array(null, null, array('source'), true, array('source')),
array(null, null, array('source', 'dist'), false, array('dist', 'source')),
$svnDownloader = $this->createDownloaderMock(); array(null, null, array('dist'), false, array('dist')),
$svnDownloader array(null, null, array('source'), false, array('source')),
->expects($this->once()) );
->method('remove')
->with($initial, 'vendor/pkg');
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage', 'download'))
->getMock();
$manager
->expects($this->once())
->method('getDownloaderForInstalledPackage')
->with($initial)
->will($this->returnValue($svnDownloader));
$manager
->expects($this->once())
->method('download')
->with($target, 'vendor/pkg', true);
$manager->update($initial, $target, 'vendor/pkg');
} }
public function testUpdateMetapackage() public function testUpdateMetapackage()
@ -707,11 +680,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->exactly(2))
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($initial) ->with($initial)
->will($this->returnValue(null)); // There is no downloader for metapackages. ->will($this->returnValue(null)); // There is no downloader for metapackages.
@ -730,11 +703,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($pearDownloader)); ->will($this->returnValue($pearDownloader));
@ -747,11 +720,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue(null)); // There is no downloader for metapackages. ->will($this->returnValue(null)); // There is no downloader for metapackages.
@ -790,11 +763,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
@ -833,11 +806,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
@ -879,11 +852,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
$manager->setPreferences(array('foo/*' => 'source')); $manager->setPreferences(array('foo/*' => 'source'));
@ -926,11 +899,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
$manager->setPreferences(array('foo/*' => 'source')); $manager->setPreferences(array('foo/*' => 'source'));
@ -973,11 +946,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
$manager->setPreferences(array('foo/*' => 'auto')); $manager->setPreferences(array('foo/*' => 'auto'));
@ -1020,11 +993,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
$manager->setPreferences(array('foo/*' => 'auto')); $manager->setPreferences(array('foo/*' => 'auto'));
@ -1063,11 +1036,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
$manager->setPreferences(array('foo/*' => 'source')); $manager->setPreferences(array('foo/*' => 'source'));
@ -1106,11 +1079,11 @@ class DownloadManagerTest extends TestCase
$manager = $this->getMockBuilder('Composer\Downloader\DownloadManager') $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
->setConstructorArgs(array($this->io, false, $this->filesystem)) ->setConstructorArgs(array($this->io, false, $this->filesystem))
->setMethods(array('getDownloaderForInstalledPackage')) ->setMethods(array('getDownloaderForPackage'))
->getMock(); ->getMock();
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('getDownloaderForInstalledPackage') ->method('getDownloaderForPackage')
->with($package) ->with($package)
->will($this->returnValue($downloader)); ->will($this->returnValue($downloader));
$manager->setPreferences(array('foo/*' => 'dist')); $manager->setPreferences(array('foo/*' => 'dist'));

View File

@ -15,6 +15,8 @@ namespace Composer\Test\Downloader;
use Composer\Downloader\FileDownloader; use Composer\Downloader\FileDownloader;
use Composer\Test\TestCase; use Composer\Test\TestCase;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use Composer\Util\Http\Response;
use Composer\Util\Loop;
class FileDownloaderTest extends TestCase class FileDownloaderTest extends TestCase
{ {
@ -23,6 +25,11 @@ class FileDownloaderTest extends TestCase
$io = $io ?: $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $io = $io ?: $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
$config = $config ?: $this->getMockBuilder('Composer\Config')->getMock(); $config = $config ?: $this->getMockBuilder('Composer\Config')->getMock();
$httpDownloader = $httpDownloader ?: $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(); $httpDownloader = $httpDownloader ?: $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock();
$httpDownloader
->expects($this->any())
->method('addCopy')
->will($this->returnValue(\React\Promise\resolve(new Response(array('url' => 'http://example.org/'), 200, array(), 'file~'))));
$this->httpDownloader = $httpDownloader;
return new FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $filesystem); return new FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $filesystem);
} }
@ -84,7 +91,7 @@ class FileDownloaderTest extends TestCase
$method = new \ReflectionMethod($downloader, 'getFileName'); $method = new \ReflectionMethod($downloader, 'getFileName');
$method->setAccessible(true); $method->setAccessible(true);
$this->assertEquals('/path/script.js', $method->invoke($downloader, $packageMock, '/path')); $this->assertEquals('/path_script.js', $method->invoke($downloader, $packageMock, '/path'));
} }
public function testDownloadButFileIsUnsaved() public function testDownloadButFileIsUnsaved()
@ -118,8 +125,11 @@ class FileDownloaderTest extends TestCase
$downloader = $this->getDownloader($ioMock); $downloader = $this->getDownloader($ioMock);
try { try {
$downloader->download($packageMock, $path); $promise = $downloader->download($packageMock, $path);
$this->fail(); $loop = new Loop($this->httpDownloader);
$loop->wait(array($promise));
$this->fail('Download was expected to throw');
} catch (\Exception $e) { } catch (\Exception $e) {
if (is_dir($path)) { if (is_dir($path)) {
$fs = new Filesystem(); $fs = new Filesystem();
@ -128,7 +138,7 @@ class FileDownloaderTest extends TestCase
unlink($path); unlink($path);
} }
$this->assertInstanceOf('UnexpectedValueException', $e); $this->assertInstanceOf('UnexpectedValueException', $e, $e->getMessage());
$this->assertContains('could not be saved to', $e->getMessage()); $this->assertContains('could not be saved to', $e->getMessage());
} }
} }
@ -188,11 +198,14 @@ class FileDownloaderTest extends TestCase
$path = $this->getUniqueTmpDirectory(); $path = $this->getUniqueTmpDirectory();
$downloader = $this->getDownloader(null, null, null, null, null, $filesystem); $downloader = $this->getDownloader(null, null, null, null, null, $filesystem);
// make sure the file expected to be downloaded is on disk already // make sure the file expected to be downloaded is on disk already
touch($path.'/script.js'); touch($path.'_script.js');
try { try {
$downloader->download($packageMock, $path); $promise = $downloader->download($packageMock, $path);
$this->fail(); $loop = new Loop($this->httpDownloader);
$loop->wait(array($promise));
$this->fail('Download was expected to throw');
} catch (\Exception $e) { } catch (\Exception $e) {
if (is_dir($path)) { if (is_dir($path)) {
$fs = new Filesystem(); $fs = new Filesystem();
@ -201,7 +214,7 @@ class FileDownloaderTest extends TestCase
unlink($path); unlink($path);
} }
$this->assertInstanceOf('UnexpectedValueException', $e); $this->assertInstanceOf('UnexpectedValueException', $e, $e->getMessage());
$this->assertContains('checksum verification', $e->getMessage()); $this->assertContains('checksum verification', $e->getMessage());
} }
} }
@ -232,17 +245,25 @@ class FileDownloaderTest extends TestCase
$ioMock = $this->getMock('Composer\IO\IOInterface'); $ioMock = $this->getMock('Composer\IO\IOInterface');
$ioMock->expects($this->at(0)) $ioMock->expects($this->at(0))
->method('writeError')
->with($this->stringContains('Downloading'));
$ioMock->expects($this->at(1))
->method('writeError') ->method('writeError')
->with($this->stringContains('Downgrading')); ->with($this->stringContains('Downgrading'));
$path = $this->getUniqueTmpDirectory(); $path = $this->getUniqueTmpDirectory();
touch($path.'/script.js'); touch($path.'_script.js');
$filesystem = $this->getMock('Composer\Util\Filesystem'); $filesystem = $this->getMock('Composer\Util\Filesystem');
$filesystem->expects($this->once()) $filesystem->expects($this->once())
->method('removeDirectory') ->method('removeDirectory')
->will($this->returnValue(true)); ->will($this->returnValue(true));
$downloader = $this->getDownloader($ioMock, null, null, null, null, $filesystem); $downloader = $this->getDownloader($ioMock, null, null, null, null, $filesystem);
$promise = $downloader->download($newPackage, $path, $oldPackage);
$loop = new Loop($this->httpDownloader);
$loop->wait(array($promise));
$downloader->update($oldPackage, $newPackage, $path); $downloader->update($oldPackage, $newPackage, $path);
} }
} }

View File

@ -56,7 +56,7 @@ class FossilDownloaderTest extends TestCase
->will($this->returnValue(null)); ->will($this->returnValue(null));
$downloader = $this->getDownloaderMock(); $downloader = $this->getDownloaderMock();
$downloader->download($packageMock, '/path'); $downloader->install($packageMock, '/path');
} }
public function testDownload() public function testDownload()
@ -89,7 +89,7 @@ class FossilDownloaderTest extends TestCase
->will($this->returnValue(0)); ->will($this->returnValue(0));
$downloader = $this->getDownloaderMock(null, null, $processExecutor); $downloader = $this->getDownloaderMock(null, null, $processExecutor);
$downloader->download($packageMock, 'repo'); $downloader->install($packageMock, 'repo');
} }
/** /**

View File

@ -79,7 +79,7 @@ class GitDownloaderTest extends TestCase
->will($this->returnValue(null)); ->will($this->returnValue(null));
$downloader = $this->getDownloaderMock(); $downloader = $this->getDownloaderMock();
$downloader->download($packageMock, '/path'); $downloader->install($packageMock, '/path');
} }
public function testDownload() public function testDownload()
@ -130,7 +130,7 @@ class GitDownloaderTest extends TestCase
->will($this->returnValue(0)); ->will($this->returnValue(0));
$downloader = $this->getDownloaderMock(null, null, $processExecutor); $downloader = $this->getDownloaderMock(null, null, $processExecutor);
$downloader->download($packageMock, 'composerPath'); $downloader->install($packageMock, 'composerPath');
} }
public function testDownloadWithCache() public function testDownloadWithCache()
@ -195,7 +195,7 @@ class GitDownloaderTest extends TestCase
->will($this->returnValue(0)); ->will($this->returnValue(0));
$downloader = $this->getDownloaderMock(null, $config, $processExecutor); $downloader = $this->getDownloaderMock(null, $config, $processExecutor);
$downloader->download($packageMock, 'composerPath'); $downloader->install($packageMock, 'composerPath');
@rmdir($cachePath); @rmdir($cachePath);
} }
@ -265,7 +265,7 @@ class GitDownloaderTest extends TestCase
->will($this->returnValue(0)); ->will($this->returnValue(0));
$downloader = $this->getDownloaderMock(null, new Config(), $processExecutor); $downloader = $this->getDownloaderMock(null, new Config(), $processExecutor);
$downloader->download($packageMock, 'composerPath'); $downloader->install($packageMock, 'composerPath');
} }
public function pushUrlProvider() public function pushUrlProvider()
@ -329,7 +329,7 @@ class GitDownloaderTest extends TestCase
$config->merge(array('config' => array('github-protocols' => $protocols))); $config->merge(array('config' => array('github-protocols' => $protocols)));
$downloader = $this->getDownloaderMock(null, $config, $processExecutor); $downloader = $this->getDownloaderMock(null, $config, $processExecutor);
$downloader->download($packageMock, 'composerPath'); $downloader->install($packageMock, 'composerPath');
} }
/** /**
@ -360,7 +360,7 @@ class GitDownloaderTest extends TestCase
->will($this->returnValue(1)); ->will($this->returnValue(1));
$downloader = $this->getDownloaderMock(null, null, $processExecutor); $downloader = $this->getDownloaderMock(null, null, $processExecutor);
$downloader->download($packageMock, 'composerPath'); $downloader->install($packageMock, 'composerPath');
} }
/** /**

View File

@ -56,7 +56,7 @@ class HgDownloaderTest extends TestCase
->will($this->returnValue(null)); ->will($this->returnValue(null));
$downloader = $this->getDownloaderMock(); $downloader = $this->getDownloaderMock();
$downloader->download($packageMock, '/path'); $downloader->install($packageMock, '/path');
} }
public function testDownload() public function testDownload()
@ -83,7 +83,7 @@ class HgDownloaderTest extends TestCase
->will($this->returnValue(0)); ->will($this->returnValue(0));
$downloader = $this->getDownloaderMock(null, null, $processExecutor); $downloader = $this->getDownloaderMock(null, null, $processExecutor);
$downloader->download($packageMock, 'composerPath'); $downloader->install($packageMock, 'composerPath');
} }
/** /**

View File

@ -138,7 +138,7 @@ class PerforceDownloaderTest extends TestCase
$perforce->expects($this->at(5))->method('syncCodeBase')->with($label); $perforce->expects($this->at(5))->method('syncCodeBase')->with($label);
$perforce->expects($this->at(6))->method('cleanupClientSpec'); $perforce->expects($this->at(6))->method('cleanupClientSpec');
$this->downloader->setPerforce($perforce); $this->downloader->setPerforce($perforce);
$this->downloader->doDownload($this->package, $this->testPath, 'url'); $this->downloader->doInstall($this->package, $this->testPath, 'url');
} }
/** /**
@ -161,6 +161,6 @@ class PerforceDownloaderTest extends TestCase
$perforce->expects($this->at(5))->method('syncCodeBase')->with($label); $perforce->expects($this->at(5))->method('syncCodeBase')->with($label);
$perforce->expects($this->at(6))->method('cleanupClientSpec'); $perforce->expects($this->at(6))->method('cleanupClientSpec');
$this->downloader->setPerforce($perforce); $this->downloader->setPerforce($perforce);
$this->downloader->doDownload($this->package, $this->testPath, 'url'); $this->downloader->doInstall($this->package, $this->testPath, 'url');
} }
} }

View File

@ -16,6 +16,7 @@ use Composer\Downloader\XzDownloader;
use Composer\Test\TestCase; use Composer\Test\TestCase;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use Composer\Util\Platform; use Composer\Util\Platform;
use Composer\Util\Loop;
use Composer\Util\HttpDownloader; use Composer\Util\HttpDownloader;
class XzDownloaderTest extends TestCase class XzDownloaderTest extends TestCase
@ -66,10 +67,14 @@ class XzDownloaderTest extends TestCase
->method('get') ->method('get')
->with('vendor-dir') ->with('vendor-dir')
->will($this->returnValue($this->testDir)); ->will($this->returnValue($this->testDir));
$downloader = new XzDownloader($io, $config, new HttpDownloader($io, $this->getMockBuilder('Composer\Config')->getMock()), null, null, null); $downloader = new XzDownloader($io, $config, $httpDownloader = new HttpDownloader($io, $this->getMockBuilder('Composer\Config')->getMock()), null, null, null);
try { try {
$downloader->download($packageMock, $this->getUniqueTmpDirectory()); $promise = $downloader->download($packageMock, $this->testDir);
$loop = new Loop($httpDownloader);
$loop->wait(array($promise));
$downloader->install($packageMock, $this->testDir);
$this->fail('Download of invalid tarball should throw an exception'); $this->fail('Download of invalid tarball should throw an exception');
} catch (\RuntimeException $e) { } catch (\RuntimeException $e) {
$this->assertRegexp('/(File format not recognized|Unrecognized archive format)/i', $e->getMessage()); $this->assertRegexp('/(File format not recognized|Unrecognized archive format)/i', $e->getMessage());

View File

@ -17,6 +17,7 @@ use Composer\Package\PackageInterface;
use Composer\Test\TestCase; use Composer\Test\TestCase;
use Composer\Util\Filesystem; use Composer\Util\Filesystem;
use Composer\Util\HttpDownloader; use Composer\Util\HttpDownloader;
use Composer\Util\Loop;
class ZipDownloaderTest extends TestCase class ZipDownloaderTest extends TestCase
{ {
@ -27,6 +28,7 @@ class ZipDownloaderTest extends TestCase
private $prophet; private $prophet;
private $io; private $io;
private $config; private $config;
private $package;
public function setUp() public function setUp()
{ {
@ -35,6 +37,7 @@ class ZipDownloaderTest extends TestCase
$this->config = $this->getMockBuilder('Composer\Config')->getMock(); $this->config = $this->getMockBuilder('Composer\Config')->getMock();
$dlConfig = $this->getMockBuilder('Composer\Config')->getMock(); $dlConfig = $this->getMockBuilder('Composer\Config')->getMock();
$this->httpDownloader = new HttpDownloader($this->io, $dlConfig); $this->httpDownloader = new HttpDownloader($this->io, $dlConfig);
$this->package = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
} }
public function tearDown() public function tearDown()
@ -71,16 +74,15 @@ class ZipDownloaderTest extends TestCase
->with('vendor-dir') ->with('vendor-dir')
->will($this->returnValue($this->testDir)); ->will($this->returnValue($this->testDir));
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $this->package->expects($this->any())
$packageMock->expects($this->any())
->method('getDistUrl') ->method('getDistUrl')
->will($this->returnValue($distUrl = 'file://'.__FILE__)) ->will($this->returnValue($distUrl = 'file://'.__FILE__))
; ;
$packageMock->expects($this->any()) $this->package->expects($this->any())
->method('getDistUrls') ->method('getDistUrls')
->will($this->returnValue(array($distUrl))) ->will($this->returnValue(array($distUrl)))
; ;
$packageMock->expects($this->atLeastOnce()) $this->package->expects($this->atLeastOnce())
->method('getTransportOptions') ->method('getTransportOptions')
->will($this->returnValue(array())) ->will($this->returnValue(array()))
; ;
@ -90,7 +92,11 @@ class ZipDownloaderTest extends TestCase
$this->setPrivateProperty('hasSystemUnzip', false); $this->setPrivateProperty('hasSystemUnzip', false);
try { try {
$downloader->download($packageMock, sys_get_temp_dir().'/composer-zip-test'); $promise = $downloader->download($this->package, $path = sys_get_temp_dir().'/composer-zip-test');
$loop = new Loop($this->httpDownloader);
$loop->wait(array($promise));
$downloader->install($this->package, $path);
$this->fail('Download of invalid zip files should throw an exception'); $this->fail('Download of invalid zip files should throw an exception');
} catch (\Exception $e) { } catch (\Exception $e) {
$this->assertContains('is not a zip archive', $e->getMessage()); $this->assertContains('is not a zip archive', $e->getMessage());
@ -119,7 +125,7 @@ class ZipDownloaderTest extends TestCase
->will($this->returnValue(false)); ->will($this->returnValue(false));
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
$downloader->extract('testfile.zip', 'vendor/dir'); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
} }
/** /**
@ -144,7 +150,7 @@ class ZipDownloaderTest extends TestCase
->will($this->throwException(new \ErrorException('Not a directory'))); ->will($this->throwException(new \ErrorException('Not a directory')));
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
$downloader->extract('testfile.zip', 'vendor/dir'); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
} }
/** /**
@ -168,7 +174,7 @@ class ZipDownloaderTest extends TestCase
->will($this->returnValue(true)); ->will($this->returnValue(true));
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
$downloader->extract('testfile.zip', 'vendor/dir'); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
} }
/** /**
@ -189,7 +195,7 @@ class ZipDownloaderTest extends TestCase
->will($this->returnValue(1)); ->will($this->returnValue(1));
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor);
$downloader->extract('testfile.zip', 'vendor/dir'); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
} }
public function testSystemUnzipOnlyGood() public function testSystemUnzipOnlyGood()
@ -206,7 +212,7 @@ class ZipDownloaderTest extends TestCase
->will($this->returnValue(0)); ->will($this->returnValue(0));
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor);
$downloader->extract('testfile.zip', 'vendor/dir'); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
} }
public function testNonWindowsFallbackGood() public function testNonWindowsFallbackGood()
@ -234,7 +240,7 @@ class ZipDownloaderTest extends TestCase
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor);
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
$downloader->extract('testfile.zip', 'vendor/dir'); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
} }
/** /**
@ -266,7 +272,7 @@ class ZipDownloaderTest extends TestCase
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor);
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
$downloader->extract('testfile.zip', 'vendor/dir'); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
} }
public function testWindowsFallbackGood() public function testWindowsFallbackGood()
@ -294,7 +300,7 @@ class ZipDownloaderTest extends TestCase
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor);
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
$downloader->extract('testfile.zip', 'vendor/dir'); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
} }
/** /**
@ -326,7 +332,7 @@ class ZipDownloaderTest extends TestCase
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor); $downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor);
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader); $this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
$downloader->extract('testfile.zip', 'vendor/dir'); $downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
} }
} }
@ -337,8 +343,13 @@ class MockedZipDownloader extends ZipDownloader
return; return;
} }
public function extract($file, $path) public function install(PackageInterface $package, $path, $output = true)
{ {
parent::extract($file, $path); return;
}
public function extract(PackageInterface $package, $file, $path)
{
parent::extract($package, $file, $path);
} }
} }

View File

@ -101,7 +101,7 @@ class EventDispatcherTest extends TestCase
$composer->setPackage($package); $composer->setPackage($package);
$composer->setRepositoryManager($this->getRepositoryManagerMockForDevModePassingTest()); $composer->setRepositoryManager($this->getRepositoryManagerMockForDevModePassingTest());
$composer->setInstallationManager($this->getMockBuilder('Composer\Installer\InstallationManager')->getMock()); $composer->setInstallationManager($this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock());
$dispatcher = new EventDispatcher( $dispatcher = new EventDispatcher(
$composer, $composer,

View File

@ -13,6 +13,7 @@
namespace Composer\Test\Installer; namespace Composer\Test\Installer;
use Composer\Installer\InstallationManager; use Composer\Installer\InstallationManager;
use Composer\Installer\NoopInstaller;
use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\Operation\UninstallOperation;
@ -21,9 +22,11 @@ use PHPUnit\Framework\TestCase;
class InstallationManagerTest extends TestCase class InstallationManagerTest extends TestCase
{ {
protected $repository; protected $repository;
protected $loop;
public function setUp() public function setUp()
{ {
$this->loop = $this->getMockBuilder('Composer\Util\Loop')->disableOriginalConstructor()->getMock();
$this->repository = $this->getMockBuilder('Composer\Repository\InstalledRepositoryInterface')->getMock(); $this->repository = $this->getMockBuilder('Composer\Repository\InstalledRepositoryInterface')->getMock();
} }
@ -38,7 +41,7 @@ class InstallationManagerTest extends TestCase
return $arg === 'vendor'; return $arg === 'vendor';
})); }));
$manager = new InstallationManager(); $manager = new InstallationManager($this->loop);
$manager->addInstaller($installer); $manager->addInstaller($installer);
$this->assertSame($installer, $manager->getInstaller('vendor')); $this->assertSame($installer, $manager->getInstaller('vendor'));
@ -67,7 +70,7 @@ class InstallationManagerTest extends TestCase
return $arg === 'vendor'; return $arg === 'vendor';
})); }));
$manager = new InstallationManager(); $manager = new InstallationManager($this->loop);
$manager->addInstaller($installer); $manager->addInstaller($installer);
$this->assertSame($installer, $manager->getInstaller('vendor')); $this->assertSame($installer, $manager->getInstaller('vendor'));
@ -80,16 +83,21 @@ class InstallationManagerTest extends TestCase
public function testExecute() public function testExecute()
{ {
$manager = $this->getMockBuilder('Composer\Installer\InstallationManager') $manager = $this->getMockBuilder('Composer\Installer\InstallationManager')
->setConstructorArgs(array($this->loop))
->setMethods(array('install', 'update', 'uninstall')) ->setMethods(array('install', 'update', 'uninstall'))
->getMock(); ->getMock();
$installOperation = new InstallOperation($this->createPackageMock()); $installOperation = new InstallOperation($package = $this->createPackageMock());
$removeOperation = new UninstallOperation($this->createPackageMock()); $removeOperation = new UninstallOperation($package);
$updateOperation = new UpdateOperation( $updateOperation = new UpdateOperation(
$this->createPackageMock(), $package,
$this->createPackageMock() $package
); );
$package->expects($this->any())
->method('getType')
->will($this->returnValue('library'));
$manager $manager
->expects($this->once()) ->expects($this->once())
->method('install') ->method('install')
@ -103,6 +111,7 @@ class InstallationManagerTest extends TestCase
->method('update') ->method('update')
->with($this->repository, $updateOperation); ->with($this->repository, $updateOperation);
$manager->addInstaller(new NoopInstaller());
$manager->execute($this->repository, $installOperation); $manager->execute($this->repository, $installOperation);
$manager->execute($this->repository, $removeOperation); $manager->execute($this->repository, $removeOperation);
$manager->execute($this->repository, $updateOperation); $manager->execute($this->repository, $updateOperation);
@ -111,7 +120,7 @@ class InstallationManagerTest extends TestCase
public function testInstall() public function testInstall()
{ {
$installer = $this->createInstallerMock(); $installer = $this->createInstallerMock();
$manager = new InstallationManager(); $manager = new InstallationManager($this->loop);
$manager->addInstaller($installer); $manager->addInstaller($installer);
$package = $this->createPackageMock(); $package = $this->createPackageMock();
@ -139,7 +148,7 @@ class InstallationManagerTest extends TestCase
public function testUpdateWithEqualTypes() public function testUpdateWithEqualTypes()
{ {
$installer = $this->createInstallerMock(); $installer = $this->createInstallerMock();
$manager = new InstallationManager(); $manager = new InstallationManager($this->loop);
$manager->addInstaller($installer); $manager->addInstaller($installer);
$initial = $this->createPackageMock(); $initial = $this->createPackageMock();
@ -173,18 +182,17 @@ class InstallationManagerTest extends TestCase
{ {
$libInstaller = $this->createInstallerMock(); $libInstaller = $this->createInstallerMock();
$bundleInstaller = $this->createInstallerMock(); $bundleInstaller = $this->createInstallerMock();
$manager = new InstallationManager(); $manager = new InstallationManager($this->loop);
$manager->addInstaller($libInstaller); $manager->addInstaller($libInstaller);
$manager->addInstaller($bundleInstaller); $manager->addInstaller($bundleInstaller);
$initial = $this->createPackageMock(); $initial = $this->createPackageMock();
$target = $this->createPackageMock();
$operation = new UpdateOperation($initial, $target, 'test');
$initial $initial
->expects($this->once()) ->expects($this->once())
->method('getType') ->method('getType')
->will($this->returnValue('library')); ->will($this->returnValue('library'));
$target = $this->createPackageMock();
$target $target
->expects($this->once()) ->expects($this->once())
->method('getType') ->method('getType')
@ -213,13 +221,14 @@ class InstallationManagerTest extends TestCase
->method('install') ->method('install')
->with($this->repository, $target); ->with($this->repository, $target);
$operation = new UpdateOperation($initial, $target, 'test');
$manager->update($this->repository, $operation); $manager->update($this->repository, $operation);
} }
public function testUninstall() public function testUninstall()
{ {
$installer = $this->createInstallerMock(); $installer = $this->createInstallerMock();
$manager = new InstallationManager(); $manager = new InstallationManager($this->loop);
$manager->addInstaller($installer); $manager->addInstaller($installer);
$package = $this->createPackageMock(); $package = $this->createPackageMock();
@ -249,7 +258,7 @@ class InstallationManagerTest extends TestCase
$installer = $this->getMockBuilder('Composer\Installer\LibraryInstaller') $installer = $this->getMockBuilder('Composer\Installer\LibraryInstaller')
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->getMock();
$manager = new InstallationManager(); $manager = new InstallationManager($this->loop);
$manager->addInstaller($installer); $manager->addInstaller($installer);
$package = $this->createPackageMock(); $package = $this->createPackageMock();
@ -281,7 +290,9 @@ class InstallationManagerTest extends TestCase
private function createPackageMock() private function createPackageMock()
{ {
return $this->getMockBuilder('Composer\Package\PackageInterface') $mock = $this->getMockBuilder('Composer\Package\PackageInterface')
->getMock(); ->getMock();
return $mock;
} }
} }

View File

@ -113,7 +113,7 @@ class LibraryInstallerTest extends TestCase
$this->dm $this->dm
->expects($this->once()) ->expects($this->once())
->method('download') ->method('install')
->with($package, $this->vendorDir.'/some/package'); ->with($package, $this->vendorDir.'/some/package');
$this->repository $this->repository

View File

@ -20,6 +20,7 @@ use Composer\Repository\WritableRepositoryInterface;
use Composer\Installer; use Composer\Installer;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\Test\TestCase; use Composer\Test\TestCase;
use Composer\Util\Loop;
class FactoryMock extends Factory class FactoryMock extends Factory
{ {
@ -39,9 +40,9 @@ class FactoryMock extends Factory
{ {
} }
protected function createInstallationManager() public function createInstallationManager(Loop $loop)
{ {
return new InstallationManagerMock; return new InstallationManagerMock();
} }
protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io) protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io)

View File

@ -17,6 +17,7 @@ use Composer\Repository\RepositoryInterface;
use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation; use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation;
@ -29,6 +30,18 @@ class InstallationManagerMock extends InstallationManager
private $uninstalled = array(); private $uninstalled = array();
private $trace = array(); private $trace = array();
public function __construct()
{
}
public function execute(RepositoryInterface $repo, OperationInterface $operation)
{
$method = $operation->getJobType();
// skipping download() step here for tests
$this->$method($repo, $operation);
}
public function getInstallPath(PackageInterface $package) public function getInstallPath(PackageInterface $package)
{ {
return ''; return '';

View File

@ -16,6 +16,7 @@ use Composer\IO\NullIO;
use Composer\Factory; use Composer\Factory;
use Composer\Package\Archiver\ArchiveManager; use Composer\Package\Archiver\ArchiveManager;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Util\Loop;
use Composer\Test\Mock\FactoryMock; use Composer\Test\Mock\FactoryMock;
class ArchiveManagerTest extends ArchiverTest class ArchiveManagerTest extends ArchiverTest
@ -35,9 +36,10 @@ class ArchiveManagerTest extends ArchiverTest
$dm = $factory->createDownloadManager( $dm = $factory->createDownloadManager(
$io = new NullIO, $io = new NullIO,
$config = FactoryMock::createConfig(), $config = FactoryMock::createConfig(),
$factory->createHttpDownloader($io, $config) $httpDownloader = $factory->createHttpDownloader($io, $config)
); );
$this->manager = $factory->createArchiveManager($factory->createConfig(), $dm); $loop = new Loop($httpDownloader);
$this->manager = $factory->createArchiveManager($factory->createConfig(), $dm, $loop);
$this->targetDir = $this->testDir.'/composer_archiver_tests'; $this->targetDir = $this->testDir.'/composer_archiver_tests';
} }

View File

@ -89,7 +89,7 @@ class PluginInstallerTest extends TestCase
->method('getLocalRepository') ->method('getLocalRepository')
->will($this->returnValue($this->repository)); ->will($this->returnValue($this->repository));
$im = $this->getMockBuilder('Composer\Installer\InstallationManager')->getMock(); $im = $this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock();
$im->expects($this->any()) $im->expects($this->any())
->method('getInstallPath') ->method('getInstallPath')
->will($this->returnCallback(function ($package) { ->will($this->returnCallback(function ($package) {