Added Kohana v3.0.9

This commit is contained in:
Deon George
2011-01-14 01:49:56 +11:00
parent fe11dd5f51
commit b6e9961846
520 changed files with 54728 additions and 0 deletions

View File

@@ -0,0 +1,70 @@
# Kohana-PHPUnit integration
This module integrates PHPUnit with Kohana.
If you look through any of the tests provided in this module you'll probably notice all theHorribleCamelCase.
I've chosen to do this because it's part of the PHPUnit coding conventions and is required for certain features such as auto documentation.
## Requirements
* [PHPUnit](http://www.phpunit.de/) >= 3.4
### Optional extras
* The [Archive module](http://github.com/BRMatt/kohana-archive) is required if you want to download code coverage reports from the web ui, however you can also view them without downloading.
## Installation
**Step 0**: Download this module!
To get it from git execute the following command in the root of your project:
$ git submodule add git://github.com/kohana/unittest.git modules/unittest
And watch the gitorious magic...
Of course, you can always download the code from the [github project](http://github.com/kohana/unittest) as an archive.
The following instructions will assume you've moved it to `modules/unittest`, if you haven't then you should update all paths accordingly.
**Step 1**: Enable this module in your bootstrap file:
/**
* Enable modules. Modules are referenced by a relative or absolute path.
*/
Kohana::modules(array(
'unittest' => MODPATH.'unittest', // PHPUnit integration
));
**Step 2**: In your app's bootstrap file modify the lines where the request is handled, which by default looks like:
/**
* Execute the main request using PATH_INFO. If no URI source is specified,
* the URI will be automatically detected.
*/
echo Request::instance($_SERVER['PATH_INFO'])
->execute()
->send_headers()
->response;
To:
if ( ! defined('SUPPRESS_REQUEST'))
{
/**
* Execute the main request using PATH_INFO. If no URI source is specified,
* the URI will be automatically detected.
*/
echo Request::instance($_SERVER['PATH_INFO'])
->execute()
->send_headers()
->response;
}
**Step 3**: Create a folder called `unittest` in your app's cache dir (`APPPATH/cache`). If you don't want to use this path for storing generated reports, skip this step and change the config file.
Note: make sure the settings in `config/unittest.php` are correct for your environment. If they aren't, copy the file to `application/config/unittest.php` and change the values accordingly.
**Step 4**: Start testing!
You can find more info and tutorials in the [guide/](http://github.com/kohana/unittest/tree/master/guide/) directory.

View File

@@ -0,0 +1,115 @@
<?php
/**
* The directory in which your application specific resources are located.
* The application directory must contain the bootstrap.php file.
*
* @see http://kohanaframework.org/guide/about.install#application
*/
$application = 'application';
/**
* The directory in which your modules are located.
*
* @see http://kohanaframework.org/guide/about.install#modules
*/
$modules = 'modules';
/**
* The directory in which the Kohana resources are located. The system
* directory must contain the classes/kohana.php file.
*
* @see http://kohanaframework.org/guide/about.install#system
*/
$system = 'system';
/**
* The default extension of resource files. If you change this, all resources
* must be renamed to use the new extension.
*
* @see http://kohanaframework.org/guide/about.install#ext
*/
define('EXT', '.php');
/**
* Set the PHP error reporting level. If you set this in php.ini, you remove this.
* @see http://php.net/error_reporting
*
* When developing your application, it is highly recommended to enable notices
* and strict warnings. Enable them by using: E_ALL | E_STRICT
*
* In a production environment, it is safe to ignore notices and strict warnings.
* Disable them by using: E_ALL ^ E_NOTICE
*
* When using a legacy application with PHP >= 5.3, it is recommended to disable
* deprecated notices. Disable with: E_ALL & ~E_DEPRECATED
*/
error_reporting(E_ALL | E_STRICT);
/**
* End of standard configuration! Changing any of the code below should only be
* attempted by those with a working knowledge of Kohana internals.
*
* @see http://kohanaframework.org/guide/using.configuration
*/
// Set the full path to the docroot
define('DOCROOT', realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR);
// Make the application relative to the docroot
if ( ! is_dir($application) AND is_dir(DOCROOT.$application))
$application = DOCROOT.$application;
// Make the modules relative to the docroot
if ( ! is_dir($modules) AND is_dir(DOCROOT.$modules))
$modules = DOCROOT.$modules;
// Make the system relative to the docroot
if ( ! is_dir($system) AND is_dir(DOCROOT.$system))
$system = DOCROOT.$system;
// Define the absolute paths for configured directories
define('APPPATH', realpath($application).DIRECTORY_SEPARATOR);
define('MODPATH', realpath($modules).DIRECTORY_SEPARATOR);
define('SYSPATH', realpath($system).DIRECTORY_SEPARATOR);
// Clean up the configuration vars
unset($application, $modules, $system);
/**
* Define the start time of the application, used for profiling.
*/
if ( ! defined('KOHANA_START_TIME'))
{
define('KOHANA_START_TIME', microtime(TRUE));
}
/**
* Define the memory usage at the start of the application, used for profiling.
*/
if ( ! defined('KOHANA_START_MEMORY'))
{
define('KOHANA_START_MEMORY', memory_get_usage());
}
// Load the base, low-level functions
require SYSPATH.'base'.EXT;
// Load the core Kohana class
require SYSPATH.'classes/kohana/core'.EXT;
if (is_file(APPPATH.'classes/kohana'.EXT))
{
// Application extends the core
require APPPATH.'classes/kohana'.EXT;
}
else
{
// Load empty core extension
require SYSPATH.'classes/kohana'.EXT;
}
// Bootstrap the application
require APPPATH.'bootstrap'.EXT;
// Enable the unittest module
Kohana::modules(Kohana::modules() + array('unittest' => MODPATH.'unittest'));

View File

@@ -0,0 +1,359 @@
<?php defined('SYSPATH') or die ('No direct script access.');
/**
* PHPUnit Kohana web based test runner
*
* @package Kohana/Unittest
* @author Kohana Team
* @author BRMatt <matthew@sigswitch.com>
* @author Paul Banks
* @copyright (c) 2008-2009 Kohana Team
* @license http://kohanaphp.com/license
*/
Class Controller_UnitTest extends Controller_Template
{
/**
* Whether the archive module is available
* @var boolean
*/
protected $cc_archive_available = FALSE;
/**
* Unittest config
* @var Kohana_Config
*/
protected $config = NULL;
/**
* The uri by which the report uri will be executed
* @var string
*/
protected $report_uri = '';
/**
* The uri by which the run action will be executed
* @var string
*/
protected $run_uri = '';
/**
* Is the XDEBUG extension loaded?
* @var boolean
*/
protected $xdebug_loaded = FALSE;
/**
* Template
* @var string
*/
public $template = 'unittest/layout';
/**
* Loads test suite
*/
public function before()
{
parent::before();
if ( ! Kohana_Tests::enabled())
{
// Pretend this is a normal 404 error...
$this->status = 404;
throw new Kohana_Request_Exception('Unable to find a route to match the URI: :uri',
array(':uri' => $this->request->uri));
}
// Prevent the whitelist from being autoloaded, but allow the blacklist
// to be loaded
Kohana_Tests::configure_environment(FALSE);
$this->config = Kohana::config('unittest');
// This just stops some very very long lines
$route = Route::get('unittest');
$this->report_uri = $route->uri(array('action' => 'report'));
$this->run_uri = $route->uri(array('action' => 'run'));
// Switch used to disable cc settings
$this->xdebug_loaded = extension_loaded('xdebug');
$this->cc_archive_enabled = class_exists('Archive');
Kohana_View::set_global('xdebug_enabled', $this->xdebug_loaded);
Kohana_View::set_global('cc_archive_enabled', $this->cc_archive_enabled);
}
/**
* Handles index page for /unittest/ and /unittest/index/
*/
public function action_index()
{
$this->template->body = View::factory('unittest/index')
->set('run_uri', $this->run_uri)
->set('report_uri', $this->report_uri)
->set('whitelistable_items', $this->get_whitelistable_items())
->set('groups', $this->get_groups_list(Kohana_Tests::suite()));
}
/**
* Handles report generation
*/
public function action_report()
{
// Fairly foolproof
if ( ! $this->config->cc_report_path AND ! class_exists('Archive'))
{
throw new Kohana_Exception('Cannot generate report');
}
// We don't want to use the HTML layout, we're sending the user 100111011100110010101100
$this->auto_render = FALSE;
$suite = Kohana_Tests::suite();
$temp_path = rtrim($this->config->temp_path, '/').'/';
$group = (array) Arr::get($_GET, 'group', array());
// Stop unittest from interpretting "all groups" as "no groups"
if (empty($group) OR empty($group[0]))
{
$group = array();
}
if (Arr::get($_GET, 'use_whitelist', FALSE))
{
$this->whitelist(Arr::get($_GET, 'whitelist', array()));
}
$runner = new Kohana_Unittest_Runner($suite);
// If the user wants to download a report
if ($this->cc_archive_enabled AND Arr::get($_GET, 'archive') === '1')
{
// $report is the actual directory of the report,
// $folder is the name component of directory
list($report, $folder) = $runner->generate_report($group, $temp_path);
$archive = Archive::factory('zip');
// TODO: Include the test results?
$archive->add($report, 'report', TRUE);
$filename = $folder.'.zip';
$archive->save($temp_path.$filename);
// It'd be nice to clear up afterwards but by deleting the report dir we corrupt the archive
// And once the archive has been sent to the user Request stops the script so we can't delete anything
// It'll be up to the user to delete files periodically
$this->request->send_file($temp_path.$filename, $filename);
}
else
{
$folder = trim($this->config->cc_report_path, '/').'/';
$path = DOCROOT.$folder;
if ( ! file_exists($path))
{
throw new Kohana_Exception('Report directory :dir does not exist', array(':dir' => $path));
}
if ( ! is_writable($path))
{
throw new Kohana_Exception('Script doesn\'t have permission to write to report dir :dir ', array(':dir' => $path));
}
$runner->generate_report($group, $path, FALSE);
$this->request->redirect(URL::base(FALSE, TRUE).$folder.'index.html');
}
}
/**
* Handles test running interface
*/
public function action_run()
{
$this->template->body = View::factory('unittest/results');
// Get the test suite and work out which groups we're testing
$suite = Kohana_Tests::suite();
$group = (array) Arr::get($_GET, 'group', array());
// Stop phpunit from interpretting "all groups" as "no groups"
if (empty($group) OR empty($group[0]))
{
$group = array();
}
// Only collect code coverage if the user asked for it
$collect_cc = (bool) Arr::get($_GET, 'collect_cc', FALSE);
if ($collect_cc AND Arr::get($_GET, 'use_whitelist', FALSE))
{
$whitelist = $this->whitelist(Arr::get($_GET, 'whitelist', array()));
}
$runner = new Kohana_Unittest_Runner($suite);
try
{
$runner->run($group, $collect_cc);
if ($collect_cc)
{
$this->template->body->set('coverage', $runner->calculate_cc_percentage());
}
if (isset($whitelist))
{
$this->template->body->set('coverage_explanation', $this->nice_whitelist_explanation($whitelist));
}
}
catch(Kohana_Exception $e)
{
// Code coverage is not allowed, possibly xdebug disabled?
// TODO: Tell the user this?
$runner->run($group);
}
// Show some results
$this->template->body
->set('results', $runner->results)
->set('totals', $runner->totals)
->set('time', $this->nice_time($runner->time))
// Sets group to the currently selected group, or default all groups
->set('group', Arr::get($this->get_groups_list($suite), reset($group), 'All groups'))
->set('groups', $this->get_groups_list($suite))
->set('report_uri', $this->report_uri.url::query())
// Whitelist related stuff
->set('whitelistable_items', $this->get_whitelistable_items())
->set('whitelisted_items', isset($whitelist) ? array_keys($whitelist) : array())
->set('whitelist', ! empty($whitelist));
}
/**
* Get the list of groups from the test suite, sorted with 'All groups' prefixed
*
* @return array Array of groups in the test suite
*/
protected function get_groups_list($suite)
{
// Make groups aray suitable for drop down
$groups = $suite->getGroups();
if (count($groups) > 0)
{
sort($groups);
$groups = array_combine($groups, $groups);
}
return array('' => 'All Groups') + $groups;
}
/**
* Gets a list of items that are whitelistable
*
* @return array
*/
protected function get_whitelistable_items()
{
static $whitelist;
if (count($whitelist))
{
return $whitelist;
}
$whitelist = array();
$whitelist['k_app'] = 'Application';
$k_modules = array_keys(Kohana::modules());
$whitelist += array_map('ucfirst', array_combine($k_modules, $k_modules));
$whitelist['k_sys'] = 'Kohana Core';
return $whitelist;
}
/**
* Whitelists a specified set of modules specified by the user
*
* @param array $modules
*/
protected function whitelist(array $modules)
{
$k_modules = Kohana::modules();
$whitelist = array();
// Make sure our whitelist is valid
foreach ($modules as $item)
{
if (isset($k_modules[$item]))
{
$whitelist[$item] = $k_modules[$item];
}
elseif ($item === 'k_app')
{
$whitelist[$item] = APPPATH;
}
elseif ($item === 'k_sys')
{
$whitelist[$item] = SYSPATH;
}
}
if (count($whitelist))
{
Kohana_Tests::whitelist($whitelist);
}
return $whitelist;
}
/**
* Prettifies the list of whitelisted modules
*
* @param array Array of whitelisted items
* @return string
*/
protected function nice_whitelist_explanation(array $whitelist)
{
$items = array_intersect_key($this->get_whitelistable_items(), $whitelist);
return implode(', ', $items);
}
protected function nice_time($time)
{
$parts = array();
if ($time > DATE::DAY)
{
$parts[] = floor($time/DATE::DAY).'d';
$time = $time % DATE::DAY;
}
if ($time > DATE::HOUR)
{
$parts[] = floor($time/DATE::HOUR).'h';
$time = $time % DATE::HOUR;
}
if ($time > DATE::MINUTE)
{
$parts[] = floor($time/DATE::MINUTE).'m';
$time = $time % DATE::MINUTE;
}
if ($time > 0)
{
$parts[] = round($time, 1).'s';
}
return implode(' ', $parts);
}
} // End Controller_PHPUnit

View File

@@ -0,0 +1,335 @@
<?php
/**
* PHPUnit testsuite for kohana application
*
* @package Kohana/Unittest
* @author Kohana Team
* @author BRMatt <matthew@sigswitch.com>
* @author Paul Banks
* @copyright (c) 2008-2009 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Tests
{
static protected $cache = array();
/**
* Flag to identify whether the installed version of phpunit
* is greater than or equal to 3.5
* @var boolean
*/
static protected $phpunit_v35 = FALSE;
/**
* Loads test files if they cannot be found by kohana
* @param <type> $class
*/
static function autoload($class)
{
$file = str_replace('_', '/', $class);
if ($file = Kohana::find_file('tests', $file))
{
require_once $file;
}
}
/**
* Configures the environment for testing
*
* Does the following:
*
* * Loads the phpunit framework (for the web ui)
* * Restores exception phpunit error handlers (for cli)
* * registeres an autoloader to load test files
*/
static public function configure_environment($do_whitelist = TRUE, $do_blacklist = TRUE)
{
// During a webui request we need to manually load PHPUnit
if ( ! class_exists('PHPUnit_Util_Filter', FALSE) AND ! function_exists('phpunit_autoload'))
{
try
{
include_once 'PHPUnit/Autoload.php';
}
catch (ErrorException $e)
{
include_once 'PHPUnit/Framework.php';
}
}
// Allow PHPUnit to handle exceptions and errors
if (Kohana::$is_cli)
{
restore_exception_handler();
restore_error_handler();
}
spl_autoload_register(array('Kohana_Tests', 'autoload'));
Kohana_Tests::$cache = (($cache = Kohana::cache('unittest_whitelist_cache')) === NULL) ? array() : $cache;
// As of PHPUnit v3.5 there are slight differences in the way files are black|whitelisted
self::$phpunit_v35 = function_exists('phpunit_autoload');
$config = Kohana::config('unittest');
if ($do_whitelist AND $config->use_whitelist)
{
self::whitelist();
}
if ($do_blacklist AND count($config['blacklist']))
{
Kohana_Tests::blacklist($config->blacklist);
}
}
/**
* Helper function to see if unittest is enabled in the config
*
* @return boolean
*/
static function enabled()
{
$p_environment = Kohana::config('unittest.environment');
$k_environment = Kohana::$environment;
return (is_array($p_environment) AND in_array($k_environment, $p_environment))
OR
($k_environment === $p_environment);
}
/**
* Creates the test suite for kohana
*
* @return PHPUnit_Framework_TestSuite
*/
static function suite()
{
static $suite = NULL;
if ($suite instanceof PHPUnit_Framework_TestSuite)
{
return $suite;
}
$files = Kohana::list_files('tests');
$suite = new PHPUnit_Framework_TestSuite;
self::addTests($suite, $files);
return $suite;
}
/**
* Add files to test suite $suite
*
* Uses recursion to scan subdirectories
*
* @param PHPUnit_Framework_TestSuite $suite The test suite to add to
* @param array $files Array of files to test
*/
static function addTests(PHPUnit_Framework_TestSuite $suite, array $files)
{
if (self::$phpunit_v35)
{
$filter = PHP_CodeCoverage_Filter::getInstance();
}
foreach ($files as $file)
{
if (is_array($file))
{
self::addTests($suite, $file);
}
else
{
// Make sure we only include php files
if (is_file($file) AND substr($file, -strlen(EXT)) === EXT)
{
// The default PHPUnit TestCase extension
if ( ! strpos($file, 'TestCase'.EXT))
{
$suite->addTestFile($file);
}
else
{
require_once($file);
}
if (isset($filter))
{
$filter->addFileToBlacklist($file);
}
else
{
PHPUnit_Util_Filter::addFileToFilter($file);
}
}
}
}
}
/**
* Blacklist a set of files in PHPUnit code coverage
*
* @param array A set of files to blacklist
*/
static public function blacklist(array $blacklist_items)
{
if (self::$phpunit_v35)
{
$filter = PHP_CodeCoverage_Filter::getInstance();
foreach ($blacklist_items as $item)
{
if (is_dir($item))
{
$filter->addDirectoryToBlacklist($item);
}
else
{
$filter->addFileToBlacklist($item);
}
}
}
else
{
foreach ($blacklist_items as $item)
{
if (is_dir($item))
{
PHPUnit_Util_Filter::addDirectoryToFilter($item);
}
else
{
PHPUnit_Util_Filter::addFileToFilter($item);
}
}
}
}
/**
* Sets the whitelist
*
* If no directories are provided then the function'll load the whitelist
* set in the config file
*
* @param array $directories Optional directories to whitelist
*/
static public function whitelist(array $directories = NULL)
{
if (empty($directories))
{
$directories = self::get_config_whitelist();
}
if (count($directories))
{
foreach ($directories as & $directory)
{
$directory = realpath($directory).'/';
}
// Only whitelist the "top" files in the cascading filesystem
self::set_whitelist(Kohana::list_files('classes', $directories));
}
}
/**
* Works out the whitelist from the config
* Used only on the CLI
*
* @returns array Array of directories to whitelist
*/
static protected function get_config_whitelist()
{
$config = Kohana::config('unittest');
$directories = array();
if ($config->whitelist['app'])
{
$directories['k_app'] = APPPATH;
}
if ($modules = $config->whitelist['modules'])
{
$k_modules = Kohana::modules();
// Have to do this because kohana merges config...
// If you want to include all modules & override defaults then TRUE must be the first
// value in the modules array of your app/config/unittest file
if (array_search(TRUE, $modules, TRUE) === (count($modules) - 1))
{
$modules = $k_modules;
}
elseif (array_search(FALSE, $modules, TRUE) === FALSE)
{
$modules = array_intersect_key($k_modules, array_combine($modules, $modules));
}
else
{
// modules are disabled
$modules = array();
}
$directories += $modules;
}
if ($config->whitelist['system'])
{
$directories['k_sys'] = SYSPATH;
}
return $directories;
}
/**
* Recursively whitelists an array of files
*
* @param array $files Array of files to whitelist
*/
static protected function set_whitelist($files)
{
if (self::$phpunit_v35)
{
$filter = PHP_CodeCoverage_Filter::getInstance();
}
foreach ($files as $file)
{
if (is_array($file))
{
self::set_whitelist($file);
}
else
{
if ( ! isset(Kohana_Tests::$cache[$file]))
{
$relative_path = substr($file, strrpos($file, 'classes'.DIRECTORY_SEPARATOR) + 8, -strlen(EXT));
$cascading_file = Kohana::find_file('classes', $relative_path);
// The theory is that if this file is the highest one in the cascading filesystem
// then it's safe to whitelist
Kohana_Tests::$cache[$file] = ($cascading_file === $file);
}
if (Kohana_Tests::$cache[$file])
{
if (isset($filter))
{
$filter->addFileToWhitelist($file);
}
else
{
PHPUnit_Util_Filter::addFileToWhitelist($file);
}
}
}
}
}
}

View File

@@ -0,0 +1,301 @@
<?php
/**
* TestCase for testing a database
*
* @package Kohana/Unittest
* @author Kohana Team
* @author BRMatt <matthew@sigswitch.com>
* @copyright (c) 2008-2009 Kohana Team
* @license http://kohanaphp.com/license
*/
Abstract Class Kohana_Unittest_Database_TestCase extends PHPUnit_Extensions_Database_TestCase {
/**
* Whether we should enable work arounds to make the tests compatible with phpunit 3.4
* @var boolean
*/
protected static $_assert_type_compatability = NULL;
/**
* Make sure PHPUnit backs up globals
* @var boolean
*/
protected $backupGlobals = TRUE;
/**
* A set of unittest helpers that are shared between normal / database
* testcases
* @var Kohana_Unittest_Helpers
*/
protected $_helpers = NULL;
/**
* A default set of environment to be applied before each test
* @var array
*/
protected $environmentDefault = array();
/**
* Creates a predefined environment using the default environment
*
* Extending classes that have their own setUp() should call
* parent::setUp()
*/
public function setUp()
{
if(self::$_assert_type_compatability === NULL)
{
self::$_assert_type_compatability = version_compare(PHPUnit_Runner_Version::id(), '3.5.0', '<=');
}
$this->_helpers = new Kohana_Unittest_Helpers;
$this->setEnvironment($this->environmentDefault);
return parent::setUp();
}
/**
* Restores the original environment overriden with setEnvironment()
*
* Extending classes that have their own tearDown()
* should call parent::tearDown()
*/
public function tearDown()
{
$this->_helpers->restore_environment();
return parent::tearDown();
}
/**
* Creates a connection to the unittesting database
*
* @return PDO
*/
public function getConnection()
{
// Get the unittesting db connection
$config = Kohana::config('database')
->{Kohana::config('unittest')->db_connection};
if($config['type'] !== 'pdo')
{
$config['connection']['dsn'] = $config['type'].':'.
'host='.$config['connection']['hostname'].';'.
'dbname='.$config['connection']['database'];
}
$pdo = new PDO(
$config['connection']['dsn'],
$config['connection']['username'],
$config['connection']['password']
);
return $this->createDefaultDBConnection($pdo, $config['connection']['database']);
}
/**
* Gets a connection to the unittest database
*
* @return Kohana_Database The database connection
*/
public function getKohanaConnection()
{
return Database::instance(Kohana::config('unittest')->db_connection);
}
/**
* Removes all kohana related cache files in the cache directory
*/
public function cleanCacheDir()
{
return Kohana_Unittest_Helpers::clean_cache_dir();
}
/**
* Helper function that replaces all occurences of '/' with
* the OS-specific directory separator
*
* @param string $path The path to act on
* @return string
*/
public function dirSeparator($path)
{
return Kohana_Unittest_Helpers::dir_separator($path);
}
/**
* Allows easy setting & backing up of enviroment config
*
* Option types are checked in the following order:
*
* * Server Var
* * Static Variable
* * Config option
*
* @param array $environment List of environment to set
*/
public function setEnvironment(array $environment)
{
return $this->_helpers->set_environment($environment);
}
/**
* Check for internet connectivity
*
* @return boolean Whether an internet connection is available
*/
public function hasInternet()
{
return Kohana_Unittest_Helpers::has_internet();
}
/**
* Asserts that a variable is of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertInstanceOf($expected, $actual, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertType($expected, $actual, $message);
}
return parent::assertInstanceOf($expected, $actual, $message);
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param mixed $classOrObject
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertAttributeInstanceOf($expected, $attributeName, $classOrObject, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertAttributeType($expected, $attributeName, $classOrObject, $message);
}
return parent::assertAttributeInstanceOf($expected, $attributeName, $classOrObject, $message);
}
/**
* Asserts that a variable is not of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertNotInstanceOf($expected, $actual, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertNotType($expected, $actual, $message);
}
return self::assertNotInstanceOf($expected, $actual, $message);
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param mixed $classOrObject
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertAttributeNotInstanceOf($expected, $attributeName, $classOrObject, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertAttributeNotType($expected, $attributeName, $classOrObject, $message);
}
return self::assertAttributeNotInstanceOf($expected, $attributeName, $classOrObject, $message);
}
/**
* Asserts that a variable is of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertInternalType($expected, $actual, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertType($expected, $actual, $message);
}
return parent::assertInternalType($expected, $actual, $message);
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param mixed $classOrObject
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertAttributeInternalType($expected, $attributeName, $classOrObject, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertAttributeType($expected, $attributeName, $classOrObject, $message);
}
return self::assertAttributeInternalType($expected, $attributeName, $classOrObject, $message);
}
/**
* Asserts that a variable is not of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertNotInternalType($expected, $actual, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertNotType($expected, $actual, $message);
}
return self::assertNotInternalType($expected, $actual, $message);
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param mixed $classOrObject
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertAttributeNotInternalType($expected, $attributeName, $classOrObject, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertAttributeNotType($expected, $attributeName, $classOrObject, $message);
}
return self::assertAttributeNotInternalType($expected, $attributeName, $classOrObject, $message);
}
}

View File

@@ -0,0 +1,176 @@
<?php
/**
* Unit testing helpers
*
* @package Kohana/Unittest
* @author Kohana Team
* @author BRMatt <matthew@sigswitch.com>
* @author Paul Banks
* @copyright (c) 2008-2009 Kohana Team
* @license http://kohanaphp.com/license
*/
class Kohana_Unittest_Helpers
{
/**
* Static variable used to work out whether we have an internet
* connection
* @see has_internet
* @var boolean
*/
static protected $_has_internet = NULL;
/**
* Check for internet connectivity
*
* @return boolean Whether an internet connection is available
*/
public static function has_internet()
{
if ( ! isset(self::$_has_internet))
{
// The @ operator is used here to avoid DNS errors when there is no connection.
$sock = @fsockopen("www.google.com", 80, $errno, $errstr, 1);
self::$_has_internet = (bool) $sock ? TRUE : FALSE;
}
return self::$_has_internet;
}
/**
* Helper function which replaces the "/" to OS-specific delimiter
*
* @param string $path
* @return string
*/
static public function dir_separator($path)
{
return str_replace('/', DIRECTORY_SEPARATOR, $path);
}
/**
* Removes all cache files from the kohana cache dir
*
* @return void
*/
static public function clean_cache_dir()
{
$cache_dir = opendir(Kohana::$cache_dir);
while ($dir = readdir($cache_dir))
{
// Cache files are split into directories based on first two characters of hash
if ($dir[0] !== '.' AND strlen($dir) === 2)
{
$dir = self::dir_separator(Kohana::$cache_dir.'/'.$dir.'/');
$cache = opendir($dir);
while ($file = readdir($cache))
{
if ($file[0] !== '.')
{
unlink($dir.$file);
}
}
closedir($cache);
rmdir($dir);
}
}
closedir($cache_dir);
}
/**
* Backup of the environment variables
* @see set_environment
* @var array
*/
protected $_environment_backup = array();
/**
* Allows easy setting & backing up of enviroment config
*
* Option types are checked in the following order:
*
* * Server Var
* * Static Variable
* * Config option
*
* @param array $environment List of environment to set
*/
public function set_environment(array $environment)
{
if ( ! count($environment))
return FALSE;
foreach ($environment as $option => $value)
{
$backup_needed = ! array_key_exists($option, $this->_environment_backup);
// Handle changing superglobals
if (in_array($option, array('_GET', '_POST', '_SERVER', '_FILES')))
{
// For some reason we need to do this in order to change the superglobals
global $$option;
if ($backup_needed)
{
$this->_environment_backup[$option] = $$option;
}
// PHPUnit makes a backup of superglobals automatically
$$option = $value;
}
// If this is a static property i.e. Html::$windowed_urls
elseif (strpos($option, '::$') !== FALSE)
{
list($class, $var) = explode('::$', $option, 2);
$class = new ReflectionClass($class);
if ($backup_needed)
{
$this->_environment_backup[$option] = $class->getStaticPropertyValue($var);
}
$class->setStaticPropertyValue($var, $value);
}
// If this is an environment variable
elseif (preg_match('/^[A-Z_-]+$/', $option) OR isset($_SERVER[$option]))
{
if ($backup_needed)
{
$this->_environment_backup[$option] = isset($_SERVER[$option]) ? $_SERVER[$option] : '';
}
$_SERVER[$option] = $value;
}
// Else we assume this is a config option
else
{
if ($backup_needed)
{
$this->_environment_backup[$option] = Kohana::config($option);
}
list($group, $var) = explode('.', $option, 2);
Kohana::config($group)->set($var, $value);
}
}
}
/**
* Restores the environment to the original state
*
* @chainable
* @return Kohana_Unittest_Helpers $this
*/
public function restore_environment()
{
$this->set_environment($this->_environment_backup);
}
}

View File

@@ -0,0 +1,304 @@
<?php
/**
* PHPUnit test runner for kohana
*
* @package Kohana/Unittest
* @author Kohana Team
* @author BRMatt <matthew@sigswitch.com>
* @author Paul Banks
* @copyright (c) 2008-2009 Kohana Team
* @license http://kohanaphp.com/license
*/
Class Kohana_Unittest_Runner implements PHPUnit_Framework_TestListener
{
/**
* Results
* @var array
*/
protected $results = array(
'errors' => array(),
'failures' => array(),
'skipped' => array(),
'incomplete' => array(),
);
/**
* Test result totals
* @var array
*/
protected $totals = array(
'tests' => 0,
'passed' => 0,
'errors' => 0,
'failures' => 0,
'skipped' => 0,
'incomplete' => 0,
'assertions' => 0,
);
/**
* Info about the current test running
* @var array
*/
protected $current = array();
/**
* Time for tests to run (seconds)
* @var float
*/
protected $time = 0;
/**
* Result collector
* @var PHPUnit_Framework_TestResult
*/
protected $result = NULL;
/**
* the test suite to run
* @var PHPUnit_Framework_TestSuite
*/
protected $suite = NULL;
/**
* Constructor
*
* @param PHPUnit_Framework_TestSuite $suite The suite to test
* @param PHPUnit_Framework_TestResult $result Optional result object to use
*/
function __construct(PHPUnit_Framework_TestSuite $suite, PHPUnit_Framework_TestResult $result = NULL)
{
if ($result === NULL)
{
$result = new PHPUnit_Framework_TestResult;
}
$result->addListener($this);
$this->suite = $suite;
$this->result = $result;
}
/**
* Magic getter to allow access to member variables
*
* @param string $var Variable to get
* @return mixed
*/
function __get($var)
{
return $this->$var;
}
/**
* Calcualtes stats for each file covered by the code testing
*
* Each member of the returned array is formatted like so:
*
* <code>
* array(
* 'coverage' => $coverage_percent_for_file,
* 'loc' => $lines_of_code,
* 'locExecutable' => $lines_of_executable_code,
* 'locExecuted' => $lines_of_code_executed
* );
* </code>
*
* @return array Statistics for code coverage of each file
*/
public function calculate_cc()
{
if ($this->result->getCollectCodeCoverageInformation())
{
$coverage = $this->result->getCodeCoverageInformation();
$coverage_summary = PHPUnit_Util_CodeCoverage::getSummary($coverage);
$stats = array();
foreach ($coverage_summary as $file => $_lines)
{
$stats[$file] = PHPUnit_Util_CodeCoverage::getStatistics($coverage_summary, $file);
}
return $stats;
}
return FALSE;
}
/**
* Calculates the percentage code coverage information
*
* @return boolean|float FALSE if cc is not enabled, float for coverage percent
*/
public function calculate_cc_percentage()
{
if ($stats = $this->calculate_cc())
{
$executable = 0;
$executed = 0;
foreach ($stats as $stat)
{
$executable += $stat['locExecutable'];
$executed += $stat['locExecuted'];
}
return ($executable > 0) ? ($executed * 100 / $executable) : 100;
}
return FALSE;
}
/**
* Generate a report using the specified $temp_path
*
* @param array $groups Groups to test
* @param string $temp_path Temporary path to use while generating report
*/
public function generate_report(array $groups, $temp_path, $create_sub_dir = TRUE)
{
if ( ! is_writable($temp_path))
{
throw new Kohana_Exception('Temp path :path does not exist or is not writable by the webserver', array(':path' => $temp_path));
}
$folder_path = $temp_path;
if ($create_sub_dir === TRUE)
{
// Icky, highly unlikely, but do it anyway
// Basically adds "(n)" to the end of the filename until there's a free file
$count = 0;
do
{
$folder_name = date('Y-m-d_H:i:s')
.(empty($groups) ? '' : ('['.implode(',', $groups).']'))
.(($count > 0) ? ('('.$count.')') : '');
++$count;
}
while (is_dir($folder_path.$folder_name));
$folder_path .= $folder_name;
mkdir($folder_path, 0777);
}
else
{
$folder_name = basename($folder_path);
}
$this->run($groups, TRUE);
require_once 'PHPUnit/Runner/Version.php';
require_once 'PHPUnit/Util/Report.php';
PHPUnit_Util_Report::render($this->result, $folder_path);
return array($folder_path, $folder_name);
}
/**
* Runs the test suite using the result specified in the constructor
*
* @param array $groups Optional array of groups to test
* @param bool $collect_cc Optional, Should code coverage be collected?
* @return Kohana_PHPUnit Instance of $this
*/
public function run(array $groups = array(), $collect_cc = FALSE)
{
if ($collect_cc AND ! extension_loaded('xdebug'))
{
throw new Kohana_Exception('Code coverage cannot be collected because the xdebug extension is not loaded');
}
$this->result->collectCodeCoverageInformation( (bool) $collect_cc);
// Run the tests.
$this->suite->run($this->result, FALSE, $groups);
return $this;
}
public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
{
$this->totals['errors']++;
$this->current['result'] = 'errors';
$this->current['message'] = $test->getStatusMessage();
}
public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
{
$this->totals['failures']++;
$this->current['result'] = 'failures';
$this->current['message'] = $test->getStatusMessage();
}
public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
{
$this->totals['incomplete']++;
$this->current['result'] = 'incomplete';
$this->current['message'] = $test->getStatusMessage();
}
public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
{
$this->totals['skipped']++;
$this->current['result'] = 'skipped';
$this->current['message'] = $test->getStatusMessage();
}
public function startTest(PHPUnit_Framework_Test $test)
{
$this->current['name'] = $test->getName(FALSE);
$this->current['description'] = $test->toString();
$this->current['result'] = 'passed';
}
public function endTest(PHPUnit_Framework_Test $test, $time)
{
// Add totals
$this->totals['tests']++;
$this->totals['assertions'] += $test->getNumAssertions();
// Handle passed tests
if ($this->current['result'] == 'passed')
{
// Add to total
$this->totals['passed']++;
}
else
{
// Add to results
$this->results[$this->current['result']][] = $this->current;
}
$this->current = array();
$this->time += $time;
}
public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
{
}
public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
{
// Parse test descriptions to make them look nicer
foreach ($this->results as $case => $testresults)
{
foreach ($testresults as $type => $result)
{
preg_match('/^(?:([a-z0-9_]+?)::)?([a-z0-9_]+)(?: with data set (#\d+ \(.*?\)))?/i', $result['description'], $m);
$this->results[$case][$type] += array(
'class' => $m[1],
'test' => $m[2],
'data_set' => isset($m[3]) ? $m[3] : FALSE,
);
}
}
}
}

View File

@@ -0,0 +1,261 @@
<?php
/**
* TestCase for unittesting
*
* @package Kohana/Unittest
* @author Kohana Team
* @author BRMatt <matthew@sigswitch.com>
* @author Paul Banks
* @copyright (c) 2008-2009 Kohana Team
* @license http://kohanaphp.com/license
*/
abstract class Kohana_Unittest_TestCase extends PHPUnit_Framework_TestCase {
/**
* Whether we should enable work arounds to make the tests compatible with phpunit 3.4
* @var boolean
*/
protected static $_assert_type_compatability = NULL;
/**
* Make sure PHPUnit backs up globals
* @var boolean
*/
protected $backupGlobals = TRUE;
/**
* A set of unittest helpers that are shared between normal / database
* testcases
* @var Kohana_Unittest_Helpers
*/
protected $_helpers = NULL;
/**
* A default set of environment to be applied before each test
* @var array
*/
protected $environmentDefault = array();
/**
* Creates a predefined environment using the default environment
*
* Extending classes that have their own setUp() should call
* parent::setUp()
*/
public function setUp()
{
if(self::$_assert_type_compatability === NULL)
{
self::$_assert_type_compatability = version_compare(PHPUnit_Runner_Version::id(), '3.5.0', '<=');
}
$this->_helpers = new Kohana_Unittest_Helpers;
$this->setEnvironment($this->environmentDefault);
}
/**
* Restores the original environment overriden with setEnvironment()
*
* Extending classes that have their own tearDown()
* should call parent::tearDown()
*/
public function tearDown()
{
$this->_helpers->restore_environment();
}
/**
* Removes all kohana related cache files in the cache directory
*/
public function cleanCacheDir()
{
return Kohana_Unittest_Helpers::clean_cache_dir();
}
/**
* Helper function that replaces all occurences of '/' with
* the OS-specific directory separator
*
* @param string $path The path to act on
* @return string
*/
public function dirSeparator($path)
{
return Kohana_Unittest_Helpers::dir_separator($path);
}
/**
* Allows easy setting & backing up of enviroment config
*
* Option types are checked in the following order:
*
* * Server Var
* * Static Variable
* * Config option
*
* @param array $environment List of environment to set
*/
public function setEnvironment(array $environment)
{
return $this->_helpers->set_environment($environment);
}
/**
* Check for internet connectivity
*
* @return boolean Whether an internet connection is available
*/
public function hasInternet()
{
return Kohana_Unittest_Helpers::has_internet();
}
/**
* Asserts that a variable is of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertInstanceOf($expected, $actual, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertType($expected, $actual, $message);
}
return parent::assertInstanceOf($expected, $actual, $message);
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param mixed $classOrObject
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertAttributeInstanceOf($expected, $attributeName, $classOrObject, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertAttributeType($expected, $attributeName, $classOrObject, $message);
}
return parent::assertAttributeInstanceOf($expected, $attributeName, $classOrObject, $message);
}
/**
* Asserts that a variable is not of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertNotInstanceOf($expected, $actual, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertNotType($expected, $actual, $message);
}
return self::assertNotInstanceOf($expected, $actual, $message);
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param mixed $classOrObject
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertAttributeNotInstanceOf($expected, $attributeName, $classOrObject, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertAttributeNotType($expected, $attributeName, $classOrObject, $message);
}
return self::assertAttributeNotInstanceOf($expected, $attributeName, $classOrObject, $message);
}
/**
* Asserts that a variable is of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertInternalType($expected, $actual, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertType($expected, $actual, $message);
}
return parent::assertInternalType($expected, $actual, $message);
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param mixed $classOrObject
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertAttributeInternalType($expected, $attributeName, $classOrObject, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertAttributeType($expected, $attributeName, $classOrObject, $message);
}
return self::assertAttributeInternalType($expected, $attributeName, $classOrObject, $message);
}
/**
* Asserts that a variable is not of a given type.
*
* @param string $expected
* @param mixed $actual
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertNotInternalType($expected, $actual, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertNotType($expected, $actual, $message);
}
return self::assertNotInternalType($expected, $actual, $message);
}
/**
* Asserts that an attribute is of a given type.
*
* @param string $expected
* @param string $attributeName
* @param mixed $classOrObject
* @param string $message
* @since Method available since Release 3.5.0
*/
public static function assertAttributeNotInternalType($expected, $attributeName, $classOrObject, $message = '')
{
if(self::$_assert_type_compatability)
{
return self::assertAttributeNotType($expected, $attributeName, $classOrObject, $message);
}
return self::assertAttributeNotInternalType($expected, $attributeName, $classOrObject, $message);
}
}

View File

@@ -0,0 +1,53 @@
<?php defined('SYSPATH') or die('No direct script access.');
return array(
// The only environment in which the web runner is allowed to run
// You can run tests from phpunit cli command regardless of this setting
// This can also be set to an array for multiple environments
'environment' => Kohana::DEVELOPMENT,
// This is the folder where we generate and zip all the reports for downloading
// Needs to be readable and writable
'temp_path' => Kohana::$cache_dir.'/unittest',
// Path from DOCROOT (i.e. http://yourdomain/) to the folder where HTML cc reports can be published.
// If you'd prefer not to allow users to do this then simply set the value to FALSE.
// Example value of 'cc_report_path' would allow devs to see report at http://yourdomain/report/
'cc_report_path' => 'report',
// If you don't use a whitelist then only files included during the request will be counted
// If you do, then only whitelisted items will be counted
'use_whitelist' => FALSE,
// Items to whitelist, only used in cli
// Web runner ui allows user to choose which items to whitelist
'whitelist' => array(
// Should the app be whitelisted?
// Useful if you just want to test your application
'app' => TRUE,
// Set to array(TRUE) to include all modules, or use an array of module names
// (the keys of the array passed to Kohana::modules() in the bootstrap)
// Or set to FALSE to exclude all modules
'modules' => array(TRUE),
// If you don't want the Kohana code coverage reports to pollute your app's,
// then set this to FALSE
'system' => TRUE,
),
// Does what it says on the tin
// Blacklisted files won't be included in code coverage reports
// If you use a whitelist then the blacklist will be ignored
'use_blacklist' => FALSE,
// List of individual files/folders to blacklist
'blacklist' => array(
),
// A database connection that can be used when testing
// This doesn't overwrite anything, tests will have to use this value manually
'db_connection' => 'unittest',
);

View File

@@ -0,0 +1,23 @@
<?php defined('SYSPATH') or die('No direct script access.');
return array(
// Leave this alone
'modules' => array(
// This should be the path to this modules userguide pages, without the 'guide/'. Ex: '/guide/modulename/' would be 'modulename'
'unittest' => array(
// Whether this modules userguide pages should be shown
'enabled' => TRUE,
// The name that should show up on the userguide index page
'name' => 'Unittest',
// A short description of this module, shown on the index page
'description' => 'Unit testing for Kohana using PHPUnit',
// Copyright message, shown in the footer for this module
'copyright' => '&copy; 20082010 Kohana Team',
)
)
);

View File

@@ -0,0 +1,16 @@
<!--
This is an example phpunit.xml file to get you started
Copy it to a directory, update the relative paths and rename to phpunit.xml
Then to run tests cd into it's directory and just run
phpunit
(it'll automatically use any phpunit.xml file in the current directory)
Any options you specify when calling phpunit will override the ones in here
-->
<phpunit colors="true" bootstrap="rel/path/to/index.php">
<testsuites>
<testsuite name="Kohana Tests">
<file>rel/path/to/unittest/tests.php</file>
</testsuite>
</testsuites>
</phpunit>

View File

@@ -0,0 +1,13 @@
# Unittest
Unittest is a module that provides unittesting for Kohana using [PHPUnit](http://www.phpunit.de/).
### Running from the command line
$ phpunit --bootstrap=index.php modules/unittest/tests.php
Of course, you'll need to make sure the path to the tests.php file is correct. If you want you can copy it to a more accessible location.
### Running from the web
Just navigate to `http://example.com/unittest`. You may need to use `http://example.com/index.php/unittest` if you have not enabled url rewriting in your `.htaccess`.

View File

@@ -0,0 +1,41 @@
# Installing PHPUnit
Before the Unittest module will work, you need to install PHPUnit.
## Installing on Windows/XAMPP
**(written by bitshift, see [this forum post](http://forum.kohanaframework.org/discussion/7346))**
Assuming xampp is installed to `C:\xampp`, and that you have pear installed, do the following:
1. Open a command prompt and go to C:\xampp\php
2. Type `pear update-channels` (updates channel definitions)
3. Type `pear upgrade` (upgrades all existing packages and pear)
4. Type `pear channel-discover components.ez.no` (This is needed for PHPUnit)
5. Type `pear channel-discover pear.symfony-project.com` (Also needed by PHPUnit)
6. Type `pear channel-discover pear.phpunit.de` (This is the phpunit channel)
7. Type `pear install --alldeps phpunit/PHPUnit` (This actually installs PHPUnit and all dependencies)
[!!] You may have to edit `memory_limit` in your `php.ini` if you get some sort of memory error, just set it to something really large, then back when your done.
Please see [this forum post](http://forum.kohanaframework.org/discussion/7346) for more information.
## Installing on *nix
If any of these fail, try again with sudo, most of them require root priveledges.
1. Install the 'php-pear' package.
- On Ubuntu/Debian type `sudo apt-get install php-pear`
2. Make sure Pear is up-to-date
- Type `pear update-channels`
- Type `pear upgrade`
3. Add the required channels
- Type `pear channel-discover components.ez.no`
- Type `pear channel-discover pear.symfony-project.com`
- Type `pear channel-discover pear.phpunit.de`
4. Install PHPUnit itself
- Type `pear install --alldeps phpunit/PHPUnit`
## Class 'PHPUnit_Framework_TestSuite' not found
If you get this error than it means... I don't even know.

View File

@@ -0,0 +1,6 @@
## [UnitTest]()
- [Installing PHPUnit](installing)
- [Testing](testing)
- [Mock Objects](mockobjects)
- [Troubleshooting](troubleshooting)
- [Testing workflows](workflows)

View File

@@ -0,0 +1,265 @@
# Mock objects
Sometimes when writing tests you need to test something that depends on an object being in a certain state.
Say for example you're testing a model - you want to make sure that the model is running the correct query, but you don't want it to run on a real database server. You can create a mock database connection which responds in the way the model expects, but doesn't actually connect to a physical database.
PHPUnit has a built in mock object creator which can generate mocks for classes (inc. abstract ones) on the fly.
It creates a class that extends the one you want to mock. You can also tell PHPUnit to override certain functions to return set values / assert that they're called in a specific way.
## Creating an instance of a mock class
You create mocks from within testcases using the getMock() function, which is defined in `PHPUnit_Framework_TestCase` like so:
getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE)
`$originalClassName`
: The name of the class that you want to mock
`$methods`
: The methods of $originalClassName that you want to mock.
You need to tell PHPUnit in advance because PHP doesn't allow you to extend an object once it's been initialised.
`$arguments`
: An array of arguments to pass to the mock's constructor
`$mockClassName`
: Allows you to specify the name that will be given to the mock
`$callOriginalConstructor`
: Should the mock call its parent's constructor automatically?
`$callOriginalClone`
: Should the mock call its parent's clone method?
Most of the time you'll only need to use the first two parameters, i.e.:
$mock = $this->getMock('ORM');
`$mock` now contains a mock of ORM and can be handled as though it were a vanilla instance of `ORM`
$mock = $this->getMock('ORM', array('check'));
`$mock` now contains a mock of ORM, but this time we're also mocking the check() method.
## Mocking methods
Assuming we've created a mock object like so:
$mock = $this->getMock('ORM', array('check'));
We now need to tell PHPUnit how to mock the check function when its called.
### How many times should it be called?
You start off by telling PHPUnit how many times the method should be called by calling expects() on the mock object:
$mock->expects($matcher);
`expects()` takes one argument, an invoker matcher which you can create using factory methods defined in `PHPUnit_Framework_TestCase`:
#### Possible invoker matchers:
`$this->any()`
: Returns a matcher that allows the method to be called any number of times
`$this->never()`
: Returns a matcher that asserts that the method is never called
`$this->once()`
: Returns a matcher that asserts that the method is only called once
`$this->atLeastOnce()`
: Returns a matcher that asserts that the method is called at least once
`$this->exactly($count)`
: Returns a matcher that asserts that the method is called at least `$count` times
`$this->at($index)`
: Returns a matcher that matches when the method it is evaluated for is invoked at the given $index.
In our example we want `check()` to be called once on our mock object, so if we update it accordingly:
$mock = $this->getMock('ORM', array('check'));
$mock->expects($this->once());
### What is the method we're mocking?
Although we told PHPUnit what methods we want to mock, we haven't actually told it what method these rules we're specifiying apply to.
You do this by calling `method()` on the returned from `expects()`:
$mock->expects($matcher)
->method($methodName);
As you can probably guess, `method()` takes one parameter, the name of the method you're mocking.
There's nothing very fancy about this function.
$mock = $this->GetMock('ORM', array('check'));
$mock->expects($this->once())
->method('check');
### What parameters should our mock method expect?
There are two ways to do this, either
* Tell the method to accept any parameters
* Tell the method to accept a specific set of parameters
The former can be achieved by calling `withAnyParameters()` on the object returned from `method()`
$mock->expects($matcher)
->method($methodName)
->withAnyParameters();
To only allow specific parameters you can use the `with()` method which accepts any number of parameters.
The order in which you define the parameters is the order that it expects them to be in when called.
$mock->expects($matcher)
->method($methodName)
->with($param1, $param2);
Calling `with()` without any parameters will force the mock method to accept no parameters.
PHPUnit has a fairly complex way of comparing parameters passed to the mock method with the expected values, which can be summarised like so -
* If the values are identical, they are equal
* If the values are of different types they are not equal
* If the values are numbers they they are considered equal if their difference is equal to zero (this level of accuracy can be changed)
* If the values are objects then they are converted to arrays and are compared as arrays
* If the values are arrays then any sub-arrays deeper than x levels (default 10) are ignored in the comparision
* If the values are arrays and one contains more than elements that the other (at any depth up to the max depth), then they are not equal
#### More advanced parameter comparisions
Sometimes you need to be more specific about how PHPUnit should compare parameters, i.e. if you want to make sure that one of the parameters is an instance of an object, yet isn't necessarily identical to a particular instance.
In PHPUnit, the logic for validating objects and datatypes has been refactored into "constraint objects". If you look in any of the assertX() methods you can see that they are nothing more than wrappers for associating constraint objects with tests.
If a parameter passed to `with()` is not an instance of a constraint object (one which extends `PHPUnit_Framework_Constraint`) then PHPUnit creates a new `IsEqual` comparision object for it.
i.e., the following methods produce the same result:
->with('foo', 1);
->with($this->equalTo('foo'), $this->equalTo(1));
Here are some of the wrappers PHPUnit provides for creating constraint objects:
`$this->arrayHasKey($key)`
: Asserts that the parameter will have an element with index `$key`
`$this->attribute(PHPUnit_Framework_Constraint $constraint, $attributeName)`
: Asserts that object attribute `$attributeName` of the parameter will satisfy `$constraint`, where constraint is an instance of a constraint (i.e. `$this->equalTo()`)
`$this->fileExists()`
: Accepts no parameters, asserts that the parameter is a path to a valid file (i.e. `file_exists() === TRUE`)
`$this->greaterThan($value)`
: Asserts that the parameter is greater than `$value`
`$this->anything()`
: Returns TRUE regardless of what the parameter is
`$this->equalTo($value, $delta = 0, $canonicalizeEOL = FALSE, $ignoreCase = False)`
: Asserts that the parameter is equal to `$value` (same as not passing a constraint object to `with()`)
: `$delta` is the degree of accuracy to use when comparing numbers. i.e. 0 means numbers need to be identical, 1 means numbers can be within a distance of one from each other
: If `$canonicalizeEOL` is TRUE then all newlines in string values will be converted to `\n` before comparision
: If `$ignoreCase` is TRUE then both strings will be converted to lowercase before comparision
`$this->identicalTo($value)`
: Asserts that the parameter is identical to `$value`
`$this->isType($type)`
: Asserts that the parameter is of type `$type`, where `$type` is a string representation of the core PHP data types
`$this->isInstanceOf($className)`
: Asserts that the parameter is an instance of `$className`
`$this->lessThan($value)`
: Asserts that the parameter is less than `$value`
`$this->objectHasAttribute($attribute)`
: Asserts that the paramater (which is assumed to be an object) has an attribute `$attribute`
`$this->matchesRegularExpression($pattern)`
: Asserts that the parameter matches the PCRE pattern `$pattern` (using `preg_match()`)
`$this->stringContains($string, $ignoreCase = FALSE)`
: Asserts that the parameter contains the string `$string`. If `$ignoreCase` is TRUE then a case insensitive comparision is done
`$this->stringEndsWith($suffix)`
: Asserts that the parameter ends with `$suffix` (assumes parameter is a string)
`$this->stringStartsWith($prefix)`
: Asserts that the parameter starts with `$prefix` (assumes parameter is a string)
`$this->contains($value)`
: Asserts that the parameter contains at least one value that is identical to `$value` (assumes parameter is array or `SplObjectStorage`)
`$this->containsOnly($type, $isNativeType = TRUE)`
: Asserts that the parameter only contains items of type `$type`. `$isNativeType` should be set to TRUE when `$type` refers to a built in PHP data type (i.e. int, string etc.) (assumes parameter is array)
There are more constraint objects than listed here, look in `PHPUnit_Framework_Assert` and `PHPUnit/Framework/Constraint` if you need more constraints.
If we continue our example, we have the following:
$mock->expects($this->once())
->method('check')
->with();
So far PHPUnit knows that we want the `check()` method to be called once, with no parameters. Now we just need to get it to return something...
### What should the method return?
This is the final stage of mocking a method.
By default PHPUnit can return either
* A fixed value
* One of the parameters that were passed to it
* The return value of a specified callback
Specifying a return value is easy, just call `will()` on the object returned by either `method()` or `with()`.
The function is defined like so:
public function will(PHPUnit_Framework_MockObject_Stub $stub)
PHPUnit provides some MockObject stubs out of the box, you can access them via (when called from a testcase):
`$this->returnValue($value)`
: Returns `$value` when the mocked method is called
`$this->returnArgument($argumentIndex)`
: Returns the `$argumentIndex`th argument that was passed to the mocked method
`$this->returnCallback($callback)`
: Returns the value of the callback, useful for more complicated mocking.
: `$callback` should a valid callback (i.e. `is_callable($callback) === TRUE`). PHPUnit will pass the callback all of the parameters that the mocked method was passed, in the same order / argument index (i.e. the callback is invoked by `call_user_func_array()`).
: You can usually create the callback in your testcase, as long as doesn't begin with "test"
Obviously if you really want to you can create your own MockObject stub, but these three should cover most situations.
Updating our example gives:
$mock->expects($this->once())
->method('check')
->with()
->will($this->returnValue(TRUE));
And we're done!
If you now call `$mock->check()` the value TRUE should be returned.
If you don't call a mocked method and PHPUnit expects it to be called then the test the mock was generated for will fail.
<!--
### What about if the mocked method should change everytime its called?
-->

View File

@@ -0,0 +1,107 @@
## Writing tests
If you're writing a test for your application, place it in `application/tests`. Similarly, if you're writing a test for a module place it in `modules/<modulefolder>/tests`.
Rather than tell you how to write tests I'll point you in the direction of the [PHPUnit Manual](http://www.phpunit.de/manual/3.4/en/index.html). One thing you should bear in mind when writing tests is that testcases should extend [Kohana_Unittest_Testcase] rather than `PHPUnit_Framework_TestCase`.
Here's a taster of some of the cool things you can do with phpunit:
### Data Providers
Sometimes you want to be able to run a specific test with different sets of data to try and test every eventuality
Ordinarily you could use a foreach loop to iterate over an array of test data, however PHPUnit already can take care of this for us rather easily using "Data Providers". A data provider is a function that returns an array of arguments that can be passed to a test.
<?php
Class ReallyCoolTest extends Kohana_Unittest_TestCase
{
function providerStrLen()
{
return array(
array('One set of testcase data', 24),
array('This is a different one', 23),
);
}
/**
* @dataProvider providerStrLen
*/
function testStrLen($string, $length)
{
$this->assertSame(
$length,
strlen($string)
);
}
}
The key thing to notice is the `@dataProvider` tag in the doccomment, this is what tells PHPUnit to use a data provider. The provider prefix is totally optional but it's a nice standard to identify providers.
For more info see:
* [Data Providers in PHPUnit 3.2](http://sebastian-bergmann.de/archives/702-Data-Providers-in-PHPUnit-3.2.html)
* [Data Providers](http://www.phpunit.de/manual/3.4/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers)
### Grouping tests
To allow users to selectively run tests you need to organise your tests into groups. Here's an example test showing how to do this:
<?php
/**
* This is a description for my testcase
*
* @group somegroup
* @group somegroup.morespecific
*/
Class AnotherReallyCoolTest extends Kohana_Unittest_TestCase
{
/**
* Tests can also be grouped too!
*
* @group somegroup.morespecific.annoyingstuff
*/
function testSomeAnnoyingCase()
{
// CODE!!
}
}
Our convention is to use lowercase group names, with more specific levels in a group seperated by periods. i.e. The Validate helper tests are part of the following groups:
kohana
kohana.validation
kohana.validation.helpers
To actually limit your testing to the "somegroup" group, use:
$ phpunit --boostrap=index.php --group=somegroup modules/unittest/tests.php
This functionality can be used to record which bug reports a test is for:
/**
*
* @group bugs.1477
*/
function testAccountCannotGoBelowZero()
{
// Some arbitary code
}
To see all groups that are available in your code run:
$ phpunit --boostrap=index.php --list-groups modules/unittest/tests.php
*Note:* the `--list-groups` switch should appear before the path to the test suite loader
You can also exclude groups while testing using the `--exclude-group` switch. This can be useful if you want to ignore all kohana tests:
$ phpunit --bootstrap=index.php --exclude-group=kohana modules/unittest/tests.php
For more info see:
* [Better PHPUnit Group Annotations](http://mikenaberezny.com/2007/09/04/better-phpunit-group-annotations/)
* [TestNG style Grouping of Tests in PHPUnit 3.2](http://sebastian-bergmann.de/archives/697-TestNG-style-Grouping-of-Tests.html)

View File

@@ -0,0 +1,21 @@
# Troubleshooting
#### I get the error "Class Kohana_Tests could not be found" when testing from the CLI
You need to running PHPUnit >= 3.4, there is a bug in 3.3 which causes this.
#### Some of my classes aren't getting whitelisted for code coverage even though their module is
Only the "highest" files in the cascading filesystem are whitelisted for code coverage.
To test your module's file, remove the higher file from the cascading filesystem by disabling their respective module.
A good way of testing is to create a "vanilla" testing environment for your module, devoid of anything that isn't required by the module.
#### I get a blank page when trying to generate a code coverage report
Try the following:
1. Generate a html report from the command line using `phpunit {bootstrap info} --coverage-html ./report {insert path to tests.php}`. If any error messages show up, fix them and try to generate the report again
2. Increase the php memory limit
3. Make sure that display_errors is set to "on" in your php.ini config file (this value can sometimes be overriden in a .htaccess file)

View File

@@ -0,0 +1,51 @@
# Testing workflows
Having unittests for your application is a nice idea, but unless you actually use them they're about as useful as a chocolate firegaurd. There are quite a few ways of getting tests "into" your development process and this guide aims to cover a few of them.
## Testing through the webui
The web ui is a fairly temporary solution, aimed at helping developers get into unittesting and code coverage. Eventually it's hoped that people migrate on to CI servers, but it's fine for calculating code coverage locally.
To access it goto
http://example.com/unittest/
*Note:* Your site will need to be in the correct environment in order to use the webui. See the config file for more details. You may also need to use http://example.com/index.php/unittest/
## Integrating with IDEs
Modern IDEs have come a long way in the last couple of years and ones like netbeans have pretty decent PHP / PHPUnit support.
### Netbeans (6.8+)
*Note:* Netbeans runs under the assumption that you only have one tests folder per project.
If you want to run tests across multiple modules it might be best creating a separate project for each module.
0. Install the unittest module
1. Open the project which you want to enable phpunit testing for.
2. Now open the project's properties dialouge and in the "Tests Dir" field enter the path to your module's (or application's) test directory.
In this case the only tests in this project are within the unittest module
3. Select the phpunit section from the left hand pane and in the area labelled bootstrap enter the path to your app's index.php file
You can also specify a custom test suite loader (enter the path to your tests.php file) and/or a custom configuration file (enter the path to your phpunit.xml file)
## Looping shell
I personally prefer to do all of my development in an advanced text editor such as vim/gedit/np++.
To test while I work I run tests in an infinte looping. It's very easy to setup and only takes a few commands to setup.
On nix you can run the following commands in the terminal:
while(true) do clear; phpunit; sleep 8; done;
In my experience this gives you just enough time to see what's going wrong before the tests are rerun.
It's also quite handy to store common phpunit settings (like path to the bootstrap) in a a phpunit xml file to reduce the amount that has to be written in order to start a loop.
## Continuous Integration (CI)
Continuous integration is a team based tool which enables developers to keep tabs on whether changes committed to a project break the application. If a commit causes a test to fail then the build is classed as "broken" and the CI server then alerts developers by email, RSS, IM or glowing (bears|lava lamps) to the fact that someone has broken the build and that all hell's broken lose.
The two more popular CI servers are [Hudson](https://hudson.dev.java.net/) and [phpUnderControl](http://www.phpundercontrol.org/about.html), both of which use [Phing](http://phing.info/) to run the build tasks for your application.

View File

@@ -0,0 +1,16 @@
<?php
// If we're on the CLI then PHPUnit will already be loaded
if (class_exists('PHPUnit_Util_Filter', FALSE) OR function_exists('phpunit_autoload'))
{
Kohana_Tests::configure_environment();
// Stop kohana from processing the request
define('SUPPRESS_REQUEST', TRUE);
}
Route::set('unittest', 'unittest(/<action>)')
->defaults(array(
'controller' => 'unittest',
'action' => 'index',
));

View File

@@ -0,0 +1,20 @@
<?php
if ( ! class_exists('Kohana'))
{
die('Please include the kohana bootstrap file (see README.markdown)');
}
if ($file = Kohana::find_file('classes', 'kohana/tests'))
{
require_once $file;
// PHPUnit requires a test suite class to be in this file,
// so we create a faux one that uses the kohana base
Class TestSuite extends Kohana_Tests
{}
}
else
{
die('Could not include the test suite');
}

View File

@@ -0,0 +1,66 @@
<?php defined('SYSPATH') or die('No direct script access.') ?>
<div id="select">
<h1>PHPUnit for Kohana 3</h1>
<div id="groups">
<fieldset class="tests">
<legend>Run Tests</legend>
<?php echo Form::open($run_uri, array('method' => 'GET'));?>
<?php echo Form::label('run_group', __('Run a test group')) ?>
<?php echo Form::select('group', $groups, NULL, array('id' => 'run_group'));?>
<?php if ($xdebug_enabled): ?>
<?php echo Form::label('run_collect_cc', __('Calculate code coverage')) ?>
<?php echo Form::checkbox('collect_cc', 1, TRUE, array('id' => 'run_collect_cc')) ?>
<div depends_on="#run_collect_cc">
<?php echo Form::label('run_use_whitelist', __('Use code coverage whitelist'));?>
<?php echo Form::checkbox('use_whitelist', 1, TRUE, array('id' => 'run_use_whitelist')) ?>
<div depends_on="#run_use_whitelist">
<?php echo Form::label('run_whitelist', __('Only calculate coverage for files in selected modules')) ?>
<?php echo Form::select('whitelist[]', $whitelistable_items, array(), array('id' => 'run_whitelist', 'multiple' => 'multiple')) ?>
</div>
</div>
<?php endif ?>
<?php echo Form::submit('submit', 'Run');?>
<?php echo Form::close();?>
</fieldset>
<fieldset class="reports">
<legend>Code Coverage Reports</legend>
<?php if ( ! $xdebug_enabled): ?>
<p><?php echo __('Xdebug needs to be installed to generate reports') ?></p>
<?php else: ?>
<?php echo Form::open($report_uri, array('method' => 'GET')) ?>
<?php echo Form::label('cc_group', __('Generate report for')) ?>
<?php echo Form::select('group', $groups, NULL, array('id' => 'cc_group'));?>
<?php echo Form::label('report_archive', __('Download as archive?'));?>
<?php echo Form::checkbox('archive', 1, FALSE, array('id' => 'report_archive')) ?>
<?php echo Form::label('report_use_whitelist', __('Use code coverage whitelist'));?>
<?php echo Form::checkbox('use_whitelist', 1, TRUE, array('id' => 'report_use_whitelist')) ?>
<div depends_on="#report_use_whitelist">
<?php echo Form::label('run_whitelist', __('Only calculate coverage for files in selected modules')) ?>
<?php echo Form::select('whitelist[]', $whitelistable_items, array(), array('id' => 'run_whitelist', 'multiple' => 'multiple')) ?>
</div>
<?php echo Form::submit('submit', 'Run');?>
<?php echo Form::close();?>
<?php endif ?>
</fieldset>
</div>
<h2>Useful links</h2>
<ul>
<li><a href="http://www.phpunit.de/manual/current/en/">PHPUnit Manual</a></li>
<li><a href="http://github.com/kohana/unittest">Module README</a></li>
</ul>
</div>

View File

@@ -0,0 +1,255 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<title>PHPUnit for Kohana</title>
<style type="text/css">
#select {
font-family: sans-serif;
border: 2px solid black;
padding: 20px;
margin: 40px 80px;
}
#select #groups {
overflow: auto;
margin-bottom: 25px;
}
#header {
background-color: #263038;
color: #fff;
padding: 20px;
font-family: sans-serif;
overflow: auto;
}
#header span {
display: block;
color: #83919C;
margin: 5px 0 0 0;
}
#header span b {
color: #ddd;
}
#header span.code_coverage .excellent {
color: #13CC1C;
}
#header span.code_coverage .ok {
color: #448BFD;
}
#header span.code_coverage .terrible {
color: #FF0A0A;
}
#header fieldset form label {
display: block;
}
#header a {
color: #4e7aa0;
}
li {
margin-bottom: 5px;
}
fieldset {
color: #000;
background: #E5EFF8;
border: 4px solid #4D6171;
padding: 20px 20px 0px;
font-size: 1.2em;
width: 43%;
display: block;
-moz-border-radius: 2px;
}
fieldset legend {
padding: 5px;
-moz-border-radius: 2px;
color: #FEFEFE;
background: #4D6171;
}
form {
display: inline;
}
fieldset form {
display: block;
}
fieldset#results-options {
width: 38%;
float:right;
}
fieldset form label {
display: block;
}
fieldset select {
width: 45%;
}
fieldset#results-options form label {
clear: left;
float: left;
width: 43%;
}
fieldset#results-options form input {
display: block;
float:left;
}
fieldset#results-options form input[type="submit"] {
float: none;
clear: both;
}
fieldset#results-options form select {
float: left;
}
fieldset.tests {
float: left;
}
fieldset.tests legend {
background: #4D6171;
}
fieldset.reports {
float: right;
background: #FC817B;
border-color: #D02820;
}
fieldset.reports legend {
background: #D02820;
}
fieldset form input[type="submit"] {
margin-top: 15px;
display: block;
}
#results {
font-family: sans-serif;
}
#results > div {
margin-top: 10px;
padding: 20px;
}
#results > div ol li{
font-size: 1.1em;
margin-bottom: 15px;
font-weight: bold;
}
h1, h2 {
margin: 0 0 15px;
}
div.failures-list {
background-color: #ffcccc;
}
div.errors-list {
background-color: #ffc;
}
span.test-case {
color: #83919C;
}
span.test-name {
color: #222;
}
span.test-data-set {
display: block;
color: #666;
font-weight: normal;
}
span.test-message {
display: block;
color: #444;
}
div.big-message {
font-size: 2em;
text-align: center;
}
div.all-pass {
background-color: #E0FFE0;
border: 3px solid #b0FFb0;
}
div.no-tests {
background-color: #FFFFE0;
border: 3px solid #FFFFb0;
}
span.show {
font-size: 0.7em;
font-weight: normal;
color:#4D6171;
}
.hidden {
display: none;
}
span[title]{
border-bottom: 1px dashed #83919C;
}
</style>
</head>
<body>
<?php echo $body ?>
<?php echo HTML::script('http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js') ?>
<script type="text/javascript">
$(document).ready(function(){
// Using our own attribute is a little messy but gets the job done
// this isn't very important code anyway...
$('[depends_on]').each(function(){
var dependent = $(this);
var controller = $(dependent.attr('depends_on'));
// We do this in the loop so that it can access dependent
// Javascript scopes are slightly crazy
var toggler = function(){
if ($(this).attr('checked')){
dependent.show();
} else {
dependent.hide();
}
}
// Register the toggler
controller.change(toggler);
// And degrade nicely...
if ( ! controller.attr('checked')){
dependent.hide();
}
});
});
document.write('<style type="text/css"> .collapsed { display: none; } </style>');
function toggle(type)
{
var elem = document.getElementById(type+'-ol');
var plus = document.getElementById(type+'-show');
if (elem.style && elem.style['display'])
// Only works with the "style" attr
var disp = elem.style['display'];
else if (elem.currentStyle)
// For MSIE, naturally
var disp = elem.currentStyle['display'];
else if (window.getComputedStyle)
// For most other browsers
var disp = document.defaultView.getComputedStyle(elem, null).getPropertyValue('display');
// Toggle the state of the "display" style
elem.style.display = disp == 'block' ? 'none' : 'block';
plus.innerHTML = disp == 'block' ? '[<?php echo __('show') ?>]' : '[<?php echo __('hide') ?>]';
return false;
}
</script>
</body>
</html>

View File

@@ -0,0 +1,83 @@
<?php defined('SYSPATH') or die('No direct script access.') ?>
<div id="header" class="results">
<fieldset id="results-options">
<legend>Options</legend>
<?php echo Form::open(NULL, array('method' => 'get'));?>
<?php echo Form::label('group', __('Switch Group')) ?>
<?php echo Form::select('group', $groups, $group, array('id' => 'group'));?>
<?php if ($xdebug_enabled): ?>
<?php echo Form::label('collect_cc', __('Collect Coverage')) ?>
<?php echo Form::checkbox('collect_cc', 1, isset($coverage), array('id' => 'collect_cc')) ?>
<div depends_on="#collect_cc">
<?php echo Form::label('use_whitelist', __('Use whitelist'));?>
<?php echo Form::checkbox('use_whitelist', 1, $whitelist, array('id' => 'use_whitelist')) ?>
<div depends_on="#use_whitelist">
<?php echo Form::label('whitelist', __('Whitelisted modules')) ?>
<?php echo Form::select('whitelist[]', $whitelistable_items, $whitelisted_items, array('id' => 'whitelist', 'multiple' => 'multiple')) ?>
</div>
</div>
<?php endif ?>
<?php echo Form::submit('run', 'Run');?>
<?php echo Form::close();?>
</fieldset>
<h1><?php echo is_null($group) ? __('All Groups') : (__('Group').': ') ?> <?php echo $group ?></h1>
<span class="time"><?php echo __('Time') ?>: <b><?php echo $time?></b></span>
<span class="summary">
<?php echo __('Tests') ?> <b><?php echo $totals['tests']?></b>,
<?php echo __('Assertions') ?> <b><?php echo $totals['assertions']?></b>,
<?php echo __('Failures') ?> <b><?php echo $totals['failures']?></b>,
<?php echo __('Skipped') ?> <b><?php echo $totals['skipped']?></b>,
<?php echo __('Errors') ?> <b><?php echo $totals['errors']?></b>.
</span>
<?php if ($xdebug_enabled AND isset($coverage)): ?>
<span class="code_coverage">
<?php $level_class = ($coverage > 75) ? 'excellent' : (($coverage > 35) ? 'ok' : 'terrible') ?>
<?php
echo __('Tests covered :percent of the :codebase',
array
(
':percent' => '<b class="'.$level_class.'">'.num::format($coverage, 2).'%</b>',
':codebase' => empty($coverage_explanation) ? 'codebase' : ('<span title="'.$coverage_explanation.'" style="display:inline;">modules</span>'),
)
);
?>,
<?php echo HTML::anchor($report_uri, 'View') ?>
or
<?php echo HTML::anchor($report_uri.'&archive=1', 'Download') ?>
the report
</span>
<?php endif ?>
</div>
<div id="results">
<?php if ($totals['tests'] == 0): ?>
<div class="big-message no-tests">No tests in group</div>
<?php elseif ($totals['tests'] === $totals['passed']): ?>
<div class="big-message all-pass"><?php echo $totals['tests']?> Tests Passed</div>
<?php else: ?>
<?php foreach ($results as $type => $tests):?>
<?php if (count($tests) < 1):
continue;
endif ?>
<?php $hide = ($type === 'skipped' OR $type === 'incomplete') ?>
<div class="<?php echo $type?>-list">
<h2 onclick="toggle('<?php echo $type?>');"><?php echo count($tests)?>
<?php echo __(ucfirst(Inflector::singular($type, count($tests))))?>
<span id="<?php echo $type?>-show" class="show">[<?php echo $hide ? __('show') : __('hide') ?>]</span></h2>
<ol id="<?php echo $type?>-ol" class="<?php echo $hide ? 'hidden' : '' ?>">
<?php foreach ($tests as $result): ?>
<li>
<span class="test-case"><?php echo $result['class']?>::</span><span class="test-name"><?php echo $result['test']?></span>
<?php if ($result['data_set']) : ?>
<span class="test-data-set"> <?php echo __('with data set')?> <span><?php echo $result['data_set']?></span></span>
<?php endif ?>
<span class="test-message"><?php echo htmlentities($result['message'])?></span>
</li>
<?php endforeach ?>
</ol>
</div>
<?php endforeach;?>
<?php endif ?>
</div>