InstallerTest.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Test;
  12. use Composer\Installer;
  13. use Composer\Console\Application;
  14. use Composer\Config;
  15. use Composer\Json\JsonFile;
  16. use Composer\Repository\ArrayRepository;
  17. use Composer\Repository\RepositoryManager;
  18. use Composer\Repository\RepositoryInterface;
  19. use Composer\Package\PackageInterface;
  20. use Composer\Package\Link;
  21. use Composer\Package\Locker;
  22. use Composer\Test\Mock\FactoryMock;
  23. use Composer\Test\Mock\InstalledFilesystemRepositoryMock;
  24. use Composer\Test\Mock\InstallationManagerMock;
  25. use Composer\Test\Mock\WritableRepositoryMock;
  26. use Symfony\Component\Console\Input\StringInput;
  27. use Symfony\Component\Console\Output\StreamOutput;
  28. class InstallerTest extends TestCase
  29. {
  30. /**
  31. * @dataProvider provideInstaller
  32. */
  33. public function testInstaller(PackageInterface $rootPackage, $repositories, array $options)
  34. {
  35. $io = $this->getMock('Composer\IO\IOInterface');
  36. $downloadManager = $this->getMock('Composer\Downloader\DownloadManager');
  37. $config = $this->getMock('Composer\Config');
  38. $repositoryManager = new RepositoryManager($io, $config);
  39. $repositoryManager->setLocalRepository(new WritableRepositoryMock());
  40. $repositoryManager->setLocalDevRepository(new WritableRepositoryMock());
  41. if (!is_array($repositories)) {
  42. $repositories = array($repositories);
  43. }
  44. foreach ($repositories as $repository) {
  45. $repositoryManager->addRepository($repository);
  46. }
  47. $locker = $this->getMockBuilder('Composer\Package\Locker')->disableOriginalConstructor()->getMock();
  48. $installationManager = new InstallationManagerMock();
  49. $eventDispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')->disableOriginalConstructor()->getMock();
  50. $autoloadGenerator = $this->getMock('Composer\Autoload\AutoloadGenerator');
  51. $installer = new Installer($io, clone $rootPackage, $downloadManager, $repositoryManager, $locker, $installationManager, $eventDispatcher, $autoloadGenerator);
  52. $result = $installer->run();
  53. $this->assertTrue($result);
  54. $expectedInstalled = isset($options['install']) ? $options['install'] : array();
  55. $expectedUpdated = isset($options['update']) ? $options['update'] : array();
  56. $expectedUninstalled = isset($options['uninstall']) ? $options['uninstall'] : array();
  57. $installed = $installationManager->getInstalledPackages();
  58. $this->assertSame($expectedInstalled, $installed);
  59. $updated = $installationManager->getUpdatedPackages();
  60. $this->assertSame($expectedUpdated, $updated);
  61. $uninstalled = $installationManager->getUninstalledPackages();
  62. $this->assertSame($expectedUninstalled, $uninstalled);
  63. }
  64. public function provideInstaller()
  65. {
  66. $cases = array();
  67. // when A requires B and B requires A, and A is a non-published root package
  68. // the install of B should succeed
  69. $a = $this->getPackage('A', '1.0.0');
  70. $a->setRequires(array(
  71. new Link('A', 'B', $this->getVersionConstraint('=', '1.0.0')),
  72. ));
  73. $b = $this->getPackage('B', '1.0.0');
  74. $b->setRequires(array(
  75. new Link('B', 'A', $this->getVersionConstraint('=', '1.0.0')),
  76. ));
  77. $cases[] = array(
  78. $a,
  79. new ArrayRepository(array($b)),
  80. array(
  81. 'install' => array($b)
  82. ),
  83. );
  84. // #480: when A requires B and B requires A, and A is a published root package
  85. // only B should be installed, as A is the root
  86. $a = $this->getPackage('A', '1.0.0');
  87. $a->setRequires(array(
  88. new Link('A', 'B', $this->getVersionConstraint('=', '1.0.0')),
  89. ));
  90. $b = $this->getPackage('B', '1.0.0');
  91. $b->setRequires(array(
  92. new Link('B', 'A', $this->getVersionConstraint('=', '1.0.0')),
  93. ));
  94. $cases[] = array(
  95. $a,
  96. new ArrayRepository(array($a, $b)),
  97. array(
  98. 'install' => array($b)
  99. ),
  100. );
  101. return $cases;
  102. }
  103. /**
  104. * @dataProvider getIntegrationTests
  105. */
  106. public function testIntegration($file, $message, $condition, $composer, $lock, $installed, $installedDev, $run, $expect)
  107. {
  108. if ($condition) {
  109. eval('$res = '.$condition.';');
  110. if (!$res) {
  111. $this->markTestSkipped($condition);
  112. }
  113. }
  114. $output = null;
  115. $io = $this->getMock('Composer\IO\IOInterface');
  116. $io->expects($this->any())
  117. ->method('write')
  118. ->will($this->returnCallback(function ($text, $newline) use (&$output) {
  119. $output .= $text . ($newline ? "\n":"");
  120. }));
  121. $composer = FactoryMock::create($io, $composer);
  122. $jsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock();
  123. $jsonMock->expects($this->any())
  124. ->method('read')
  125. ->will($this->returnValue($installed));
  126. $jsonMock->expects($this->any())
  127. ->method('exists')
  128. ->will($this->returnValue(true));
  129. $devJsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock();
  130. $devJsonMock->expects($this->any())
  131. ->method('read')
  132. ->will($this->returnValue($installedDev));
  133. $devJsonMock->expects($this->any())
  134. ->method('exists')
  135. ->will($this->returnValue(true));
  136. $repositoryManager = $composer->getRepositoryManager();
  137. $repositoryManager->setLocalRepository(new InstalledFilesystemRepositoryMock($jsonMock));
  138. $repositoryManager->setLocalDevRepository(new InstalledFilesystemRepositoryMock($devJsonMock));
  139. $lockJsonMock = $this->getMockBuilder('Composer\Json\JsonFile')->disableOriginalConstructor()->getMock();
  140. $lockJsonMock->expects($this->any())
  141. ->method('read')
  142. ->will($this->returnValue($lock));
  143. $locker = new Locker($lockJsonMock, $repositoryManager, isset($lock['hash']) ? $lock['hash'] : '');
  144. $composer->setLocker($locker);
  145. $autoloadGenerator = $this->getMock('Composer\Autoload\AutoloadGenerator');
  146. $installer = Installer::create(
  147. $io,
  148. $composer,
  149. null,
  150. $autoloadGenerator
  151. );
  152. $application = new Application;
  153. $application->get('install')->setCode(function ($input, $output) use ($installer) {
  154. $installer->setDevMode($input->getOption('dev'));
  155. return $installer->run();
  156. });
  157. $application->get('update')->setCode(function ($input, $output) use ($installer) {
  158. $installer
  159. ->setDevMode($input->getOption('dev'))
  160. ->setUpdate(true)
  161. ->setUpdateWhitelist($input->getArgument('packages'));
  162. return $installer->run();
  163. });
  164. if (!preg_match('{^(install|update)\b}', $run)) {
  165. throw new \UnexpectedValueException('The run command only supports install and update');
  166. }
  167. $application->setAutoExit(false);
  168. $appOutput = fopen('php://memory', 'w+');
  169. $result = $application->run(new StringInput($run), new StreamOutput($appOutput));
  170. fseek($appOutput, 0);
  171. $this->assertEquals(0, $result, $output . stream_get_contents($appOutput));
  172. $installationManager = $composer->getInstallationManager();
  173. $this->assertSame($expect, implode("\n", $installationManager->getTrace()));
  174. }
  175. public function getIntegrationTests()
  176. {
  177. $fixturesDir = realpath(__DIR__.'/Fixtures/installer/');
  178. $tests = array();
  179. foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
  180. if (!preg_match('/\.test$/', $file)) {
  181. continue;
  182. }
  183. $test = file_get_contents($file->getRealpath());
  184. $content = '(?:.(?!--[A-Z]))+';
  185. $pattern = '{^
  186. --TEST--\s*(?P<test>.*?)\s*
  187. (?:--CONDITION--\s*(?P<condition>'.$content.'))?\s*
  188. --COMPOSER--\s*(?P<composer>'.$content.')\s*
  189. (?:--LOCK--\s*(?P<lock>'.$content.'))?\s*
  190. (?:--INSTALLED--\s*(?P<installed>'.$content.'))?\s*
  191. (?:--INSTALLED:DEV--\s*(?P<installedDev>'.$content.'))?\s*
  192. --RUN--\s*(?P<run>.*?)\s*
  193. --EXPECT--\s*(?P<expect>.*?)\s*
  194. $}xs';
  195. $installed = array();
  196. $installedDev = array();
  197. $lock = array();
  198. if (preg_match($pattern, $test, $match)) {
  199. try {
  200. $message = $match['test'];
  201. $condition = !empty($match['condition']) ? $match['condition'] : null;
  202. $composer = JsonFile::parseJson($match['composer']);
  203. if (!empty($match['lock'])) {
  204. $lock = JsonFile::parseJson($match['lock']);
  205. }
  206. if (!empty($match['installed'])) {
  207. $installed = JsonFile::parseJson($match['installed']);
  208. }
  209. if (!empty($match['installedDev'])) {
  210. $installedDev = JsonFile::parseJson($match['installedDev']);
  211. }
  212. $run = $match['run'];
  213. $expect = $match['expect'];
  214. } catch (\Exception $e) {
  215. die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
  216. }
  217. } else {
  218. die(sprintf('Test "%s" is not valid, did not match the expected format.', str_replace($fixturesDir.'/', '', $file)));
  219. }
  220. $tests[] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $installedDev, $run, $expect);
  221. }
  222. return $tests;
  223. }
  224. }