PageRenderTime 39ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/phpspec/phpspec/src/PhpSpec/Formatter/Presenter/StringPresenter.php

https://gitlab.com/judielsm/Handora
PHP | 488 lines | 278 code | 71 blank | 139 comment | 39 complexity | d2a270b4aa4be305524641f9c6af6c8d MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of PhpSpec, A php toolset to drive emergent
  4. * design by specification.
  5. *
  6. * (c) Marcello Duarte <marcello.duarte@gmail.com>
  7. * (c) Konstantin Kudryashov <ever.zet@gmail.com>
  8. *
  9. * For the full copyright and license information, please view the LICENSE
  10. * file that was distributed with this source code.
  11. */
  12. namespace PhpSpec\Formatter\Presenter;
  13. use Exception;
  14. use PhpSpec\Exception\Exception as PhpSpecException;
  15. use PhpSpec\Exception\Example\NotEqualException;
  16. use PhpSpec\Exception\Example\ErrorException;
  17. use PhpSpec\Exception\Example\PendingException;
  18. use Prophecy\Argument\Token\ExactValueToken;
  19. use Prophecy\Exception\Call\UnexpectedCallException;
  20. use Prophecy\Exception\Exception as ProphecyException;
  21. use Prophecy\Prophecy\MethodProphecy;
  22. class StringPresenter implements PresenterInterface
  23. {
  24. /**
  25. * @var Differ\Differ
  26. */
  27. private $differ;
  28. /**
  29. * The PhpSpec base path.
  30. *
  31. * This property is used as a constant but PHP does not support setting the value with dirname().
  32. *
  33. * @var string
  34. */
  35. private $phpspecPath;
  36. /**
  37. * The PhpSpec Runner base path.
  38. *
  39. * This property is used as a constant but PHP does not support setting the value with dirname().
  40. *
  41. * @var string
  42. */
  43. private $runnerPath;
  44. /**
  45. * @param Differ\Differ $differ
  46. */
  47. public function __construct(Differ\Differ $differ)
  48. {
  49. $this->differ = $differ;
  50. $this->phpspecPath = dirname(dirname(__DIR__));
  51. $this->runnerPath = $this->phpspecPath.DIRECTORY_SEPARATOR.'Runner';
  52. }
  53. /**
  54. * @param mixed $value
  55. *
  56. * @return string
  57. */
  58. public function presentValue($value)
  59. {
  60. if (is_callable($value)) {
  61. return $this->presentString($this->presentCallable($value));
  62. }
  63. if ($value instanceof Exception) {
  64. return $this->presentString(sprintf(
  65. '[exc:%s("%s")]',
  66. get_class($value),
  67. $value->getMessage()
  68. ));
  69. }
  70. switch ($type = strtolower(gettype($value))) {
  71. case 'null':
  72. return $this->presentString('null');
  73. case 'boolean':
  74. return $this->presentString(sprintf('%s', true === $value ? 'true' : 'false'));
  75. case 'object':
  76. return $this->presentString(sprintf('[obj:%s]', get_class($value)));
  77. case 'array':
  78. return $this->presentString(sprintf('[array:%d]', count($value)));
  79. case 'string':
  80. if (25 > strlen($value) && false === strpos($value, "\n")) {
  81. return $this->presentString(sprintf('"%s"', $value));
  82. }
  83. $lines = explode("\n", $value);
  84. return $this->presentString(sprintf('"%s..."', substr($lines[0], 0, 25)));
  85. default:
  86. return $this->presentString(sprintf('[%s:%s]', $type, $value));
  87. }
  88. }
  89. /**
  90. * @param Exception $exception
  91. * @param bool $verbose
  92. *
  93. * @return string
  94. */
  95. public function presentException(Exception $exception, $verbose = false)
  96. {
  97. if ($exception instanceof PhpSpecException) {
  98. $presentation = wordwrap($exception->getMessage(), 120);
  99. } elseif ($exception instanceof ProphecyException) {
  100. $presentation = $exception->getMessage();
  101. } else {
  102. $presentation = sprintf('Exception %s has been thrown.', $this->presentValue($exception));
  103. }
  104. if (!$verbose || $exception instanceof PendingException) {
  105. return $presentation;
  106. }
  107. if ($exception instanceof NotEqualException) {
  108. if ($diff = $this->presentExceptionDifference($exception)) {
  109. $presentation .= "\n".$diff;
  110. }
  111. }
  112. if ($exception instanceof PhpSpecException && !$exception instanceof ErrorException) {
  113. list($file, $line) = $this->getExceptionExamplePosition($exception);
  114. $presentation .= "\n".$this->presentFileCode($file, $line);
  115. }
  116. if ($exception instanceof UnexpectedCallException) {
  117. $presentation .= $this->presentCallArgumentsDifference($exception);
  118. }
  119. if (trim($trace = $this->presentExceptionStackTrace($exception))) {
  120. $presentation .= "\n".$trace;
  121. }
  122. return $presentation;
  123. }
  124. /**
  125. * @param string $string
  126. *
  127. * @return string
  128. */
  129. public function presentString($string)
  130. {
  131. return $string;
  132. }
  133. /**
  134. * @param string $file
  135. * @param integer $lineno
  136. * @param integer $context
  137. *
  138. * @return string
  139. */
  140. protected function presentFileCode($file, $lineno, $context = 6)
  141. {
  142. $lines = explode("\n", file_get_contents($file));
  143. $offset = max(0, $lineno - ceil($context / 2));
  144. $lines = array_slice($lines, $offset, $context);
  145. $text = "\n";
  146. foreach ($lines as $line) {
  147. $offset++;
  148. if ($offset == $lineno) {
  149. $text .= $this->presentHighlight(sprintf('%4d', $offset).' '.$line);
  150. } else {
  151. $text .= $this->presentCodeLine(sprintf('%4d', $offset), $line);
  152. }
  153. $text .= "\n";
  154. }
  155. return $text;
  156. }
  157. /**
  158. * @param integer $number
  159. * @param integer $line
  160. *
  161. * @return string
  162. */
  163. protected function presentCodeLine($number, $line)
  164. {
  165. return $number.' '.$line;
  166. }
  167. /**
  168. * @param string $line
  169. *
  170. * @return string
  171. */
  172. protected function presentHighlight($line)
  173. {
  174. return $line;
  175. }
  176. /**
  177. * @param NotEqualException $exception
  178. *
  179. * @return string
  180. */
  181. protected function presentExceptionDifference(NotEqualException $exception)
  182. {
  183. return $this->differ->compare($exception->getExpected(), $exception->getActual());
  184. }
  185. /**
  186. * @param Exception $exception
  187. *
  188. * @return string
  189. */
  190. protected function presentExceptionStackTrace(Exception $exception)
  191. {
  192. $offset = 0;
  193. $text = "\n";
  194. $text .= $this->presentExceptionTraceLocation($offset++, $exception->getFile(), $exception->getLine());
  195. $text .= $this->presentExceptionTraceFunction(
  196. 'throw new '.get_class($exception),
  197. array($exception->getMessage())
  198. );
  199. foreach ($exception->getTrace() as $call) {
  200. // skip internal framework calls
  201. if ($this->shouldStopTracePresentation($call)) {
  202. break;
  203. }
  204. if ($this->shouldSkipTracePresentation($call)) {
  205. continue;
  206. }
  207. if (isset($call['file'])) {
  208. $text .= $this->presentExceptionTraceLocation($offset++, $call['file'], $call['line']);
  209. } else {
  210. $text .= $this->presentExceptionTraceHeader(sprintf("%2d [internal]", $offset++));
  211. }
  212. if (isset($call['class'])) {
  213. $text .= $this->presentExceptionTraceMethod(
  214. $call['class'],
  215. $call['type'],
  216. $call['function'],
  217. isset($call['args']) ? $call['args'] : array()
  218. );
  219. } elseif (isset($call['function'])) {
  220. $text .= $this->presentExceptionTraceFunction(
  221. $call['function'],
  222. isset($call['args']) ? $call['args'] : array()
  223. );
  224. }
  225. }
  226. return $text;
  227. }
  228. /**
  229. * @param string $header
  230. *
  231. * @return string
  232. */
  233. protected function presentExceptionTraceHeader($header)
  234. {
  235. return $header."\n";
  236. }
  237. /**
  238. * @param string $class
  239. * @param string $type
  240. * @param string $method
  241. * @param array $args
  242. *
  243. * @return string
  244. */
  245. protected function presentExceptionTraceMethod($class, $type, $method, array $args)
  246. {
  247. $args = array_map(array($this, 'presentValue'), $args);
  248. return sprintf(" %s%s%s(%s)\n", $class, $type, $method, implode(', ', $args));
  249. }
  250. /**
  251. * @param string $function
  252. * @param array $args
  253. *
  254. * @return string
  255. */
  256. protected function presentExceptionTraceFunction($function, array $args)
  257. {
  258. $args = array_map(array($this, 'presentValue'), $args);
  259. return sprintf(" %s(%s)\n", $function, implode(', ', $args));
  260. }
  261. /**
  262. * @param PhpSpecException $exception
  263. *
  264. * @return array
  265. */
  266. protected function getExceptionExamplePosition(PhpSpecException $exception)
  267. {
  268. $refl = $exception->getCause();
  269. foreach ($exception->getTrace() as $call) {
  270. if (!isset($call['file'])) {
  271. continue;
  272. }
  273. if (!empty($refl) && $refl->getFilename() === $call['file']) {
  274. return array($call['file'], $call['line']);
  275. }
  276. }
  277. return array($exception->getFile(), $exception->getLine());
  278. }
  279. /**
  280. * @param int $offset
  281. * @param string $file
  282. * @param int $line
  283. *
  284. * @return string
  285. */
  286. private function presentExceptionTraceLocation($offset, $file, $line)
  287. {
  288. return $this->presentExceptionTraceHeader(sprintf(
  289. "%2d %s:%d",
  290. $offset,
  291. str_replace(getcwd().DIRECTORY_SEPARATOR, '', $file),
  292. $line
  293. ));
  294. }
  295. private function shouldStopTracePresentation(array $call)
  296. {
  297. return isset($call['file']) && false !== strpos($call['file'], $this->runnerPath);
  298. }
  299. private function shouldSkipTracePresentation(array $call)
  300. {
  301. if (isset($call['file']) && 0 === strpos($call['file'], $this->phpspecPath)) {
  302. return true;
  303. }
  304. return isset($call['class']) && 0 === strpos($call['class'], "PhpSpec\\");
  305. }
  306. /**
  307. * @param callable $value
  308. *
  309. * @return string
  310. */
  311. private function presentCallable($value)
  312. {
  313. if (is_array($value)) {
  314. $type = is_object($value[0]) ? $this->presentValue($value[0]) : $value[0];
  315. return sprintf('%s::%s()', $type, $value[1]);
  316. }
  317. if ($value instanceof \Closure) {
  318. return '[closure]';
  319. }
  320. if (is_object($value)) {
  321. return sprintf('[obj:%s]', get_class($value));
  322. }
  323. return sprintf('[%s()]', $value);
  324. }
  325. /**
  326. * @param UnexpectedCallException $exception
  327. *
  328. * @return string
  329. */
  330. private function presentCallArgumentsDifference(UnexpectedCallException $exception)
  331. {
  332. $actualArguments = $exception->getArguments();
  333. $methodProphecies = $exception->getObjectProphecy()->getMethodProphecies($exception->getMethodName());
  334. if ($this->noMethodPropheciesForUnexpectedCall($methodProphecies)) {
  335. return '';
  336. }
  337. $presentedMethodProphecy = $this->findMethodProphecyOfFirstNotExpectedArgumentsCall($methodProphecies, $exception);
  338. if (is_null($presentedMethodProphecy)) {
  339. return '';
  340. }
  341. $expectedTokens = $presentedMethodProphecy->getArgumentsWildcard()->getTokens();
  342. if ($this->parametersCountMismatch($expectedTokens, $actualArguments)) {
  343. return '';
  344. }
  345. $expectedArguments = $this->convertArgumentTokensToDiffableValues($expectedTokens);
  346. $text = $this->generateArgumentsDifferenceText($actualArguments, $expectedArguments);
  347. return $text;
  348. }
  349. private function noMethodPropheciesForUnexpectedCall(array $methodProphecies)
  350. {
  351. return count($methodProphecies) === 0;
  352. }
  353. /**
  354. * @param MethodProphecy[] $methodProphecies
  355. * @param UnexpectedCallException $exception
  356. *
  357. * @return MethodProphecy
  358. */
  359. private function findMethodProphecyOfFirstNotExpectedArgumentsCall(array $methodProphecies, UnexpectedCallException $exception)
  360. {
  361. $objectProphecy = $exception->getObjectProphecy();
  362. foreach ($methodProphecies as $methodProphecy) {
  363. $calls = $objectProphecy->findProphecyMethodCalls(
  364. $exception->getMethodName(),
  365. $methodProphecy->getArgumentsWildcard()
  366. );
  367. if (count($calls)) {
  368. continue;
  369. }
  370. return $methodProphecy;
  371. }
  372. }
  373. /**
  374. * @param array $expectedTokens
  375. * @param array $actualArguments
  376. *
  377. * @return bool
  378. */
  379. private function parametersCountMismatch(array $expectedTokens, array $actualArguments)
  380. {
  381. return count($expectedTokens) !== count($actualArguments);
  382. }
  383. /**
  384. * @param array $tokens
  385. *
  386. * @return array
  387. */
  388. private function convertArgumentTokensToDiffableValues(array $tokens)
  389. {
  390. $values = array();
  391. foreach ($tokens as $token) {
  392. if ($token instanceof ExactValueToken) {
  393. $values[] = $token->getValue();
  394. } else {
  395. $values[] = (string)$token;
  396. }
  397. }
  398. return $values;
  399. }
  400. /**
  401. * @param array $actualArguments
  402. * @param array $expectedArguments
  403. *
  404. * @return string
  405. */
  406. private function generateArgumentsDifferenceText(array $actualArguments, array $expectedArguments)
  407. {
  408. $text = '';
  409. foreach($actualArguments as $i => $actualArgument) {
  410. $expectedArgument = $expectedArguments[$i];
  411. $actualArgument = is_null($actualArgument) ? 'null' : $actualArgument;
  412. $expectedArgument = is_null($expectedArgument) ? 'null' : $expectedArgument;
  413. $text .= $this->differ->compare($expectedArgument, $actualArgument);
  414. }
  415. return $text;
  416. }
  417. }