InspectorTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\tests\cases\analysis;
  9. use ReflectionMethod;
  10. use lithium\analysis\Inspector;
  11. use lithium\core\Libraries;
  12. use lithium\tests\mocks\analysis\MockEmptyClass;
  13. use lithium\tests\mocks\core\MockMethodFiltering;
  14. class InspectorTest extends \lithium\test\Unit {
  15. public $test = 'foo';
  16. public static $test2 = 'bar';
  17. protected $_test = 'baz';
  18. /**
  19. * Tests that basic method lists and information are queried properly.
  20. *
  21. * @return void
  22. */
  23. public function testBasicMethodInspection() {
  24. $class = 'lithium\analysis\Inspector';
  25. $parent = 'lithium\core\StaticObject';
  26. $expected = array_diff(get_class_methods($class), get_class_methods($parent));
  27. $result = array_keys(Inspector::methods($class, 'extents'));
  28. $this->assertEqual(array_intersect($result, $expected), $result);
  29. $result = array_keys(Inspector::methods($class, 'extents', array(
  30. 'self' => true, 'public' => true
  31. )));
  32. $this->assertEqual($expected, $result);
  33. $this->assertNull(Inspector::methods('lithium\core\Foo'));
  34. $result = Inspector::methods('stdClass', 'extents');
  35. $this->assertEqual(array(), $result);
  36. }
  37. public function testMethodInspection() {
  38. $result = Inspector::methods($this, null);
  39. $this->assertTrue($result[0] instanceof ReflectionMethod);
  40. $result = Inspector::info('lithium\core\Object::_init()');
  41. $expected = '_init';
  42. $this->assertEqual($expected, $result['name']);
  43. $expected = 'void';
  44. $this->assertEqual($expected, $result['tags']['return']);
  45. }
  46. /**
  47. * Tests that the range of executable lines of this test method is properly calculated.
  48. * Recursively meta.
  49. *
  50. * @return void
  51. */
  52. public function testMethodRange() {
  53. $result = Inspector::methods(__CLASS__, 'ranges', array('methods' => __FUNCTION__));
  54. $expected = array(__FUNCTION__ => array(__LINE__ - 1, __LINE__, __LINE__ + 1));
  55. $this->assertEqual($expected, $result);
  56. }
  57. /**
  58. * Gets the executable line numbers of this file based on a manual entry of line ranges. Will
  59. * need to be updated manually if this method changes.
  60. *
  61. * @return void
  62. */
  63. public function testExecutableLines() {
  64. do {
  65. // These lines should be ignored
  66. //
  67. /* And these as well are ignored */
  68. /**
  69. * Testing never proves the absence of faults,
  70. * it only shows their presence.
  71. * - Dijkstra
  72. */
  73. } while (false);
  74. $result = Inspector::executable($this, array('methods' => __FUNCTION__));
  75. $expected = array(__LINE__ - 1, __LINE__, __LINE__ + 1);
  76. $this->assertEqual($expected, $result);
  77. }
  78. public function testExecutableLinesOnEmptyClass() {
  79. $result = Inspector::executable(new MockEmptyClass());
  80. $this->assertEqual(array(), $result);
  81. }
  82. /**
  83. * Tests reading specific line numbers of a file.
  84. *
  85. * @return void
  86. */
  87. public function testLineIntrospection() {
  88. $result = Inspector::lines(__FILE__, array(__LINE__ - 1));
  89. $expected = array(__LINE__ - 2 => "\tpublic function testLineIntrospection() {");
  90. $this->assertEqual($expected, $result);
  91. $result = Inspector::lines(__CLASS__, array(17));
  92. $expected = array(17 => 'class InspectorTest extends \lithium\test\Unit {');
  93. $this->assertEqual($expected, $result);
  94. $lines = 'This is the first line.' . PHP_EOL . 'And this the second.';
  95. $result = Inspector::lines($lines, array(2));
  96. $expected = array(2 => 'And this the second.');
  97. $this->assertEqual($expected, $result);
  98. $this->expectException('/Missing argument 2/');
  99. $this->assertNull(Inspector::lines('lithium\core\Foo'));
  100. $this->assertNull(Inspector::lines(__CLASS__, array()));
  101. }
  102. /**
  103. * Tests reading specific line numbers of a file that has CRLF line endings.
  104. *
  105. * @return void
  106. */
  107. public function testLineIntrospectionWithCRLFLineEndings() {
  108. $tmpPath = Libraries::get(true, 'resources') . '/tmp/tests/inspector_crlf';
  109. $contents = implode("\r\n", array('one', 'two', 'three', 'four', 'five'));
  110. file_put_contents($tmpPath, $contents);
  111. $result = Inspector::lines($tmpPath, array(2));
  112. $expected = array(2 => 'two');
  113. $this->assertEqual($expected, $result);
  114. $result = Inspector::lines($tmpPath, array(1,5));
  115. $expected = array(1 => 'one', 5 => 'five');
  116. $this->assertEqual($expected, $result);
  117. $this->_cleanUp();
  118. }
  119. /**
  120. * Tests getting a list of parent classes from an object or string class name.
  121. *
  122. * @return void
  123. */
  124. public function testClassParents() {
  125. $result = Inspector::parents($this);
  126. $this->assertEqual('lithium\test\Unit', current($result));
  127. $result2 = Inspector::parents(__CLASS__);
  128. $this->assertEqual($result2, $result);
  129. $this->assertFalse(Inspector::parents('lithium\core\Foo', array('autoLoad' => false)));
  130. }
  131. public function testClassFileIntrospection() {
  132. $result = Inspector::classes(array('file' => __FILE__));
  133. $this->assertEqual(array(__CLASS__ => __FILE__), $result);
  134. $result = Inspector::classes(array('file' => __FILE__, 'group' => 'files'));
  135. $this->assertEqual(1, count($result));
  136. $this->assertEqual(__FILE__, key($result));
  137. $result = Inspector::classes(array('file' => __FILE__, 'group' => 'foo'));
  138. $this->assertEqual(array(), $result);
  139. }
  140. /**
  141. * Tests that names of classes, methods, properties and namespaces are parsed properly from
  142. * strings.
  143. *
  144. * @return void
  145. */
  146. public function testTypeDetection() {
  147. $this->assertEqual('namespace', Inspector::type('lithium\util'));
  148. $this->assertEqual('namespace', Inspector::type('lithium\analysis'));
  149. $this->assertEqual('class', Inspector::type('lithium\analysis\Inspector'));
  150. $this->assertEqual('property', Inspector::type('Inspector::$_classes'));
  151. $this->assertEqual('method', Inspector::type('Inspector::type'));
  152. $this->assertEqual('method', Inspector::type('Inspector::type()'));
  153. $this->assertEqual('class', Inspector::type('\lithium\security\Auth'));
  154. $this->assertEqual('class', Inspector::type('lithium\security\Auth'));
  155. $this->assertEqual('namespace', Inspector::type('\lithium\security\auth'));
  156. $this->assertEqual('namespace', Inspector::type('lithium\security\auth'));
  157. }
  158. /**
  159. * Tests getting reflection information based on a string identifier.
  160. *
  161. * @return void
  162. */
  163. public function testIdentifierIntrospection() {
  164. $result = Inspector::info(__METHOD__);
  165. $this->assertEqual(array('public'), $result['modifiers']);
  166. $this->assertEqual(__FUNCTION__, $result['name']);
  167. $this->assertNull(Inspector::info('\lithium\util'));
  168. $info = Inspector::info('\lithium\analysis\Inspector');
  169. $result = str_replace('\\', '/', $info['file']);
  170. $this->assertTrue(strpos($result, '/analysis/Inspector.php'));
  171. $this->assertEqual('lithium\analysis', $info['namespace']);
  172. $this->assertEqual('Inspector', $info['shortName']);
  173. $result = Inspector::info('\lithium\analysis\Inspector::$_methodMap');
  174. $this->assertEqual('_methodMap', $result['name']);
  175. $expected = 'Maps reflect method names to result array keys.';
  176. $this->assertEqual($expected, $result['description']);
  177. $this->assertEqual(array('var' => 'array'), $result['tags']);
  178. $result = Inspector::info('\lithium\analysis\Inspector::info()', array(
  179. 'modifiers', 'namespace', 'foo'
  180. ));
  181. $this->assertEqual(array('modifiers', 'namespace'), array_keys($result));
  182. $this->assertNull(Inspector::info('\lithium\analysis\Inspector::$foo'));
  183. $this->assertNull(Inspector::info('\lithium\core\Foo::$foo'));
  184. }
  185. public function testClassDependencies() {
  186. $expected = array(
  187. 'Exception', 'ReflectionClass', 'ReflectionProperty', 'ReflectionException',
  188. 'SplFileObject', 'lithium\\core\\Libraries'
  189. );
  190. $result = Inspector::dependencies($this->subject(), array('type' => 'static'));
  191. $this->assertEqual($expected, $result);
  192. $expected[] = 'lithium\\util\\Collection';
  193. $result = Inspector::dependencies($this->subject());
  194. $this->assertEqual($expected, $result);
  195. }
  196. /**
  197. * Tests that class and namepace names which are equivalent in a case-insensitive search still
  198. * match properly.
  199. *
  200. * @return void
  201. */
  202. public function testCaseSensitiveIdentifiers() {
  203. $result = Inspector::type('lithium\storage\Cache');
  204. $expected = 'class';
  205. $this->assertEqual($expected, $result);
  206. $result = Inspector::type('lithium\storage\cache');
  207. $expected = 'namespace';
  208. $this->assertEqual($expected, $result);
  209. }
  210. /**
  211. * Tests getting static and non-static properties from various types of classes.
  212. *
  213. * @return void
  214. */
  215. public function testGetClassProperties() {
  216. $result = array_map(
  217. function($property) { return $property['name']; },
  218. Inspector::properties(__CLASS__)
  219. );
  220. $expected = array('test', 'test2');
  221. $this->assertEqual($expected, $result);
  222. $result = array_map(
  223. function($property) { return $property['name']; },
  224. Inspector::properties(__CLASS__, array('public' => false))
  225. );
  226. $expected = array('test', 'test2', '_test');
  227. $this->assertEqual($expected, $result);
  228. $result = Inspector::properties(__CLASS__);
  229. $expected = array(
  230. array(
  231. 'modifiers' => array('public'),
  232. 'docComment' => false,
  233. 'name' => 'test',
  234. 'value' => null
  235. ),
  236. array(
  237. 'modifiers' => array('public', 'static'),
  238. 'docComment' => false,
  239. 'name' => 'test2',
  240. 'value' => 'bar'
  241. )
  242. );
  243. $this->assertEqual($expected, $result);
  244. $result = array_map(
  245. function($property) { return $property['name']; },
  246. Inspector::properties('lithium\action\Controller')
  247. );
  248. $this->assertTrue(in_array('request', $result));
  249. $this->assertTrue(in_array('response', $result));
  250. $this->assertFalse(in_array('_render', $result));
  251. $this->assertFalse(in_array('_classes', $result));
  252. $result = array_map(
  253. function($property) { return $property['name']; },
  254. Inspector::properties('lithium\action\Controller', array('public' => false))
  255. );
  256. $this->assertTrue(in_array('request', $result));
  257. $this->assertTrue(in_array('response', $result));
  258. $this->assertTrue(in_array('_render', $result));
  259. $this->assertTrue(in_array('_classes', $result));
  260. $this->assertNull(Inspector::properties('\lithium\core\Foo'));
  261. }
  262. public function testCallableObjectWithBadMethods() {
  263. $stdObj = new MockEmptyClass;
  264. $this->assertFalse(Inspector::isCallable($stdObj, 'foo', 0));
  265. $this->assertFalse(Inspector::isCallable($stdObj, 'bar', 0));
  266. $this->assertFalse(Inspector::isCallable($stdObj, 'baz', 0));
  267. }
  268. public function testCallableClassWithBadMethods() {
  269. $this->assertFalse(Inspector::isCallable('lithium\action\Dispatcher', 'foo', 0));
  270. $this->assertFalse(Inspector::isCallable('lithium\action\Dispatcher', 'bar', 0));
  271. $this->assertFalse(Inspector::isCallable('lithium\action\Dispatcher', 'baz', 0));
  272. }
  273. public function testCallableObjectWithRealMethods() {
  274. $obj = new MockMethodFiltering();
  275. $this->assertTrue(Inspector::isCallable($obj, 'method', 0));
  276. $this->assertTrue(Inspector::isCallable($obj, 'method2', 0));
  277. $this->assertTrue(Inspector::isCallable($obj, 'manual', 0));
  278. }
  279. public function testCallableClassWithRealMethods() {
  280. $this->assertTrue(Inspector::isCallable('lithium\action\Dispatcher', 'config', 0));
  281. $this->assertTrue(Inspector::isCallable('lithium\action\Dispatcher', 'run', 0));
  282. $this->assertTrue(Inspector::isCallable('lithium\action\Dispatcher', 'applyRules', 0));
  283. }
  284. public function testCallableVisibility() {
  285. $obj = new MockMethodFiltering();
  286. $this->assertTrue(Inspector::isCallable($obj, 'method', 0));
  287. $this->assertTrue(Inspector::isCallable($obj, 'method', 1));
  288. $this->assertFalse(Inspector::isCallable('lithium\action\Dispatcher', '_callable', 0));
  289. $this->assertTrue(Inspector::isCallable('lithium\action\Dispatcher', '_callable', 1));
  290. }
  291. }
  292. ?>