EventDispatcherTest.php 21 KB

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