PageRenderTime 42ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/tinyspec.php

http://github.com/StanAngeloff/php-tinyspec
PHP | 335 lines | 278 code | 57 blank | 0 comment | 34 complexity | 0bd1513b33ca48b4fca8a0a05158d1a6 MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. namespace Spec;
  3. function describe($description)
  4. { return new Group($description); }
  5. function format($string)
  6. {
  7. return str_replace(
  8. array('{bold}', '{/bold}', '{underline}', '{/underline}', '{yellow}', '{/yellow}', '{red}', '{/red}', '{green}', '{/green}', '{white}', '{/white}'),
  9. array("\033[1m", "\033[22m", "\033[4m", "\033[24m", "\033[33m", "\033[39m", "\033[31m", "\033[39m", "\033[32m", "\033[39m", "\033[37m", "\033[39m"),
  10. $string
  11. );
  12. }
  13. final class assert
  14. {
  15. public static function throws($needle)
  16. {
  17. self::queue(function() use ($needle) {
  18. assert::is_true(Group::$unit['failed'], format("{red}did not throw '{bold}$needle{/bold}'{/red}"));
  19. assert::is_not_false(strpos(Group::$unit['message'], $needle), format('{red}' . Group::$unit['message'] . " did not contain '{bold}$needle{/bold}'{/red}"));
  20. Group::$unit['failed'] = false;
  21. });
  22. }
  23. public static function equals($value, $subject, $message)
  24. { self::must($subject == $value, $message); }
  25. public static function not_equals($value, $subject, $message)
  26. { self::must($subject != $value, $message); }
  27. public static function strict_equals($value, $subject, $message)
  28. { self::must($subject === $value, $message); }
  29. public static function strict_not_equals($value, $subject, $message)
  30. { self::must($subject !== $value, $message); }
  31. public static function hash_equals($reference, $subject, $message)
  32. { self::strict_equals(strcmp(sha1(serialize($subject)), sha1(serialize($reference))), 0, $message); }
  33. public static function contains($needle, $subject, $message = null)
  34. {
  35. $description = (isset ($message) ? $message : "'$needle' was not found");
  36. if (is_array($subject)) {
  37. self::is_true(in_array($needle, $subject), $message);
  38. } else if (is_string($subject)) {
  39. self::is_not_false(strpos($subject, $needle), $message);
  40. } else {
  41. $iterator_has_value = false;
  42. foreach ($subject as $value) {
  43. if ($value === $needle) {
  44. $iterator_has_value = true;
  45. break;
  46. }
  47. }
  48. self::is_true($iterator_has_value, $message);
  49. }
  50. }
  51. public static function is_true($subject, $message)
  52. { self::strict_equals(true, $subject, $message); }
  53. public static function is_not_true($subject, $message)
  54. { self::strict_not_equals(true, $subject, $message); }
  55. public static function is_false($subject, $message)
  56. { self::strict_equals(false, $subject, $message); }
  57. public static function is_not_false($subject, $message)
  58. { self::strict_not_equals(false, $subject, $message); }
  59. public static function is_null($subject, $message)
  60. { self::strict_equals(null, $subject, $message); }
  61. public static function is_not_null($subject, $message)
  62. { self::strict_not_equals(null, $subject, $message); }
  63. public static function is_array($subject, $message = null)
  64. { self::is_true(is_array($subject), (isset ($message) ? $message : "'" . var_export($subject, true) . "' is not an 'array'")); }
  65. public static function is_bool($subject, $message = null)
  66. { self::is_true(is_bool($subject), (isset ($message) ? $message : "'" . var_export($subject, true) . "' is not a 'bool'")); }
  67. public static function is_callable($subject, $message = null)
  68. { self::is_true(is_callable($subject), (isset ($message) ? $message : "'" . var_export($subject, true) . "' is not a 'callable'")); }
  69. public static function is_double($subject, $message = null)
  70. { self::is_true(is_double($subject), (isset ($message) ? $message : "'" . var_export($subject, true) . "' is not a 'double'")); }
  71. public static function is_float($subject, $message = null)
  72. { self::is_true(is_float($subject), (isset ($message) ? $message : "'" . var_export($subject, true) . "' is not a 'float'")); }
  73. public static function is_integer($subject, $message = null)
  74. { self::is_true(is_integer($subject), (isset ($message) ? $message : "'" . var_export($subject, true) . "' is not an 'integer'")); }
  75. public static function is_long($subject, $message = null)
  76. { self::is_true(is_long($subject), (isset ($message) ? $message : "'" . var_export($subject, true) . "' is not a 'long'")); }
  77. public static function is_numeric($subject, $message = null)
  78. { self::is_true(is_numeric($subject), (isset ($message) ? $message : "'" . var_export($subject, true) . "' is not 'numeric'")); }
  79. public static function is_object($subject, $message = null)
  80. { self::is_true(is_object($subject), (isset ($message) ? $message : "'" . var_export($subject, true) . "' is not an 'object'")); }
  81. public static function is_real($subject, $message = null)
  82. { self::is_true(is_real($subject), (isset ($message) ? $message : "'" . var_export($subject, true) . "' is not a 'real'")); }
  83. public static function is_resource($subject, $message = null)
  84. { self::is_true(is_resource($subject), (isset ($message) ? $message : "'" . var_export($subject, true) . "' is not a 'resource'")); }
  85. public static function is_scalar($subject, $message = null)
  86. { self::is_true(is_scalar($subject), (isset ($message) ? $message : "'" . var_export($subject, true) . "' is not a 'scalar'")); }
  87. public static function is_string($subject, $message = null)
  88. { self::is_true(is_string($subject), (isset ($message) ? $message : "'" . var_export($subject, true) . "' is not a 'string'")); }
  89. public static function is_reference(&$reference, &$subject, $message = null)
  90. {
  91. if (is_array($reference) && is_array($subject)) {
  92. $reference['__assert_reference'] = true;
  93. self::is_true(isset ($subject['__assert_reference']), (isset ($message) ? $message : 'array $subject did not appear to be a reference to array $reference'));
  94. unset ($subject['__assert_reference']);
  95. } else if (is_object($reference) && is_object($subject)) {
  96. $reference->__assert_reference = true;
  97. self::is_true(isset ($subject->__assert_reference), (isset ($message) ? $message : 'objects $subject did not appear to be a reference to object $reference'));
  98. unset ($subject->__assert_reference);
  99. } else {
  100. self::strict_equals($reference, $subject, $message);
  101. }
  102. }
  103. public static function key_missing($key, $array, $message = null)
  104. { self::is_false(array_key_exists($key, $array), (isset ($message) ? $message : "'$key' key was present")); }
  105. public static function key_not_missing($key, $array, $message = null)
  106. { self::is_true(array_key_exists($key, $array), (isset ($message) ? $message : "'$key' key was missing")); }
  107. public static function value_empty($subject, $message)
  108. { self::is_true(empty($subject), $message); }
  109. public static function value_not_empty($subject, $message)
  110. { self::is_false(empty($subject), $message); }
  111. public static function must($condition, $message)
  112. {
  113. if ( ! $condition) {
  114. throw new AssertException($message);
  115. }
  116. }
  117. const QUEUE_EMPTY = ':empty';
  118. const QUEUE_POP = ':pop';
  119. public static function queue($block)
  120. {
  121. static $queue = array();
  122. if (self::QUEUE_EMPTY === $block) {
  123. return $queue = array();
  124. } else if (self::QUEUE_POP === $block) {
  125. return array_pop($queue);
  126. }
  127. array_push($queue, $block);
  128. return sizeof ($queue);
  129. }
  130. public static function before()
  131. { self::queue(self::QUEUE_EMPTY); }
  132. public static function after()
  133. {
  134. while ($operation = self::queue(self::QUEUE_POP)) {
  135. call_user_func($operation);
  136. if (Group::$unit['failed']) {
  137. break;
  138. }
  139. }
  140. }
  141. }
  142. final class Group
  143. {
  144. public static $unit;
  145. private $children;
  146. private $description;
  147. private $total;
  148. private $passed;
  149. private $failed;
  150. private $level;
  151. private $previous;
  152. public function __construct($description)
  153. {
  154. $this->children = array();
  155. $this->description = $description;
  156. $this->total = 0;
  157. $this->passed = 0;
  158. $this->failed = 0;
  159. $this->level = 0;
  160. }
  161. public function add($group)
  162. {
  163. array_push($this->children, $group);
  164. return $this;
  165. }
  166. public function run()
  167. {
  168. $this->set_up();
  169. $this->total = 0;
  170. $this->passed = 0;
  171. $this->failed = 0;
  172. $this->level = 1;
  173. print format("{yellow}{bold}{underline}" . $this->description . "{/underline}:{/bold}{/yellow}\n");
  174. foreach ($this->children as $group) {
  175. $this->run_group($group);
  176. }
  177. print "\n";
  178. $this->tear_down();
  179. print format(
  180. "{bold}Expectations: {$this->total}, {/bold}"
  181. . "{bold}passed: {/bold}{green}{$this->passed}{/green}{bold}, {/bold}"
  182. . "{bold}failed {/bold}{red}{$this->failed}{/red}{bold}; {/bold}"
  183. . '{bold}' . sprintf('%.2f%% successful', round(($this->passed / $this->total) * 100, 2)) . ".{/bold}\n"
  184. );
  185. exit ($this->failed ? 0x01 : 0x00);
  186. }
  187. private function set_up()
  188. {
  189. $this->previous = set_error_handler(array($this, 'catch_error'));
  190. }
  191. private function tear_down()
  192. {
  193. set_error_handler($this->previous);
  194. $this->previous = null;
  195. }
  196. private function padd($offset = 0)
  197. {
  198. return str_repeat(' ', $this->level + $offset - 1);
  199. }
  200. private function run_group($group, $topic = null)
  201. {
  202. foreach ($group as $message => $action) {
  203. $this->level ++;
  204. if (is_array($action)) {
  205. print $this->padd() . format("{yellow}{bold}" . $message . ":{/bold}{/yellow}\n");
  206. $this->run_group($action, $topic);
  207. } else if (is_callable($action)) {
  208. if ('topic' === $message) {
  209. $topic = $this->invoke($action);
  210. if (self::$unit['failed']) {
  211. print ($padd = $this->padd()) . format("{white}- $message: {/white}");
  212. $this->failed --;
  213. } else {
  214. $this->total --;
  215. $this->passed --;
  216. }
  217. } else {
  218. print ($padd = $this->padd()) . format("{white}- $message: {/white}");
  219. $this->invoke($action, array($topic));
  220. print str_repeat(' ', 64 - strlen($padd) - strlen($message));
  221. }
  222. if (self::$unit['failed']) {
  223. print format("{red}{bold}" . 'FAIL' . "{/bold}{/red}\n");
  224. print ($padd = $this->padd(+1)) . str_replace(array("\r\n", "\r", "\n"), "\n" . $padd, wordwrap(self::$unit['message'], 120)) . "\n";
  225. } else if ('topic' !== $message) {
  226. print format("{green}{bold}" . 'PASS' . "{/bold}{/green}\n");
  227. }
  228. }
  229. $this->level --;
  230. }
  231. }
  232. private function invoke($callable, array $args = array())
  233. {
  234. self::$unit = array('failed' => false, 'message' => null);
  235. $result = null;
  236. $this->total ++;
  237. try {
  238. assert::before();
  239. $result = call_user_func_array($callable, $args);
  240. } catch (\Exception $exception) {
  241. $this->catch_exception($exception);
  242. }
  243. try {
  244. assert::after();
  245. } catch (\Exception $exception) {
  246. $this->catch_exception($exception);
  247. }
  248. if (self::$unit['failed']) {
  249. $this->failed ++;
  250. } else {
  251. $this->passed ++;
  252. }
  253. return $result;
  254. }
  255. public function catch_error($code, $message, $file, $line)
  256. {
  257. if ($code == (error_reporting() && $code)) {
  258. self::$unit['failed'] = true;
  259. self::$unit['message'] = format("{red}$message in $file at line $line{/red}");
  260. }
  261. }
  262. private function catch_exception($exception)
  263. {
  264. self::$unit['failed'] = true;
  265. self::$unit['message'] = format('{red}' . (string) $exception . '{/red}');
  266. }
  267. }
  268. final class AssertException extends \Exception
  269. {
  270. public function __construct($message)
  271. {
  272. $this->message = $message;
  273. }
  274. public function __toString()
  275. {
  276. return basename(__CLASS__) . ': ' . $this->message;
  277. }
  278. }