PluginInstallerTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  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\Plugin;
  12. use Composer\Composer;
  13. use Composer\Config;
  14. use Composer\Installer\PluginInstaller;
  15. use Composer\Package\CompletePackage;
  16. use Composer\Package\Loader\JsonLoader;
  17. use Composer\Package\Loader\ArrayLoader;
  18. use Composer\Plugin\PluginManager;
  19. use Symfony\Component\Console\Output\OutputInterface;
  20. use Composer\IO\BufferIO;
  21. use Composer\EventDispatcher\EventDispatcher;
  22. use Composer\Autoload\AutoloadGenerator;
  23. use Composer\Test\TestCase;
  24. use Composer\Util\Filesystem;
  25. class PluginInstallerTest extends TestCase
  26. {
  27. /**
  28. * @var Composer
  29. */
  30. protected $composer;
  31. /**
  32. * @var PluginManager
  33. */
  34. protected $pm;
  35. /**
  36. * @var AutoloadGenerator
  37. */
  38. protected $autoloadGenerator;
  39. /**
  40. * @var CompletePackage[]
  41. */
  42. protected $packages;
  43. /**
  44. * @var string
  45. */
  46. protected $directory;
  47. /**
  48. * @var \PHPUnit_Framework_MockObject_MockObject
  49. */
  50. protected $im;
  51. /**
  52. * @var \PHPUnit_Framework_MockObject_MockObject
  53. */
  54. protected $repository;
  55. /**
  56. * @var \PHPUnit_Framework_MockObject_MockObject
  57. */
  58. protected $io;
  59. protected function setUp()
  60. {
  61. $loader = new JsonLoader(new ArrayLoader());
  62. $this->packages = array();
  63. $this->directory = $this->getUniqueTmpDirectory();
  64. for ($i = 1; $i <= 8; $i++) {
  65. $filename = '/Fixtures/plugin-v'.$i.'/composer.json';
  66. mkdir(dirname($this->directory . $filename), 0777, true);
  67. $this->packages[] = $loader->load(__DIR__ . $filename);
  68. }
  69. $dm = $this->getMockBuilder('Composer\Downloader\DownloadManager')
  70. ->disableOriginalConstructor()
  71. ->getMock();
  72. $this->repository = $this->getMockBuilder('Composer\Repository\InstalledRepositoryInterface')->getMock();
  73. $rm = $this->getMockBuilder('Composer\Repository\RepositoryManager')
  74. ->disableOriginalConstructor()
  75. ->getMock();
  76. $rm->expects($this->any())
  77. ->method('getLocalRepository')
  78. ->will($this->returnValue($this->repository));
  79. $im = $this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock();
  80. $im->expects($this->any())
  81. ->method('getInstallPath')
  82. ->will($this->returnCallback(function ($package) {
  83. return __DIR__.'/Fixtures/'.$package->getPrettyName();
  84. }));
  85. $this->io = new BufferIO();
  86. $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock();
  87. $this->autoloadGenerator = new AutoloadGenerator($dispatcher);
  88. $this->composer = new Composer();
  89. $config = new Config(false);
  90. $this->composer->setConfig($config);
  91. $this->composer->setDownloadManager($dm);
  92. $this->composer->setRepositoryManager($rm);
  93. $this->composer->setInstallationManager($im);
  94. $this->composer->setAutoloadGenerator($this->autoloadGenerator);
  95. $this->composer->setEventDispatcher(new EventDispatcher($this->composer, $this->io));
  96. $this->pm = new PluginManager($this->io, $this->composer);
  97. $this->composer->setPluginManager($this->pm);
  98. $config->merge(array(
  99. 'config' => array(
  100. 'vendor-dir' => $this->directory.'/Fixtures/',
  101. 'home' => $this->directory.'/Fixtures',
  102. 'bin-dir' => $this->directory.'/Fixtures/bin',
  103. ),
  104. ));
  105. }
  106. protected function tearDown()
  107. {
  108. $filesystem = new Filesystem();
  109. $filesystem->removeDirectory($this->directory);
  110. }
  111. public function testInstallNewPlugin()
  112. {
  113. $this->repository
  114. ->expects($this->any())
  115. ->method('getPackages')
  116. ->will($this->returnValue(array()));
  117. $installer = new PluginInstaller($this->io, $this->composer);
  118. $this->pm->loadInstalledPlugins();
  119. $installer->install($this->repository, $this->packages[0]);
  120. $plugins = $this->pm->getPlugins();
  121. $this->assertEquals('installer-v1', $plugins[0]->version);
  122. $this->assertEquals('activate v1'.PHP_EOL, $this->io->getOutput());
  123. }
  124. public function testInstallMultiplePlugins()
  125. {
  126. $this->repository
  127. ->expects($this->any())
  128. ->method('getPackages')
  129. ->will($this->returnValue(array($this->packages[3])));
  130. $installer = new PluginInstaller($this->io, $this->composer);
  131. $this->pm->loadInstalledPlugins();
  132. $installer->install($this->repository, $this->packages[3]);
  133. $plugins = $this->pm->getPlugins();
  134. $this->assertEquals('plugin1', $plugins[0]->name);
  135. $this->assertEquals('installer-v4', $plugins[0]->version);
  136. $this->assertEquals('plugin2', $plugins[1]->name);
  137. $this->assertEquals('installer-v4', $plugins[1]->version);
  138. $this->assertEquals('activate v4-plugin1'.PHP_EOL.'activate v4-plugin2'.PHP_EOL, $this->io->getOutput());
  139. }
  140. public function testUpgradeWithNewClassName()
  141. {
  142. $this->repository
  143. ->expects($this->any())
  144. ->method('getPackages')
  145. ->will($this->returnValue(array($this->packages[0])));
  146. $this->repository
  147. ->expects($this->exactly(2))
  148. ->method('hasPackage')
  149. ->will($this->onConsecutiveCalls(true, false));
  150. $installer = new PluginInstaller($this->io, $this->composer);
  151. $this->pm->loadInstalledPlugins();
  152. $installer->update($this->repository, $this->packages[0], $this->packages[1]);
  153. $plugins = $this->pm->getPlugins();
  154. $this->assertCount(1, $plugins);
  155. $this->assertEquals('installer-v2', $plugins[1]->version);
  156. $this->assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'activate v2'.PHP_EOL, $this->io->getOutput());
  157. }
  158. public function testUninstall()
  159. {
  160. $this->repository
  161. ->expects($this->any())
  162. ->method('getPackages')
  163. ->will($this->returnValue(array($this->packages[0])));
  164. $this->repository
  165. ->expects($this->exactly(1))
  166. ->method('hasPackage')
  167. ->will($this->onConsecutiveCalls(true, false));
  168. $installer = new PluginInstaller($this->io, $this->composer);
  169. $this->pm->loadInstalledPlugins();
  170. $installer->uninstall($this->repository, $this->packages[0]);
  171. $plugins = $this->pm->getPlugins();
  172. $this->assertCount(0, $plugins);
  173. $this->assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'uninstall v1'.PHP_EOL, $this->io->getOutput());
  174. }
  175. public function testUpgradeWithSameClassName()
  176. {
  177. $this->repository
  178. ->expects($this->any())
  179. ->method('getPackages')
  180. ->will($this->returnValue(array($this->packages[1])));
  181. $this->repository
  182. ->expects($this->exactly(2))
  183. ->method('hasPackage')
  184. ->will($this->onConsecutiveCalls(true, false));
  185. $installer = new PluginInstaller($this->io, $this->composer);
  186. $this->pm->loadInstalledPlugins();
  187. $installer->update($this->repository, $this->packages[1], $this->packages[2]);
  188. $plugins = $this->pm->getPlugins();
  189. $this->assertEquals('installer-v3', $plugins[1]->version);
  190. $this->assertEquals('activate v2'.PHP_EOL.'deactivate v2'.PHP_EOL.'activate v3'.PHP_EOL, $this->io->getOutput());
  191. }
  192. public function testRegisterPluginOnlyOneTime()
  193. {
  194. $this->repository
  195. ->expects($this->any())
  196. ->method('getPackages')
  197. ->will($this->returnValue(array()));
  198. $installer = new PluginInstaller($this->io, $this->composer);
  199. $this->pm->loadInstalledPlugins();
  200. $installer->install($this->repository, $this->packages[0]);
  201. $installer->install($this->repository, clone $this->packages[0]);
  202. $plugins = $this->pm->getPlugins();
  203. $this->assertCount(1, $plugins);
  204. $this->assertEquals('installer-v1', $plugins[0]->version);
  205. $this->assertEquals('activate v1'.PHP_EOL, $this->io->getOutput());
  206. }
  207. /**
  208. * @param string $newPluginApiVersion
  209. * @param CompletePackage[] $plugins
  210. */
  211. private function setPluginApiVersionWithPlugins($newPluginApiVersion, array $plugins = array())
  212. {
  213. // reset the plugin manager's installed plugins
  214. $this->pm = $this->getMockBuilder('Composer\Plugin\PluginManager')
  215. ->setMethods(array('getPluginApiVersion'))
  216. ->setConstructorArgs(array($this->io, $this->composer))
  217. ->getMock();
  218. // mock the Plugin API version
  219. $this->pm->expects($this->any())
  220. ->method('getPluginApiVersion')
  221. ->will($this->returnValue($newPluginApiVersion));
  222. $plugApiInternalPackage = $this->getPackage(
  223. 'composer-plugin-api',
  224. $newPluginApiVersion,
  225. 'Composer\Package\CompletePackage'
  226. );
  227. // Add the plugins to the repo along with the internal Plugin package on which they all rely.
  228. $this->repository
  229. ->expects($this->any())
  230. ->method('getPackages')
  231. ->will($this->returnCallback(function () use ($plugApiInternalPackage, $plugins) {
  232. return array_merge(array($plugApiInternalPackage), $plugins);
  233. }));
  234. $this->pm->loadInstalledPlugins();
  235. }
  236. public function testStarPluginVersionWorksWithAnyAPIVersion()
  237. {
  238. $starVersionPlugin = array($this->packages[4]);
  239. $this->setPluginApiVersionWithPlugins('1.0.0', $starVersionPlugin);
  240. $this->assertCount(1, $this->pm->getPlugins());
  241. $this->setPluginApiVersionWithPlugins('1.9.9', $starVersionPlugin);
  242. $this->assertCount(1, $this->pm->getPlugins());
  243. $this->setPluginApiVersionWithPlugins('2.0.0-dev', $starVersionPlugin);
  244. $this->assertCount(1, $this->pm->getPlugins());
  245. $this->setPluginApiVersionWithPlugins('100.0.0-stable', $starVersionPlugin);
  246. $this->assertCount(1, $this->pm->getPlugins());
  247. }
  248. public function testPluginConstraintWorksOnlyWithCertainAPIVersion()
  249. {
  250. $pluginWithApiConstraint = array($this->packages[5]);
  251. $this->setPluginApiVersionWithPlugins('1.0.0', $pluginWithApiConstraint);
  252. $this->assertCount(0, $this->pm->getPlugins());
  253. $this->setPluginApiVersionWithPlugins('1.1.9', $pluginWithApiConstraint);
  254. $this->assertCount(0, $this->pm->getPlugins());
  255. $this->setPluginApiVersionWithPlugins('1.2.0', $pluginWithApiConstraint);
  256. $this->assertCount(1, $this->pm->getPlugins());
  257. $this->setPluginApiVersionWithPlugins('1.9.9', $pluginWithApiConstraint);
  258. $this->assertCount(1, $this->pm->getPlugins());
  259. }
  260. public function testPluginRangeConstraintsWorkOnlyWithCertainAPIVersion()
  261. {
  262. $pluginWithApiConstraint = array($this->packages[6]);
  263. $this->setPluginApiVersionWithPlugins('1.0.0', $pluginWithApiConstraint);
  264. $this->assertCount(0, $this->pm->getPlugins());
  265. $this->setPluginApiVersionWithPlugins('3.0.0', $pluginWithApiConstraint);
  266. $this->assertCount(1, $this->pm->getPlugins());
  267. $this->setPluginApiVersionWithPlugins('5.5.0', $pluginWithApiConstraint);
  268. $this->assertCount(0, $this->pm->getPlugins());
  269. }
  270. public function testCommandProviderCapability()
  271. {
  272. $this->repository
  273. ->expects($this->any())
  274. ->method('getPackages')
  275. ->will($this->returnValue(array($this->packages[7])));
  276. $installer = new PluginInstaller($this->io, $this->composer);
  277. $this->pm->loadInstalledPlugins();
  278. $caps = $this->pm->getPluginCapabilities('Composer\Plugin\Capability\CommandProvider', array('composer' => $this->composer, 'io' => $this->io));
  279. $this->assertCount(1, $caps);
  280. $this->assertInstanceOf('Composer\Plugin\Capability\CommandProvider', $caps[0]);
  281. $commands = $caps[0]->getCommands();
  282. $this->assertCount(1, $commands);
  283. $this->assertInstanceOf('Composer\Command\BaseCommand', $commands[0]);
  284. }
  285. public function testIncapablePluginIsCorrectlyDetected()
  286. {
  287. $plugin = $this->getMockBuilder('Composer\Plugin\PluginInterface')
  288. ->getMock();
  289. $this->assertNull($this->pm->getPluginCapability($plugin, 'Fake\Ability'));
  290. }
  291. public function testCapabilityImplementsComposerPluginApiClassAndIsConstructedWithArgs()
  292. {
  293. $capabilityApi = 'Composer\Plugin\Capability\Capability';
  294. $capabilityImplementation = 'Composer\Test\Plugin\Mock\Capability';
  295. $plugin = $this->getMockBuilder('Composer\Test\Plugin\Mock\CapablePluginInterface')
  296. ->getMock();
  297. $plugin->expects($this->once())
  298. ->method('getCapabilities')
  299. ->will($this->returnCallback(function () use ($capabilityImplementation, $capabilityApi) {
  300. return array($capabilityApi => $capabilityImplementation);
  301. }));
  302. $capability = $this->pm->getPluginCapability($plugin, $capabilityApi, array('a' => 1, 'b' => 2));
  303. $this->assertInstanceOf($capabilityApi, $capability);
  304. $this->assertInstanceOf($capabilityImplementation, $capability);
  305. $this->assertSame(array('a' => 1, 'b' => 2, 'plugin' => $plugin), $capability->args);
  306. }
  307. public function invalidImplementationClassNames()
  308. {
  309. return array(
  310. array(null),
  311. array(""),
  312. array(0),
  313. array(1000),
  314. array(" "),
  315. array(array(1)),
  316. array(array()),
  317. array(new \stdClass()),
  318. );
  319. }
  320. public function nonExistingOrInvalidImplementationClassTypes()
  321. {
  322. return array(
  323. array('\stdClass'),
  324. array('NonExistentClassLikeMiddleClass'),
  325. );
  326. }
  327. /**
  328. * @dataProvider invalidImplementationClassNames
  329. * @expectedException \UnexpectedValueException
  330. */
  331. public function testQueryingWithInvalidCapabilityClassNameThrows($invalidImplementationClassNames)
  332. {
  333. $capabilityApi = 'Composer\Plugin\Capability\Capability';
  334. $plugin = $this->getMockBuilder('Composer\Test\Plugin\Mock\CapablePluginInterface')
  335. ->getMock();
  336. $plugin->expects($this->once())
  337. ->method('getCapabilities')
  338. ->will($this->returnCallback(function () use ($invalidImplementationClassNames, $capabilityApi) {
  339. return array($capabilityApi => $invalidImplementationClassNames);
  340. }));
  341. $this->pm->getPluginCapability($plugin, $capabilityApi);
  342. }
  343. public function testQueryingNonProvidedCapabilityReturnsNullSafely()
  344. {
  345. $capabilityApi = 'Composer\Plugin\Capability\MadeUpCapability';
  346. $plugin = $this->getMockBuilder('Composer\Test\Plugin\Mock\CapablePluginInterface')
  347. ->getMock();
  348. $plugin->expects($this->once())
  349. ->method('getCapabilities')
  350. ->will($this->returnCallback(function () {
  351. return array();
  352. }));
  353. $this->assertNull($this->pm->getPluginCapability($plugin, $capabilityApi));
  354. }
  355. /**
  356. * @dataProvider nonExistingOrInvalidImplementationClassTypes
  357. * @expectedException \RuntimeException
  358. */
  359. public function testQueryingWithNonExistingOrWrongCapabilityClassTypesThrows($wrongImplementationClassTypes)
  360. {
  361. $this->testQueryingWithInvalidCapabilityClassNameThrows($wrongImplementationClassTypes);
  362. }
  363. }