PageRenderTime 52ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/htdocs/solar/1.1.1/source/solar/Solar/Cli/MakeTests.php

http://github.com/pmjones/php-framework-benchmarks
PHP | 332 lines | 138 code | 42 blank | 152 comment | 22 complexity | b4abaa7817f0331de934c548ef38ef79 MD5 | raw file
Possible License(s): LGPL-3.0, Apache-2.0, BSD-3-Clause, ISC, AGPL-3.0, LGPL-2.1
  1. <?php
  2. /**
  3. *
  4. * Command to make a test class (or set of classes) from a given class.
  5. *
  6. * Examples
  7. * ========
  8. *
  9. * Make test files for a class and its subdirectories.
  10. *
  11. * $ ./script/solar make-tests Vendor_Example
  12. *
  13. * Make only the Vendor_Example test (no subdirectories):
  14. *
  15. * $ solar make-tests --only Vendor_Example
  16. *
  17. * @category Solar
  18. *
  19. * @package Solar_Cli
  20. *
  21. * @author Paul M. Jones <pmjones@solarphp.com>
  22. *
  23. * @license http://opensource.org/licenses/bsd-license.php BSD
  24. *
  25. * @version $Id: MakeTests.php 4436 2010-02-25 21:38:34Z pmjones $
  26. *
  27. */
  28. class Solar_Cli_MakeTests extends Solar_Controller_Command
  29. {
  30. /**
  31. *
  32. * Skeleton templates for classes and methods.
  33. *
  34. * @var array
  35. *
  36. */
  37. protected $_tpl;
  38. /**
  39. *
  40. * The source class to work with.
  41. *
  42. * @var string
  43. *
  44. */
  45. protected $_class;
  46. /**
  47. *
  48. * The target directory for writing tests.
  49. *
  50. * @var string
  51. *
  52. */
  53. protected $_target;
  54. /**
  55. *
  56. * Name of the test file to work with.
  57. *
  58. * @var string
  59. *
  60. */
  61. protected $_file;
  62. /**
  63. *
  64. * The code in the test file.
  65. *
  66. * @var string
  67. *
  68. */
  69. protected $_code;
  70. /**
  71. *
  72. * Builds one or more test files starting at the requested class, usually
  73. * descending recursively into subdirectories of that class.
  74. *
  75. * @param string $class The class name to build tests for.
  76. *
  77. * @return void
  78. *
  79. */
  80. protected function _exec($class = null)
  81. {
  82. $this->_outln("Making tests.");
  83. // make sure we have a class to work with
  84. if (! $class) {
  85. throw $this->_exception('ERR_NO_CLASS');
  86. }
  87. // make sure we have a target directory
  88. $this->_setTarget();
  89. // get all the class and method templates
  90. $this->_loadTemplates();
  91. // build a class-to-file map object for later use
  92. $map = Solar::factory('Solar_Class_Map');
  93. // tell the user where the source and targets are
  94. $this->_outln("Source: " . $map->getBase());
  95. $this->_outln("Target: $this->_target");
  96. // get the class and file locations
  97. $class_file = $map->fetch($class);
  98. foreach ($class_file as $class => $file) {
  99. // if this is an exception class, skip it
  100. if (strpos($class, '_Exception')) {
  101. $this->_outln("$class: skip (exception class)");
  102. continue;
  103. } else {
  104. // tell the user what class we're on
  105. $this->_outln("$class");
  106. }
  107. // load the class and get its API reference
  108. $apiref = Solar::factory('Solar_Docs_Apiref');
  109. $apiref->addClass($class);
  110. $api = $apiref->api[$class];
  111. // set the file name, creating if needed
  112. $this->_setFile($class, $api);
  113. // skip adding methods on adapter classes; they should get their
  114. // methods from the parent class
  115. $pos = strrpos($class, '_Adapter_');
  116. if ($pos === false) {
  117. // get the code currently in the file
  118. $this->_code = file_get_contents($this->_file);
  119. // add new test methods
  120. $this->_addTestMethods($api);
  121. // write the file back out again
  122. file_put_contents($this->_file, $this->_code);
  123. }
  124. }
  125. // done with all classes.
  126. $this->_outln('Done.');
  127. }
  128. /**
  129. *
  130. * Loads the template array from skeleton files.
  131. *
  132. * @return void
  133. *
  134. */
  135. protected function _loadTemplates()
  136. {
  137. $this->_tpl = array();
  138. $dir = Solar_Dir::fix(dirname(__FILE__) . '/MakeTests/Data');
  139. $list = glob($dir . '*.php');
  140. foreach ($list as $file) {
  141. $key = substr(basename($file), 0, -4);
  142. $text = file_get_contents($file);
  143. if (substr($key, 0, 5) == 'class') {
  144. // we need to add the php-open tag ourselves, instead of
  145. // having it in the template file, becuase the PEAR packager
  146. // complains about parsing the skeleton code.
  147. $text = "<?php\n$text";
  148. }
  149. $this->_tpl[$key] = $text;
  150. }
  151. }
  152. /**
  153. *
  154. * Sets the base directory target.
  155. *
  156. * @return void
  157. *
  158. */
  159. protected function _setTarget()
  160. {
  161. $target = Solar::$system . "/include";
  162. $this->_target = Solar_Dir::fix($target);
  163. }
  164. /**
  165. *
  166. * Sets the file name for the test file, creating it if needed.
  167. *
  168. * Uses a different class template for abstract, factory, and normal
  169. * (concrete) classes. Also looks to see if this is an Adapter-based
  170. * class and takes that into account.
  171. *
  172. * @param string $class The class name to work with.
  173. *
  174. * @param array $api The list of methods in the class API to write test
  175. * stubs for.
  176. *
  177. * @return void
  178. *
  179. */
  180. protected function _setFile($class, $api)
  181. {
  182. $this->_file = $this->_target
  183. . str_replace('_', DIRECTORY_SEPARATOR, "Test_$class")
  184. . '.php';
  185. // create the file if needed
  186. if (file_exists($this->_file)) {
  187. return;
  188. }
  189. // create the directory if needed
  190. $dir = dirname($this->_file);
  191. if (! is_dir($dir)) {
  192. mkdir($dir, 0777, true);
  193. }
  194. // use the right code template
  195. if ($api['abstract']) {
  196. $code = $this->_tpl['classAbstract'];
  197. } elseif (in_array('Solar_Factory', $api['from'])) {
  198. $code = $this->_tpl['classFactory'];
  199. } else {
  200. $code = $this->_tpl['classConcrete'];
  201. }
  202. // use the right template for adapter abstract classes
  203. if (substr($class, -8) == '_Adapter') {
  204. $code = $this->_tpl['classAdapterAbstract'];
  205. }
  206. // use the right "extends" for adapter concrete classes
  207. $pos = strrpos($class, '_Adapter_');
  208. if ($pos === false) {
  209. // normal test extends
  210. $extends = 'Solar_Test';
  211. } else {
  212. // adapter extends: Test_Foo_Adapter_Bar extends Test_Foo_Adapter
  213. $extends = 'Test_' . substr($class, 0, $pos + 8);
  214. $code = $this->_tpl['classAdapterConcrete'];
  215. }
  216. // do replacements
  217. $code = str_replace(
  218. array('{:class}', '{:extends}'),
  219. array($class, $extends),
  220. $code
  221. );
  222. // write the file
  223. file_put_contents($this->_file, $code);
  224. }
  225. /**
  226. *
  227. * Adds test methods to the code for a test file.
  228. *
  229. * @param array $api The list of methods in the class API to write test
  230. * stubs for.
  231. *
  232. * @return void
  233. *
  234. */
  235. protected function _addTestMethods($api)
  236. {
  237. // prepare the testing code for appending new methods.
  238. $this->_code = trim($this->_code);
  239. // the last char should be a brace.
  240. $last = substr($this->_code, -1);
  241. if ($last != '}') {
  242. throw $this->_exception('ERR_LAST_BRACE', array(
  243. 'file' => $this->_file
  244. ));
  245. }
  246. // strip the last brace
  247. $this->_code = substr($this->_code, 0, -1);
  248. // ignore these methods
  249. $ignore = array('__construct', '__destruct', 'dump', 'locale');
  250. // look for methods and add them if needed
  251. foreach ($api['methods'] as $name => $info) {
  252. // is this an ignored method?
  253. if (in_array($name, $ignore)) {
  254. $this->_outln(" . $name");
  255. continue;
  256. }
  257. // is this a public method?
  258. if ($info['access'] != 'public') {
  259. $this->_outln(" . $name");
  260. continue;
  261. };
  262. // the test-method name
  263. $test_name = 'test' . ucfirst($name);
  264. // does the test-method definition already exist?
  265. $def = "function {$test_name}()";
  266. $pos = strpos($this->_code, $def);
  267. if ($pos) {
  268. $this->_outln(" . $name");
  269. continue;
  270. }
  271. // use the right code template
  272. if ($info['abstract']) {
  273. $test_code = $this->_tpl['methodAbstract'];
  274. } else {
  275. $test_code = $this->_tpl['methodConcrete'];
  276. }
  277. // do replacements
  278. $test_code = str_replace(
  279. array('{:name}', '{:summ}'),
  280. array($test_name, $info['summ']),
  281. $test_code
  282. );
  283. // append to the test code
  284. $this->_code .= $test_code;
  285. $this->_outln(" + $name");
  286. }
  287. // append the last brace
  288. $this->_code = trim($this->_code) . "\n}\n";
  289. }
  290. }