]> source.dussan.org Git - nextcloud-server.git/commitdiff
adding coverage extension for simpletest
authorThomas Mueller <thomas.mueller@tmit.eu>
Sat, 25 Aug 2012 23:35:25 +0000 (01:35 +0200)
committerThomas Mueller <thomas.mueller@tmit.eu>
Sat, 25 Aug 2012 23:36:06 +0000 (01:36 +0200)
21 files changed:
3rdparty/simpletest/extensions/coverage/autocoverage.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/bin/php-coverage-close.php [new file with mode: 0755]
3rdparty/simpletest/extensions/coverage/bin/php-coverage-open.php [new file with mode: 0755]
3rdparty/simpletest/extensions/coverage/bin/php-coverage-report.php [new file with mode: 0755]
3rdparty/simpletest/extensions/coverage/coverage.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/coverage_calculator.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/coverage_data_handler.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/coverage_reporter.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/coverage_utils.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/coverage_writer.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/simple_coverage_writer.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/templates/file.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/templates/index.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/test/coverage_calculator_test.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/test/coverage_data_handler_test.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/test/coverage_reporter_test.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/test/coverage_test.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/test/coverage_utils_test.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/test/sample/code.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/test/simple_coverage_writer_test.php [new file with mode: 0644]
3rdparty/simpletest/extensions/coverage/test/test.php [new file with mode: 0644]

diff --git a/3rdparty/simpletest/extensions/coverage/autocoverage.php b/3rdparty/simpletest/extensions/coverage/autocoverage.php
new file mode 100644 (file)
index 0000000..9fc961b
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * @package        SimpleTest
+ * @subpackage     Extensions
+ */
+/**
+ * Include this in any file to start coverage, coverage will automatically end
+ * when process dies.
+ */
+require_once(dirname(__FILE__) .'/coverage.php');
+
+if (CodeCoverage::isCoverageOn()) {
+    $coverage = CodeCoverage::getInstance();
+    $coverage->startCoverage();
+    register_shutdown_function("stop_coverage");
+}
+
+function stop_coverage() {
+    # hack until i can think of a way to run tests first and w/o exiting
+    $autorun = function_exists("run_local_tests");
+    if ($autorun) {
+        $result = run_local_tests();
+    }
+    CodeCoverage::getInstance()->stopCoverage();
+    if ($autorun) {
+        exit($result ? 0 : 1);
+    }
+}
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/bin/php-coverage-close.php b/3rdparty/simpletest/extensions/coverage/bin/php-coverage-close.php
new file mode 100755 (executable)
index 0000000..9a5a52b
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+/**
+ * Close code coverage data collection, next step is to generate report
+ * @package        SimpleTest
+ * @subpackage     Extensions
+ */
+/**
+ * include coverage files
+ */
+require_once(dirname(__FILE__) . '/../coverage.php');
+$cc = CodeCoverage::getInstance();
+$cc->readSettings();
+$cc->writeUntouched();
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/bin/php-coverage-open.php b/3rdparty/simpletest/extensions/coverage/bin/php-coverage-open.php
new file mode 100755 (executable)
index 0000000..c04e1fb
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Initialize code coverage data collection, next step is to run your tests
+ * with ini setting auto_prepend_file=autocoverage.php ...
+ *
+ * @package        SimpleTest
+ * @subpackage     Extensions
+ */ 
+# optional arguments:
+#  --include=<some filepath regexp>      these files should be included coverage report
+#  --exclude=<come filepath regexp>      these files should not be included in coverage report
+#  --maxdepth=2                          when considering which file were not touched, scan directories 
+#
+# Example: 
+# php-coverage-open.php --include='.*\.php$' --include='.*\.inc$' --exclude='.*/tests/.*' 
+/**#@+
+ * include coverage files
+ */
+require_once(dirname(__FILE__) . '/../coverage_utils.php');
+CoverageUtils::requireSqlite();
+require_once(dirname(__FILE__) . '/../coverage.php');
+/**#@-*/
+$cc = new CodeCoverage();
+$cc->log = 'coverage.sqlite';
+$args = CoverageUtils::parseArguments($_SERVER['argv'], TRUE);
+$cc->includes = CoverageUtils::issetOr($args['include[]'], array('.*\.php$'));
+$cc->excludes = CoverageUtils::issetOr($args['exclude[]']); 
+$cc->maxDirectoryDepth = (int)CoverageUtils::issetOr($args['maxdepth'], '1');
+$cc->resetLog();
+$cc->writeSettings();
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/bin/php-coverage-report.php b/3rdparty/simpletest/extensions/coverage/bin/php-coverage-report.php
new file mode 100755 (executable)
index 0000000..d61c822
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Generate a code coverage report
+ *
+ * @package        SimpleTest
+ * @subpackage     Extensions
+ */
+# optional arguments:
+#  --reportDir=some/directory    the default is ./coverage-report
+#  --title='My Coverage Report'  title the main page of your report
+
+/**#@+
+ * include coverage files
+ */
+require_once(dirname(__FILE__) . '/../coverage_utils.php');
+require_once(dirname(__FILE__) . '/../coverage.php');
+require_once(dirname(__FILE__) . '/../coverage_reporter.php');
+/**#@-*/
+$cc = CodeCoverage::getInstance();
+$cc->readSettings();
+$handler = new CoverageDataHandler($cc->log);
+$report = new CoverageReporter();
+$args = CoverageUtils::parseArguments($_SERVER['argv']);
+$report->reportDir = CoverageUtils::issetOr($args['reportDir'], 'coverage-report');
+$report->title = CoverageUtils::issetOr($args['title'], "Simpletest Coverage");
+$report->coverage = $handler->read();
+$report->untouched = $handler->readUntouchedFiles();
+$report->generate();
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/coverage.php b/3rdparty/simpletest/extensions/coverage/coverage.php
new file mode 100644 (file)
index 0000000..44e5b67
--- /dev/null
@@ -0,0 +1,196 @@
+<?php
+/**
+* @package        SimpleTest
+* @subpackage     Extensions
+*/
+/**
+* load coverage data handle
+*/
+require_once dirname(__FILE__) . '/coverage_data_handler.php';
+
+/**
+ * Orchestrates code coverage both in this thread and in subthread under apache
+ * Assumes this is running on same machine as apache.
+ * @package        SimpleTest
+ * @subpackage     Extensions
+ */
+class CodeCoverage  {
+    var $log;
+    var $root;
+    var $includes;
+    var $excludes;
+    var $directoryDepth;
+    var $maxDirectoryDepth = 20; // reasonable, otherwise arbitrary
+    var $title = "Code Coverage";
+
+    # NOTE: This assumes all code shares the same current working directory.
+    var $settingsFile = './code-coverage-settings.dat';
+
+    static $instance;
+
+    function writeUntouched() {
+        $touched = array_flip($this->getTouchedFiles());
+        $untouched = array();
+        $this->getUntouchedFiles($untouched, $touched, '.', '.');
+        $this->includeUntouchedFiles($untouched);
+    }
+
+    function &getTouchedFiles() {
+        $handler = new CoverageDataHandler($this->log);
+        $touched = $handler->getFilenames();
+        return $touched;
+    }
+
+    function includeUntouchedFiles($untouched) {
+        $handler = new CoverageDataHandler($this->log);
+        foreach ($untouched as $file) {
+            $handler->writeUntouchedFile($file);
+        }
+    }
+
+    function getUntouchedFiles(&$untouched, $touched, $parentPath, $rootPath, $directoryDepth = 1) {
+        $parent = opendir($parentPath);
+        while ($file = readdir($parent)) {
+            $path = "$parentPath/$file";
+            if (is_dir($path)) {
+                if ($file != '.' && $file != '..') {
+                    if ($this->isDirectoryIncluded($path, $directoryDepth)) {
+                        $this->getUntouchedFiles($untouched, $touched, $path, $rootPath, $directoryDepth + 1);
+                    }
+                }
+            }
+            else if ($this->isFileIncluded($path)) {
+                $relativePath = CoverageDataHandler::ltrim($rootPath .'/', $path);
+                if (!array_key_exists($relativePath, $touched)) {
+                    $untouched[] = $relativePath;
+                }
+            }
+        }
+        closedir($parent);
+    }
+
+    function resetLog() {
+        error_log('reseting log');
+        $new_file = fopen($this->log, "w");
+        if (!$new_file) {
+            throw new Exception("Could not create ". $this->log);
+        }
+        fclose($new_file);
+        if (!chmod($this->log, 0666)) {
+            throw new Exception("Could not change ownership on file  ". $this->log);
+        }
+        $handler = new CoverageDataHandler($this->log);
+        $handler->createSchema();
+    }
+
+    function startCoverage() {
+        $this->root = getcwd();
+        if(!extension_loaded("xdebug")) {
+            throw new Exception("Could not load xdebug extension");
+        };
+        xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
+    }
+
+    function stopCoverage() {
+        $cov = xdebug_get_code_coverage();
+        $this->filter($cov);
+        $data = new CoverageDataHandler($this->log);
+        chdir($this->root);
+        $data->write($cov);
+        unset($data); // release sqlite connection
+        xdebug_stop_code_coverage();
+        // make sure we wind up on same current working directory, otherwise
+        // coverage handler writer doesn't know what directory to chop off
+        chdir($this->root);
+    }
+
+    function readSettings() {
+        if (file_exists($this->settingsFile)) {
+            $this->setSettings(file_get_contents($this->settingsFile));
+        } else {
+            error_log("could not find file ". $this->settingsFile);
+        }
+    }
+
+    function writeSettings() {       
+        file_put_contents($this->settingsFile, $this->getSettings());
+    }
+
+    function getSettings() {
+        $data = array(
+       'log' => realpath($this->log), 
+       'includes' => $this->includes, 
+       'excludes' => $this->excludes);
+        return serialize($data);
+    }
+
+    function setSettings($settings) {
+        $data = unserialize($settings);
+        $this->log = $data['log'];
+        $this->includes = $data['includes'];
+        $this->excludes = $data['excludes'];
+    }
+
+    function filter(&$coverage) {
+        foreach ($coverage as $file => $line) {
+            if (!$this->isFileIncluded($file)) {
+                unset($coverage[$file]);
+            }
+        }
+    }
+
+    function isFileIncluded($file)  {
+        if (!empty($this->excludes)) {
+            foreach ($this->excludes as $path) {
+                if (preg_match('|' . $path . '|', $file)) {
+                    return False;
+                }
+            }
+        }
+
+        if (!empty($this->includes)) {
+            foreach ($this->includes as $path) {
+                if (preg_match('|' . $path . '|', $file)) {
+                    return True;
+                }
+            }
+            return False;
+        }
+
+        return True;
+    }
+
+    function isDirectoryIncluded($dir, $directoryDepth)  {
+        if ($directoryDepth >= $this->maxDirectoryDepth) {
+            return false;
+        }
+        if (isset($this->excludes)) {
+            foreach ($this->excludes as $path) {
+                if (preg_match('|' . $path . '|', $dir)) {
+                    return False;
+                }
+            }
+        }
+
+        return True;
+    }
+
+    static function isCoverageOn() {
+        $coverage = self::getInstance();
+        $coverage->readSettings();
+        if (empty($coverage->log) || !file_exists($coverage->log)) {
+            trigger_error('No coverage log');
+            return False;
+        }
+        return True;
+    }
+
+    static function getInstance() {
+        if (self::$instance == NULL) {
+            self::$instance = new CodeCoverage();
+            self::$instance->readSettings();
+        }
+        return self::$instance;
+    }
+}
+?>
diff --git a/3rdparty/simpletest/extensions/coverage/coverage_calculator.php b/3rdparty/simpletest/extensions/coverage/coverage_calculator.php
new file mode 100644 (file)
index 0000000..f1aa57b
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+/**
+* @package        SimpleTest
+* @subpackage     Extensions
+*/
+/**
+* @package        SimpleTest
+* @subpackage     Extensions
+*/
+class CoverageCalculator {
+
+    function coverageByFileVariables($file, $coverage) {
+        $hnd = fopen($file, 'r');
+        if ($hnd == null) {
+            throw new Exception("File $file is missing");
+        }
+        $lines = array();
+        for ($i = 1; !feof($hnd); $i++) {
+            $line = fgets($hnd);
+            $lineCoverage = $this->lineCoverageCodeToStyleClass($coverage, $i);
+            $lines[$i] = array('lineCoverage' => $lineCoverage, 'code' => $line);
+        }
+
+        fclose($hnd);
+
+        $var = compact('file', 'lines', 'coverage');
+        return $var;
+    }
+
+    function lineCoverageCodeToStyleClass($coverage, $line) {
+        if (!array_key_exists($line, $coverage)) {
+            return "comment";
+        }
+        $code = $coverage[$line];
+        if (empty($code)) {
+            return "comment";
+        }
+        switch ($code) {
+            case -1:
+                return "missed";
+            case -2:
+                return "dead";
+        }
+
+        return "covered";
+    }
+
+    function totalLoc($total, $coverage) {
+        return $total + sizeof($coverage);
+    }
+
+    function lineCoverage($total, $line) {
+        # NOTE: counting dead code as covered, as it's almost always an executable line
+        # strange artifact of xdebug or underlying system
+        return $total + ($line > 0 || $line == -2 ? 1 : 0);
+    }
+
+    function totalCoverage($total, $coverage) {
+        return $total + array_reduce($coverage, array(&$this, "lineCoverage"));
+    }
+
+    static function reportFilename($filename) {
+        return preg_replace('|[/\\\\]|', '_', $filename) . '.html';
+    }
+
+    function percentCoverageByFile($coverage, $file, &$results) {
+        $byFileReport = self::reportFilename($file);
+
+        $loc = sizeof($coverage);
+        if ($loc == 0)
+        return 0;
+        $lineCoverage = array_reduce($coverage, array(&$this, "lineCoverage"));
+        $percentage = 100 * ($lineCoverage / $loc);
+        $results[0][$file] = array('byFileReport' => $byFileReport, 'percentage' => $percentage);
+    }
+
+    function variables($coverage, $untouched) {
+        $coverageByFile = array();
+        array_walk($coverage, array(&$this, "percentCoverageByFile"), array(&$coverageByFile));
+
+        $totalLoc = array_reduce($coverage, array(&$this, "totalLoc"));
+
+        if ($totalLoc > 0) {
+            $totalLinesOfCoverage = array_reduce($coverage, array(&$this, "totalCoverage"));
+            $totalPercentCoverage = 100 * ($totalLinesOfCoverage / $totalLoc);
+        }
+
+        $untouchedPercentageDenominator = sizeof($coverage) + sizeof($untouched);
+        if ($untouchedPercentageDenominator > 0) {
+            $filesTouchedPercentage = 100 * sizeof($coverage) / $untouchedPercentageDenominator;
+        }
+
+        $var = compact('coverageByFile', 'totalPercentCoverage', 'totalLoc', 'totalLinesOfCoverage', 'filesTouchedPercentage');
+        $var['untouched'] = $untouched;
+        return $var;
+    }
+}
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/coverage_data_handler.php b/3rdparty/simpletest/extensions/coverage/coverage_data_handler.php
new file mode 100644 (file)
index 0000000..bbf8110
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+/**
+ * @package        SimpleTest
+ * @subpackage     Extensions
+ */
+/**
+ * @todo       which db abstraction layer is this?
+ */
+require_once 'DB/sqlite.php';
+
+/**
+ * Persists code coverage data into SQLite database and aggregate data for convienent
+ * interpretation in report generator.  Be sure to not to keep an instance longer
+ * than you have, otherwise you risk overwriting database edits from another process
+ * also trying to make updates.
+ * @package        SimpleTest
+ * @subpackage     Extensions
+ */
+class CoverageDataHandler {
+
+    var $db;
+
+    function __construct($filename) {
+        $this->filename = $filename;
+        $this->db = new SQLiteDatabase($filename);
+        if (empty($this->db)) {
+            throw new Exception("Could not create sqlite db ". $filename);
+        }
+    }
+
+    function createSchema() {
+        $this->db->queryExec("create table untouched (filename text)");
+        $this->db->queryExec("create table coverage (name text, coverage text)");
+    }
+
+    function &getFilenames() {
+        $filenames = array();
+        $cursor = $this->db->unbufferedQuery("select distinct name from coverage");
+        while ($row = $cursor->fetch()) {
+            $filenames[] = $row[0];
+        }
+
+        return $filenames;
+    }
+
+    function write($coverage) {
+        foreach ($coverage as $file => $lines) {
+            $coverageStr = serialize($lines);
+            $relativeFilename = self::ltrim(getcwd() . '/', $file);
+            $sql = "insert into coverage (name, coverage) values ('$relativeFilename', '$coverageStr')";
+            # if this fails, check you have write permission
+            $this->db->queryExec($sql);
+        }
+    }
+
+    function read() {
+        $coverage = array_flip($this->getFilenames());
+        foreach($coverage as $file => $garbage) {
+            $coverage[$file] = $this->readFile($file);
+        }
+        return $coverage;
+    }
+
+    function &readFile($file) {
+        $sql = "select coverage from coverage where name = '$file'";
+        $aggregate = array();
+        $result = $this->db->query($sql);
+        while ($result->valid()) {
+            $row = $result->current();
+            $this->aggregateCoverage($aggregate, unserialize($row[0]));
+            $result->next();
+        }
+
+        return $aggregate;
+    }
+
+    function aggregateCoverage(&$total, $next) {
+        foreach ($next as $lineno => $code) {
+            if (!isset($total[$lineno])) {
+                $total[$lineno] = $code;
+            } else {
+                $total[$lineno] = $this->aggregateCoverageCode($total[$lineno], $code);
+            }
+        }
+    }
+
+    function aggregateCoverageCode($code1, $code2) {
+        switch($code1) {
+            case -2: return -2;
+            case -1: return $code2;
+            default:
+                switch ($code2) {
+                    case -2: return -2;
+                    case -1: return $code1;
+                }
+        }
+        return $code1 + $code2;
+    }
+
+    static function ltrim($cruft, $pristine) {
+        if(stripos($pristine, $cruft) === 0) {
+            return substr($pristine, strlen($cruft));
+        }
+        return $pristine;
+    }
+
+    function writeUntouchedFile($file) {
+        $relativeFile = CoverageDataHandler::ltrim('./', $file);
+        $sql = "insert into untouched values ('$relativeFile')";
+        $this->db->queryExec($sql);
+    }
+
+    function &readUntouchedFiles() {
+        $untouched = array();
+        $result = $this->db->query("select filename from untouched order by filename");
+        while ($result->valid()) {
+            $row = $result->current();
+            $untouched[] = $row[0];
+            $result->next();
+        }
+
+        return $untouched;
+    }
+}
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/coverage_reporter.php b/3rdparty/simpletest/extensions/coverage/coverage_reporter.php
new file mode 100644 (file)
index 0000000..ba4e716
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+/**
+ * @package        SimpleTest
+ * @subpackage     Extensions
+ */
+/**#@+
+ * include additional coverage files
+ */
+require_once dirname(__FILE__) .'/coverage_calculator.php';
+require_once dirname(__FILE__) .'/coverage_utils.php';
+require_once dirname(__FILE__) .'/simple_coverage_writer.php';
+/**#@-*/
+
+/**
+ * Take aggregated coverage data and generate reports from it using smarty
+ * templates
+ * @package        SimpleTest
+ * @subpackage     Extensions
+ */
+class CoverageReporter {
+    var $coverage;
+    var $untouched;
+    var $reportDir;
+    var $title = 'Coverage';
+    var $writer;
+    var $calculator;
+
+    function __construct() {
+        $this->writer = new SimpleCoverageWriter();
+        $this->calculator = new CoverageCalculator();
+    }
+
+    function generateSummaryReport($out) {
+        $variables = $this->calculator->variables($this->coverage, $this->untouched);
+        $variables['title'] = $this->title;
+        $report = $this->writer->writeSummary($out, $variables);
+        fwrite($out, $report);
+    }
+
+    function generate() {
+        CoverageUtils::mkdir($this->reportDir);
+
+        $index = $this->reportDir .'/index.html';
+        $hnd = fopen($index, 'w');
+        $this->generateSummaryReport($hnd);
+        fclose($hnd);
+
+        foreach ($this->coverage as $file => $cov) {
+            $byFile = $this->reportDir .'/'. self::reportFilename($file);
+            $byFileHnd = fopen($byFile, 'w');
+            $this->generateCoverageByFile($byFileHnd, $file, $cov);
+            fclose($byFileHnd);
+        }
+
+        echo "generated report $index\n";
+    }
+
+    function generateCoverageByFile($out, $file, $cov) {
+        $variables = $this->calculator->coverageByFileVariables($file, $cov);
+        $variables['title'] = $this->title .' - '. $file;
+        $this->writer->writeByFile($out, $variables);
+    }
+
+    static function reportFilename($filename) {
+        return preg_replace('|[/\\\\]|', '_', $filename) . '.html';
+    }
+}
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/coverage_utils.php b/3rdparty/simpletest/extensions/coverage/coverage_utils.php
new file mode 100644 (file)
index 0000000..d2c3a63
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/**
+ * @package        SimpleTest
+ * @subpackage     Extensions
+ */
+/**
+ * @package        SimpleTest
+ * @subpackage     Extensions
+ */
+class CoverageUtils {
+
+    static function mkdir($dir) {
+        if (!file_exists($dir)) {
+            mkdir($dir, 0777, True);
+        } else {
+            if (!is_dir($dir)) {
+                throw new Exception($dir .' exists as a file, not a directory');
+            }
+        }
+    }
+
+    static function requireSqlite() {
+        if (!self::isPackageClassAvailable('DB/sqlite.php', 'SQLiteDatabase')) {
+            echo "sqlite library is required to be installed and available in include_path";
+            exit(1);
+        }
+    }
+
+    static function isPackageClassAvailable($includeFile, $class) {
+        @include_once($includeFile);
+        return class_exists($class);
+    }
+
+    /**
+     * Parses simple parameters from CLI.
+     *
+     * Puts trailing parameters into string array in 'extraArguments'
+     *
+     * Example:
+     * $args = CoverageUtil::parseArguments($_SERVER['argv']);
+     * if ($args['verbose']) echo "Verbose Mode On\n";
+     * $files = $args['extraArguments'];
+     *
+     * Example CLI:
+     *  --foo=blah -x -h  some trailing arguments
+     *
+     * if multiValueMode is true
+     * Example CLI:
+     *  --include=a --include=b --exclude=c
+     * Then
+     *  $args = CoverageUtil::parseArguments($_SERVER['argv']);
+     *  $args['include[]'] will equal array('a', 'b')
+     *  $args['exclude[]'] will equal array('c')
+     *  $args['exclude'] will equal c
+     *  $args['include'] will equal b   NOTE: only keeps last value
+     *
+     * @param unknown_type $argv
+     * @param supportMutliValue - will store 2nd copy of value in an array with key "foo[]"
+     * @return unknown
+     */
+    static public function parseArguments($argv, $mutliValueMode = False) {
+        $args = array();
+        $args['extraArguments'] = array();
+        array_shift($argv); // scriptname
+        foreach ($argv as $arg) {
+            if (ereg('^--([^=]+)=(.*)', $arg, $reg)) {
+                $args[$reg[1]] = $reg[2];
+                if ($mutliValueMode) {
+                    self::addItemAsArray($args, $reg[1], $reg[2]);
+                }
+            } elseif (ereg('^[-]{1,2}([^[:blank:]]+)', $arg, $reg)) {
+                $nonnull = '';
+                $args[$reg[1]] = $nonnull;
+                if ($mutliValueMode) {
+                    self::addItemAsArray($args, $reg[1], $nonnull);
+                }
+            } else {
+                $args['extraArguments'][] = $arg;
+            }
+        }
+
+        return $args;
+    }
+
+    /**
+     * Adds a value as an array of one, or appends to an existing array elements
+     *
+     * @param unknown_type $array
+     * @param unknown_type $item
+     */
+    static function addItemAsArray(&$array, $key, $item) {
+        $array_key = $key .'[]';
+        if (array_key_exists($array_key, $array)) {
+            $array[$array_key][] = $item;
+        } else {
+            $array[$array_key] = array($item);
+        }
+    }
+
+    /**
+     * isset function with default value
+     *
+     * Example:  $z = CoverageUtils::issetOr($array[$key], 'no value given')
+     *
+     * @param unknown_type $val
+     * @param unknown_type $default
+     * @return first value unless value is not set then returns 2nd arg or null if no 2nd arg
+     */
+    static public function issetOr(&$val, $default = null)
+    {
+        return isset($val) ? $val : $default;
+    }
+}
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/coverage_writer.php b/3rdparty/simpletest/extensions/coverage/coverage_writer.php
new file mode 100644 (file)
index 0000000..0a8519c
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+/**
+ * @package        SimpleTest
+ * @subpackage     Extensions
+ */
+/**
+ * @package        SimpleTest
+ * @subpackage     Extensions
+ */
+interface CoverageWriter {
+
+    function writeSummary($out, $variables);
+
+    function writeByFile($out, $variables);
+}
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/simple_coverage_writer.php b/3rdparty/simpletest/extensions/coverage/simple_coverage_writer.php
new file mode 100644 (file)
index 0000000..7eb73fc
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+/**
+ *  SimpleCoverageWriter class file
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ *  @version    $Id: unit_tester.php 1882 2009-07-01 14:30:05Z lastcraft $
+ */
+/**
+ * base coverage writer class
+ */
+require_once dirname(__FILE__) .'/coverage_writer.php';
+
+/**
+ *  SimpleCoverageWriter class
+ *  @package    SimpleTest
+ *  @subpackage UnitTester
+ */
+class SimpleCoverageWriter implements CoverageWriter {
+
+    function writeSummary($out, $variables) {
+        extract($variables);
+        $now = date("F j, Y, g:i a");
+        ob_start();
+        include dirname(__FILE__) . '/templates/index.php';
+        $contents = ob_get_contents();
+        fwrite ($out, $contents);
+        ob_end_clean();
+    }
+
+    function writeByFile($out, $variables) {
+        extract($variables);
+        ob_start();
+        include dirname(__FILE__) . '/templates/file.php';
+        $contents = ob_get_contents();
+        fwrite ($out, $contents);
+        ob_end_clean();
+    }
+}
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/templates/file.php b/3rdparty/simpletest/extensions/coverage/templates/file.php
new file mode 100644 (file)
index 0000000..70f6903
--- /dev/null
@@ -0,0 +1,60 @@
+<html>
+<head>
+<title><?php echo $title ?></title>
+</head>
+<style type="text/css">
+body {
+  font-family: "Gill Sans MT", "Gill Sans", GillSans, Arial, Helvetica, sans-serif;
+}
+h1 {
+  font-size: medium;
+}
+#code {
+  border-spacing: 0;
+}
+.lineNo {
+  color: #ccc;
+}
+.code, .lineNo {
+  white-space: pre;
+  font-family: monospace;
+}
+.covered {
+  color: #090;
+}
+.missed {
+  color: #f00;
+}
+.dead {
+  color: #00f;
+}
+.comment {
+  color: #333;
+}
+</style>
+<body>
+<h1 id="title"><?php echo $title ?></h1>
+<table id="code">
+  <tbody>
+<?php foreach ($lines as $lineNo => $line) { ?>
+    <tr>
+       <td><span class="lineNo"><?php echo $lineNo ?></span></td>
+       <td><span class="<?php echo $line['lineCoverage'] ?> code"><?php echo htmlentities($line['code']) ?></span></td>
+    </tr>
+<?php } ?>
+  </tbody>
+</table>
+<h2>Legend</h2>
+<dl>
+  <dt><span class="missed">Missed</span></dt>
+  <dd>lines code that <strong>were not</strong> excersized during program execution.</dd>
+  <dt><span class="covered">Covered</span></dt>
+  <dd>lines code <strong>were</strong> excersized during program execution.</dd>
+  <dt><span class="comment">Comment/non executable</span></dt>
+  <dd>Comment or non-executable line of code.</dd>
+  <dt><span class="dead">Dead</span></dt>
+  <dd>lines of code that according to xdebug could not be executed.  This is counted as coverage code because 
+  in almost all cases it is code that runnable.</dd>
+</dl>
+</body>
+</html>
diff --git a/3rdparty/simpletest/extensions/coverage/templates/index.php b/3rdparty/simpletest/extensions/coverage/templates/index.php
new file mode 100644 (file)
index 0000000..e4374e2
--- /dev/null
@@ -0,0 +1,106 @@
+<html>
+<head>
+<title><?php echo $title ?></title>
+</head>
+<style type="text/css">
+h1 {
+       font-size: medium;
+}
+
+body {
+       font-family: "Gill Sans MT", "Gill Sans", GillSans, Arial, Helvetica,
+               sans-serif;
+}
+
+td.percentage {
+       text-align: right;
+}
+
+caption {
+       border-bottom: thin solid;
+       font-weight: bolder;
+}
+
+dt {
+       font-weight: bolder;
+}
+
+table {
+       margin: 1em;
+}
+</style>
+<body>
+<h1 id="title"><?php echo $title ?></h1>
+<table>
+       <caption>Summary</caption>
+       <tbody>
+               <tr>
+                       <td>Total Coverage (<a href="#total-coverage">?</a>) :</td>
+                       <td class="percentage"><span class="totalPercentCoverage"><?php echo number_format($totalPercentCoverage, 0) ?>%</span></td>
+               </tr>
+               <tr>
+                       <td>Total Files Covered (<a href="#total-files-covered">?</a>) :</td>
+                       <td class="percentage"><span class="filesTouchedPercentage"><?php  echo number_format($filesTouchedPercentage, 0) ?>%</span></td>
+               </tr>
+               <tr>
+                       <td>Report Generation Date :</td>
+                       <td><?php echo $now ?></td>
+               </tr>
+       </tbody>
+</table>
+<table id="covered-files">
+       <caption>Coverage (<a href="#coverage">?</a>)</caption>
+       <thead>
+               <tr>
+                       <th>File</th>
+                       <th>Coverage</th>
+               </tr>
+       </thead>
+       <tbody>
+               <?php foreach ($coverageByFile as $file => $coverage) { ?>
+               <tr>
+                       <td><a class="byFileReportLink" href="<?php echo $coverage['byFileReport']  ?>"><?php echo $file ?></a></td>
+                       <td class="percentage"><span class="percentCoverage"><?php echo number_format($coverage['percentage'], 0) ?>%</span></td>
+               </tr>
+               <?php } ?>
+       </tbody>
+</table>
+<table>
+       <caption>Files Not Covered (<a href="#untouched">?</a>)</caption>
+       <tbody>
+               <?php foreach ($untouched as $key => $file) { ?>
+               <tr>
+                       <td><span class="untouchedFile"><?php echo $file ?></span></td>
+               </tr>
+               <?php } ?>
+       </tbody>
+</table>
+
+<h2>Glossary</h2>
+<dl>
+       <dt><a name="total-coverage">Total Coverage</a></dt>
+       <dd>Ratio of all the lines of executable code that were executed to the
+       lines of code that were not executed. This does not include the files
+       that were not covered at all.</dd>
+       <dt><a name="total-files-covered">Total Files Covered</a></dt>
+       <dd>This is the ratio of the number of files tested, to the number of
+       files not tested at all.</dd>
+       <dt><a name="coverage">Coverage</a></dt>
+       <dd>These files were parsed and loaded by the php interpreter while
+       running the tests. Percentage is determined by the ratio of number of
+       lines of code executed to the number of possible executable lines of
+       code. "dead" lines of code, or code that could not be executed
+       according to xdebug, are counted as covered because in almost all cases
+       it is the end of a logical loop.</dd>
+       <dt><a name="untouched">Files Not Covered</a></dt>
+       <dd>These files were not loaded by the php interpreter at anytime
+       during a unit test. You could consider these files having 0% coverage,
+       but because it is difficult to determine the total coverage unless you
+       could count the lines for executable code, this is not reflected in the
+       Total Coverage calculation.</dd>
+</dl>
+
+<p>Code coverage generated by <a href="http://www.simpletest.org">SimpleTest</a></p>
+
+</body>
+</html>
diff --git a/3rdparty/simpletest/extensions/coverage/test/coverage_calculator_test.php b/3rdparty/simpletest/extensions/coverage/test/coverage_calculator_test.php
new file mode 100644 (file)
index 0000000..64bd8d4
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+require_once(dirname(__FILE__) . '/../../../autorun.php');
+
+class CoverageCalculatorTest extends UnitTestCase {
+    function skip() {
+        $this->skipIf(
+                       !file_exists('DB/sqlite.php'),
+                'The Coverage extension needs to have PEAR installed');
+    }
+    
+       function setUp() {
+               require_once dirname(__FILE__) .'/../coverage_calculator.php';
+        $this->calc = new CoverageCalculator();
+    }
+
+    function testVariables() {
+        $coverage = array('file' => array(1,1,1,1));
+        $untouched = array('missed-file');
+        $variables = $this->calc->variables($coverage, $untouched);
+        $this->assertEqual(4, $variables['totalLoc']);
+        $this->assertEqual(100, $variables['totalPercentCoverage']);
+        $this->assertEqual(4, $variables['totalLinesOfCoverage']);
+        $expected = array('file' => array('byFileReport' => 'file.html', 'percentage' => 100));
+        $this->assertEqual($expected, $variables['coverageByFile']);
+        $this->assertEqual(50, $variables['filesTouchedPercentage']);
+        $this->assertEqual($untouched, $variables['untouched']);
+    }
+
+    function testPercentageCoverageByFile() {
+        $coverage = array(0,0,0,1,1,1);
+        $results = array();
+        $this->calc->percentCoverageByFile($coverage, 'file', $results);
+        $pct = $results[0];
+        $this->assertEqual(50, $pct['file']['percentage']);
+        $this->assertEqual('file.html', $pct['file']['byFileReport']);
+    }
+
+    function testTotalLoc() {
+        $this->assertEqual(13, $this->calc->totalLoc(10, array(1,2,3)));
+    }
+
+    function testLineCoverage() {
+        $this->assertEqual(10, $this->calc->lineCoverage(10, -1));
+        $this->assertEqual(10, $this->calc->lineCoverage(10, 0));
+        $this->assertEqual(11, $this->calc->lineCoverage(10, 1));
+    }
+
+    function testTotalCoverage() {
+        $this->assertEqual(11, $this->calc->totalCoverage(10, array(-1,1)));
+    }
+
+    static function getAttribute($element, $attribute) {
+        $a = $element->attributes();
+        return $a[$attribute];
+    }
+
+    static function dom($stream) {
+        rewind($stream);
+        $actual = stream_get_contents($stream);
+        $html = DOMDocument::loadHTML($actual);
+        return simplexml_import_dom($html);
+    }
+}
+
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/test/coverage_data_handler_test.php b/3rdparty/simpletest/extensions/coverage/test/coverage_data_handler_test.php
new file mode 100644 (file)
index 0000000..54c67a4
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+require_once(dirname(__FILE__) . '/../../../autorun.php');
+
+class CoverageDataHandlerTest extends UnitTestCase {
+    function skip() {
+        $this->skipIf(
+                       !file_exists('DB/sqlite.php'),
+                'The Coverage extension needs to have PEAR installed');
+    }
+    
+       function setUp() {
+               require_once dirname(__FILE__) .'/../coverage_data_handler.php';
+    }
+
+    function testAggregateCoverageCode() {
+        $handler = new CoverageDataHandler($this->tempdb());
+        $this->assertEqual(-2, $handler->aggregateCoverageCode(-2, -2));
+        $this->assertEqual(-2, $handler->aggregateCoverageCode(-2, 10));
+        $this->assertEqual(-2, $handler->aggregateCoverageCode(10, -2));
+        $this->assertEqual(-1, $handler->aggregateCoverageCode(-1, -1));
+        $this->assertEqual(10, $handler->aggregateCoverageCode(-1, 10));
+        $this->assertEqual(10, $handler->aggregateCoverageCode(10, -1));
+        $this->assertEqual(20, $handler->aggregateCoverageCode(10, 10));
+    }
+
+    function testSimpleWriteRead() {
+        $handler = new CoverageDataHandler($this->tempdb());
+        $handler->createSchema();
+        $coverage = array(10 => -2, 20 => -1, 30 => 0, 40 => 1);
+        $handler->write(array('file' => $coverage));
+
+        $actual = $handler->readFile('file');
+        $expected = array(10 => -2, 20 => -1, 30 => 0, 40 => 1);
+        $this->assertEqual($expected, $actual);
+    }
+
+    function testMultiFileWriteRead() {
+        $handler = new CoverageDataHandler($this->tempdb());
+        $handler->createSchema();
+        $handler->write(array(
+       'file1' => array(-2, -1, 1), 
+       'file2' => array(-2, -1, 1)
+        ));
+        $handler->write(array(
+       'file1' => array(-2, -1, 1)
+        ));
+
+        $expected = array(
+       'file1' => array(-2, -1, 2),
+       'file2' => array(-2, -1, 1)
+        );
+        $actual = $handler->read();
+        $this->assertEqual($expected, $actual);
+    }
+
+    function testGetfilenames() {
+        $handler = new CoverageDataHandler($this->tempdb());
+        $handler->createSchema();
+        $rawCoverage = array('file0' => array(), 'file1' => array());
+        $handler->write($rawCoverage);
+        $actual = $handler->getFilenames();
+        $this->assertEqual(array('file0', 'file1'), $actual);
+    }
+
+    function testWriteUntouchedFiles() {
+        $handler = new CoverageDataHandler($this->tempdb());
+        $handler->createSchema();
+        $handler->writeUntouchedFile('bluejay');
+        $handler->writeUntouchedFile('robin');
+        $this->assertEqual(array('bluejay', 'robin'), $handler->readUntouchedFiles());
+    }
+
+    function testLtrim() {
+        $this->assertEqual('ber', CoverageDataHandler::ltrim('goo', 'goober'));
+        $this->assertEqual('some/file', CoverageDataHandler::ltrim('./', './some/file'));
+        $this->assertEqual('/x/y/z/a/b/c', CoverageDataHandler::ltrim('/a/b/', '/x/y/z/a/b/c'));
+    }
+
+    function tempdb() {
+        return tempnam(NULL, 'coverage.test.db');
+    }
+}
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/test/coverage_reporter_test.php b/3rdparty/simpletest/extensions/coverage/test/coverage_reporter_test.php
new file mode 100644 (file)
index 0000000..a8b0996
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+require_once(dirname(__FILE__) . '/../../../autorun.php');
+
+class CoverageReporterTest extends UnitTestCase {
+    function skip() {
+        $this->skipIf(
+                       !file_exists('DB/sqlite.php'),
+                'The Coverage extension needs to have PEAR installed');
+    }
+       
+       function setUp() {
+        require_once dirname(__FILE__) .'/../coverage_reporter.php';
+        new CoverageReporter();
+    }
+
+    function testreportFilename() {
+        $this->assertEqual("parula.php.html", CoverageReporter::reportFilename("parula.php"));
+        $this->assertEqual("warbler_parula.php.html", CoverageReporter::reportFilename("warbler/parula.php"));
+        $this->assertEqual("warbler_parula.php.html", CoverageReporter::reportFilename("warbler\\parula.php"));
+    }
+}
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/test/coverage_test.php b/3rdparty/simpletest/extensions/coverage/test/coverage_test.php
new file mode 100644 (file)
index 0000000..f09d03f
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+require_once(dirname(__FILE__) . '/../../../autorun.php');
+require_once(dirname(__FILE__) . '/../../../mock_objects.php');
+
+class CodeCoverageTest extends UnitTestCase {
+    function skip() {
+        $this->skipIf(
+                       !file_exists('DB/sqlite.php'),
+                'The Coverage extension needs to have PEAR installed');
+    }
+       
+       function setUp() {
+        require_once dirname(__FILE__) .'/../coverage.php';
+    }
+       
+    function testIsFileIncluded() {
+        $coverage = new CodeCoverage();
+        $this->assertTrue($coverage->isFileIncluded('aaa'));
+        $coverage->includes = array('a');
+        $this->assertTrue($coverage->isFileIncluded('aaa'));
+        $coverage->includes = array('x');
+        $this->assertFalse($coverage->isFileIncluded('aaa'));
+        $coverage->excludes = array('aa');
+        $this->assertFalse($coverage->isFileIncluded('aaa'));
+    }
+
+    function testIsFileIncludedRegexp() {
+        $coverage = new CodeCoverage();
+        $coverage->includes = array('modules/.*\.php$');
+        $coverage->excludes = array('bad-bunny.php');
+        $this->assertFalse($coverage->isFileIncluded('modules/a.test'));
+        $this->assertFalse($coverage->isFileIncluded('modules/bad-bunny.test'));
+        $this->assertTrue($coverage->isFileIncluded('modules/test.php'));
+        $this->assertFalse($coverage->isFileIncluded('module-bad/good-bunny.php'));
+        $this->assertTrue($coverage->isFileIncluded('modules/good-bunny.php'));
+    }
+
+    function testIsDirectoryIncludedPastMaxDepth() {
+        $coverage = new CodeCoverage();
+        $coverage->maxDirectoryDepth = 5;
+        $this->assertTrue($coverage->isDirectoryIncluded('aaa', 1));
+        $this->assertFalse($coverage->isDirectoryIncluded('aaa', 5));
+    }
+
+    function testIsDirectoryIncluded() {
+        $coverage = new CodeCoverage();
+        $this->assertTrue($coverage->isDirectoryIncluded('aaa', 0));
+        $coverage->excludes = array('b$');
+        $this->assertTrue($coverage->isDirectoryIncluded('aaa', 0));
+        $coverage->includes = array('a$'); // includes are ignore, all dirs are included unless excluded
+        $this->assertTrue($coverage->isDirectoryIncluded('aaa', 0));
+        $coverage->excludes = array('.*a$');
+        $this->assertFalse($coverage->isDirectoryIncluded('aaa', 0));
+    }
+
+    function testFilter() {
+        $coverage = new CodeCoverage();
+        $data = array('a' => 0, 'b' => 0, 'c' => 0);
+        $coverage->includes = array('b');
+        $coverage->filter($data);
+        $this->assertEqual(array('b' => 0), $data);
+    }
+
+    function testUntouchedFiles() {
+        $coverage = new CodeCoverage();
+        $touched = array_flip(array("test/coverage_test.php"));
+        $actual = array();
+        $coverage->includes = array('coverage_test\.php$');
+        $parentDir = realpath(dirname(__FILE__));
+        $coverage->getUntouchedFiles($actual, $touched, $parentDir, $parentDir);
+        $this->assertEqual(array("coverage_test.php"), $actual);
+    }
+
+    function testResetLog() {
+        $coverage = new CodeCoverage();
+        $coverage->log = tempnam(NULL, 'php.xdebug.coverage.test.');
+        $coverage->resetLog();
+        $this->assertTrue(file_exists($coverage->log));
+    }
+
+    function testSettingsSerialization() {
+        $coverage = new CodeCoverage();
+        $coverage->log = '/banana/boat';
+        $coverage->includes = array('apple', 'orange');
+        $coverage->excludes = array('tomato', 'pea');
+        $data = $coverage->getSettings();
+        $this->assertNotNull($data);
+
+        $actual = new CodeCoverage();
+        $actual->setSettings($data);
+        $this->assertEqual('/banana/boat', $actual->log);
+        $this->assertEqual(array('apple', 'orange'), $actual->includes);
+        $this->assertEqual(array('tomato', 'pea'), $actual->excludes);
+    }
+
+    function testSettingsCanBeReadWrittenToDisk() {
+        $settings_file = 'banana-boat-coverage-settings-test.dat';
+        $coverage = new CodeCoverage();
+        $coverage->log = '/banana/boat';
+        $coverage->settingsFile = $settings_file;
+        $coverage->writeSettings();
+
+        $actual = new CodeCoverage();
+        $actual->settingsFile = $settings_file;
+        $actual->readSettings();
+        $this->assertEqual('/banana/boat', $actual->log);
+    }
+}
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/test/coverage_utils_test.php b/3rdparty/simpletest/extensions/coverage/test/coverage_utils_test.php
new file mode 100644 (file)
index 0000000..b900c5d
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+require_once dirname(__FILE__) . '/../../../autorun.php';
+
+class CoverageUtilsTest extends UnitTestCase {
+    function skip() {
+        $this->skipIf(
+                       !file_exists('DB/sqlite.php'),
+                'The Coverage extension needs to have PEAR installed');
+    }
+       
+       function setUp() {
+       require_once dirname(__FILE__) .'/../coverage_utils.php';
+       }
+       
+    function testMkdir() {
+        CoverageUtils::mkdir(dirname(__FILE__));
+        try {
+            CoverageUtils::mkdir(__FILE__);
+            $this->fail("Should give error about cannot create dir of a file");
+        } catch (Exception $expected) {
+        }
+    }
+
+    function testIsPackageClassAvailable() {
+        $coverageSource = dirname(__FILE__) .'/../coverage_calculator.php';
+        $this->assertTrue(CoverageUtils::isPackageClassAvailable($coverageSource, 'CoverageCalculator'));
+        $this->assertFalse(CoverageUtils::isPackageClassAvailable($coverageSource, 'BogusCoverage'));
+        $this->assertFalse(CoverageUtils::isPackageClassAvailable('bogus-file', 'BogusCoverage'));
+        $this->assertTrue(CoverageUtils::isPackageClassAvailable('bogus-file', 'CoverageUtils'));
+    }
+
+    function testParseArgumentsMultiValue() {
+        $actual = CoverageUtils::parseArguments(array('scriptname', '--a=b', '--a=c'), True);
+        $expected = array('extraArguments' => array(), 'a' => 'c', 'a[]' => array('b', 'c'));
+        $this->assertEqual($expected, $actual);
+    }
+
+    function testParseArguments() {
+        $actual = CoverageUtils::parseArguments(array('scriptname', '--a=b', '-c', 'xxx'));
+        $expected = array('a' => 'b', 'c' => '', 'extraArguments' => array('xxx'));
+        $this->assertEqual($expected, $actual);
+    }
+
+    function testParseDoubleDashNoArguments() {
+        $actual = CoverageUtils::parseArguments(array('scriptname', '--aa'));
+        $this->assertTrue(isset($actual['aa']));
+    }
+
+    function testParseHyphenedExtraArguments() {
+        $actual = CoverageUtils::parseArguments(array('scriptname', '--alpha-beta=b', 'gamma-lambda'));
+        $expected = array('alpha-beta' => 'b', 'extraArguments' => array('gamma-lambda'));
+        $this->assertEqual($expected, $actual);
+    }
+
+    function testAddItemAsArray() {
+        $actual = array();
+        CoverageUtils::addItemAsArray($actual, 'bird', 'duck');
+        $this->assertEqual(array('bird[]' => array('duck')), $actual);
+
+        CoverageUtils::addItemAsArray(&$actual, 'bird', 'pigeon');
+        $this->assertEqual(array('bird[]' => array('duck', 'pigeon')), $actual);
+    }
+
+    function testIssetOr() {
+        $data = array('bird' => 'gull');
+        $this->assertEqual('lab', CoverageUtils::issetOr($data['dog'], 'lab'));
+        $this->assertEqual('gull', CoverageUtils::issetOr($data['bird'], 'sparrow'));
+    }
+}
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/test/sample/code.php b/3rdparty/simpletest/extensions/coverage/test/sample/code.php
new file mode 100644 (file)
index 0000000..a2438f4
--- /dev/null
@@ -0,0 +1,4 @@
+<?php
+// sample code
+$x = 1 + 2;
+if (false) echo "dead";
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/test/simple_coverage_writer_test.php b/3rdparty/simpletest/extensions/coverage/test/simple_coverage_writer_test.php
new file mode 100644 (file)
index 0000000..2c9f9ab
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+require_once(dirname(__FILE__) . '/../../../autorun.php');
+
+class SimpleCoverageWriterTest extends UnitTestCase {
+       function skip() {
+        $this->skipIf(
+                       !file_exists('DB/sqlite.php'),
+                'The Coverage extension needs to have PEAR installed');
+    }
+               
+       function setUp() {
+               require_once dirname(__FILE__) .'/../simple_coverage_writer.php';
+               require_once dirname(__FILE__) .'/../coverage_calculator.php';
+               
+       }
+
+       function testGenerateSummaryReport() {
+        $writer = new SimpleCoverageWriter();
+        $coverage = array('file' => array(0, 1));
+        $untouched = array('missed-file');
+        $calc = new CoverageCalculator();
+        $variables = $calc->variables($coverage, $untouched);
+        $variables['title'] = 'coverage';
+        $out = fopen("php://memory", 'w');
+        $writer->writeSummary($out, $variables);
+        $dom = self::dom($out);
+        $totalPercentCoverage = $dom->elements->xpath("//span[@class='totalPercentCoverage']");
+        $this->assertEqual('50%', (string)$totalPercentCoverage[0]);
+
+        $fileLinks = $dom->elements->xpath("//a[@class='byFileReportLink']");
+        $fileLinkAttr = $fileLinks[0]->attributes();
+        $this->assertEqual('file.html', $fileLinkAttr['href']);
+        $this->assertEqual('file', (string)($fileLinks[0]));
+
+        $untouchedFile = $dom->elements->xpath("//span[@class='untouchedFile']");
+        $this->assertEqual('missed-file', (string)$untouchedFile[0]);
+    }
+
+    function testGenerateCoverageByFile() {
+        $writer = new SimpleCoverageWriter();
+        $cov = array(3 => 1, 4 => -2); // 2 comments, 1 code, 1 dead  (1-based indexes)
+        $out = fopen("php://memory", 'w');
+        $file = dirname(__FILE__) .'/sample/code.php';
+        $calc = new CoverageCalculator();
+        $variables = $calc->coverageByFileVariables($file, $cov);
+        $variables['title'] = 'coverage';
+        $writer->writeByFile($out, $variables);
+        $dom = self::dom($out);
+
+        $cells = $dom->elements->xpath("//table[@id='code']/tbody/tr/td/span");
+        $this->assertEqual("comment code", self::getAttribute($cells[1], 'class'));
+        $this->assertEqual("comment code", self::getAttribute($cells[3], 'class'));
+        $this->assertEqual("covered code", self::getAttribute($cells[5], 'class'));
+        $this->assertEqual("dead code", self::getAttribute($cells[7], 'class'));
+    }
+
+    static function getAttribute($element, $attribute) {
+        $a = $element->attributes();
+        return $a[$attribute];
+    }
+
+    static function dom($stream) {
+        rewind($stream);
+        $actual = stream_get_contents($stream);
+        $html = DOMDocument::loadHTML($actual);
+        return simplexml_import_dom($html);
+    }
+}
+?>
\ No newline at end of file
diff --git a/3rdparty/simpletest/extensions/coverage/test/test.php b/3rdparty/simpletest/extensions/coverage/test/test.php
new file mode 100644 (file)
index 0000000..0af4dbf
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+// $Id: $
+require_once(dirname(__FILE__) . '/../../../autorun.php');
+
+class CoverageUnitTests extends TestSuite {
+    function CoverageUnitTests() {
+        $this->TestSuite('Coverage Unit tests');
+        $path = dirname(__FILE__) . '/*_test.php';
+        foreach(glob($path) as $test) {
+            $this->addFile($test);
+        }
+    }
+}
+?>
\ No newline at end of file