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

View File

@ -38,6 +38,7 @@ use Symfony\Component\Finder\Finder;
use Composer\Json\JsonFile;
use Composer\Config\JsonConfigSource;
use Composer\Util\Filesystem;
use Composer\Util\Loop;
use Composer\Package\Version\VersionParser;
/**
@ -345,15 +346,18 @@ EOT
$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)
->setPreferDist($preferDist)
->setOutputProgress(!$noProgress);
$projectInstaller = new ProjectInstaller($directory, $dm);
$im = $this->createInstallationManager();
$im = $factory->createInstallationManager(new Loop($httpDownloader));
$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);
// collect suggestions
@ -369,16 +373,4 @@ EOT
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
foreach ($installedRepo->getCanonicalPackages() as $package) {
$downloader = $dm->getDownloaderForInstalledPackage($package);
$downloader = $dm->getDownloaderForPackage($package);
$targetDir = $im->getInstallPath($package);
if ($downloader instanceof ChangeReportInterface) {

View File

@ -30,21 +30,23 @@ abstract class ArchiveDownloader extends FileDownloader
* @throws \RuntimeException
* @throws \UnexpectedValueException
*/
public function download(PackageInterface $package, $path, $output = true)
public function install(PackageInterface $package, $path, $output = true)
{
if ($output) {
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
}
$temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8);
$retries = 3;
while ($retries--) {
$fileName = parent::download($package, $path, $output);
$fileName = $this->getFileName($package, $path);
if ($output) {
$this->io->writeError(' Extracting archive', false, IOInterface::VERBOSE);
$this->io->writeError(' Extracting archive', true, IOInterface::VERBOSE);
}
try {
$this->filesystem->ensureDirectoryExists($temporaryDir);
try {
$this->extract($fileName, $temporaryDir);
$this->extract($package, $fileName, $temporaryDir);
} catch (\Exception $e) {
// remove cache if the file was corrupted
parent::clearLastCacheWrite($package);
@ -53,10 +55,25 @@ abstract class ArchiveDownloader extends FileDownloader
$this->filesystem->unlink($fileName);
$contentDir = $this->getFolderContent($temporaryDir);
$renameAsOne = false;
if (!file_exists($path) || ($this->filesystem->isDirEmpty($path) && $this->filesystem->removeDirectory($path))) {
$renameAsOne = true;
}
$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);
} else {
// 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));
}
@ -65,6 +82,7 @@ abstract class ArchiveDownloader extends FileDownloader
$file = (string) $file;
$this->filesystem->rename($file, $path . '/' . basename($file));
}
}
$this->filesystem->removeDirectory($temporaryDir);
if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) {
@ -77,24 +95,12 @@ abstract class ArchiveDownloader extends FileDownloader
// 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;
if (file_exists($fileName)) {
$this->filesystem->unlink($fileName);
}
throw $e;
}
break;
}
}
/**
@ -102,7 +108,7 @@ abstract class ArchiveDownloader extends FileDownloader
*/
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
*/
abstract protected function extract($file, $path);
abstract protected function extract(PackageInterface $package, $file, $path);
/**
* Returns the folder content, excluding dotfiles

View File

@ -15,6 +15,7 @@ namespace Composer\Downloader;
use Composer\Package\PackageInterface;
use Composer\IO\IOInterface;
use Composer\Util\Filesystem;
use React\Promise\PromiseInterface;
/**
* Downloaders manager.
@ -24,6 +25,7 @@ use Composer\Util\Filesystem;
class DownloadManager
{
private $io;
private $httpDownloader;
private $preferDist = false;
private $preferSource = false;
private $packagePreferences = array();
@ -140,7 +142,7 @@ class DownloadManager
* wrong type
* @return DownloaderInterface|null
*/
public function getDownloaderForInstalledPackage(PackageInterface $package)
public function getDownloaderForPackage(PackageInterface $package)
{
$installationSource = $package->getInstallationSource();
@ -154,7 +156,7 @@ class DownloadManager
$downloader = $this->getDownloader($package->getSourceType());
} else {
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;
}
public function getDownloaderType(DownloaderInterface $downloader)
{
return array_search($downloader, $this->downloaders);
}
/**
* Downloads package into target dir.
*
* @param PackageInterface $package package instance
* @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, $preferSource = null)
public function download(PackageInterface $package, $targetDir, PackageInterface $prevPackage = null)
{
$preferSource = null !== $preferSource ? $preferSource : $this->preferSource;
$sourceType = $package->getSourceType();
$distType = $package->getDistType();
$this->filesystem->ensureDirectoryExists(dirname($targetDir));
$sources = array();
if ($sourceType) {
$sources[] = 'source';
}
if ($distType) {
$sources[] = 'dist';
}
$sources = $this->getAvailableSources($package, $prevPackage);
if (empty($sources)) {
throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
}
$io = $this->io;
$self = $this;
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>');
$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);
try {
$downloader = $this->getDownloaderForInstalledPackage($package);
if ($downloader) {
$downloader->download($package, $targetDir);
$downloader = $self->getDownloaderForPackage($package);
if (!$downloader) {
return \React\Promise\resolve();
}
break;
} catch (\RuntimeException $e) {
if ($i === count($sources) - 1) {
$handleError = function ($e) use ($sources, $source, $package, $io, $download) {
if ($e instanceof \RuntimeException) {
if (!$sources) {
throw $e;
}
$this->io->writeError(
$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 \RuntimeException
*/
public function install(PackageInterface $package, $targetDir)
{
$downloader = $this->getDownloaderForPackage($package);
if ($downloader) {
$downloader->install($package, $targetDir);
}
}
@ -242,31 +276,23 @@ class DownloadManager
*/
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) {
$initialDownloader->remove($initial, $targetDir);
return;
}
$installationSource = $initial->getInstallationSource();
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;
}
$initialType = $this->getDownloaderType($initialDownloader);
$targetType = $this->getDownloaderType($downloader);
if ($initialType === $targetType) {
$target->setInstallationSource($installationSource);
try {
$downloader->update($initial, $target, $targetDir);
@ -282,8 +308,12 @@ class DownloadManager
}
}
$downloader->remove($initial, $targetDir);
$this->download($target, $targetDir, 'source' === $installationSource);
// if downloader type changed, or update failed and user asks for reinstall,
// 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)
{
$downloader = $this->getDownloaderForInstalledPackage($package);
$downloader = $this->getDownloaderForPackage($package);
if ($downloader) {
$downloader->remove($package, $targetDir);
}
@ -322,4 +352,48 @@ class DownloadManager
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;
use Composer\Package\PackageInterface;
use React\Promise\PromiseInterface;
/**
* Downloader interface.
@ -29,13 +30,20 @@ interface DownloaderInterface
*/
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.
*
* @param PackageInterface $package package instance
* @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.

View File

@ -26,6 +26,7 @@ use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\Filesystem;
use Composer\Util\HttpDownloader;
use Composer\Util\Url as UrlUtil;
use Composer\Downloader\TransportException;
/**
* Base downloader for files
@ -43,7 +44,10 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
protected $filesystem;
protected $cache;
protected $outputProgress = true;
private $lastCacheWrites = array();
/**
* @private this is only public for php 5.3 support in closures
*/
public $lastCacheWrites = array();
private $eventDispatcher;
/**
@ -87,108 +91,149 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
throw new \InvalidArgumentException('The given package is missing url information');
}
if ($output) {
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): ", false);
}
$retries = 3;
$urls = $package->getDistUrls();
while ($url = array_shift($urls)) {
try {
$fileName = $this->doDownload($package, $path, $url);
break;
} catch (\Exception $e) {
if ($this->io->isDebug()) {
$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);
foreach ($urls as $index => $url) {
$processedUrl = $this->processUrl($package, $url);
$urls[$index] = array(
'base' => $url,
'processed' => $processedUrl,
'cacheKey' => $this->getCacheKey($package, $processedUrl)
);
}
if (!count($urls)) {
throw $e;
}
}
$this->filesystem->emptyDirectory($path);
$fileName = $this->getFileName($package, $path);
$io = $this->io;
$cache = $this->cache;
$originalHttpDownloader = $this->httpDownloader;
$eventDispatcher = $this->eventDispatcher;
$filesystem = $this->filesystem;
$self = $this;
$accept = null;
$reject = null;
$download = function () use ($io, $output, $originalHttpDownloader, $cache, $eventDispatcher, $package, $fileName, $path, &$urls, &$accept, &$reject) {
$url = reset($urls);
$httpDownloader = $originalHttpDownloader;
if ($eventDispatcher) {
$preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $httpDownloader, $url['processed']);
$eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
$httpDownloader = $preFileDownloadEvent->getHttpDownloader();
}
$checksum = $package->getDistSha1Checksum();
$cacheKey = $url['cacheKey'];
// use from cache if it is present and has a valid checksum or we have no checksum to check against
if ($cache && (!$checksum || $checksum === $cache->sha1($cacheKey)) && $cache->copyTo($cacheKey, $fileName)) {
if ($output) {
$this->io->writeError('');
$io->writeError(" - Loading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>) from cache");
}
$result = \React\Promise\resolve($fileName);
} else {
if ($output) {
$io->writeError(" - Downloading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
}
$result = $httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions())
->then($accept, $reject);
}
return $result->then(function ($result) use ($fileName, $checksum, $url) {
// in case of retry, the first call's Promise chain finally calls this twice at the end,
// 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;
}
protected function doDownload(PackageInterface $package, $path, $url)
{
$this->filesystem->emptyDirectory($path);
$fileName = $this->getFileName($package, $path);
$processedUrl = $this->processUrl($package, $url);
$preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $processedUrl);
if ($this->eventDispatcher) {
$this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
}
$httpDownloader = $preFileDownloadEvent->getHttpDownloader();
try {
$checksum = $package->getDistSha1Checksum();
$cacheKey = $this->getCacheKey($package, $processedUrl);
// 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)) {
$this->io->writeError('Loading from cache', false);
} else {
// download if cache restore failed
if (!$this->outputProgress) {
$this->io->writeError('Downloading', false);
}
// try to download 3 times then fail hard
$retries = 3;
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)) {
throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the'
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.')');
}
} catch (\Exception $e) {
// clean up
$this->filesystem->removeDirectory($path);
$this->clearLastCacheWrite($package);
throw $e;
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);
}
$response->collect();
return $fileName;
};
$reject = function ($e) use ($io, &$urls, $download, $fileName, $path, $package, &$retries, $filesystem, $self) {
// clean up
$filesystem->removeDirectory($path);
$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;
};
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>)");
}
$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;
}
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()])) {
$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->remove($initial, $path, false);
$this->download($target, $path, false);
$this->install($target, $path, false);
$this->io->writeError('');
}
@ -249,7 +298,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
*/
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;
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->setSource($targetDir.'_compare');

View File

@ -23,7 +23,7 @@ class FossilDownloader extends VcsDownloader
/**
* {@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
$this->config->prohibitUrlByConfig($url, $this->io);

View File

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

View File

@ -36,9 +36,10 @@ class GzipDownloader extends ArchiveDownloader
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
if (!Platform::isWindows()) {
@ -63,14 +64,6 @@ class GzipDownloader extends ArchiveDownloader
$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)
{
$archiveFile = gzopen($file, 'rb');

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -55,6 +55,14 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
* {@inheritDoc}
*/
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()) {
throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
@ -87,7 +95,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
$url = $needle . $url;
}
}
$this->doDownload($package, $path, $url);
$this->doInstall($package, $path, $url);
break;
} catch (\Exception $e) {
// 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 $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.

View File

@ -37,7 +37,7 @@ class XzDownloader extends ArchiveDownloader
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);
@ -49,12 +49,4 @@ class XzDownloader extends ArchiveDownloader
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 $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
if (self::$isWindows) {

View File

@ -24,6 +24,7 @@ use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\HttpDownloader;
use Composer\Util\Loop;
use Composer\Util\Silencer;
use Composer\Plugin\PluginEvents;
use Composer\EventDispatcher\Event;
@ -326,6 +327,7 @@ class Factory
}
$httpDownloader = self::createHttpDownloader($io, $config);
$loop = new Loop($httpDownloader);
// initialize event dispatcher
$dispatcher = new EventDispatcher($composer, $io);
@ -352,7 +354,7 @@ class Factory
$composer->setPackage($package);
// initialize installation manager
$im = $this->createInstallationManager();
$im = $this->createInstallationManager($loop);
$composer->setInstallationManager($im);
if ($fullLoad) {
@ -365,7 +367,7 @@ class Factory
$composer->setAutoloadGenerator($generator);
// initialize archive manager
$am = $this->createArchiveManager($config, $dm);
$am = $this->createArchiveManager($config, $dm, $loop);
$composer->setArchiveManager($am);
}
@ -501,9 +503,9 @@ class Factory
* @param Downloader\DownloadManager $dm Manager use to download sources
* @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\PharArchiver);
@ -525,9 +527,9 @@ class Factory
/**
* @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\MarkAliasUninstalledOperation;
use Composer\Util\StreamContextFactory;
use Composer\Util\Loop;
/**
* Package operation manager.
@ -37,6 +38,12 @@ class InstallationManager
private $installers = array();
private $cache = array();
private $notifiablePackages = array();
private $loop;
public function __construct(Loop $loop)
{
$this->loop = $loop;
}
public function reset()
{
@ -156,7 +163,24 @@ class InstallationManager
*/
public function execute(RepositoryInterface $repo, OperationInterface $operation)
{
// TODO this should take all operations in one go
$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);
}
@ -194,7 +218,8 @@ class InstallationManager
$this->markForNotification($target);
} else {
$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\Repository\InstalledRepositoryInterface;
use InvalidArgumentException;
use React\Promise\PromiseInterface;
/**
* Interface for the package installation manager.
@ -42,6 +43,15 @@ interface InstallerInterface
*/
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.
*

View File

@ -85,6 +85,14 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
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}
*/
@ -194,7 +202,7 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
protected function installCode(PackageInterface $package)
{
$downloadPath = $this->getInstallPath($package);
$this->downloadManager->download($package, $downloadPath);
$this->downloadManager->install($package, $downloadPath);
}
protected function updateCode(PackageInterface $initial, PackageInterface $target)

View File

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

View File

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

View File

@ -50,13 +50,21 @@ class PluginInstaller extends LibraryInstaller
/**
* {@inheritDoc}
*/
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
{
$extra = $package->getExtra();
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.');
}
return parent::download($package, $prevPackage);
}
/**
* {@inheritDoc}
*/
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
{
parent::install($repo, $package);
try {
$this->composer->getPluginManager()->registerPackage($package, true);

View File

@ -58,7 +58,7 @@ class ProjectInstaller implements InstallerInterface
/**
* {@inheritDoc}
*/
public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
{
$installPath = $this->installPath;
if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) {
@ -67,7 +67,16 @@ class ProjectInstaller implements InstallerInterface
if (!is_dir($installPath)) {
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\RootPackageInterface;
use Composer\Util\Filesystem;
use Composer\Util\Loop;
use Composer\Json\JsonFile;
/**
@ -25,6 +26,7 @@ use Composer\Json\JsonFile;
class ArchiveManager
{
protected $downloadManager;
protected $loop;
protected $archivers = array();
@ -36,9 +38,10 @@ class ArchiveManager
/**
* @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->loop = $loop;
}
/**
@ -148,7 +151,9 @@ class ArchiveManager
$filesystem->ensureDirectoryExists($sourcePath);
// 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
if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) {

View File

@ -22,6 +22,7 @@ use Composer\Config;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Util\HttpDownloader;
use Composer\Util\Loop;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PreFileDownloadEvent;
use Composer\EventDispatcher\EventDispatcher;
@ -42,6 +43,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
private $baseUrl;
private $io;
private $httpDownloader;
private $loop;
protected $cache;
protected $notifyUrl;
protected $searchUrl;
@ -107,6 +109,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$this->httpDownloader = $httpDownloader;
$this->eventDispatcher = $eventDispatcher;
$this->repoConfig = $repoConfig;
$this->loop = new Loop($this->httpDownloader);
}
public function getRepoConfig()
@ -569,6 +572,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$this->loadRootServerFile();
$packages = array();
$promises = array();
$repo = $this;
if (!$this->lazyProvidersUrl) {
@ -592,7 +596,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$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) {
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();
}
}
}, function ($e) {
// TODO use ->done() above instead with react/promise 2.0
throw $e;
});
}
$this->httpDownloader->wait();
$this->loop->wait($promises);
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
@ -1119,7 +1120,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
}
$degradedMode = true;
return true;
throw $e;
};
return $httpDownloader->add($filename, $options)->then($accept, $reject);

View File

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

View File

@ -160,7 +160,10 @@ class HttpDownloader
if ($job['request']['copyTo']) {
$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 {
$body = $rfs->getContents($job['origin'], $url, false /* TODO progress */, $options);
$headers = $rfs->getLastHeaders();
@ -191,6 +194,7 @@ class HttpDownloader
$job['exception'] = $e;
$downloader->markJobDone();
$downloader->scheduleNextJob();
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()
{
$composer = new Composer();
$manager = $this->getMockBuilder('Composer\Installer\InstallationManager')->getMock();
$manager = $this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock();
$composer->setInstallationManager($manager);
$this->assertSame($manager, $composer->getInstallationManager());

View File

@ -29,7 +29,7 @@ class ArchiveDownloaderTest extends TestCase
$method->setAccessible(true);
$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'));
}

View File

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

View File

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

View File

@ -56,7 +56,7 @@ class FossilDownloaderTest extends TestCase
->will($this->returnValue(null));
$downloader = $this->getDownloaderMock();
$downloader->download($packageMock, '/path');
$downloader->install($packageMock, '/path');
}
public function testDownload()
@ -89,7 +89,7 @@ class FossilDownloaderTest extends TestCase
->will($this->returnValue(0));
$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));
$downloader = $this->getDownloaderMock();
$downloader->download($packageMock, '/path');
$downloader->install($packageMock, '/path');
}
public function testDownload()
@ -130,7 +130,7 @@ class GitDownloaderTest extends TestCase
->will($this->returnValue(0));
$downloader = $this->getDownloaderMock(null, null, $processExecutor);
$downloader->download($packageMock, 'composerPath');
$downloader->install($packageMock, 'composerPath');
}
public function testDownloadWithCache()
@ -195,7 +195,7 @@ class GitDownloaderTest extends TestCase
->will($this->returnValue(0));
$downloader = $this->getDownloaderMock(null, $config, $processExecutor);
$downloader->download($packageMock, 'composerPath');
$downloader->install($packageMock, 'composerPath');
@rmdir($cachePath);
}
@ -265,7 +265,7 @@ class GitDownloaderTest extends TestCase
->will($this->returnValue(0));
$downloader = $this->getDownloaderMock(null, new Config(), $processExecutor);
$downloader->download($packageMock, 'composerPath');
$downloader->install($packageMock, 'composerPath');
}
public function pushUrlProvider()
@ -329,7 +329,7 @@ class GitDownloaderTest extends TestCase
$config->merge(array('config' => array('github-protocols' => $protocols)));
$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));
$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));
$downloader = $this->getDownloaderMock();
$downloader->download($packageMock, '/path');
$downloader->install($packageMock, '/path');
}
public function testDownload()
@ -83,7 +83,7 @@ class HgDownloaderTest extends TestCase
->will($this->returnValue(0));
$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(6))->method('cleanupClientSpec');
$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(6))->method('cleanupClientSpec');
$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\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\Loop;
use Composer\Util\HttpDownloader;
class XzDownloaderTest extends TestCase
@ -66,10 +67,14 @@ class XzDownloaderTest extends TestCase
->method('get')
->with('vendor-dir')
->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 {
$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');
} catch (\RuntimeException $e) {
$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\Util\Filesystem;
use Composer\Util\HttpDownloader;
use Composer\Util\Loop;
class ZipDownloaderTest extends TestCase
{
@ -27,6 +28,7 @@ class ZipDownloaderTest extends TestCase
private $prophet;
private $io;
private $config;
private $package;
public function setUp()
{
@ -35,6 +37,7 @@ class ZipDownloaderTest extends TestCase
$this->config = $this->getMockBuilder('Composer\Config')->getMock();
$dlConfig = $this->getMockBuilder('Composer\Config')->getMock();
$this->httpDownloader = new HttpDownloader($this->io, $dlConfig);
$this->package = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
}
public function tearDown()
@ -71,16 +74,15 @@ class ZipDownloaderTest extends TestCase
->with('vendor-dir')
->will($this->returnValue($this->testDir));
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any())
$this->package->expects($this->any())
->method('getDistUrl')
->will($this->returnValue($distUrl = 'file://'.__FILE__))
;
$packageMock->expects($this->any())
$this->package->expects($this->any())
->method('getDistUrls')
->will($this->returnValue(array($distUrl)))
;
$packageMock->expects($this->atLeastOnce())
$this->package->expects($this->atLeastOnce())
->method('getTransportOptions')
->will($this->returnValue(array()))
;
@ -90,7 +92,11 @@ class ZipDownloaderTest extends TestCase
$this->setPrivateProperty('hasSystemUnzip', false);
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');
} catch (\Exception $e) {
$this->assertContains('is not a zip archive', $e->getMessage());
@ -119,7 +125,7 @@ class ZipDownloaderTest extends TestCase
->will($this->returnValue(false));
$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')));
$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));
$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));
$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()
@ -206,7 +212,7 @@ class ZipDownloaderTest extends TestCase
->will($this->returnValue(0));
$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()
@ -234,7 +240,7 @@ class ZipDownloaderTest extends TestCase
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor);
$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);
$this->setPrivateProperty('zipArchiveObject', $zipArchive, $downloader);
$downloader->extract('testfile.zip', 'vendor/dir');
$downloader->extract($this->package, 'testfile.zip', 'vendor/dir');
}
public function testWindowsFallbackGood()
@ -294,7 +300,7 @@ class ZipDownloaderTest extends TestCase
$downloader = new MockedZipDownloader($this->io, $this->config, $this->httpDownloader, null, null, $processExecutor);
$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);
$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;
}
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->setRepositoryManager($this->getRepositoryManagerMockForDevModePassingTest());
$composer->setInstallationManager($this->getMockBuilder('Composer\Installer\InstallationManager')->getMock());
$composer->setInstallationManager($this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock());
$dispatcher = new EventDispatcher(
$composer,

View File

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

View File

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

View File

@ -20,6 +20,7 @@ use Composer\Repository\WritableRepositoryInterface;
use Composer\Installer;
use Composer\IO\IOInterface;
use Composer\Test\TestCase;
use Composer\Util\Loop;
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)

View File

@ -17,6 +17,7 @@ use Composer\Repository\RepositoryInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation;
@ -29,6 +30,18 @@ class InstallationManagerMock extends InstallationManager
private $uninstalled = 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)
{
return '';

View File

@ -16,6 +16,7 @@ use Composer\IO\NullIO;
use Composer\Factory;
use Composer\Package\Archiver\ArchiveManager;
use Composer\Package\PackageInterface;
use Composer\Util\Loop;
use Composer\Test\Mock\FactoryMock;
class ArchiveManagerTest extends ArchiverTest
@ -35,9 +36,10 @@ class ArchiveManagerTest extends ArchiverTest
$dm = $factory->createDownloadManager(
$io = new NullIO,
$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';
}

View File

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