2008-05-30 11:40:08 +00:00
< ? php
/**
* The TestTask handles creating and updating test files .
*
* PHP versions 4 and 5
*
2009-11-06 06:46:59 +00:00
* CakePHP ( tm ) : Rapid Development Framework ( http :// cakephp . org )
* Copyright 2005 - 2009 , Cake Software Foundation , Inc . ( http :// cakefoundation . org )
2008-05-30 11:40:08 +00:00
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice .
*
2009-11-06 06:46:59 +00:00
* @ copyright Copyright 2005 - 2009 , Cake Software Foundation , Inc . ( http :// cakefoundation . org )
2009-11-06 06:00:11 +00:00
* @ link http :// cakephp . org CakePHP ( tm ) Project
2008-10-30 17:30:26 +00:00
* @ package cake
* @ subpackage cake . cake . console . libs . tasks
2009-07-17 03:55:41 +00:00
* @ since CakePHP ( tm ) v 1.3
2009-11-06 06:51:51 +00:00
* @ license MIT License ( http :// www . opensource . org / licenses / mit - license . php )
2008-05-30 11:40:08 +00:00
*/
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Task class for creating and updating test files .
*
2008-10-30 17:30:26 +00:00
* @ package cake
* @ subpackage cake . cake . console . libs . tasks
2008-05-30 11:40:08 +00:00
*/
class TestTask extends Shell {
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Name of plugin
*
* @ var string
* @ access public
2008-09-24 13:49:37 +00:00
*/
2008-05-30 11:40:08 +00:00
var $plugin = null ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* path to TESTS directory
*
* @ var string
* @ access public
*/
var $path = TESTS ;
2009-05-25 00:12:17 +00:00
2009-05-25 03:50:21 +00:00
/**
* Tasks used .
*
* @ var array
2009-11-14 12:19:25 +00:00
*/
2009-05-25 03:50:21 +00:00
var $tasks = array ( 'Template' );
2009-07-24 19:18:37 +00:00
2009-05-25 00:12:17 +00:00
/**
* class types that methods can be generated for
*
* @ var array
2009-11-14 12:19:25 +00:00
*/
2009-05-25 00:12:17 +00:00
var $classTypes = array ( 'Model' , 'Controller' , 'Component' , 'Behavior' , 'Helper' );
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Internal list of fixtures that have been added so far .
*
* @ var string
2009-11-14 12:19:25 +00:00
*/
2009-05-25 03:50:21 +00:00
var $_fixtures = array ();
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Flag for interactive mode
*
* @ var boolean
2009-11-14 12:19:25 +00:00
*/
2009-05-25 03:50:21 +00:00
var $interactive = false ;
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Execution method always used for tasks
*
* @ access public
*/
function execute () {
if ( empty ( $this -> args )) {
$this -> __interactive ();
}
2008-09-24 13:49:37 +00:00
2008-05-30 11:40:08 +00:00
if ( count ( $this -> args ) == 1 ) {
$this -> __interactive ( $this -> args [ 0 ]);
}
if ( count ( $this -> args ) > 1 ) {
2009-05-25 03:50:21 +00:00
$type = Inflector :: underscore ( $this -> args [ 0 ]);
if ( $this -> bake ( $type , $this -> args [ 1 ])) {
2008-05-30 11:40:08 +00:00
$this -> out ( 'done' );
}
}
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
* Handles interactive baking
*
* @ access private
*/
2009-05-25 00:12:17 +00:00
function __interactive ( $type = null ) {
2009-05-25 03:50:21 +00:00
$this -> interactive = true ;
2008-05-30 11:40:08 +00:00
$this -> hr ();
2009-05-25 00:12:17 +00:00
$this -> out ( __ ( 'Bake Tests' , true ));
$this -> out ( sprintf ( __ ( " Path: %s " , true ), $this -> path ));
2008-05-30 11:40:08 +00:00
$this -> hr ();
2008-09-24 13:49:37 +00:00
2009-05-25 00:12:17 +00:00
$selection = null ;
if ( $type ) {
$type = Inflector :: camelize ( $type );
2009-05-25 03:50:21 +00:00
if ( ! in_array ( $type , $this -> classTypes )) {
unset ( $type );
2008-05-30 11:40:08 +00:00
}
}
2009-05-25 03:50:21 +00:00
if ( ! $type ) {
$type = $this -> getObjectType ();
2009-05-25 00:12:17 +00:00
}
2009-05-25 03:50:21 +00:00
$className = $this -> getClassName ( $type );
return $this -> bake ( $type , $className );
2008-05-30 11:40:08 +00:00
}
2009-07-24 19:18:37 +00:00
2009-05-25 00:12:17 +00:00
/**
2009-05-25 03:50:21 +00:00
* Completes final steps for generating data to create test case .
2009-05-25 00:12:17 +00:00
*
2009-05-25 03:50:21 +00:00
* @ param string $type Type of object to bake test case for ie . Model , Controller
2009-07-24 19:18:37 +00:00
* @ param string $className the 'cake name' for the class ie . Posts for the PostsController
2009-05-25 03:50:21 +00:00
* @ access public
*/
function bake ( $type , $className ) {
if ( $this -> typeCanDetectFixtures ( $type ) && $this -> isLoadableClass ( $type , $className )) {
$this -> out ( __ ( 'Bake is detecting possible fixtures..' , true ));
$testSubject =& $this -> buildTestSubject ( $type , $className );
$this -> generateFixtureList ( $testSubject );
} elseif ( $this -> interactive ) {
$this -> getUserFixtures ();
}
$fullClassName = $this -> getRealClassName ( $type , $className );
2009-05-25 00:12:17 +00:00
2009-05-25 03:50:21 +00:00
$methods = array ();
if ( class_exists ( $fullClassName )) {
$methods = $this -> getTestableMethods ( $fullClassName );
2009-05-25 00:12:17 +00:00
}
2009-05-26 03:48:41 +00:00
$mock = $this -> hasMockClass ( $type , $fullClassName );
2009-05-25 04:54:23 +00:00
$construction = $this -> generateConstructor ( $type , $fullClassName );
$plugin = null ;
if ( $this -> plugin ) {
$plugin = $this -> plugin . '.' ;
}
2009-05-25 03:50:21 +00:00
$this -> Template -> set ( 'fixtures' , $this -> _fixtures );
2009-05-25 04:54:23 +00:00
$this -> Template -> set ( 'plugin' , $plugin );
$this -> Template -> set ( compact ( 'className' , 'methods' , 'type' , 'fullClassName' , 'mock' , 'construction' ));
2009-07-01 04:50:38 +00:00
$out = $this -> Template -> generate ( 'classes' , 'test' );
2009-05-25 03:50:21 +00:00
2009-06-07 01:03:26 +00:00
$filename = $this -> testCaseFileName ( $type , $className );
$made = $this -> createFile ( $filename , $out );
2009-05-25 03:50:21 +00:00
if ( $made ) {
return $out ;
}
return false ;
2008-05-30 11:40:08 +00:00
}
2009-07-24 19:18:37 +00:00
2008-05-30 11:40:08 +00:00
/**
2009-05-25 03:50:21 +00:00
* Interact with the user and get their chosen type . Can exit the script .
2008-05-30 11:40:08 +00:00
*
2009-05-25 03:50:21 +00:00
* @ return string Users chosen type .
2009-11-14 12:19:25 +00:00
*/
2009-05-25 03:50:21 +00:00
function getObjectType () {
$this -> hr ();
$this -> out ( __ ( " Select an object type: " , true ));
$this -> hr ();
$keys = array ();
foreach ( $this -> classTypes as $key => $option ) {
$this -> out ( ++ $key . '. ' . $option );
$keys [] = $key ;
2008-05-30 11:40:08 +00:00
}
2009-05-25 03:50:21 +00:00
$keys [] = 'q' ;
$selection = $this -> in ( __ ( " Enter the type of object to bake a test for or (q)uit " , true ), $keys , 'q' );
if ( $selection == 'q' ) {
return $this -> _stop ();
}
return $this -> classTypes [ $selection - 1 ];
}
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Get the user chosen Class name for the chosen type
*
* @ param string $objectType Type of object to list classes for i . e . Model , Controller .
* @ return string Class name the user chose .
2009-11-14 12:19:25 +00:00
*/
2009-05-25 03:50:21 +00:00
function getClassName ( $objectType ) {
2009-08-08 02:45:48 +00:00
$options = App :: objects ( strtolower ( $objectType ));
2009-05-25 03:50:21 +00:00
$this -> out ( sprintf ( __ ( 'Choose a %s class' , true ), $objectType ));
$keys = array ();
foreach ( $options as $key => $option ) {
$this -> out ( ++ $key . '. ' . $option );
$keys [] = $key ;
}
$selection = $this -> in ( __ ( 'Choose an existing class, or enter the name of a class that does not exist' , true ));
if ( isset ( $options [ $selection - 1 ])) {
return $options [ $selection - 1 ];
}
return $selection ;
}
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Checks whether the chosen type can find its own fixtures .
* Currently only model , and controller are supported
*
* @ return boolean
2009-11-14 12:19:25 +00:00
*/
2009-05-25 03:50:21 +00:00
function typeCanDetectFixtures ( $type ) {
$type = strtolower ( $type );
return ( $type == 'controller' || $type == 'model' );
}
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Check if a class with the given type is loaded or can be loaded .
*
* @ return boolean
2009-11-14 12:19:25 +00:00
*/
2009-05-25 03:50:21 +00:00
function isLoadableClass ( $type , $class ) {
return App :: import ( $type , $class );
}
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Construct an instance of the class to be tested .
2009-05-25 04:54:23 +00:00
* So that fixtures can be detected
2009-05-25 03:50:21 +00:00
*
* @ return object
2009-11-14 12:19:25 +00:00
*/
2009-05-25 03:50:21 +00:00
function & buildTestSubject ( $type , $class ) {
2009-05-26 04:36:25 +00:00
ClassRegistry :: flush ();
2009-05-25 03:50:21 +00:00
App :: import ( $type , $class );
$class = $this -> getRealClassName ( $type , $class );
if ( strtolower ( $type ) == 'model' ) {
$instance =& ClassRegistry :: init ( $class );
} else {
$instance =& new $class ();
}
return $instance ;
}
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Gets the real class name from the cake short form .
*
* @ return string Real classname
2009-11-14 12:19:25 +00:00
*/
2009-05-25 03:50:21 +00:00
function getRealClassName ( $type , $class ) {
if ( strtolower ( $type ) == 'model' ) {
return $class ;
}
return $class . $type ;
2008-05-30 11:40:08 +00:00
}
2009-07-24 19:18:37 +00:00
2009-05-24 03:48:25 +00:00
/**
* Get methods declared in the class given .
* No parent methods will be returned
*
* @ param string $className Name of class to look at .
* @ return array Array of method names .
2009-11-14 12:19:25 +00:00
*/
2009-05-24 03:48:25 +00:00
function getTestableMethods ( $className ) {
$classMethods = get_class_methods ( $className );
$parentMethods = get_class_methods ( get_parent_class ( $className ));
$thisMethods = array_diff ( $classMethods , $parentMethods );
$out = array ();
foreach ( $thisMethods as $method ) {
2009-08-02 22:17:31 +00:00
if ( substr ( $method , 0 , 1 ) != '_' && $method != strtolower ( $className )) {
2009-05-24 03:48:25 +00:00
$out [] = $method ;
}
}
return $out ;
}
2009-07-24 19:18:37 +00:00
2009-05-24 05:15:31 +00:00
/**
2009-05-24 05:30:04 +00:00
* Generate the list of fixtures that will be required to run this test based on
2009-05-24 05:15:31 +00:00
* loaded models .
*
* @ param object The object you want to generate fixtures for .
* @ return array Array of fixtures to be included in the test .
2009-11-14 12:19:25 +00:00
*/
2009-05-24 05:15:31 +00:00
function generateFixtureList ( & $subject ) {
$this -> _fixtures = array ();
if ( is_a ( $subject , 'Model' )) {
$this -> _processModel ( $subject );
} elseif ( is_a ( $subject , 'Controller' )) {
$this -> _processController ( $subject );
}
return array_values ( $this -> _fixtures );
}
2009-07-24 19:18:37 +00:00
2009-05-24 05:15:31 +00:00
/**
2009-05-24 05:30:04 +00:00
* Process a model recursively and pull out all the
2009-05-24 05:15:31 +00:00
* model names converting them to fixture names .
*
* @ return void
2009-05-25 03:50:21 +00:00
* @ access protected
2009-11-14 12:19:25 +00:00
*/
2009-05-24 05:15:31 +00:00
function _processModel ( & $subject ) {
$this -> _addFixture ( $subject -> name );
$associated = $subject -> getAssociated ();
foreach ( $associated as $alias => $type ) {
$className = $subject -> { $alias } -> name ;
if ( ! isset ( $this -> _fixtures [ $className ])) {
$this -> _processModel ( $subject -> { $alias });
}
if ( $type == 'hasAndBelongsToMany' ) {
$joinModel = Inflector :: classify ( $subject -> hasAndBelongsToMany [ $alias ][ 'joinTable' ]);
if ( ! isset ( $this -> _fixtures [ $joinModel ])) {
$this -> _processModel ( $subject -> { $joinModel });
}
}
}
}
2009-07-24 19:18:37 +00:00
2009-05-24 05:30:04 +00:00
/**
* Process all the models attached to a controller
* and generate a fixture list .
*
* @ return void
2009-05-25 03:50:21 +00:00
* @ access protected
2009-11-14 12:19:25 +00:00
*/
2009-05-24 05:30:04 +00:00
function _processController ( & $subject ) {
$subject -> constructClasses ();
$models = array ( Inflector :: classify ( $subject -> name ));
if ( ! empty ( $subject -> uses )) {
$models = $subject -> uses ;
}
foreach ( $models as $model ) {
$this -> _processModel ( $subject -> { $model });
}
}
2009-07-24 19:18:37 +00:00
2009-05-24 05:15:31 +00:00
/**
* Add classname to the fixture list .
* Sets the app . or plugin . plugin_name . prefix .
*
* @ return void
2009-05-25 03:50:21 +00:00
* @ access protected
2009-11-14 12:19:25 +00:00
*/
2009-05-24 05:15:31 +00:00
function _addFixture ( $name ) {
$parent = get_parent_class ( $name );
$prefix = 'app.' ;
if ( strtolower ( $parent ) != 'appmodel' && strtolower ( substr ( $parent , - 8 )) == 'appmodel' ) {
$pluginName = substr ( $parent , 0 , strlen ( $parent ) - 8 );
$prefix = 'plugin.' . Inflector :: underscore ( $pluginName ) . '.' ;
}
$fixture = $prefix . Inflector :: underscore ( $name );
$this -> _fixtures [ $name ] = $fixture ;
}
2009-07-24 19:18:37 +00:00
2009-05-25 03:50:21 +00:00
/**
* Interact with the user to get additional fixtures they want to use .
*
* @ return void
2009-11-14 12:19:25 +00:00
*/
2009-05-25 03:50:21 +00:00
function getUserFixtures () {
$proceed = $this -> in ( __ ( 'Bake could not detect fixtures, would you like to add some?' , true ), array ( 'y' , 'n' ), 'n' );
$fixtures = array ();
if ( strtolower ( $proceed ) == 'y' ) {
$fixtureList = $this -> in ( __ ( " Please provide a comma separated list of the fixtures names you'd like to use. \n Example: 'app.comment, app.post, plugin.forums.post' " , true ));
$fixtureListTrimmed = str_replace ( ' ' , '' , $fixtureList );
$fixtures = explode ( ',' , $fixtureListTrimmed );
}
$this -> _fixtures = array_merge ( $this -> _fixtures , $fixtures );
return $fixtures ;
}
2009-07-24 19:18:37 +00:00
2009-05-25 04:54:23 +00:00
/**
2009-05-26 03:48:41 +00:00
* Is a mock class required for this type of test ?
* Controllers require a mock class .
2009-05-25 04:54:23 +00:00
*
2009-05-26 03:48:41 +00:00
* @ return boolean
2009-11-14 12:19:25 +00:00
*/
2009-05-26 03:48:41 +00:00
function hasMockClass ( $type ) {
$type = strtolower ( $type );
return $type == 'controller' ;
2009-05-25 04:54:23 +00:00
}
2009-07-24 19:18:37 +00:00
2009-05-25 04:54:23 +00:00
/**
* Generate a constructor code snippet for the type and classname
*
* @ return string Constructor snippet for the thing you are building .
2009-11-14 12:19:25 +00:00
*/
2009-05-25 04:54:23 +00:00
function generateConstructor ( $type , $fullClassName ) {
$type = strtolower ( $type );
if ( $type == 'model' ) {
2009-05-25 05:02:59 +00:00
return " ClassRegistry::init(' $fullClassName '); \n " ;
2009-05-25 04:54:23 +00:00
}
if ( $type == 'controller' ) {
2009-08-06 00:45:54 +00:00
$className = substr ( $fullClassName , 0 , strlen ( $fullClassName ) - 10 );
return " new Test $fullClassName (); \n \t \t \$ this-> { $className } ->constructClasses(); \n " ;
2009-05-25 04:54:23 +00:00
}
2009-05-25 05:02:59 +00:00
return " new $fullClassName () \n " ;
2009-05-25 04:54:23 +00:00
}
2009-07-24 19:18:37 +00:00
2009-06-07 01:03:26 +00:00
/**
* make the filename for the test case . resolve the suffixes for controllers
* and get the plugin path if needed .
*
* @ return string filename the test should be created on
2009-11-14 12:19:25 +00:00
*/
2009-06-07 01:03:26 +00:00
function testCaseFileName ( $type , $className ) {
$path = $this -> path ;
if ( isset ( $this -> plugin )) {
$path = $this -> _pluginPath ( $this -> plugin ) . 'tests' . DS ;
}
$path .= 'cases' . DS . Inflector :: tableize ( $type ) . DS ;
if ( strtolower ( $type ) == 'controller' ) {
$className = $this -> getRealClassName ( $type , $className );
}
return $path . Inflector :: underscore ( $className ) . '.test.php' ;
}
2009-08-17 01:32:10 +00:00
/**
* Show help file .
*
* @ return void
2009-11-14 12:19:25 +00:00
*/
2009-08-17 01:32:10 +00:00
function help () {
$this -> hr ();
$this -> out ( " Usage: cake bake test <type> <class> " );
$this -> hr ();
$this -> out ( 'Commands:' );
$this -> out ( " " );
$this -> out ( " test model post \n \t bakes a test case for the post model. " );
$this -> out ( " " );
$this -> out ( " test controller comments \n \t bakes a test case for the comments controller. " );
$this -> out ( " " );
$this -> out ( 'Arguments:' );
$this -> out ( " \t <type> Can be any of the following 'controller', 'model', 'helper', \n \t 'component', 'behavior'. " );
$this -> out ( " \t <class> Any existing class for the chosen type. " );
$this -> out ( " " );
$this -> out ( " Parameters: " );
$this -> out ( " \t -plugin CamelCased name of plugin to bake tests for. " );
$this -> out ( " " );
$this -> _stop ();
}
2008-05-30 11:40:08 +00:00
}
?>