From 54f54d97941d074888963528e120c00bc850425f Mon Sep 17 00:00:00 2001 From: tmont Date: Sun, 28 Jun 2009 00:02:24 +0000 Subject: [PATCH] added initial test coverage and filtering stuff --- src/TUnit/bootstrap.php | 2 + src/TUnit/framework/BaseTestRunner.php | 100 +++++++++----- src/TUnit/framework/TestRunners.php | 122 ++++++++++++++++-- .../framework/reporting/CoverageFilter.php | 54 ++++++++ .../framework/reporting/CoverageReporter.php | 49 +++++++ src/TUnit/manifest.php | 5 +- src/TUnit/util/TestAccumulator.php | 2 + src/TUnit/util/entry_console.php | 36 +++--- 8 files changed, 307 insertions(+), 63 deletions(-) create mode 100644 src/TUnit/framework/reporting/CoverageFilter.php create mode 100644 src/TUnit/framework/reporting/CoverageReporter.php diff --git a/src/TUnit/bootstrap.php b/src/TUnit/bootstrap.php index fbe0a47..46fe3d2 100644 --- a/src/TUnit/bootstrap.php +++ b/src/TUnit/bootstrap.php @@ -16,5 +16,7 @@ Autoloader::loadClassMapFromFile(dirname(__FILE__) . '/manifest.php'); spl_autoload_register('Autoloader::autoload'); + + CoverageFilter::addDefaultFilters(); ?> \ No newline at end of file diff --git a/src/TUnit/framework/BaseTestRunner.php b/src/TUnit/framework/BaseTestRunner.php index 7a45dbc..1254887 100644 --- a/src/TUnit/framework/BaseTestRunner.php +++ b/src/TUnit/framework/BaseTestRunner.php @@ -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(); - } ?> \ No newline at end of file diff --git a/src/TUnit/framework/TestRunners.php b/src/TUnit/framework/TestRunners.php index 64e329e..a8b7f4a 100644 --- a/src/TUnit/framework/TestRunners.php +++ b/src/TUnit/framework/TestRunners.php @@ -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 ); } diff --git a/src/TUnit/framework/reporting/CoverageFilter.php b/src/TUnit/framework/reporting/CoverageFilter.php new file mode 100644 index 0000000..486a9d2 --- /dev/null +++ b/src/TUnit/framework/reporting/CoverageFilter.php @@ -0,0 +1,54 @@ + $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; + } + + } + +?> \ No newline at end of file diff --git a/src/TUnit/framework/reporting/CoverageReporter.php b/src/TUnit/framework/reporting/CoverageReporter.php new file mode 100644 index 0000000..a522062 --- /dev/null +++ b/src/TUnit/framework/reporting/CoverageReporter.php @@ -0,0 +1,49 @@ + $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'); + } + + + } + + } + +?> \ No newline at end of file diff --git a/src/TUnit/manifest.php b/src/TUnit/manifest.php index b7a3db2..4a7299f 100644 --- a/src/TUnit/manifest.php +++ b/src/TUnit/manifest.php @@ -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', diff --git a/src/TUnit/util/TestAccumulator.php b/src/TUnit/util/TestAccumulator.php index 2d91e93..d20485c 100644 --- a/src/TUnit/util/TestAccumulator.php +++ b/src/TUnit/util/TestAccumulator.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); diff --git a/src/TUnit/util/entry_console.php b/src/TUnit/util/entry_console.php index 87faa6d..38a683f 100644 --- a/src/TUnit/util/entry_console.php +++ b/src/TUnit/util/entry_console.php @@ -43,11 +43,14 @@ global $switches; $switches = new CliSwitchCollection(); - $switches->addSwitch(new CliSwitch(null, null, true, '', '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 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); - } - - require_once $options['bootstrap']; - } + $runner->addTests($tests) + ->addListener(new ConsoleListener()) + ->run(); - $runner = new ConsoleTestRunner( - array(new TestSuite('Main Test Suite', $tests)), - array(new ConsoleListener()), - $options - ); - - $runner->run(); exit(0); ?> \ No newline at end of file