PageRenderTime 41ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/cake/console/libs/tasks/test.php

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