diff --git a/src/Composer/Command/ArchiveCommand.php b/src/Composer/Command/ArchiveCommand.php index f893ed679..e26aa59a1 100644 --- a/src/Composer/Command/ArchiveCommand.php +++ b/src/Composer/Command/ArchiveCommand.php @@ -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) { diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index a1c364539..e165649a6 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -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(); - } } diff --git a/src/Composer/Command/StatusCommand.php b/src/Composer/Command/StatusCommand.php index 3e46b7fa0..06fc7638b 100644 --- a/src/Composer/Command/StatusCommand.php +++ b/src/Composer/Command/StatusCommand.php @@ -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) { diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index d041a7f88..3c53a086e 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -30,33 +30,50 @@ 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) { - $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8); - $retries = 3; - while ($retries--) { - $fileName = parent::download($package, $path, $output); + if ($output) { + $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); + } - if ($output) { - $this->io->writeError(' Extracting archive', false, IOInterface::VERBOSE); + $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8); + $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->ensureDirectoryExists($temporaryDir); - try { - $this->extract($fileName, $temporaryDir); - } catch (\Exception $e) { - // remove cache if the file was corrupted - parent::clearLastCacheWrite($package); - throw $e; + $this->filesystem->unlink($fileName); + + $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->unlink($fileName); - - $contentDir = $this->getFolderContent($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,35 +82,24 @@ 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/')) { - $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) { - 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 diff --git a/src/Composer/Downloader/DownloadManager.php b/src/Composer/Downloader/DownloadManager.php index 15c00a6e6..4bc865827 100644 --- a/src/Composer/Downloader/DownloadManager.php +++ b/src/Composer/Downloader/DownloadManager.php @@ -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(); @@ -33,9 +35,9 @@ class DownloadManager /** * Initializes download manager. * - * @param IOInterface $io The Input Output Interface - * @param bool $preferSource prefer downloading from source - * @param Filesystem|null $filesystem custom Filesystem object + * @param IOInterface $io The Input Output Interface + * @param bool $preferSource prefer downloading from source + * @param Filesystem|null $filesystem custom Filesystem object */ public function __construct(IOInterface $io, $preferSource = false, Filesystem $filesystem = null) { @@ -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, 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(' Now trying to download from ' . $source . ''); + } + $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( + ' Failed to download '. + $package->getPrettyName(). + ' from ' . $source . ': '. + $e->getMessage().'' + ); + + 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 download(PackageInterface $package, $targetDir, $preferSource = null) + public function install(PackageInterface $package, $targetDir) { - $preferSource = null !== $preferSource ? $preferSource : $this->preferSource; - $sourceType = $package->getSourceType(); - $distType = $package->getDistType(); - - $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(' Now trying to download from ' . $source . ''); - } - $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( - ' Failed to download '. - $package->getPrettyName(). - ' from ' . $source . ': '. - $e->getMessage().'' - ); - } + $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; + } } diff --git a/src/Composer/Downloader/DownloaderInterface.php b/src/Composer/Downloader/DownloaderInterface.php index 713bf36dc..ac56583c4 100644 --- a/src/Composer/Downloader/DownloaderInterface.php +++ b/src/Composer/Downloader/DownloaderInterface.php @@ -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. diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 8b196e60c..3418eef84 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -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 " . $package->getName() . " (" . $package->getFullPrettyVersion() . "): ", 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); - } - - if (!count($urls)) { - throw $e; - } - } + foreach ($urls as $index => $url) { + $processedUrl = $this->processUrl($package, $url); + $urls[$index] = array( + 'base' => $url, + 'processed' => $processedUrl, + 'cacheKey' => $this->getCacheKey($package, $processedUrl) + ); } - if ($output) { - $this->io->writeError(''); - } - - return $fileName; - } - - protected function doDownload(PackageInterface $package, $path, $url) - { $this->filesystem->emptyDirectory($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); - if ($this->eventDispatcher) { - $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); - } - $httpDownloader = $preFileDownloadEvent->getHttpDownloader(); + $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(); + } - try { $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 - if ($this->cache && (!$checksum || $checksum === $this->cache->sha1($cacheKey)) && $this->cache->copyTo($cacheKey, $fileName)) { - $this->io->writeError('Loading from cache', false); + if ($cache && (!$checksum || $checksum === $cache->sha1($cacheKey)) && $cache->copyTo($cacheKey, $fileName)) { + if ($output) { + $io->writeError(" - Loading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ") from cache"); + } + $result = \React\Promise\resolve($fileName); } else { - // download if cache restore failed - if (!$this->outputProgress) { - $this->io->writeError('Downloading', false); + if ($output) { + $io->writeError(" - Downloading " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); } - // 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(' (100%)', false); - } - - if ($this->cache) { - $this->lastCacheWrites[$package->getName()] = $cacheKey; - $this->cache->copyFrom($cacheKey, $fileName); - } + $result = $httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions()) + ->then($accept, $reject); } - if (!file_exists($fileName)) { - throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the' - .' directory is writable and you have internet connectivity'); + 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; + } + + 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) { - throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url.')'); - } - } catch (\Exception $e) { + $response->collect(); + + return $fileName; + }; + + $reject = function ($e) use ($io, &$urls, $download, $fileName, $path, $package, &$retries, $filesystem, $self) { // clean up - $this->filesystem->removeDirectory($path); - $this->clearLastCacheWrite($package); + $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 " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); } - 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; } - 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 . " " . $name . " (" . $from . " => " . $to . "): ", 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'); diff --git a/src/Composer/Downloader/FossilDownloader.php b/src/Composer/Downloader/FossilDownloader.php index 135e973e0..a814f89b7 100644 --- a/src/Composer/Downloader/FossilDownloader.php +++ b/src/Composer/Downloader/FossilDownloader.php @@ -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); diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 869d5330b..ff398f300 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -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); diff --git a/src/Composer/Downloader/GzipDownloader.php b/src/Composer/Downloader/GzipDownloader.php index f65fcf27d..9748b91ac 100644 --- a/src/Composer/Downloader/GzipDownloader.php +++ b/src/Composer/Downloader/GzipDownloader.php @@ -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'); diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index 2921cc4b7..add381a75 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -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); diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index e7084bd97..ea583cfc0 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -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); diff --git a/src/Composer/Downloader/PerforceDownloader.php b/src/Composer/Downloader/PerforceDownloader.php index a472b84c6..df270417f 100644 --- a/src/Composer/Downloader/PerforceDownloader.php +++ b/src/Composer/Downloader/PerforceDownloader.php @@ -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); diff --git a/src/Composer/Downloader/PharDownloader.php b/src/Composer/Downloader/PharDownloader.php index 13fec244b..62741ee0e 100644 --- a/src/Composer/Downloader/PharDownloader.php +++ b/src/Composer/Downloader/PharDownloader.php @@ -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); diff --git a/src/Composer/Downloader/RarDownloader.php b/src/Composer/Downloader/RarDownloader.php index 6fe4cf27c..2ebc3bf18 100644 --- a/src/Composer/Downloader/RarDownloader.php +++ b/src/Composer/Downloader/RarDownloader.php @@ -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; diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index e23958164..0aae163c6 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -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(); diff --git a/src/Composer/Downloader/TarDownloader.php b/src/Composer/Downloader/TarDownloader.php index 34c43da5f..e48407230 100644 --- a/src/Composer/Downloader/TarDownloader.php +++ b/src/Composer/Downloader/TarDownloader.php @@ -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); diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index aa666058e..237d7e49d 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -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. diff --git a/src/Composer/Downloader/XzDownloader.php b/src/Composer/Downloader/XzDownloader.php index bd7b028e2..19e51c321 100644 --- a/src/Composer/Downloader/XzDownloader.php +++ b/src/Composer/Downloader/XzDownloader.php @@ -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); - } } diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index 9eceab250..efa9fc994 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -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) { diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index c96fd2188..e66e64ed1 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -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); } /** diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index 9f50b5980..ce10dc4da 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -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); } } diff --git a/src/Composer/Installer/InstallerInterface.php b/src/Composer/Installer/InstallerInterface.php index e64dfadd2..e00877ed9 100644 --- a/src/Composer/Installer/InstallerInterface.php +++ b/src/Composer/Installer/InstallerInterface.php @@ -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. * diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index 34fbbbee4..4c2f45601 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -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) diff --git a/src/Composer/Installer/MetapackageInstaller.php b/src/Composer/Installer/MetapackageInstaller.php index 3f99ec03c..7dbf4af67 100644 --- a/src/Composer/Installer/MetapackageInstaller.php +++ b/src/Composer/Installer/MetapackageInstaller.php @@ -38,6 +38,14 @@ class MetapackageInstaller implements InstallerInterface return $repo->hasPackage($package); } + /** + * {@inheritDoc} + */ + public function download(PackageInterface $package, PackageInterface $prevPackage = null) + { + // noop + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Installer/NoopInstaller.php b/src/Composer/Installer/NoopInstaller.php index 72cf17d22..51df3c305 100644 --- a/src/Composer/Installer/NoopInstaller.php +++ b/src/Composer/Installer/NoopInstaller.php @@ -40,6 +40,13 @@ class NoopInstaller implements InstallerInterface return $repo->hasPackage($package); } + /** + * {@inheritDoc} + */ + public function download(PackageInterface $package, PackageInterface $prevPackage = null) + { + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php index c400ca4a6..62a16fc62 100644 --- a/src/Composer/Installer/PluginInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -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); diff --git a/src/Composer/Installer/ProjectInstaller.php b/src/Composer/Installer/ProjectInstaller.php index c79238b36..350b220f5 100644 --- a/src/Composer/Installer/ProjectInstaller.php +++ b/src/Composer/Installer/ProjectInstaller.php @@ -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); } /** diff --git a/src/Composer/Package/Archiver/ArchiveManager.php b/src/Composer/Package/Archiver/ArchiveManager.php index 22f8eeafe..359d6b053 100644 --- a/src/Composer/Package/Archiver/ArchiveManager.php +++ b/src/Composer/Package/Archiver/ArchiveManager.php @@ -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')) { diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 8fe6a04ed..af627df73 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -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); diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index 3dc6710b6..ff31bf695 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -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); } diff --git a/src/Composer/Util/HttpDownloader.php b/src/Composer/Util/HttpDownloader.php index f2308d75a..172ea875a 100644 --- a/src/Composer/Util/HttpDownloader.php +++ b/src/Composer/Util/HttpDownloader.php @@ -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; }); diff --git a/src/Composer/Util/Loop.php b/src/Composer/Util/Loop.php new file mode 100644 index 000000000..1be7d478b --- /dev/null +++ b/src/Composer/Util/Loop.php @@ -0,0 +1,47 @@ + + * Jordi Boggiano + * + * 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 + */ +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; + } + } +} diff --git a/tests/Composer/Test/ComposerTest.php b/tests/Composer/Test/ComposerTest.php index c2c425e76..87270baae 100644 --- a/tests/Composer/Test/ComposerTest.php +++ b/tests/Composer/Test/ComposerTest.php @@ -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()); diff --git a/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php b/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php index ddf21c64b..d887ba103 100644 --- a/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ArchiveDownloaderTest.php @@ -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')); } diff --git a/tests/Composer/Test/Downloader/DownloadManagerTest.php b/tests/Composer/Test/Downloader/DownloadManagerTest.php index 222e541d7..307b2294f 100644 --- a/tests/Composer/Test/Downloader/DownloadManagerTest.php +++ b/tests/Composer/Test/Downloader/DownloadManagerTest.php @@ -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')); + $initial = null; + if ($prevPkgSource) { + $initial = $this->prophesize('Composer\Package\PackageInterface'); + $initial->getInstallationSource()->willReturn($prevPkgSource); + $initial->isDev()->willReturn($prevPkgIsDev); + } - $target = $this->createPackageMock(); - $target - ->expects($this->once()) - ->method('getSourceType') - ->will($this->returnValue('svn')); + $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); - $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'); + $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 function testUpdateSourceWithNotEqualTypes() + 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')); diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index 12edfe19d..9ce536474 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -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); } } diff --git a/tests/Composer/Test/Downloader/FossilDownloaderTest.php b/tests/Composer/Test/Downloader/FossilDownloaderTest.php index 623f7dec2..9ab7b6b84 100644 --- a/tests/Composer/Test/Downloader/FossilDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FossilDownloaderTest.php @@ -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'); } /** diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index c3cd31a4a..b5d0054de 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -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'); } /** diff --git a/tests/Composer/Test/Downloader/HgDownloaderTest.php b/tests/Composer/Test/Downloader/HgDownloaderTest.php index c71d463cb..a4219d143 100644 --- a/tests/Composer/Test/Downloader/HgDownloaderTest.php +++ b/tests/Composer/Test/Downloader/HgDownloaderTest.php @@ -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'); } /** diff --git a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php index 1b5041d9f..d2b8fb753 100644 --- a/tests/Composer/Test/Downloader/PerforceDownloaderTest.php +++ b/tests/Composer/Test/Downloader/PerforceDownloaderTest.php @@ -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'); } } diff --git a/tests/Composer/Test/Downloader/XzDownloaderTest.php b/tests/Composer/Test/Downloader/XzDownloaderTest.php index 451592d37..4c2fdb2af 100644 --- a/tests/Composer/Test/Downloader/XzDownloaderTest.php +++ b/tests/Composer/Test/Downloader/XzDownloaderTest.php @@ -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()); diff --git a/tests/Composer/Test/Downloader/ZipDownloaderTest.php b/tests/Composer/Test/Downloader/ZipDownloaderTest.php index 0c1311427..b754af607 100644 --- a/tests/Composer/Test/Downloader/ZipDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ZipDownloaderTest.php @@ -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); } } diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index 7786e7807..6d812e20a 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php @@ -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, diff --git a/tests/Composer/Test/Installer/InstallationManagerTest.php b/tests/Composer/Test/Installer/InstallationManagerTest.php index 86e860bc2..407a5b10f 100644 --- a/tests/Composer/Test/Installer/InstallationManagerTest.php +++ b/tests/Composer/Test/Installer/InstallationManagerTest.php @@ -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; } } diff --git a/tests/Composer/Test/Installer/LibraryInstallerTest.php b/tests/Composer/Test/Installer/LibraryInstallerTest.php index 772bb05c8..672f8eb0a 100644 --- a/tests/Composer/Test/Installer/LibraryInstallerTest.php +++ b/tests/Composer/Test/Installer/LibraryInstallerTest.php @@ -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 diff --git a/tests/Composer/Test/Mock/FactoryMock.php b/tests/Composer/Test/Mock/FactoryMock.php index 47683afcd..fcb93d2cc 100644 --- a/tests/Composer/Test/Mock/FactoryMock.php +++ b/tests/Composer/Test/Mock/FactoryMock.php @@ -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) diff --git a/tests/Composer/Test/Mock/InstallationManagerMock.php b/tests/Composer/Test/Mock/InstallationManagerMock.php index de1de514b..21e717224 100644 --- a/tests/Composer/Test/Mock/InstallationManagerMock.php +++ b/tests/Composer/Test/Mock/InstallationManagerMock.php @@ -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 ''; diff --git a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php index b9f08e693..714c9b923 100644 --- a/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php +++ b/tests/Composer/Test/Package/Archiver/ArchiveManagerTest.php @@ -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'; } diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index 01832f94d..633c5ab18 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -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) {