added initial test coverage and filtering stuff
This commit is contained in:
parent
586215680e
commit
54f54d9794
@ -17,4 +17,6 @@
|
||||
Autoloader::loadClassMapFromFile(dirname(__FILE__) . '/manifest.php');
|
||||
spl_autoload_register('Autoloader::autoload');
|
||||
|
||||
CoverageFilter::addDefaultFilters();
|
||||
|
||||
?>
|
@ -189,34 +189,55 @@
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds tests in bulk
|
||||
*
|
||||
* @author Tommy Montgomery
|
||||
* @version 1.0
|
||||
* @since 1.0
|
||||
* @uses addTest()
|
||||
*
|
||||
* @param array $tests
|
||||
* @return TestRunner
|
||||
*/
|
||||
public final function addTests(array $tests) {
|
||||
foreach ($tests as $test) {
|
||||
$this->addTest($test);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of an option
|
||||
*
|
||||
* @author Tommy Montgomery
|
||||
* @version 1.0
|
||||
* @since 1.0
|
||||
*
|
||||
* @param string $option
|
||||
* @throws InvalidArgumentException
|
||||
* @return mixed
|
||||
*/
|
||||
public final function getOption($option) {
|
||||
if (!array_key_exists($option, $this->options)) {
|
||||
throw new InvalidArgumentException('Option "' . $option . '" does not exist');
|
||||
}
|
||||
|
||||
return $this->options[$option];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses options
|
||||
*
|
||||
* @author Tommy Montgomery
|
||||
* @version 1.0
|
||||
* @since 1.0
|
||||
* @uses getAllowableOptions()
|
||||
*
|
||||
* @param array $unparsed The unparsed options
|
||||
* @throws {@link InvalidOptionException}
|
||||
* @return array The parsed options
|
||||
*/
|
||||
protected final function parseOptions(array $unparsed) {
|
||||
$allowedOptions = $this->getAllowableOptions();
|
||||
$options = array_fill_keys(array_keys($allowedOptions), null);
|
||||
foreach ($unparsed as $option => $value) {
|
||||
if (!isset($allowedOptions[$option])) {
|
||||
throw new InvalidOptionException($option);
|
||||
}
|
||||
if (gettype($value) !== $allowedOptions[$option]) {
|
||||
throw new InvalidOptionException($option, 'Expected a value of type ' . $allowedOptions[$option] . ', got ' . gettype($value));
|
||||
}
|
||||
|
||||
$options[$option] = $value;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
protected abstract function parseOptions(array $unparsed);
|
||||
|
||||
/**
|
||||
* Runs the tests and returns the results
|
||||
@ -263,6 +284,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before runTests()
|
||||
*
|
||||
* @author Tommy Montgomery
|
||||
* @version 1.0
|
||||
* @since 1.0
|
||||
*/
|
||||
protected function preRun() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs all tests and publishes the results
|
||||
*
|
||||
@ -274,17 +306,34 @@
|
||||
* @uses TestListener::afterTestRunner()
|
||||
*/
|
||||
public final function run() {
|
||||
$this->preRun();
|
||||
|
||||
foreach ($this->listeners as $listener) {
|
||||
$listener->beforeTestRunner($this);
|
||||
}
|
||||
|
||||
$this->startTime = microtime(true);
|
||||
$this->publishResults($this->runTests());
|
||||
$this->endTime = microtime(true);
|
||||
$results = $this->runTests();
|
||||
$this->endTime = microtime(true);
|
||||
|
||||
$this->publishResults($results);
|
||||
|
||||
foreach ($this->listeners as $listener) {
|
||||
$listener->afterTestRunner($this);
|
||||
}
|
||||
|
||||
$this->postRun();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after runTests()
|
||||
*
|
||||
* @author Tommy Montgomery
|
||||
* @version 1.0
|
||||
* @since 1.0
|
||||
*/
|
||||
protected function postRun() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -315,17 +364,6 @@
|
||||
return Util::countTests($this->tests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all allowable options for this test runner
|
||||
*
|
||||
* @author Tommy Montgomery
|
||||
* @version 1.0
|
||||
* @since 1.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected abstract function getAllowableOptions();
|
||||
|
||||
}
|
||||
|
||||
?>
|
@ -20,22 +20,124 @@
|
||||
class ConsoleTestRunner extends TestRunner {
|
||||
|
||||
/**
|
||||
* Gets allowable options
|
||||
*
|
||||
* Currently supported options:
|
||||
* - recursive (boolean)
|
||||
* - bootstrap (string)
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @author Tommy Montgomery
|
||||
* @version 1.0
|
||||
* @since 1.0
|
||||
* @version 1.0
|
||||
* @uses getOption()
|
||||
*/
|
||||
protected function preRun() {
|
||||
parent::preRun();
|
||||
|
||||
if ($this->getOption('coverage-html') !== null || $this->getOption('coverage-console') !== null/* || $this->getOption('coverage-xml')*/) {
|
||||
xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
|
||||
}
|
||||
|
||||
$bootstrap = $this->getOption('bootstrap');
|
||||
if ($bootstrap !== null) {
|
||||
require_once $bootstrap;
|
||||
}
|
||||
}
|
||||
|
||||
protected function postRun() {
|
||||
$html = $this->getOption('coverage-html');
|
||||
$console = $this->getOption('coverage-console');
|
||||
if ($html !== null || $console !== null) {
|
||||
$coverage = xdebug_get_code_coverage();
|
||||
xdebug_stop_code_coverage();
|
||||
if ($console !== null) {
|
||||
CoverageReporter::createConsoleReport($coverage);
|
||||
}
|
||||
if ($html !== null) {
|
||||
CoverageReporter::createHtmlReport($html, $coverage);
|
||||
}
|
||||
|
||||
unset($coverage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses test runner options
|
||||
*
|
||||
* @author Tommy Montgomery
|
||||
* @since 1.0
|
||||
* @version 1.0
|
||||
* @uses validateOptions()
|
||||
* @uses getAllowableOptions()
|
||||
*
|
||||
* @param array $unparsed Unparsed options
|
||||
* @return array The parsed options
|
||||
*/
|
||||
protected function parseOptions(array $unparsed) {
|
||||
$options = $this->getAllowableOptions();
|
||||
foreach ($options as $name => $default) {
|
||||
if (array_key_exists($name, $unparsed)) {
|
||||
$options[$name] = $unparsed[$name];
|
||||
}
|
||||
}
|
||||
|
||||
$this->validateOptions($options);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the options
|
||||
*
|
||||
* @author Tommy Montgomery
|
||||
* @since 1.0
|
||||
* @version 1.0
|
||||
*
|
||||
* @param array $options
|
||||
* @throws {@link InvalidOptionException}
|
||||
*/
|
||||
protected function validateOptions(array $options) {
|
||||
if ($options['bootstrap'] !== null && !is_file($options['bootstrap'])) {
|
||||
throw new InvalidOptionException('bootstrap', 'Bootstrap must be a file');
|
||||
}
|
||||
if (!is_bool($options['recursive'])) {
|
||||
throw new InvalidOptionException('recursive', 'recursive must be a boolean');
|
||||
}
|
||||
if (/*$options['coverage-xml'] !== null || */$options['coverage-html'] !== null || $options['coverage-console'] !== null) {
|
||||
if (!extension_loaded('xdebug')) {
|
||||
throw new InvalidOptionException('coverage-(xml|html|console)', 'xdebug extension is not loaded');
|
||||
}
|
||||
/*if ($options['coverage-xml'] !== null) {
|
||||
$dir = dirname($options['coverage-xml']);
|
||||
if (!is_dir($dir)) {
|
||||
if (!mkdir($dir, 0777, true)) {
|
||||
throw new TUnitException('Could not create directory: ' . $dir);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
if ($options['coverage-html'] !== null) {
|
||||
//create coverage directory if needed
|
||||
if (!is_dir($options['coverage-html'])) {
|
||||
if (!mkdir($options['coverage-html'], 0777, true)) {
|
||||
throw new TUnitException('Could not create directory: ' . $options['coverage-html']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets allowable options and default values
|
||||
*
|
||||
* @author Tommy Montgomery
|
||||
* @since 1.0
|
||||
* @version 1.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getAllowableOptions() {
|
||||
public function getAllowableOptions() {
|
||||
return array(
|
||||
'recursive' => 'boolean',
|
||||
'bootstrap' => 'string'
|
||||
'bootstrap' => null,
|
||||
'recursive' => false,
|
||||
//'coverage-xml' => null,
|
||||
'coverage-html' => null,
|
||||
'coverage-console' => false
|
||||
);
|
||||
}
|
||||
|
||||
|
54
src/TUnit/framework/reporting/CoverageFilter.php
Normal file
54
src/TUnit/framework/reporting/CoverageFilter.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
final class CoverageFilter {
|
||||
|
||||
private static $files = array();
|
||||
private static $directories = array();
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
public static function addFile($file) {
|
||||
self::$files[] = $file;
|
||||
}
|
||||
|
||||
public static function addDirectory($dir) {
|
||||
self::$directories[] = $dir;
|
||||
}
|
||||
|
||||
public static function clear() {
|
||||
self::$files = array();
|
||||
self::$directories = array();
|
||||
}
|
||||
|
||||
public static function getDirectories() {
|
||||
return self::$dirs;
|
||||
}
|
||||
|
||||
public static function getFiles() {
|
||||
return self::$files;
|
||||
}
|
||||
|
||||
public static function addDefaultFilters() {
|
||||
self::$directories[] = dirname(dirname(dirname(__FILE__)));
|
||||
}
|
||||
|
||||
public static function filter(array $data) {
|
||||
foreach ($data as $file => $arr) {
|
||||
if (in_array($file, self::$files)) {
|
||||
unset($data[$file]);
|
||||
} else {
|
||||
foreach (self::$directories as $dir) {
|
||||
if (strpos($file, $dir) === 0) {
|
||||
unset($data[$file]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
49
src/TUnit/framework/reporting/CoverageReporter.php
Normal file
49
src/TUnit/framework/reporting/CoverageReporter.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
class CoverageReporter {
|
||||
|
||||
const UNUSED = -1;
|
||||
const DEAD = -2;
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
public static function createConsoleReport(array $coverageData) {
|
||||
$coverageData = CoverageFilter::filter($coverageData);
|
||||
echo "\nCode coverage information:\n\n";
|
||||
//print_r($coverageData);
|
||||
foreach ($coverageData as $file => $data) {
|
||||
fwrite(STDOUT, $file . "\n");
|
||||
$loc = 0;
|
||||
$dloc = 0;
|
||||
$cloc = 0;
|
||||
|
||||
foreach ($data as $line => $unitsCovered) {
|
||||
$loc++;
|
||||
if ($unitsCovered > 0) {
|
||||
$cloc++;
|
||||
} else if ($unitsCovered === self::DEAD) {
|
||||
$dloc++;
|
||||
}
|
||||
}
|
||||
|
||||
fwrite(STDOUT, " Covered: $cloc\n");
|
||||
fwrite(STDOUT, " Dead: $dloc\n");
|
||||
fwrite(STDOUT, " Executable: $loc (" . round($cloc / $loc * 100, 2) . "%)\n");
|
||||
}
|
||||
}
|
||||
|
||||
public static function createXmlReport($file, array $coverageData) {
|
||||
|
||||
}
|
||||
|
||||
public static function createHtmlReport($dir, array $coverageData) {
|
||||
if (!is_dir($dir)) {
|
||||
throw new TUnitException($dir . ' is not a directory');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
@ -3,7 +3,7 @@
|
||||
/**
|
||||
* Autoload manifest
|
||||
*
|
||||
* Autogenerated by manifester.php on 2009-06-19 20:50:38
|
||||
* Autogenerated by manifester.php on 2009-06-27 16:39:48
|
||||
*
|
||||
* @package TUnit
|
||||
* @version 0.4.0
|
||||
@ -20,6 +20,8 @@
|
||||
'ConsoleListener' => 'TUnit/framework/listeners/ConsoleListener.php',
|
||||
'ConsoleTestRunner' => 'TUnit/framework/TestRunners.php',
|
||||
'Constraint' => 'TUnit/framework/constraints/Constraint.php',
|
||||
'CoverageFilter' => 'TUnit/framework/reporting/CoverageFilter.php',
|
||||
'CoverageReporter' => 'TUnit/framework/reporting/CoverageReporter.php',
|
||||
'DefaultConstraint' => 'TUnit/framework/constraints/DefaultConstraint.php',
|
||||
'EmptyConstraint' => 'TUnit/framework/constraints/SimpleConstraints.php',
|
||||
'EqualConstraint' => 'TUnit/framework/constraints/EqualConstraint.php',
|
||||
@ -47,6 +49,7 @@
|
||||
'Product' => 'TUnit/util/Product.php',
|
||||
'RecursivePhpFileIterator' => 'TUnit/util/PhpFileIterator.php',
|
||||
'RecursiveTestIterator' => 'TUnit/util/RecursiveTestIterator.php',
|
||||
'RecursivelyCountable' => 'TUnit/framework/test/Testable.php',
|
||||
'SimpleConstraint' => 'TUnit/framework/constraints/SimpleConstraints.php',
|
||||
'SingleTestResult' => 'TUnit/framework/result/SingleTestResult.php',
|
||||
'TUnitException' => 'TUnit/framework/exceptions/Exceptions.php',
|
||||
|
@ -108,6 +108,8 @@
|
||||
$ref = new ReflectionClass($className);
|
||||
if ($ref->isSubClassOf('TestCase')) {
|
||||
$tests[] = $ref->newInstance($className);
|
||||
//add to filter
|
||||
CoverageFilter::addFile($file);
|
||||
}
|
||||
unset($ref);
|
||||
|
||||
|
@ -43,11 +43,14 @@
|
||||
|
||||
global $switches;
|
||||
$switches = new CliSwitchCollection();
|
||||
$switches->addSwitch(new CliSwitch(null, null, true, '<files>', 'Files and/or directories to parse for test cases'))
|
||||
->addSwitch(new CliSwitch('help', 'h', false, null, 'Display this help message (also --usage)'))
|
||||
->addSwitch(new CliSwitch('usage', null, false, null, 'Display this help message'))
|
||||
->addSwitch(new CliSwitch('recursive', null, false, null, 'Recurse into subdirectories'))
|
||||
->addSwitch(new CliSwitch('bootstrap', 'b', false, 'file', 'File to include before tests are run'));
|
||||
$switches->addSwitch(new CliSwitch(null, null, true, '<files>', 'Files and/or directories to parse for test cases'))
|
||||
->addSwitch(new CliSwitch('help', 'h', false, null, 'Display this help message (also --usage)'))
|
||||
->addSwitch(new CliSwitch('usage', null, false, null, 'Display this help message'))
|
||||
->addSwitch(new CliSwitch('recursive', null, false, null, 'Recurse into subdirectories'))
|
||||
->addSwitch(new CliSwitch('bootstrap', 'b', false, 'file', 'File to include before tests are run'))
|
||||
->addSwitch(new CliSwitch('coverage-xml', null, false, 'file', 'Generate code coverage report in XML clover format (requires xdebug)'))
|
||||
->addSwitch(new CliSwitch('coverage-html', null, false, 'dir', 'Generate code coverage report in HTML (requires xdebug, optionally uses ezComponents)'))
|
||||
->addSwitch(new CliSwitch('coverage-console', null, false, null, 'Generate code coverage report suitable for console viewing'));
|
||||
|
||||
array_shift($argv);
|
||||
$args = Cli::parseArgs($argv, $switches);
|
||||
@ -65,30 +68,21 @@
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$runner = new ConsoleTestRunner();
|
||||
$runner->setOptions($options);
|
||||
|
||||
//accumulate tests
|
||||
$tests = TestAccumulator::getTests($args, isset($options['recursive']));
|
||||
$tests = TestAccumulator::getTests($args, $runner->getOption('recursive'));
|
||||
|
||||
if (empty($tests)) {
|
||||
fwrite(STDERR, 'Found no TestCase subclasses in the given files');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (isset($options['bootstrap'])) {
|
||||
if (!is_file($options['bootstrap'])) {
|
||||
fwrite(STDERR, 'Bootstrap file given (' . $options['bootstrap'] . ') is not a file');
|
||||
exit(1);
|
||||
}
|
||||
$runner->addTests($tests)
|
||||
->addListener(new ConsoleListener())
|
||||
->run();
|
||||
|
||||
require_once $options['bootstrap'];
|
||||
}
|
||||
|
||||
$runner = new ConsoleTestRunner(
|
||||
array(new TestSuite('Main Test Suite', $tests)),
|
||||
array(new ConsoleListener()),
|
||||
$options
|
||||
);
|
||||
|
||||
$runner->run();
|
||||
exit(0);
|
||||
|
||||
?>
|
Loading…
Reference in New Issue
Block a user