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

/code/ryzom/tools/server/www/webtt/cake/console/libs/tasks/test.php

https://bitbucket.org/mattraykowski/ryzomcore_demoshard
PHP | 469 lines | 281 code | 30 blank | 158 comment | 44 complexity | 9056b7e34d3c512f8f667cd74892bc58 MD5 | raw file
Possible License(s): AGPL-3.0, GPL-3.0, LGPL-2.1
  1. <?php
  2. /**
  3. * The TestTask handles creating and updating test files.
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice.
  12. *
  13. * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package cake
  16. * @subpackage cake.cake.console.libs.tasks
  17. * @since CakePHP(tm) v 1.3
  18. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  19. */
  20. include_once dirname(__FILE__) . DS . 'bake.php';
  21. /**
  22. * Task class for creating and updating test files.
  23. *
  24. * @package cake
  25. * @subpackage cake.cake.console.libs.tasks
  26. */
  27. class TestTask extends BakeTask {
  28. /**
  29. * path to TESTS directory
  30. *
  31. * @var string
  32. * @access public
  33. */
  34. var $path = TESTS;
  35. /**
  36. * Tasks used.
  37. *
  38. * @var array
  39. * @access public
  40. */
  41. var $tasks = array('Template');
  42. /**
  43. * class types that methods can be generated for
  44. *
  45. * @var array
  46. * @access public
  47. */
  48. var $classTypes = array('Model', 'Controller', 'Component', 'Behavior', 'Helper');
  49. /**
  50. * Internal list of fixtures that have been added so far.
  51. *
  52. * @var string
  53. * @access protected
  54. */
  55. var $_fixtures = array();
  56. /**
  57. * Execution method always used for tasks
  58. *
  59. * @access public
  60. */
  61. function execute() {
  62. if (empty($this->args)) {
  63. $this->__interactive();
  64. }
  65. if (count($this->args) == 1) {
  66. $this->__interactive($this->args[0]);
  67. }
  68. if (count($this->args) > 1) {
  69. $type = Inflector::underscore($this->args[0]);
  70. if ($this->bake($type, $this->args[1])) {
  71. $this->out('done');
  72. }
  73. }
  74. }
  75. /**
  76. * Handles interactive baking
  77. *
  78. * @access private
  79. */
  80. function __interactive($type = null) {
  81. $this->interactive = true;
  82. $this->hr();
  83. $this->out(__('Bake Tests', true));
  84. $this->out(sprintf(__("Path: %s", true), $this->path));
  85. $this->hr();
  86. if ($type) {
  87. $type = Inflector::camelize($type);
  88. if (!in_array($type, $this->classTypes)) {
  89. $this->error(sprintf('Incorrect type provided. Please choose one of %s', implode(', ', $this->classTypes)));
  90. }
  91. } else {
  92. $type = $this->getObjectType();
  93. }
  94. $className = $this->getClassName($type);
  95. return $this->bake($type, $className);
  96. }
  97. /**
  98. * Completes final steps for generating data to create test case.
  99. *
  100. * @param string $type Type of object to bake test case for ie. Model, Controller
  101. * @param string $className the 'cake name' for the class ie. Posts for the PostsController
  102. * @access public
  103. */
  104. function bake($type, $className) {
  105. if ($this->typeCanDetectFixtures($type) && $this->isLoadableClass($type, $className)) {
  106. $this->out(__('Bake is detecting possible fixtures..', true));
  107. $testSubject =& $this->buildTestSubject($type, $className);
  108. $this->generateFixtureList($testSubject);
  109. } elseif ($this->interactive) {
  110. $this->getUserFixtures();
  111. }
  112. $fullClassName = $this->getRealClassName($type, $className);
  113. $methods = array();
  114. if (class_exists($fullClassName)) {
  115. $methods = $this->getTestableMethods($fullClassName);
  116. }
  117. $mock = $this->hasMockClass($type, $fullClassName);
  118. $construction = $this->generateConstructor($type, $fullClassName);
  119. $plugin = null;
  120. if ($this->plugin) {
  121. $plugin = $this->plugin . '.';
  122. }
  123. $this->Template->set('fixtures', $this->_fixtures);
  124. $this->Template->set('plugin', $plugin);
  125. $this->Template->set(compact('className', 'methods', 'type', 'fullClassName', 'mock', 'construction'));
  126. $out = $this->Template->generate('classes', 'test');
  127. $filename = $this->testCaseFileName($type, $className);
  128. $made = $this->createFile($filename, $out);
  129. if ($made) {
  130. return $out;
  131. }
  132. return false;
  133. }
  134. /**
  135. * Interact with the user and get their chosen type. Can exit the script.
  136. *
  137. * @return string Users chosen type.
  138. * @access public
  139. */
  140. function getObjectType() {
  141. $this->hr();
  142. $this->out(__("Select an object type:", true));
  143. $this->hr();
  144. $keys = array();
  145. foreach ($this->classTypes as $key => $option) {
  146. $this->out(++$key . '. ' . $option);
  147. $keys[] = $key;
  148. }
  149. $keys[] = 'q';
  150. $selection = $this->in(__("Enter the type of object to bake a test for or (q)uit", true), $keys, 'q');
  151. if ($selection == 'q') {
  152. return $this->_stop();
  153. }
  154. return $this->classTypes[$selection - 1];
  155. }
  156. /**
  157. * Get the user chosen Class name for the chosen type
  158. *
  159. * @param string $objectType Type of object to list classes for i.e. Model, Controller.
  160. * @return string Class name the user chose.
  161. * @access public
  162. */
  163. function getClassName($objectType) {
  164. $type = strtolower($objectType);
  165. if ($this->plugin) {
  166. $path = Inflector::pluralize($type);
  167. if ($type === 'helper') {
  168. $path = 'views' . DS . $path;
  169. } elseif ($type === 'component') {
  170. $path = 'controllers' . DS . $path;
  171. } elseif ($type === 'behavior') {
  172. $path = 'models' . DS . $path;
  173. }
  174. $options = App::objects($type, App::pluginPath($this->plugin) . $path, false);
  175. } else {
  176. $options = App::objects($type);
  177. }
  178. $this->out(sprintf(__('Choose a %s class', true), $objectType));
  179. $keys = array();
  180. foreach ($options as $key => $option) {
  181. $this->out(++$key . '. ' . $option);
  182. $keys[] = $key;
  183. }
  184. $selection = $this->in(__('Choose an existing class, or enter the name of a class that does not exist', true));
  185. if (isset($options[$selection - 1])) {
  186. return $options[$selection - 1];
  187. }
  188. return $selection;
  189. }
  190. /**
  191. * Checks whether the chosen type can find its own fixtures.
  192. * Currently only model, and controller are supported
  193. *
  194. * @param string $type The Type of object you are generating tests for eg. controller
  195. * @param string $className the Classname of the class the test is being generated for.
  196. * @return boolean
  197. * @access public
  198. */
  199. function typeCanDetectFixtures($type) {
  200. $type = strtolower($type);
  201. return ($type == 'controller' || $type == 'model');
  202. }
  203. /**
  204. * Check if a class with the given type is loaded or can be loaded.
  205. *
  206. * @param string $type The Type of object you are generating tests for eg. controller
  207. * @param string $className the Classname of the class the test is being generated for.
  208. * @return boolean
  209. * @access public
  210. */
  211. function isLoadableClass($type, $class) {
  212. return App::import($type, $class);
  213. }
  214. /**
  215. * Construct an instance of the class to be tested.
  216. * So that fixtures can be detected
  217. *
  218. * @param string $type The Type of object you are generating tests for eg. controller
  219. * @param string $class the Classname of the class the test is being generated for.
  220. * @return object And instance of the class that is going to be tested.
  221. * @access public
  222. */
  223. function &buildTestSubject($type, $class) {
  224. ClassRegistry::flush();
  225. App::import($type, $class);
  226. $class = $this->getRealClassName($type, $class);
  227. if (strtolower($type) == 'model') {
  228. $instance =& ClassRegistry::init($class);
  229. } else {
  230. $instance =& new $class();
  231. }
  232. return $instance;
  233. }
  234. /**
  235. * Gets the real class name from the cake short form.
  236. *
  237. * @param string $type The Type of object you are generating tests for eg. controller
  238. * @param string $class the Classname of the class the test is being generated for.
  239. * @return string Real classname
  240. * @access public
  241. */
  242. function getRealClassName($type, $class) {
  243. if (strtolower($type) == 'model') {
  244. return $class;
  245. }
  246. return $class . $type;
  247. }
  248. /**
  249. * Get methods declared in the class given.
  250. * No parent methods will be returned
  251. *
  252. * @param string $className Name of class to look at.
  253. * @return array Array of method names.
  254. * @access public
  255. */
  256. function getTestableMethods($className) {
  257. $classMethods = get_class_methods($className);
  258. $parentMethods = get_class_methods(get_parent_class($className));
  259. $thisMethods = array_diff($classMethods, $parentMethods);
  260. $out = array();
  261. foreach ($thisMethods as $method) {
  262. if (substr($method, 0, 1) != '_' && $method != strtolower($className)) {
  263. $out[] = $method;
  264. }
  265. }
  266. return $out;
  267. }
  268. /**
  269. * Generate the list of fixtures that will be required to run this test based on
  270. * loaded models.
  271. *
  272. * @param object $subject The object you want to generate fixtures for.
  273. * @return array Array of fixtures to be included in the test.
  274. * @access public
  275. */
  276. function generateFixtureList(&$subject) {
  277. $this->_fixtures = array();
  278. if (is_a($subject, 'Model')) {
  279. $this->_processModel($subject);
  280. } elseif (is_a($subject, 'Controller')) {
  281. $this->_processController($subject);
  282. }
  283. return array_values($this->_fixtures);
  284. }
  285. /**
  286. * Process a model recursively and pull out all the
  287. * model names converting them to fixture names.
  288. *
  289. * @param Model $subject A Model class to scan for associations and pull fixtures off of.
  290. * @return void
  291. * @access protected
  292. */
  293. function _processModel(&$subject) {
  294. $this->_addFixture($subject->name);
  295. $associated = $subject->getAssociated();
  296. foreach ($associated as $alias => $type) {
  297. $className = $subject->{$alias}->name;
  298. if (!isset($this->_fixtures[$className])) {
  299. $this->_processModel($subject->{$alias});
  300. }
  301. if ($type == 'hasAndBelongsToMany') {
  302. $joinModel = Inflector::classify($subject->hasAndBelongsToMany[$alias]['joinTable']);
  303. if (!isset($this->_fixtures[$joinModel])) {
  304. $this->_processModel($subject->{$joinModel});
  305. }
  306. }
  307. }
  308. }
  309. /**
  310. * Process all the models attached to a controller
  311. * and generate a fixture list.
  312. *
  313. * @param Controller $subject A controller to pull model names off of.
  314. * @return void
  315. * @access protected
  316. */
  317. function _processController(&$subject) {
  318. $subject->constructClasses();
  319. $models = array(Inflector::classify($subject->name));
  320. if (!empty($subject->uses)) {
  321. $models = $subject->uses;
  322. }
  323. foreach ($models as $model) {
  324. $this->_processModel($subject->{$model});
  325. }
  326. }
  327. /**
  328. * Add classname to the fixture list.
  329. * Sets the app. or plugin.plugin_name. prefix.
  330. *
  331. * @param string $name Name of the Model class that a fixture might be required for.
  332. * @return void
  333. * @access protected
  334. */
  335. function _addFixture($name) {
  336. $parent = get_parent_class($name);
  337. $prefix = 'app.';
  338. if (strtolower($parent) != 'appmodel' && strtolower(substr($parent, -8)) == 'appmodel') {
  339. $pluginName = substr($parent, 0, strlen($parent) -8);
  340. $prefix = 'plugin.' . Inflector::underscore($pluginName) . '.';
  341. }
  342. $fixture = $prefix . Inflector::underscore($name);
  343. $this->_fixtures[$name] = $fixture;
  344. }
  345. /**
  346. * Interact with the user to get additional fixtures they want to use.
  347. *
  348. * @return array Array of fixtures the user wants to add.
  349. * @access public
  350. */
  351. function getUserFixtures() {
  352. $proceed = $this->in(__('Bake could not detect fixtures, would you like to add some?', true), array('y','n'), 'n');
  353. $fixtures = array();
  354. if (strtolower($proceed) == 'y') {
  355. $fixtureList = $this->in(__("Please provide a comma separated list of the fixtures names you'd like to use.\nExample: 'app.comment, app.post, plugin.forums.post'", true));
  356. $fixtureListTrimmed = str_replace(' ', '', $fixtureList);
  357. $fixtures = explode(',', $fixtureListTrimmed);
  358. }
  359. $this->_fixtures = array_merge($this->_fixtures, $fixtures);
  360. return $fixtures;
  361. }
  362. /**
  363. * Is a mock class required for this type of test?
  364. * Controllers require a mock class.
  365. *
  366. * @param string $type The type of object tests are being generated for eg. controller.
  367. * @return boolean
  368. * @access public
  369. */
  370. function hasMockClass($type) {
  371. $type = strtolower($type);
  372. return $type == 'controller';
  373. }
  374. /**
  375. * Generate a constructor code snippet for the type and classname
  376. *
  377. * @param string $type The Type of object you are generating tests for eg. controller
  378. * @param string $className the Classname of the class the test is being generated for.
  379. * @return string Constructor snippet for the thing you are building.
  380. * @access public
  381. */
  382. function generateConstructor($type, $fullClassName) {
  383. $type = strtolower($type);
  384. if ($type == 'model') {
  385. return "ClassRegistry::init('$fullClassName');\n";
  386. }
  387. if ($type == 'controller') {
  388. $className = substr($fullClassName, 0, strlen($fullClassName) - 10);
  389. return "new Test$fullClassName();\n\t\t\$this->{$className}->constructClasses();\n";
  390. }
  391. return "new $fullClassName();\n";
  392. }
  393. /**
  394. * Make the filename for the test case. resolve the suffixes for controllers
  395. * and get the plugin path if needed.
  396. *
  397. * @param string $type The Type of object you are generating tests for eg. controller
  398. * @param string $className the Classname of the class the test is being generated for.
  399. * @return string filename the test should be created on.
  400. * @access public
  401. */
  402. function testCaseFileName($type, $className) {
  403. $path = $this->getPath();;
  404. $path .= 'cases' . DS . strtolower($type) . 's' . DS;
  405. if (strtolower($type) == 'controller') {
  406. $className = $this->getRealClassName($type, $className);
  407. }
  408. return $path . Inflector::underscore($className) . '.test.php';
  409. }
  410. /**
  411. * Show help file.
  412. *
  413. * @return void
  414. * @access public
  415. */
  416. function help() {
  417. $this->hr();
  418. $this->out("Usage: cake bake test <type> <class>");
  419. $this->hr();
  420. $this->out('Commands:');
  421. $this->out("");
  422. $this->out("test model post\n\tbakes a test case for the post model.");
  423. $this->out("");
  424. $this->out("test controller comments\n\tbakes a test case for the comments controller.");
  425. $this->out("");
  426. $this->out('Arguments:');
  427. $this->out("\t<type> Can be any of the following 'controller', 'model', 'helper',\n\t'component', 'behavior'.");
  428. $this->out("\t<class> Any existing class for the chosen type.");
  429. $this->out("");
  430. $this->out("Parameters:");
  431. $this->out("\t-plugin CamelCased name of plugin to bake tests for.");
  432. $this->out("");
  433. $this->_stop();
  434. }
  435. }