PageRenderTime 39ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Everzet/Behat/Output/Formatter/PrettyFormatter.php

https://github.com/justinrainbow/Behat
PHP | 498 lines | 305 code | 68 blank | 125 comment | 32 complexity | 26a81a48bda05cf3e43303831cc1d595 MD5 | raw file
  1. <?php
  2. namespace Everzet\Behat\Output\Formatter;
  3. use Symfony\Component\DependencyInjection\Container;
  4. use Symfony\Component\EventDispatcher\EventDispatcher;
  5. use Symfony\Component\EventDispatcher\Event;
  6. use Everzet\Gherkin\Node\FeatureNode;
  7. use Everzet\Gherkin\Node\StepNode;
  8. use Everzet\Gherkin\Node\BackgroundNode;
  9. use Everzet\Gherkin\Node\SectionNode;
  10. use Everzet\Gherkin\Node\ScenarioNode;
  11. use Everzet\Gherkin\Node\OutlineNode;
  12. use Everzet\Gherkin\Node\PyStringNode;
  13. use Everzet\Gherkin\Node\TableNode;
  14. use Everzet\Gherkin\Node\ExamplesNode;
  15. use Everzet\Behat\Exception\Pending;
  16. use Everzet\Behat\Tester\StepTester;
  17. /*
  18. * This file is part of the Behat.
  19. * (c) 2010 Konstantin Kudryashov <ever.zet@gmail.com>
  20. *
  21. * For the full copyright and license information, please view the LICENSE
  22. * file that was distributed with this source code.
  23. */
  24. /**
  25. * Pretty Console Formatter.
  26. *
  27. * @author Konstantin Kudryashov <ever.zet@gmail.com>
  28. */
  29. class PrettyFormatter extends ConsoleFormatter implements FormatterInterface, ContainerAwareFormatterInterface
  30. {
  31. protected $container;
  32. protected $dispatcher;
  33. protected $backgroundPrinted = false;
  34. protected $outlineStepsPrinted = false;
  35. protected $maxDescriptionLength = 0;
  36. protected $outlineSubresultExceptions = array();
  37. /**
  38. * @see FormatterInterface
  39. */
  40. public function registerListeners(EventDispatcher $dispatcher)
  41. {
  42. $this->dispatcher = $dispatcher;
  43. $dispatcher->connect('feature.run.before', array($this, 'printFeatureHeader'), 10);
  44. $dispatcher->connect('outline.run.before', array($this, 'printOutlineHeader'), 10);
  45. $dispatcher->connect('outline.sub.run.after', array($this, 'printOutlineSubResult'), 10);
  46. $dispatcher->connect('outline.run.after', array($this, 'printOutlineFooter'), 10);
  47. $dispatcher->connect('scenario.run.before', array($this, 'printScenarioHeader'), 10);
  48. $dispatcher->connect('scenario.run.after', array($this, 'printScenarioFooter'), 10);
  49. $dispatcher->connect('background.run.before', array($this, 'printBackgroundHeader'), 10);
  50. $dispatcher->connect('background.run.after', array($this, 'printBackgroundFooter'), 10);
  51. $dispatcher->connect('step.run.after', array($this, 'printStep'), 10);
  52. $dispatcher->connect('suite.run.after', array($this, 'printStatistics'), 10);
  53. $dispatcher->connect('suite.run.after', array($this, 'printSnippets'), 10);
  54. }
  55. /**
  56. * @see ContainerAwareFormatterInterface
  57. */
  58. public function setContainer(Container $container)
  59. {
  60. $this->container = $container;
  61. }
  62. /**
  63. * @see ConsoleFormatter
  64. */
  65. protected function getDispatcher()
  66. {
  67. return $this->dispatcher;
  68. }
  69. /**
  70. * Listen to `feature.run.before` event & print feature header.
  71. *
  72. * @param Event $event notified event
  73. */
  74. public function printFeatureHeader(Event $event)
  75. {
  76. $feature = $event->getSubject();
  77. // Flush variables
  78. $this->backgroundPrinted = false;
  79. $this->outlineStepsPrinted = false;
  80. // Print tags if had ones
  81. if ($feature->hasTags()) {
  82. $this->write($this->getTagsString($feature), 'tag');
  83. }
  84. // Print feature title
  85. $this->write(
  86. $this->getTranslator()->trans('Feature', array(), 'messages', $feature->getLocale())
  87. . ': ' . $feature->getTitle()
  88. );
  89. // Print feature description
  90. foreach ($feature->getDescription() as $description) {
  91. $this->write(' ' . $description);
  92. }
  93. $this->write();
  94. // Run fake background to test if it runs without errors & print it output
  95. if ($feature->hasBackground()) {
  96. $this->container->get('behat.statistics_collector')->pause();
  97. $environment = $this->container->get('behat.environment_builder')->buildEnvironment();
  98. // Fire `scenario.before` hooks for 1st scenario
  99. $scenarios = $feature->getScenarios();
  100. $this->container->get('behat.hooks_container')->fireScenarioHooks(
  101. new Event($scenarios[0], 'scenario.run.before', array('environment' => $environment))
  102. );
  103. // Run fake background
  104. $tester = $this->container->get('behat.background_tester');
  105. $tester->setEnvironment($environment);
  106. $feature->getBackground()->accept($tester);
  107. $this->container->get('behat.statistics_collector')->resume();
  108. $this->backgroundPrinted = true;
  109. }
  110. }
  111. /**
  112. * Listen to `outline.run.before` event & print outline header.
  113. *
  114. * @param Event $event notified event
  115. */
  116. public function printOutlineHeader(Event $event)
  117. {
  118. $outline = $event->getSubject();
  119. $examples = $outline->getExamples()->getTable();
  120. // Recalc maximum description length (for filepath-like comments)
  121. $this->recalcMaxDescriptionLength($outline);
  122. // Print tags if had ones
  123. if ($outline->hasTags()) {
  124. $this->write(' ' . $this->getTagsString($outline), 'tag');
  125. }
  126. // Print outline description
  127. $description = sprintf(" %s:%s",
  128. $this->getTranslator()->trans('Scenario Outline', array(), 'messages', $outline->getLocale())
  129. , $outline->getTitle() ? ' ' . $outline->getTitle() : ''
  130. );
  131. $this->write($description, null, false);
  132. // Print element path & line
  133. $this->printLineSourceComment(
  134. mb_strlen($description)
  135. , $outline->getFile()
  136. , $outline->getLine()
  137. );
  138. // Print outline steps
  139. $environment = $this->container->get('behat.environment_builder')->buildEnvironment();
  140. $this->container->get('behat.statistics_collector')->pause();
  141. foreach ($outline->getSteps() as $step) {
  142. $tester = $this->container->get('behat.step_tester');
  143. $tester->setEnvironment($environment);
  144. $tester->setTokens(current($examples->getHash()));
  145. $tester->skip();
  146. $step->accept($tester);
  147. }
  148. $this->container->get('behat.statistics_collector')->resume();
  149. $this->outlineStepsPrinted = true;
  150. // Print outline examples title
  151. $this->write(sprintf("\n %s:",
  152. $this->getTranslator()->trans('Examples', array(), 'messages', $outline->getLocale())
  153. ));
  154. // print outline examples header row
  155. $this->write(
  156. preg_replace(
  157. '/|([^|]*)|/'
  158. , $this->colorize('$1', 'skipped')
  159. , ' ' . $examples->getRowAsString(0)
  160. )
  161. );
  162. }
  163. /**
  164. * Listen to `outline.sub.run.after` event & print outline subscenario results.
  165. *
  166. * @param Event $event notified event
  167. */
  168. public function printOutlineSubResult(Event $event)
  169. {
  170. $outline = $event->getSubject();
  171. $examples = $outline->getExamples()->getTable();
  172. // print current scenario results row
  173. $this->write(
  174. preg_replace(
  175. '/|([^|]*)|/'
  176. , $this->colorize('$1', $event->get('result'))
  177. , ' ' . $examples->getRowAsString($event->get('iteration') + 1)
  178. )
  179. );
  180. // Print errors
  181. foreach ($this->outlineSubresultExceptions as $exception) {
  182. if ($this->verbose) {
  183. $error = (string) $exception;
  184. } else {
  185. $error = $exception->getMessage();
  186. }
  187. if ($exception instanceof Pending) {
  188. $status = 'pending';
  189. } else {
  190. $status = 'failed';
  191. }
  192. $this->write(' ' . strtr($error, array("\n" => "\n ")), $status);
  193. }
  194. $this->outlineSubresultExceptions = array();
  195. }
  196. /**
  197. * Listen to `outline.run.after` event & print outline footer.
  198. *
  199. * @param Event $event notified event
  200. */
  201. public function printOutlineFooter(Event $event)
  202. {
  203. $this->outlineStepsPrinted = false;
  204. $this->write();
  205. }
  206. /**
  207. * Listen to `scenario.run.before` event & print scenario header.
  208. *
  209. * @param Event $event notified event
  210. */
  211. public function printScenarioHeader(Event $event)
  212. {
  213. $scenario = $event->getSubject();
  214. // Recalc maximum description length (for filepath-like comments)
  215. $this->recalcMaxDescriptionLength($scenario);
  216. // Print tags if had ones
  217. if ($scenario->hasTags()) {
  218. $this->write(' ' . $this->getTagsString($scenario), 'tag');
  219. }
  220. // Print scenario description
  221. $description = sprintf(" %s:%s",
  222. $this->getTranslator()->trans('Scenario', array(), 'messages', $scenario->getLocale())
  223. , $scenario->getTitle() ? ' ' . $scenario->getTitle() : ''
  224. );
  225. $this->write($description, null, false);
  226. // Print element path & line
  227. $this->printLineSourceComment(
  228. mb_strlen($description)
  229. , $scenario->getFile()
  230. , $scenario->getLine()
  231. );
  232. }
  233. /**
  234. * Listen to `scenario.run.after` event & print scenario footer.
  235. *
  236. * @param Event $event notified event
  237. */
  238. public function printScenarioFooter(Event $event)
  239. {
  240. $this->write();
  241. }
  242. /**
  243. * Listen to `background.run.before` event & print background header.
  244. *
  245. * @param Event $event notified event
  246. */
  247. public function printBackgroundHeader(Event $event)
  248. {
  249. if (!$this->backgroundPrinted) {
  250. $background = $event->getSubject();
  251. // Recalc maximum description length (for filepath-like comments)
  252. $this->recalcMaxDescriptionLength($background);
  253. // Print description
  254. $description = sprintf(" %s:%s",
  255. $this->getTranslator()->trans('Background', array(), 'messages', $background->getLocale())
  256. , $background->getTitle() ? ' ' . $background->getTitle() : ''
  257. );
  258. $this->write($description, null, false);
  259. // Print element path & line
  260. $this->printLineSourceComment(
  261. mb_strlen($description)
  262. , $background->getFile()
  263. , $background->getLine()
  264. );
  265. }
  266. }
  267. /**
  268. * Listen to `background.run.after` event & print background footer.
  269. *
  270. * @param Event $event notified event
  271. */
  272. public function printBackgroundFooter(Event $event)
  273. {
  274. if (!$this->backgroundPrinted) {
  275. $this->write();
  276. }
  277. }
  278. /**
  279. * Listen to `step.run.after` event & print step run information.
  280. *
  281. * @param Event $event notified event
  282. */
  283. public function printStep(Event $event)
  284. {
  285. $step = $event->getSubject();
  286. if (!($step->getParent() instanceof BackgroundNode) || !$this->backgroundPrinted) {
  287. if (!($step->getParent() instanceof OutlineNode) || !$this->outlineStepsPrinted) {
  288. // Get step description
  289. $text = $this->outlineStepsPrinted ? $step->getText() : $step->getCleanText();
  290. $printableText = $text;
  291. $description = sprintf(' %s %s', $step->getType(), $text);
  292. // Colorize arguments
  293. if (null !== $event->get('definition') && StepTester::UNDEFINED !== $event->get('result')) {
  294. $argStartCode = $this->colorizeStart($event->get('result') + 10);
  295. $argFinishCode = $this->colorizeFinish() . $this->colorizeStart($event->get('result'));
  296. $printableText = preg_replace_callback(
  297. $event->get('definition')->getRegex()
  298. , function ($matches) use($argStartCode, $argFinishCode) {
  299. $text = array_shift($matches);
  300. foreach ($matches as $match) {
  301. $text = strtr($text, array(
  302. '"' . $match . '"' => '"' . $argStartCode . $match . $argFinishCode . '"'
  303. , '\'' . $match . '\'' => '\'' . $argStartCode . $match . $argFinishCode . '\''
  304. , ' ' . $match . ' ' => ' ' . $argStartCode . $match . $argFinishCode . ' '
  305. , ' ' . $match => ' ' . $argStartCode . $match . $argFinishCode
  306. , $match . ' ' => $argStartCode . $match . $argFinishCode . ' '
  307. ));
  308. }
  309. return $text;
  310. }
  311. , $printableText
  312. );
  313. }
  314. // Print step description
  315. $printableDescription = sprintf(' %s %s', $step->getType(), $printableText);
  316. $this->write($printableDescription, $event->get('result'), false);
  317. // Print definition path if found one
  318. if (null !== $event->get('definition')) {
  319. $this->printLineSourceComment(
  320. mb_strlen($description)
  321. , $event->get('definition')->getFile()
  322. , $event->get('definition')->getLine()
  323. );
  324. } else {
  325. $this->write();
  326. }
  327. // print step arguments
  328. if ($step->hasArguments()) {
  329. foreach ($step->getArguments() as $argument) {
  330. if ($argument instanceof PyStringNode) {
  331. $this->write($this->getPyString($argument, 6), $event->get('result'));
  332. } elseif ($argument instanceof TableNode) {
  333. $this->write($this->getTableString($argument, 6), $event->get('result'));
  334. }
  335. }
  336. }
  337. // Print step exception
  338. if (null !== $event->get('exception')) {
  339. if ($this->verbose) {
  340. $error = (string) $event->get('exception');
  341. } else {
  342. $error = $event->get('exception')->getMessage();
  343. }
  344. $this->write(
  345. ' ' . strtr($error, array("\n" => "\n ")), $event->get('result')
  346. );
  347. }
  348. } else {
  349. if (null !== $event->get('exception')) {
  350. $this->outlineSubresultExceptions[] = $event->get('exception');
  351. }
  352. }
  353. }
  354. }
  355. /**
  356. * Recalculate max descriptions size for section elements.
  357. *
  358. * @param SectionNode $scenario element for calculations
  359. *
  360. * @return integer description length
  361. */
  362. protected function recalcMaxDescriptionLength(SectionNode $scenario)
  363. {
  364. $max = $this->maxDescriptionLength;
  365. $type = '';
  366. if ($scenario instanceof OutlineNode) {
  367. $type = $this->getTranslator()->trans('Scenario Outline', array(), 'messages', $scenario->getLocale());
  368. } else if ($scenario instanceof ScenarioNode) {
  369. $type = $this->getTranslator()->trans('Scenario', array(), 'messages', $scenario->getLocale());
  370. } else if ($scenario instanceof BackgroundNode) {
  371. $type = $this->getTranslator()->trans('Background', array(), 'messages', $scenario->getLocale());
  372. }
  373. $scenarioDescription = $scenario->getTitle() ? $type . ': ' . $scenario->getTitle() : $type;
  374. if (($tmp = mb_strlen($scenarioDescription) + 2) > $max) {
  375. $max = $tmp;
  376. }
  377. foreach ($scenario->getSteps() as $step) {
  378. $stepDescription = $step->getType() . ' ' . $step->getCleanText();
  379. if (($tmp = mb_strlen($stepDescription) + 4) > $max) {
  380. $max = $tmp;
  381. }
  382. }
  383. $this->maxDescriptionLength = $max;
  384. }
  385. /**
  386. * Return formatted tag string, prepared for console output.
  387. *
  388. * @param SectionNode $section section instance
  389. *
  390. * @return string
  391. */
  392. protected function getTagsString(SectionNode $section)
  393. {
  394. $tags = array();
  395. foreach ($section->getTags() as $tag) {
  396. $tags[] = '@' . $tag;
  397. }
  398. return implode(' ', $tags);
  399. }
  400. /**
  401. * Return formatted PyString, prepared for console output.
  402. *
  403. * @param PyStringNode $pystring PyString
  404. * @param integer $indent indentation spaces count
  405. *
  406. * @return string
  407. */
  408. protected function getPyString(PyStringNode $pystring, $indent = 6)
  409. {
  410. return strtr(
  411. sprintf("%s\"\"\"\n%s\n\"\"\"", str_repeat(' ', $indent), (string) $pystring),
  412. array("\n" => "\n" . str_repeat(' ', $indent))
  413. );
  414. }
  415. /**
  416. * Return formatted Table, prepared for console output.
  417. *
  418. * @param TableNode $table Table instance
  419. * @param string $indent indentation spaces count
  420. *
  421. * @return string
  422. */
  423. protected function getTableString(TableNode $table, $indent = 6)
  424. {
  425. return strtr(
  426. sprintf(str_repeat(' ', $indent).'%s', $table),
  427. array("\n" => "\n".str_repeat(' ', $indent))
  428. );
  429. }
  430. }