EventDispatcherTest.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  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\EventDispatcher;
  12. use Composer\EventDispatcher\Event;
  13. use Composer\EventDispatcher\EventDispatcher;
  14. use Composer\EventDispatcher\ScriptExecutionException;
  15. use Composer\Installer\InstallerEvents;
  16. use Composer\Config;
  17. use Composer\Composer;
  18. use Composer\Test\TestCase;
  19. use Composer\IO\BufferIO;
  20. use Composer\Script\ScriptEvents;
  21. use Composer\Script\CommandEvent;
  22. use Composer\Util\ProcessExecutor;
  23. use Symfony\Component\Console\Output\OutputInterface;
  24. class EventDispatcherTest extends TestCase
  25. {
  26. /**
  27. * @expectedException RuntimeException
  28. */
  29. public function testListenerExceptionsAreCaught()
  30. {
  31. $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
  32. $dispatcher = $this->getDispatcherStubForListenersTest(array(
  33. 'Composer\Test\EventDispatcher\EventDispatcherTest::call',
  34. ), $io);
  35. $io->expects($this->at(0))
  36. ->method('isVerbose')
  37. ->willReturn(0);
  38. $io->expects($this->at(1))
  39. ->method('writeError')
  40. ->with('> Composer\Test\EventDispatcher\EventDispatcherTest::call');
  41. $io->expects($this->at(2))
  42. ->method('writeError')
  43. ->with('<error>Script Composer\Test\EventDispatcher\EventDispatcherTest::call handling the post-install-cmd event terminated with an exception</error>');
  44. $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
  45. }
  46. /**
  47. * @group legacy
  48. */
  49. public function testDispatcherCanConvertScriptEventToCommandEventForListener()
  50. {
  51. $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
  52. $dispatcher = $this->getDispatcherStubForListenersTest(array(
  53. 'Composer\Test\EventDispatcher\EventDispatcherTest::expectsCommandEvent',
  54. ), $io);
  55. $this->setExpectedException('PHPUnit\Framework\Error\Deprecated');
  56. $this->assertEquals(1, $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false));
  57. }
  58. public function testDispatcherDoesNotAttemptConversionForListenerWithoutTypehint()
  59. {
  60. $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
  61. $dispatcher = $this->getDispatcherStubForListenersTest(array(
  62. 'Composer\Test\EventDispatcher\EventDispatcherTest::expectsVariableEvent',
  63. ), $io);
  64. $this->assertEquals(1, $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false));
  65. }
  66. /**
  67. * @dataProvider getValidCommands
  68. * @param string $command
  69. */
  70. public function testDispatcherCanExecuteSingleCommandLineScript($command)
  71. {
  72. $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
  73. $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
  74. ->setConstructorArgs(array(
  75. $this->createComposerInstance(),
  76. $this->getMockBuilder('Composer\IO\IOInterface')->getMock(),
  77. $process,
  78. ))
  79. ->setMethods(array('getListeners'))
  80. ->getMock();
  81. $listener = array($command);
  82. $dispatcher->expects($this->atLeastOnce())
  83. ->method('getListeners')
  84. ->will($this->returnValue($listener));
  85. $process->expects($this->once())
  86. ->method('execute')
  87. ->with($command)
  88. ->will($this->returnValue(0));
  89. $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
  90. }
  91. /**
  92. * @dataProvider getDevModes
  93. * @param bool $devMode
  94. */
  95. public function testDispatcherPassDevModeToAutoloadGeneratorForScriptEvents($devMode)
  96. {
  97. $composer = $this->createComposerInstance();
  98. $generator = $this->getGeneratorMockForDevModePassingTest();
  99. $generator->expects($this->atLeastOnce())
  100. ->method('setDevMode')
  101. ->with($devMode);
  102. $composer->setAutoloadGenerator($generator);
  103. $package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock();
  104. $package->method('getScripts')->will($this->returnValue(array('scriptName' => array('scriptName'))));
  105. $composer->setPackage($package);
  106. $composer->setRepositoryManager($this->getRepositoryManagerMockForDevModePassingTest());
  107. $composer->setInstallationManager($this->getMockBuilder('Composer\Installer\InstallationManager')->getMock());
  108. $dispatcher = new EventDispatcher(
  109. $composer,
  110. $this->getMockBuilder('Composer\IO\IOInterface')->getMock(),
  111. $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock()
  112. );
  113. $event = $this->getMockBuilder('Composer\Script\Event')
  114. ->disableOriginalConstructor()
  115. ->getMock();
  116. $event->method('getName')->will($this->returnValue('scriptName'));
  117. $event->expects($this->atLeastOnce())
  118. ->method('isDevMode')
  119. ->will($this->returnValue($devMode));
  120. $dispatcher->hasEventListeners($event);
  121. }
  122. public function getDevModes()
  123. {
  124. return array(
  125. array(true),
  126. array(false),
  127. );
  128. }
  129. private function getGeneratorMockForDevModePassingTest()
  130. {
  131. $generator = $this->getMockBuilder('Composer\Autoload\AutoloadGenerator')
  132. ->disableOriginalConstructor()
  133. ->setMethods(array(
  134. 'buildPackageMap',
  135. 'parseAutoloads',
  136. 'createLoader',
  137. 'setDevMode',
  138. ))
  139. ->getMock();
  140. $generator
  141. ->method('buildPackageMap')
  142. ->will($this->returnValue(array()));
  143. $generator
  144. ->method('parseAutoloads')
  145. ->will($this->returnValue(array()));
  146. $generator
  147. ->method('createLoader')
  148. ->will($this->returnValue($this->getMockBuilder('Composer\Autoload\ClassLoader')->getMock()));
  149. return $generator;
  150. }
  151. private function getRepositoryManagerMockForDevModePassingTest()
  152. {
  153. $rm = $this->getMockBuilder('Composer\Repository\RepositoryManager')
  154. ->disableOriginalConstructor()
  155. ->setMethods(array('getLocalRepository'))
  156. ->getMock();
  157. $repo = $this->getMockBuilder('Composer\Repository\InstalledRepositoryInterface')->getMock();
  158. $repo
  159. ->method('getCanonicalPackages')
  160. ->will($this->returnValue(array()));
  161. $rm
  162. ->method('getLocalRepository')
  163. ->will($this->returnValue($repo));
  164. return $rm;
  165. }
  166. public function testDispatcherCanExecuteCliAndPhpInSameEventScriptStack()
  167. {
  168. $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
  169. $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
  170. ->setConstructorArgs(array(
  171. $this->createComposerInstance(),
  172. $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE),
  173. $process,
  174. ))
  175. ->setMethods(array(
  176. 'getListeners',
  177. ))
  178. ->getMock();
  179. $process->expects($this->exactly(2))
  180. ->method('execute')
  181. ->will($this->returnValue(0));
  182. $listeners = array(
  183. 'echo -n foo',
  184. 'Composer\\Test\\EventDispatcher\\EventDispatcherTest::someMethod',
  185. 'echo -n bar',
  186. );
  187. $dispatcher->expects($this->atLeastOnce())
  188. ->method('getListeners')
  189. ->will($this->returnValue($listeners));
  190. $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
  191. $expected = '> post-install-cmd: echo -n foo'.PHP_EOL.
  192. '> post-install-cmd: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL.
  193. '> post-install-cmd: echo -n bar'.PHP_EOL;
  194. $this->assertEquals($expected, $io->getOutput());
  195. }
  196. public function testDispatcherCanPutEnv()
  197. {
  198. $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
  199. $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
  200. ->setConstructorArgs(array(
  201. $this->createComposerInstance(),
  202. $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE),
  203. $process,
  204. ))
  205. ->setMethods(array(
  206. 'getListeners',
  207. ))
  208. ->getMock();
  209. $listeners = array(
  210. '@putenv ABC=123',
  211. 'Composer\\Test\\EventDispatcher\\EventDispatcherTest::getTestEnv',
  212. );
  213. $dispatcher->expects($this->atLeastOnce())
  214. ->method('getListeners')
  215. ->will($this->returnValue($listeners));
  216. $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
  217. $expected = '> post-install-cmd: @putenv ABC=123'.PHP_EOL.
  218. '> post-install-cmd: Composer\Test\EventDispatcher\EventDispatcherTest::getTestEnv'.PHP_EOL;
  219. $this->assertEquals($expected, $io->getOutput());
  220. }
  221. public function testDispatcherAppendsDirBinOnPathForEveryListener()
  222. {
  223. $currentDirectoryBkp = getcwd();
  224. $composerBinDirBkp = getenv('COMPOSER_BIN_DIR');
  225. chdir(__DIR__);
  226. putenv('COMPOSER_BIN_DIR=' . __DIR__ . sprintf('%svendor%sbin', DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR));
  227. $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
  228. $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->setConstructorArgs(array(
  229. $this->createComposerInstance(),
  230. $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE),
  231. $process,
  232. ))->setMethods(array(
  233. 'getListeners',
  234. ))->getMock();
  235. $listeners = array(
  236. 'Composer\\Test\\EventDispatcher\\EventDispatcherTest::createsVendorBinFolderChecksEnvDoesNotContainsBin',
  237. 'Composer\\Test\\EventDispatcher\\EventDispatcherTest::createsVendorBinFolderChecksEnvContainsBin',
  238. );
  239. $dispatcher->expects($this->atLeastOnce())->method('getListeners')->will($this->returnValue($listeners));
  240. $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
  241. rmdir(__DIR__ . sprintf('%svendor%sbin', DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR));
  242. rmdir(__DIR__ . sprintf('%svendor', DIRECTORY_SEPARATOR));
  243. chdir($currentDirectoryBkp);
  244. putenv('COMPOSER_BIN_DIR' . ($composerBinDirBkp === false ? '' : '=' . $composerBinDirBkp));
  245. }
  246. static public function createsVendorBinFolderChecksEnvDoesNotContainsBin()
  247. {
  248. mkdir(__DIR__ . sprintf('%svendor%sbin', DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR), 0700, true);
  249. $val = getenv('PATH');
  250. if (!$val) {
  251. $val = getenv('Path');
  252. }
  253. self::assertFalse(strpos($val, __DIR__ . sprintf('%svendor%sbin', DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR)));
  254. }
  255. static public function createsVendorBinFolderChecksEnvContainsBin()
  256. {
  257. $val = getenv('PATH');
  258. if (!$val) {
  259. $val = getenv('Path');
  260. }
  261. self::assertNotFalse(strpos($val, __DIR__ . sprintf('%svendor%sbin', DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR)));
  262. }
  263. static public function getTestEnv() {
  264. $val = getenv('ABC');
  265. if ($val !== '123') {
  266. throw new \Exception('getenv() did not return the expected value. expected 123 got '. var_export($val, true));
  267. }
  268. }
  269. public function testDispatcherCanExecuteComposerScriptGroups()
  270. {
  271. $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
  272. $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
  273. ->setConstructorArgs(array(
  274. $composer = $this->createComposerInstance(),
  275. $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE),
  276. $process,
  277. ))
  278. ->setMethods(array(
  279. 'getListeners',
  280. ))
  281. ->getMock();
  282. $process->expects($this->exactly(3))
  283. ->method('execute')
  284. ->will($this->returnValue(0));
  285. $dispatcher->expects($this->atLeastOnce())
  286. ->method('getListeners')
  287. ->will($this->returnCallback(function (Event $event) {
  288. if ($event->getName() === 'root') {
  289. return array('@group');
  290. }
  291. if ($event->getName() === 'group') {
  292. return array('echo -n foo', '@subgroup', 'echo -n bar');
  293. }
  294. if ($event->getName() === 'subgroup') {
  295. return array('echo -n baz');
  296. }
  297. return array();
  298. }));
  299. $dispatcher->dispatch('root', new CommandEvent('root', $composer, $io));
  300. $expected = '> root: @group'.PHP_EOL.
  301. '> group: echo -n foo'.PHP_EOL.
  302. '> group: @subgroup'.PHP_EOL.
  303. '> subgroup: echo -n baz'.PHP_EOL.
  304. '> group: echo -n bar'.PHP_EOL;
  305. $this->assertEquals($expected, $io->getOutput());
  306. }
  307. public function testRecursionInScriptsNames()
  308. {
  309. $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
  310. $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
  311. ->setConstructorArgs(array(
  312. $composer = $this->createComposerInstance(),
  313. $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE),
  314. $process
  315. ))
  316. ->setMethods(array(
  317. 'getListeners'
  318. ))
  319. ->getMock();
  320. $process->expects($this->exactly(1))
  321. ->method('execute')
  322. ->will($this->returnValue(0));
  323. $dispatcher->expects($this->atLeastOnce())
  324. ->method('getListeners')
  325. ->will($this->returnCallback(function (Event $event) {
  326. if($event->getName() === 'hello') {
  327. return array('echo Hello');
  328. }
  329. if($event->getName() === 'helloWorld') {
  330. return array('@hello World');
  331. }
  332. return array();
  333. }));
  334. $dispatcher->dispatch('helloWorld', new CommandEvent('helloWorld', $composer, $io));
  335. $expected = "> helloWorld: @hello World".PHP_EOL.
  336. "> hello: echo Hello " .escapeshellarg('World').PHP_EOL;
  337. $this->assertEquals($expected, $io->getOutput());
  338. }
  339. /**
  340. * @expectedException RuntimeException
  341. */
  342. public function testDispatcherDetectInfiniteRecursion()
  343. {
  344. $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
  345. $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
  346. ->setConstructorArgs(array(
  347. $composer = $this->createComposerInstance(),
  348. $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(),
  349. $process,
  350. ))
  351. ->setMethods(array(
  352. 'getListeners',
  353. ))
  354. ->getMock();
  355. $dispatcher->expects($this->atLeastOnce())
  356. ->method('getListeners')
  357. ->will($this->returnCallback(function (Event $event) {
  358. if ($event->getName() === 'root') {
  359. return array('@recurse');
  360. }
  361. if ($event->getName() === 'recurse') {
  362. return array('@root');
  363. }
  364. return array();
  365. }));
  366. $dispatcher->dispatch('root', new CommandEvent('root', $composer, $io));
  367. }
  368. private function getDispatcherStubForListenersTest($listeners, $io)
  369. {
  370. $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
  371. ->setConstructorArgs(array(
  372. $this->createComposerInstance(),
  373. $io,
  374. ))
  375. ->setMethods(array('getListeners'))
  376. ->getMock();
  377. $dispatcher->expects($this->atLeastOnce())
  378. ->method('getListeners')
  379. ->will($this->returnValue($listeners));
  380. return $dispatcher;
  381. }
  382. public function getValidCommands()
  383. {
  384. return array(
  385. array('phpunit'),
  386. array('echo foo'),
  387. array('echo -n foo'),
  388. );
  389. }
  390. public function testDispatcherOutputsCommand()
  391. {
  392. $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
  393. ->setConstructorArgs(array(
  394. $this->createComposerInstance(),
  395. $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(),
  396. new ProcessExecutor($io),
  397. ))
  398. ->setMethods(array('getListeners'))
  399. ->getMock();
  400. $listener = array('echo foo');
  401. $dispatcher->expects($this->atLeastOnce())
  402. ->method('getListeners')
  403. ->will($this->returnValue($listener));
  404. $io->expects($this->once())
  405. ->method('writeError')
  406. ->with($this->equalTo('> echo foo'));
  407. $io->expects($this->once())
  408. ->method('write')
  409. ->with($this->equalTo('foo'.PHP_EOL), false);
  410. $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
  411. }
  412. public function testDispatcherOutputsErrorOnFailedCommand()
  413. {
  414. $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
  415. ->setConstructorArgs(array(
  416. $this->createComposerInstance(),
  417. $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(),
  418. new ProcessExecutor,
  419. ))
  420. ->setMethods(array('getListeners'))
  421. ->getMock();
  422. $code = 'exit 1';
  423. $listener = array($code);
  424. $dispatcher->expects($this->atLeastOnce())
  425. ->method('getListeners')
  426. ->will($this->returnValue($listener));
  427. $io->expects($this->at(0))
  428. ->method('isVerbose')
  429. ->willReturn(0);
  430. $io->expects($this->at(1))
  431. ->method('writeError')
  432. ->willReturn('> exit 1');
  433. $io->expects($this->at(2))
  434. ->method('writeError')
  435. ->with($this->equalTo('<error>Script '.$code.' handling the post-install-cmd event returned with error code 1</error>'));
  436. $this->setExpectedException('RuntimeException');
  437. $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false);
  438. }
  439. public function testDispatcherInstallerEvents()
  440. {
  441. $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
  442. $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')
  443. ->setConstructorArgs(array(
  444. $this->createComposerInstance(),
  445. $this->getMockBuilder('Composer\IO\IOInterface')->getMock(),
  446. $process,
  447. ))
  448. ->setMethods(array('getListeners'))
  449. ->getMock();
  450. $dispatcher->expects($this->atLeastOnce())
  451. ->method('getListeners')
  452. ->will($this->returnValue(array()));
  453. $policy = $this->getMockBuilder('Composer\DependencyResolver\PolicyInterface')->getMock();
  454. $pool = $this->getMockBuilder('Composer\DependencyResolver\Pool')->disableOriginalConstructor()->getMock();
  455. $installedRepo = $this->getMockBuilder('Composer\Repository\CompositeRepository')->disableOriginalConstructor()->getMock();
  456. $request = $this->getMockBuilder('Composer\DependencyResolver\Request')->disableOriginalConstructor()->getMock();
  457. $dispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, true, $policy, $pool, $installedRepo, $request);
  458. $dispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, true, $policy, $pool, $installedRepo, $request, array());
  459. }
  460. public static function call()
  461. {
  462. throw new \RuntimeException();
  463. }
  464. public static function expectsCommandEvent(CommandEvent $event)
  465. {
  466. return false;
  467. }
  468. public static function expectsVariableEvent($event)
  469. {
  470. return false;
  471. }
  472. public static function someMethod()
  473. {
  474. return true;
  475. }
  476. private function createComposerInstance()
  477. {
  478. $composer = new Composer;
  479. $config = new Config;
  480. $composer->setConfig($config);
  481. $package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock();
  482. $composer->setPackage($package);
  483. return $composer;
  484. }
  485. }