Commit 61399a52 authored by meus's avatar meus

added spikephpcoverage but did not get it to work. if somebody wants to play...

added spikephpcoverage but did not get it to work. if somebody wants to play with it look in DoctrineTest on line 122. fixed the coverage report a little
parent 3cea839f
......@@ -118,6 +118,29 @@ class DoctrineTest
//generate coverage report
if (isset($options['coverage'])) {
/*
* The below code will not work for me (meus). It would be nice if
* somebody could give it a try. Just replace this block of code
* with the one below
*
define('PHPCOVERAGE_HOME', dirname(dirname(__FILE__)) . '/vendor/spikephpcoverage');
require_once PHPCOVERAGE_HOME . '/CoverageRecorder.php';
require_once PHPCOVERAGE_HOME . '/reporter/HtmlCoverageReporter.php';
$covReporter = new HtmlCoverageReporter('Doctrine Code Coverage Report', '', 'coverage2');
$includePaths = array('../lib');
$excludePaths = array();
$cov = new CoverageRecorder($includePaths, $excludePaths, $covReporter);
$cov->startInstrumentation();
$testGroup->run($reporter, $filter);
$cov->stopInstrumentation();
$cov->generateReport();
$covReporter->printTextSummary();
*/
xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
$testGroup->run($reporter, $filter);
$result['coverage'] = xdebug_get_code_coverage();
......@@ -126,6 +149,7 @@ class DoctrineTest
require_once dirname(__FILE__) . '/DoctrineTest/Coverage.php';
$coverageGeneration = new DoctrineTest_Coverage();
$coverageGeneration->generateReport();
// */
} else {
$testGroup->run($reporter, $filter);
}
......
......@@ -98,13 +98,14 @@ class DoctrineTest_Coverage
$totals['notcovered'] , '</td><td></tr>';
foreach($coveredArray as $class => $info){
echo '<tr><td>' . $class . '</td><td>' . $info['percentage'] . ' % </td><td>' . $info['total'] . '</td><td>' . $info['covered'] . '</td><td>' . $info['maybe'] . '</td><td>' . $info['notcovered']. '</td>';
echo '<tr><td>';
if ( $info['type'] == "covered") {
echo '<td><a href="' , $class , '.html">', $class , '</a></td>';
} else {
echo '<td>not tested</td>';
echo '<a href="' , $class , '.html">', $class , '</a>';
}else{
echo $class;
}
echo '</tr>';
echo '<td>' . $info['percentage'] . ' % </td><td>' . $info['total'] . '</td><td>' . $info['covered'] . '</td><td>' . $info['maybe'] . '</td><td>' . $info['notcovered']. '</td></tr>';
}
}
......
......@@ -40,6 +40,7 @@ $revision = $svn_info[1];
<h1>Coverage report for Doctrine</h1>
<p>Report generated against revision <?php echo $reporter->getRevision(); ?> current HEAD revision is <?php echo $revision ?>.</p>
<p>Default mode shows results sorted by percentage with highest first. Customize the ordering with the following GET parameters:<br /> <ul><li>order = covered|total|maybe|notcovered|percentage</li><li>flip=true</li></ul></p>
<p>Files that are not linked in the below table are not tested at all.</p>
<table>
<tr>
<th></th>
......
<?php
/*
* $Id: CoverageRecorder.php 14665 2005-03-23 19:37:50Z npac $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
if(!defined("__PHPCOVERAGE_HOME")) {
define("__PHPCOVERAGE_HOME", dirname(__FILE__));
}
require_once __PHPCOVERAGE_HOME . "/conf/phpcoverage.conf.php";
require_once __PHPCOVERAGE_HOME . "/util/Utility.php";
require_once __PHPCOVERAGE_HOME . "/reporter/CoverageReporter.php";
/**
*
* The Coverage Recorder utility
*
* This is the main class for the CoverageRecorder. User should
* instantiate this class and set various parameters of it.
* The startInstrumentation and stopInstrumentation methods will
* switch code coverage recording on and off respectively.
*
* The code coverage is recorded using XDebug Zend Extension. Therefore,
* it is required to install that extension on the system where
* code coverage measurement is going to take place. See
* {@link http://www.xdebug.org www.xdebug.org} for more
* information.
*
* @author Nimish Pachapurkar <npac@spikesource.com>
* @version $Revision: 14665 $
*/
class CoverageRecorder {
// {{{ Members
protected $includePaths;
protected $excludePaths;
protected $reporter;
protected $coverageData;
protected $isRemote = false;
protected $stripped = false;
protected $phpCoverageFiles = array("phpcoverage.inc.php");
protected $version;
protected $logger;
/**
* What extensions are treated as php files.
*
* @param "php" Array of extension strings
*/
protected $phpExtensions;
// }}}
// {{{ Constructor
/**
* Constructor (PHP5 only)
*
* @param $includePaths Directories to be included in code coverage report
* @param $excludePaths Directories to be excluded from code coverage report
* @param $reporter Instance of a Reporter subclass
* @access public
*/
public function __construct(
$includePaths=array("."),
$excludePaths=array(),
$reporter="new HtmlCoverageReporter()"
) {
$this->includePaths = $includePaths;
$this->excludePaths = $excludePaths;
$this->reporter = $reporter;
// Set back reference
$this->reporter->setCoverageRecorder($this);
$this->excludeCoverageDir();
$this->version = "0.8";
// Configuration
global $spc_config;
$this->phpExtensions = $spc_config['extensions'];
global $util;
$this->logger = $util->getLogger();
}
// }}}
// {{{ public function startInstrumentation()
/**
* Starts the code coverage recording
*
* @access public
*/
public function startInstrumentation() {
if(extension_loaded("xdebug")) {
xdebug_start_code_coverage();
return true;
}
$this->logger->critical("[CoverageRecorder::startInstrumentation()] "
. "ERROR: Xdebug not loaded.", __FILE__, __LINE__);
return false;
}
// }}}
// {{{ public function stopInstrumentation()
/**
* Stops code coverage recording
*
* @access public
*/
public function stopInstrumentation() {
if(extension_loaded("xdebug")) {
$this->coverageData = xdebug_get_code_coverage();
xdebug_stop_code_coverage();
$this->logger->debug("[CoverageRecorder::stopInstrumentation()] Code coverage: " . print_r($this->coverageData, true),
__FILE__, __LINE__);
return true;
}
else {
$this->logger->critical("[CoverageRecorder::stopInstrumentation()] Xdebug not loaded.", __FILE__, __LINE__);
}
return false;
}
// }}}
// {{{ public function generateReport()
/**
* Generate the code coverage report
*
* @access public
*/
public function generateReport() {
if($this->isRemote) {
$this->logger->info("[CoverageRecorder::generateReport()] "
."Writing report.", __FILE__, __LINE__);
}
else {
$this->logger->info("[CoverageRecorder::generateReport()] "
. "Writing report:\t\t", __FILE__, __LINE__);
}
$this->logger->debug("[CoverageRecoder::generateReport()] " . print_r($this->coverageData, true),
__FILE__, __LINE__);
$this->unixifyCoverageData();
$this->coverageData = $this->stripCoverageData();
$this->reporter->generateReport($this->coverageData);
if($this->isRemote) {
$this->logger->info("[CoverageRecorder::generateReport()] [done]", __FILE__, __LINE__);
}
else {
$this->logger->info("[done]", __FILE__, __LINE__);
}
}
// }}}
/*{{{ protected function removeAbsentPaths() */
/**
* Remove the directories that do not exist from the input array
*
* @param &$dirs Array of directory names
* @access protected
*/
protected function removeAbsentPaths(&$dirs) {
for($i = 0; $i < count($dirs); $i++) {
if(! file_exists($dirs[$i])) {
// echo "Not found: " . $dirs[$i] . "\n";
$this->errors[] = "Not found: " . $dirs[$i]
. ". Removing ...";
array_splice($dirs, $i, 1);
$i--;
}
else {
$dirs[$i] = realpath($dirs[$i]);
}
}
}
/*}}}*/
// {{{ protected function processSourcePaths()
/**
* Processes and validates the source directories
*
* @access protected
*/
protected function processSourcePaths() {
$this->removeAbsentPaths($this->includePaths);
$this->removeAbsentPaths($this->excludePaths);
sort($this->includePaths, SORT_STRING);
}
// }}}
/*{{{ protected function getFilesAndDirs() */
/**
* Get the list of files that match the extensions in $this->phpExtensions
*
* @param $dir Root directory
* @param &$files Array of filenames to append to
* @access protected
*/
protected function getFilesAndDirs($dir, &$files) {
global $util;
$dirs[] = $dir;
while(count($dirs) > 0) {
$currDir = realpath(array_pop($dirs));
if(!is_readable($currDir)) {
continue;
}
//echo "Current Dir: $currDir \n";
$currFiles = scandir($currDir);
//print_r($currFiles);
for($j = 0; $j < count($currFiles); $j++) {
if($currFiles[$j] == "." || $currFiles[$j] == "..") {
continue;
}
$currFiles[$j] = $currDir . "/" . $currFiles[$j];
//echo "Current File: " . $currFiles[$j] . "\n";
if(is_file($currFiles[$j])) {
$pathParts = pathinfo($currFiles[$j]);
if(isset($pathParts['extension']) && in_array($pathParts['extension'], $this->phpExtensions)) {
$files[] = $util->replaceBackslashes($currFiles[$j]);
}
}
if(is_dir($currFiles[$j])) {
$dirs[] = $currFiles[$j];
}
}
}
}
/*}}}*/
/*{{{ protected function addFiles() */
/**
* Add all source files to the list of files that need to be parsed.
*
* @access protected
*/
protected function addFiles() {
global $util;
$files = array();
for($i = 0; $i < count($this->includePaths); $i++) {
$this->includePaths[$i] = $util->replaceBackslashes($this->includePaths[$i]);
if(is_dir($this->includePaths[$i])) {
//echo "Calling getFilesAndDirs with " . $this->includePaths[$i] . "\n";
$this->getFilesAndDirs($this->includePaths[$i], $files);
}
else if(is_file($this->includePaths[$i])) {
$files[] = $this->includePaths[$i];
}
}
$this->logger->debug("Found files:" . print_r($files, true),
__FILE__, __LINE__);
for($i = 0; $i < count($this->excludePaths); $i++) {
$this->excludePaths[$i] = $util->replaceBackslashes($this->excludePaths[$i]);
}
for($i = 0; $i < count($files); $i++) {
for($j = 0; $j < count($this->excludePaths); $j++) {
$this->logger->debug($files[$i] . "\t" . $this->excludePaths[$j] . "\n", __FILE__, __LINE__);
if(strpos($files[$i], $this->excludePaths[$j]) === 0) {
continue;
}
}
if(!array_key_exists($files[$i], $this->coverageData)) {
$this->coverageData[$files[$i]] = array();
}
}
}
/*}}}*/
// {{{ protected function stripCoverageData()
/**
* Removes the unwanted coverage data from the recordings
*
* @return Processed coverage data
* @access protected
*/
protected function stripCoverageData() {
if($this->stripped) {
$this->logger->debug("[CoverageRecorder::stripCoverageData()] Already stripped!", __FILE__, __LINE__);
return $this->coverageData;
}
$this->stripped = true;
if(empty($this->coverageData)) {
$this->logger->warn("[CoverageRecorder::stripCoverageData()] No coverage data found.", __FILE__, __LINE__);
return $this->coverageData;
}
$this->processSourcePaths();
$this->logger->debug("!!!!!!!!!!!!! Source Paths !!!!!!!!!!!!!!",
__FILE__, __LINE__);
$this->logger->debug(print_r($this->includePaths, true),
__FILE__, __LINE__);
$this->logger->debug(print_r($this->excludePaths, true),
__FILE__, __LINE__);
$this->logger->debug("!!!!!!!!!!!!! Source Paths !!!!!!!!!!!!!!",
__FILE__, __LINE__);
$this->addFiles();
$altCoverageData = array();
foreach ($this->coverageData as $filename => &$lines) {
$preserve = false;
$realFile = $filename;
for($i = 0; $i < count($this->includePaths); $i++) {
if(strpos($realFile, $this->includePaths[$i]) === 0) {
$preserve = true;
}
else {
$this->logger->debug("File: " . $realFile
. "\nDoes not match: " . $this->includePaths[$i],
__FILE__, __LINE__);
}
}
// Exclude dirs have a precedence over includes.
for($i = 0; $i < count($this->excludePaths); $i++) {
if(strpos($realFile, $this->excludePaths[$i]) === 0) {
$preserve = false;
}
else if(in_array(basename($realFile), $this->phpCoverageFiles)) {
$preserve = false;
}
}
if($preserve) {
// Should be preserved
$altCoverageData[$filename] = $lines;
}
}
array_multisort($altCoverageData, SORT_STRING);
return $altCoverageData;
}
// }}}
/*{{{ protected function unixifyCoverageData() */
/**
* Convert filepaths in coverage data to forward slash separated
* paths.
*
* @access protected
*/
protected function unixifyCoverageData() {
global $util;
$tmpCoverageData = array();
foreach($this->coverageData as $file => &$lines) {
$tmpCoverageData[$util->replaceBackslashes(realpath($file))] = $lines;
}
$this->coverageData = $tmpCoverageData;
}
/*}}}*/
// {{{ public function getErrors()
/**
* Returns the errors array containing all error encountered so far.
*
* @return Array of error messages
* @access public
*/
public function getErrors() {
return $this->errors;
}
// }}}
// {{{ public function logErrors()
/**
* Writes all error messages to error log
*
* @access public
*/
public function logErrors() {
$this->logger->error(print_r($this->errors, true),
__FILE__, __LINE__);
}
// }}}
/*{{{ Getters and Setters */
public function getIncludePaths() {
return $this->includePaths;
}
public function setIncludePaths($includePaths) {
$this->includePaths = $includePaths;
}
public function getExcludePaths() {
return $this->excludePaths;
}
public function setExcludePaths($excludePaths) {
$this->excludePaths = $excludePaths;
$this->excludeCoverageDir();
}
public function getReporter() {
return $this->reporter;
}
public function setReporter(&$reporter) {
$this->reporter = $reporter;
}
public function getPhpExtensions() {
return $this->phpExtensions;
}
public function setPhpExtensions(&$extensions) {
$this->phpExtensions = $extensions;
}
public function getVersion() {
return $this->version;
}
/*}}}*/
/*{{{ public function excludeCoverageDir() */
/**
* Exclude the directory containing the coverage measurement code.
*
* @access public
*/
public function excludeCoverageDir() {
$f = __FILE__;
if(is_link($f)) {
$f = readlink($f);
}
$this->excludePaths[] = realpath(dirname($f));
}
/*}}}*/
}
?>
<?php
//
// +--------------------------------------------------------------------+
// | PEAR, the PHP Extension and Application Repository |
// +--------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +--------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +--------------------------------------------------------------------+
// | Authors: Sterling Hughes <sterling@php.net> |
// | Stig Bakken <ssb@php.net> |
// | Tomas V.V.Cox <cox@idecnet.com> |
// +--------------------------------------------------------------------+
//
// $Id: PEAR.php,v 1.82.2.1 2005/03/28 16:46:06 cellog Exp $
//
define('PEAR_ERROR_RETURN', 1);
define('PEAR_ERROR_PRINT', 2);
define('PEAR_ERROR_TRIGGER', 4);
define('PEAR_ERROR_DIE', 8);
define('PEAR_ERROR_CALLBACK', 16);
/**
* WARNING: obsolete
* @deprecated
*/
define('PEAR_ERROR_EXCEPTION', 32);
define('PEAR_ZE2', (function_exists('version_compare') &&
version_compare(zend_version(), "2-dev", "ge")));
if (substr(PHP_OS, 0, 3) == 'WIN') {
define('OS_WINDOWS', true);
define('OS_UNIX', false);
define('PEAR_OS', 'Windows');
} else {
define('OS_WINDOWS', false);
define('OS_UNIX', true);
define('PEAR_OS', 'Unix'); // blatant assumption
}
// instant backwards compatibility
if (!defined('PATH_SEPARATOR')) {
if (OS_WINDOWS) {
define('PATH_SEPARATOR', ';');
} else {
define('PATH_SEPARATOR', ':');
}
}
$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN;
$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE;
$GLOBALS['_PEAR_destructor_object_list'] = array();
$GLOBALS['_PEAR_shutdown_funcs'] = array();
$GLOBALS['_PEAR_error_handler_stack'] = array();
@ini_set('track_errors', true);
/**
* Base class for other PEAR classes. Provides rudimentary
* emulation of destructors.
*
* If you want a destructor in your class, inherit PEAR and make a
* destructor method called _yourclassname (same name as the
* constructor, but with a "_" prefix). Also, in your constructor you
* have to call the PEAR constructor: $this->PEAR();.
* The destructor method will be called without parameters. Note that
* at in some SAPI implementations (such as Apache), any output during
* the request shutdown (in which destructors are called) seems to be
* discarded. If you need to get any debug information from your
* destructor, use error_log(), syslog() or something similar.
*
* IMPORTANT! To use the emulated destructors you need to create the
* objects by reference: $obj =& new PEAR_child;
*
* @since PHP 4.0.2
* @author Stig Bakken <ssb@php.net>
* @see http://pear.php.net/manual/
*/
class PEAR
{
// {{{ properties
/**
* Whether to enable internal debug messages.
*
* @var bool
* @access private
*/
var $_debug = false;
/**
* Default error mode for this object.
*
* @var int
* @access private
*/
var $_default_error_mode = null;
/**
* Default error options used for this object when error mode
* is PEAR_ERROR_TRIGGER.
*
* @var int
* @access private
*/
var $_default_error_options = null;
/**
* Default error handler (callback) for this object, if error mode is
* PEAR_ERROR_CALLBACK.
*
* @var string
* @access private
*/
var $_default_error_handler = '';
/**
* Which class to use for error objects.
*
* @var string
* @access private
*/
var $_error_class = 'PEAR_Error';
/**
* An array of expected errors.
*
* @var array
* @access private
*/
var $_expected_errors = array();
// }}}
// {{{ constructor
/**
* Constructor. Registers this object in
* $_PEAR_destructor_object_list for destructor emulation if a
* destructor object exists.
*
* @param string $error_class (optional) which class to use for
* error objects, defaults to PEAR_Error.
* @access public
* @return void
*/
function PEAR($error_class = null)
{
$classname = strtolower(get_class($this));
if ($this->_debug) {
print "PEAR constructor called, class=$classname\n";
}
if ($error_class !== null) {
$this->_error_class = $error_class;
}
while ($classname && strcasecmp($classname, "pear")) {
$destructor = "_$classname";
if (method_exists($this, $destructor)) {
global $_PEAR_destructor_object_list;
$_PEAR_destructor_object_list[] = &$this;
if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
register_shutdown_function("_PEAR_call_destructors");
$GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
}
break;
} else {
$classname = get_parent_class($classname);
}
}
}
// }}}
// {{{ destructor
/**
* Destructor (the emulated type of...). Does nothing right now,
* but is included for forward compatibility, so subclass
* destructors should always call it.
*
* See the note in the class desciption about output from
* destructors.
*
* @access public
* @return void
*/
function _PEAR() {
if ($this->_debug) {
printf("PEAR destructor called, class=%s\n", strtolower(get_class($this)));
}
}
// }}}
// {{{ getStaticProperty()
/**
* If you have a class that's mostly/entirely static, and you need static
* properties, you can use this method to simulate them. Eg. in your method(s)
* do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar');
* You MUST use a reference, or they will not persist!
*
* @access public
* @param string $class The calling classname, to prevent clashes
* @param string $var The variable to retrieve.
* @return mixed A reference to the variable. If not set it will be
* auto initialised to NULL.
*/
function &getStaticProperty($class, $var)
{
static $properties;
return $properties[$class][$var];
}
// }}}
// {{{ registerShutdownFunc()
/**
* Use this function to register a shutdown method for static
* classes.
*
* @access public
* @param mixed $func The function name (or array of class/method) to call
* @param mixed $args The arguments to pass to the function
* @return void
*/
function registerShutdownFunc($func, $args = array())
{
$GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args);
}
// }}}
// {{{ isError()
/**
* Tell whether a value is a PEAR error.
*
* @param mixed $data the value to test
* @param int $code if $data is an error object, return true
* only if $code is a string and
* $obj->getMessage() == $code or
* $code is an integer and $obj->getCode() == $code
* @access public
* @return bool true if parameter is an error
*/
function isError($data, $code = null)
{
if (is_a($data, 'PEAR_Error')) {
if (is_null($code)) {
return true;
} elseif (is_string($code)) {
return $data->getMessage() == $code;
} else {
return $data->getCode() == $code;
}
}
return false;
}
// }}}
// {{{ setErrorHandling()
/**
* Sets how errors generated by this object should be handled.
* Can be invoked both in objects and statically. If called
* statically, setErrorHandling sets the default behaviour for all
* PEAR objects. If called in an object, setErrorHandling sets
* the default behaviour for that object.
*
* @param int $mode
* One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
* PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
* PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION.
*
* @param mixed $options
* When $mode is PEAR_ERROR_TRIGGER, this is the error level (one
* of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
*
* When $mode is PEAR_ERROR_CALLBACK, this parameter is expected
* to be the callback function or method. A callback
* function is a string with the name of the function, a
* callback method is an array of two elements: the element
* at index 0 is the object, and the element at index 1 is
* the name of the method to call in the object.
*
* When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is
* a printf format string used when printing the error
* message.
*
* @access public
* @return void
* @see PEAR_ERROR_RETURN
* @see PEAR_ERROR_PRINT
* @see PEAR_ERROR_TRIGGER
* @see PEAR_ERROR_DIE
* @see PEAR_ERROR_CALLBACK
* @see PEAR_ERROR_EXCEPTION
*
* @since PHP 4.0.5
*/
function setErrorHandling($mode = null, $options = null)
{
if (isset($this) && is_a($this, 'PEAR')) {
$setmode = &$this->_default_error_mode;
$setoptions = &$this->_default_error_options;
} else {
$setmode = &$GLOBALS['_PEAR_default_error_mode'];
$setoptions = &$GLOBALS['_PEAR_default_error_options'];
}
switch ($mode) {
case PEAR_ERROR_EXCEPTION:
case PEAR_ERROR_RETURN:
case PEAR_ERROR_PRINT:
case PEAR_ERROR_TRIGGER:
case PEAR_ERROR_DIE:
case null:
$setmode = $mode;
$setoptions = $options;
break;
case PEAR_ERROR_CALLBACK:
$setmode = $mode;
// class/object method callback
if (is_callable($options)) {
$setoptions = $options;
} else {
trigger_error("invalid error callback", E_USER_WARNING);
}
break;
default:
trigger_error("invalid error mode", E_USER_WARNING);
break;
}
}
// }}}
// {{{ expectError()
/**
* This method is used to tell which errors you expect to get.
* Expected errors are always returned with error mode
* PEAR_ERROR_RETURN. Expected error codes are stored in a stack,
* and this method pushes a new element onto it. The list of
* expected errors are in effect until they are popped off the
* stack with the popExpect() method.
*
* Note that this method can not be called statically
*
* @param mixed $code a single error code or an array of error codes to expect
*
* @return int the new depth of the "expected errors" stack
* @access public
*/
function expectError($code = '*')
{
if (is_array($code)) {
array_push($this->_expected_errors, $code);
} else {
array_push($this->_expected_errors, array($code));
}
return sizeof($this->_expected_errors);
}
// }}}
// {{{ popExpect()
/**
* This method pops one element off the expected error codes
* stack.
*
* @return array the list of error codes that were popped
*/
function popExpect()
{
return array_pop($this->_expected_errors);
}
// }}}
// {{{ _checkDelExpect()
/**
* This method checks unsets an error code if available
*
* @param mixed error code
* @return bool true if the error code was unset, false otherwise
* @access private
* @since PHP 4.3.0
*/
function _checkDelExpect($error_code)
{
$deleted = false;
foreach ($this->_expected_errors AS $key => $error_array) {
if (in_array($error_code, $error_array)) {
unset($this->_expected_errors[$key][array_search($error_code, $error_array)]);
$deleted = true;
}
// clean up empty arrays
if (0 == count($this->_expected_errors[$key])) {
unset($this->_expected_errors[$key]);
}
}
return $deleted;
}
// }}}
// {{{ delExpect()
/**
* This method deletes all occurences of the specified element from
* the expected error codes stack.
*
* @param mixed $error_code error code that should be deleted
* @return mixed list of error codes that were deleted or error
* @access public
* @since PHP 4.3.0
*/
function delExpect($error_code)
{
$deleted = false;
if ((is_array($error_code) && (0 != count($error_code)))) {
// $error_code is a non-empty array here;
// we walk through it trying to unset all
// values
foreach($error_code as $key => $error) {
if ($this->_checkDelExpect($error)) {
$deleted = true;
} else {
$deleted = false;
}
}
return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
} elseif (!empty($error_code)) {
// $error_code comes alone, trying to unset it
if ($this->_checkDelExpect($error_code)) {
return true;
} else {
return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
}
} else {
// $error_code is empty
return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME
}
}
// }}}
// {{{ raiseError()
/**
* This method is a wrapper that returns an instance of the
* configured error class with this object's default error
* handling applied. If the $mode and $options parameters are not
* specified, the object's defaults are used.
*
* @param mixed $message a text error message or a PEAR error object
*
* @param int $code a numeric error code (it is up to your class
* to define these if you want to use codes)
*
* @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
* PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
* PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION.
*
* @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter
* specifies the PHP-internal error level (one of
* E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
* If $mode is PEAR_ERROR_CALLBACK, this
* parameter specifies the callback function or
* method. In other error modes this parameter
* is ignored.
*
* @param string $userinfo If you need to pass along for example debug
* information, this parameter is meant for that.
*
* @param string $error_class The returned error object will be
* instantiated from this class, if specified.
*
* @param bool $skipmsg If true, raiseError will only pass error codes,
* the error message parameter will be dropped.
*
* @access public
* @return object a PEAR error object
* @see PEAR::setErrorHandling
* @since PHP 4.0.5
*/
function raiseError($message = null,
$code = null,
$mode = null,
$options = null,
$userinfo = null,
$error_class = null,
$skipmsg = false)
{
// The error is yet a PEAR error object
if (is_object($message)) {
$code = $message->getCode();
$userinfo = $message->getUserInfo();
$error_class = $message->getType();
$message->error_message_prefix = '';
$message = $message->getMessage();
}
if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) {
if ($exp[0] == "*" ||
(is_int(reset($exp)) && in_array($code, $exp)) ||
(is_string(reset($exp)) && in_array($message, $exp))) {
$mode = PEAR_ERROR_RETURN;
}
}
// No mode given, try global ones
if ($mode === null) {
// Class error handler
if (isset($this) && isset($this->_default_error_mode)) {
$mode = $this->_default_error_mode;
$options = $this->_default_error_options;
// Global error handler
} elseif (isset($GLOBALS['_PEAR_default_error_mode'])) {
$mode = $GLOBALS['_PEAR_default_error_mode'];
$options = $GLOBALS['_PEAR_default_error_options'];
}
}
if ($error_class !== null) {
$ec = $error_class;
} elseif (isset($this) && isset($this->_error_class)) {
$ec = $this->_error_class;
} else {
$ec = 'PEAR_Error';
}
if ($skipmsg) {
return new $ec($code, $mode, $options, $userinfo);
} else {
return new $ec($message, $code, $mode, $options, $userinfo);
}
}
// }}}
// {{{ throwError()
/**
* Simpler form of raiseError with fewer options. In most cases
* message, code and userinfo are enough.
*
* @param string $message
*
*/
function throwError($message = null,
$code = null,
$userinfo = null)
{
if (isset($this) && is_a($this, 'PEAR')) {
return $this->raiseError($message, $code, null, null, $userinfo);
} else {
return PEAR::raiseError($message, $code, null, null, $userinfo);
}
}
// }}}
function staticPushErrorHandling($mode, $options = null)
{
$stack = &$GLOBALS['_PEAR_error_handler_stack'];
$def_mode = &$GLOBALS['_PEAR_default_error_mode'];
$def_options = &$GLOBALS['_PEAR_default_error_options'];
$stack[] = array($def_mode, $def_options);
switch ($mode) {
case PEAR_ERROR_EXCEPTION:
case PEAR_ERROR_RETURN:
case PEAR_ERROR_PRINT:
case PEAR_ERROR_TRIGGER:
case PEAR_ERROR_DIE:
case null:
$def_mode = $mode;
$def_options = $options;
break;
case PEAR_ERROR_CALLBACK:
$def_mode = $mode;
// class/object method callback
if (is_callable($options)) {
$def_options = $options;
} else {
trigger_error("invalid error callback", E_USER_WARNING);
}
break;
default:
trigger_error("invalid error mode", E_USER_WARNING);
break;
}
$stack[] = array($mode, $options);
return true;
}
function staticPopErrorHandling()
{
$stack = &$GLOBALS['_PEAR_error_handler_stack'];
$setmode = &$GLOBALS['_PEAR_default_error_mode'];
$setoptions = &$GLOBALS['_PEAR_default_error_options'];
array_pop($stack);
list($mode, $options) = $stack[sizeof($stack) - 1];
array_pop($stack);
switch ($mode) {
case PEAR_ERROR_EXCEPTION:
case PEAR_ERROR_RETURN:
case PEAR_ERROR_PRINT:
case PEAR_ERROR_TRIGGER:
case PEAR_ERROR_DIE:
case null:
$setmode = $mode;
$setoptions = $options;
break;
case PEAR_ERROR_CALLBACK:
$setmode = $mode;
// class/object method callback
if (is_callable($options)) {
$setoptions = $options;
} else {
trigger_error("invalid error callback", E_USER_WARNING);
}
break;
default:
trigger_error("invalid error mode", E_USER_WARNING);
break;
}
return true;
}
// {{{ pushErrorHandling()
/**
* Push a new error handler on top of the error handler options stack. With this
* you can easily override the actual error handler for some code and restore
* it later with popErrorHandling.
*
* @param mixed $mode (same as setErrorHandling)
* @param mixed $options (same as setErrorHandling)
*
* @return bool Always true
*
* @see PEAR::setErrorHandling
*/
function pushErrorHandling($mode, $options = null)
{
$stack = &$GLOBALS['_PEAR_error_handler_stack'];
if (isset($this) && is_a($this, 'PEAR')) {
$def_mode = &$this->_default_error_mode;
$def_options = &$this->_default_error_options;
} else {
$def_mode = &$GLOBALS['_PEAR_default_error_mode'];
$def_options = &$GLOBALS['_PEAR_default_error_options'];
}
$stack[] = array($def_mode, $def_options);
if (isset($this) && is_a($this, 'PEAR')) {
$this->setErrorHandling($mode, $options);
} else {
PEAR::setErrorHandling($mode, $options);
}
$stack[] = array($mode, $options);
return true;
}
// }}}
// {{{ popErrorHandling()
/**
* Pop the last error handler used
*
* @return bool Always true
*
* @see PEAR::pushErrorHandling
*/
function popErrorHandling()
{
$stack = &$GLOBALS['_PEAR_error_handler_stack'];
array_pop($stack);
list($mode, $options) = $stack[sizeof($stack) - 1];
array_pop($stack);
if (isset($this) && is_a($this, 'PEAR')) {
$this->setErrorHandling($mode, $options);
} else {
PEAR::setErrorHandling($mode, $options);
}
return true;
}
// }}}
// {{{ loadExtension()
/**
* OS independant PHP extension load. Remember to take care
* on the correct extension name for case sensitive OSes.
*
* @param string $ext The extension name
* @return bool Success or not on the dl() call
*/
function loadExtension($ext)
{
if (!extension_loaded($ext)) {
// if either returns true dl() will produce a FATAL error, stop that
if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) {
return false;
}
if (OS_WINDOWS) {
$suffix = '.dll';
} elseif (PHP_OS == 'HP-UX') {
$suffix = '.sl';
} elseif (PHP_OS == 'AIX') {
$suffix = '.a';
} elseif (PHP_OS == 'OSX') {
$suffix = '.bundle';
} else {
$suffix = '.so';
}
return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
}
return true;
}
// }}}
}
// {{{ _PEAR_call_destructors()
function _PEAR_call_destructors()
{
global $_PEAR_destructor_object_list;
if (is_array($_PEAR_destructor_object_list) &&
sizeof($_PEAR_destructor_object_list))
{
reset($_PEAR_destructor_object_list);
if (@PEAR::getStaticProperty('PEAR', 'destructlifo')) {
$_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list);
}
while (list($k, $objref) = each($_PEAR_destructor_object_list)) {
$classname = get_class($objref);
while ($classname) {
$destructor = "_$classname";
if (method_exists($objref, $destructor)) {
$objref->$destructor();
break;
} else {
$classname = get_parent_class($classname);
}
}
}
// Empty the object list to ensure that destructors are
// not called more than once.
$_PEAR_destructor_object_list = array();
}
// Now call the shutdown functions
if (is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_shutdown_funcs'])) {
foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) {
call_user_func_array($value[0], $value[1]);
}
}
}
// }}}
class PEAR_Error
{
// {{{ properties
var $error_message_prefix = '';
var $mode = PEAR_ERROR_RETURN;
var $level = E_USER_NOTICE;
var $code = -1;
var $message = '';
var $userinfo = '';
var $backtrace = null;
// }}}
// {{{ constructor
/**
* PEAR_Error constructor
*
* @param string $message message
*
* @param int $code (optional) error code
*
* @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN,
* PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER,
* PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION
*
* @param mixed $options (optional) error level, _OR_ in the case of
* PEAR_ERROR_CALLBACK, the callback function or object/method
* tuple.
*
* @param string $userinfo (optional) additional user/debug info
*
* @access public
*
*/
function PEAR_Error($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null)
{
if ($mode === null) {
$mode = PEAR_ERROR_RETURN;
}
$this->message = $message;
$this->code = $code;
$this->mode = $mode;
$this->userinfo = $userinfo;
if (function_exists("debug_backtrace")) {
if (@!PEAR::getStaticProperty('PEAR_Error', 'skiptrace')) {
$this->backtrace = debug_backtrace();
}
}
if ($mode & PEAR_ERROR_CALLBACK) {
$this->level = E_USER_NOTICE;
$this->callback = $options;
} else {
if ($options === null) {
$options = E_USER_NOTICE;
}
$this->level = $options;
$this->callback = null;
}
if ($this->mode & PEAR_ERROR_PRINT) {
if (is_null($options) || is_int($options)) {
$format = "%s";
} else {
$format = $options;
}
printf($format, $this->getMessage());
}
if ($this->mode & PEAR_ERROR_TRIGGER) {
trigger_error($this->getMessage(), $this->level);
}
if ($this->mode & PEAR_ERROR_DIE) {
$msg = $this->getMessage();
if (is_null($options) || is_int($options)) {
$format = "%s";
if (substr($msg, -1) != "\n") {
$msg .= "\n";
}
} else {
$format = $options;
}
die(sprintf($format, $msg));
}
if ($this->mode & PEAR_ERROR_CALLBACK) {
if (is_callable($this->callback)) {
call_user_func($this->callback, $this);
}
}
if ($this->mode & PEAR_ERROR_EXCEPTION) {
trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_ErrorStack for exceptions", E_USER_WARNING);
eval('$e = new Exception($this->message, $this->code);$e->PEAR_Error = $this;throw($e);');
}
}
// }}}
// {{{ getMode()
/**
* Get the error mode from an error object.
*
* @return int error mode
* @access public
*/
function getMode() {
return $this->mode;
}
// }}}
// {{{ getCallback()
/**
* Get the callback function/method from an error object.
*
* @return mixed callback function or object/method array
* @access public
*/
function getCallback() {
return $this->callback;
}
// }}}
// {{{ getMessage()
/**
* Get the error message from an error object.
*
* @return string full error message
* @access public
*/
function getMessage()
{
return ($this->error_message_prefix . $this->message);
}
// }}}
// {{{ getCode()
/**
* Get error code from an error object
*
* @return int error code
* @access public
*/
function getCode()
{
return $this->code;
}
// }}}
// {{{ getType()
/**
* Get the name of this error/exception.
*
* @return string error/exception name (type)
* @access public
*/
function getType()
{
return get_class($this);
}
// }}}
// {{{ getUserInfo()
/**
* Get additional user-supplied information.
*
* @return string user-supplied information
* @access public
*/
function getUserInfo()
{
return $this->userinfo;
}
// }}}
// {{{ getDebugInfo()
/**
* Get additional debug information supplied by the application.
*
* @return string debug information
* @access public
*/
function getDebugInfo()
{
return $this->getUserInfo();
}
// }}}
// {{{ getBacktrace()
/**
* Get the call backtrace from where the error was generated.
* Supported with PHP 4.3.0 or newer.
*
* @param int $frame (optional) what frame to fetch
* @return array Backtrace, or NULL if not available.
* @access public
*/
function getBacktrace($frame = null)
{
if ($frame === null) {
return $this->backtrace;
}
return $this->backtrace[$frame];
}
// }}}
// {{{ addUserInfo()
function addUserInfo($info)
{
if (empty($this->userinfo)) {
$this->userinfo = $info;
} else {
$this->userinfo .= " ** $info";
}
}
// }}}
// {{{ toString()
/**
* Make a string representation of this object.
*
* @return string a string with an object summary
* @access public
*/
function toString() {
$modes = array();
$levels = array(E_USER_NOTICE => 'notice',
E_USER_WARNING => 'warning',
E_USER_ERROR => 'error');
if ($this->mode & PEAR_ERROR_CALLBACK) {
if (is_array($this->callback)) {
$callback = (is_object($this->callback[0]) ?
strtolower(get_class($this->callback[0])) :
$this->callback[0]) . '::' .
$this->callback[1];
} else {
$callback = $this->callback;
}
return sprintf('[%s: message="%s" code=%d mode=callback '.
'callback=%s prefix="%s" info="%s"]',
strtolower(get_class($this)), $this->message, $this->code,
$callback, $this->error_message_prefix,
$this->userinfo);
}
if ($this->mode & PEAR_ERROR_PRINT) {
$modes[] = 'print';
}
if ($this->mode & PEAR_ERROR_TRIGGER) {
$modes[] = 'trigger';
}
if ($this->mode & PEAR_ERROR_DIE) {
$modes[] = 'die';
}
if ($this->mode & PEAR_ERROR_RETURN) {
$modes[] = 'return';
}
return sprintf('[%s: message="%s" code=%d mode=%s level=%s '.
'prefix="%s" info="%s"]',
strtolower(get_class($this)), $this->message, $this->code,
implode("|", $modes), $levels[$this->level],
$this->error_message_prefix,
$this->userinfo);
}
// }}}
}
/*
* Local Variables:
* mode: php
* tab-width: 4
* c-basic-offset: 4
* End:
*/
?>
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Stig Bakken <ssb@fast.no> |
// | Tomas V.V.Cox <cox@idecnet.com> |
// | Stephan Schmidt <schst@php-tools.net> |
// +----------------------------------------------------------------------+
//
// $Id: Parser.php,v 1.28 2006/12/01 16:23:22 schst Exp $
/**
* XML Parser class.
*
* This is an XML parser based on PHP's "xml" extension,
* based on the bundled expat library.
*
* @category XML
* @package XML_Parser
* @author Stig Bakken <ssb@fast.no>
* @author Tomas V.V.Cox <cox@idecnet.com>
* @author Stephan Schmidt <schst@php-tools.net>
*/
/**
* uses PEAR's error handling
*/
require_once 'PEAR.php';
/**
* resource could not be created
*/
define('XML_PARSER_ERROR_NO_RESOURCE', 200);
/**
* unsupported mode
*/
define('XML_PARSER_ERROR_UNSUPPORTED_MODE', 201);
/**
* invalid encoding was given
*/
define('XML_PARSER_ERROR_INVALID_ENCODING', 202);
/**
* specified file could not be read
*/
define('XML_PARSER_ERROR_FILE_NOT_READABLE', 203);
/**
* invalid input
*/
define('XML_PARSER_ERROR_INVALID_INPUT', 204);
/**
* remote file cannot be retrieved in safe mode
*/
define('XML_PARSER_ERROR_REMOTE', 205);
/**
* XML Parser class.
*
* This is an XML parser based on PHP's "xml" extension,
* based on the bundled expat library.
*
* Notes:
* - It requires PHP 4.0.4pl1 or greater
* - From revision 1.17, the function names used by the 'func' mode
* are in the format "xmltag_$elem", for example: use "xmltag_name"
* to handle the <name></name> tags of your xml file.
*
* @category XML
* @package XML_Parser
* @author Stig Bakken <ssb@fast.no>
* @author Tomas V.V.Cox <cox@idecnet.com>
* @author Stephan Schmidt <schst@php-tools.net>
* @todo create XML_Parser_Namespace to parse documents with namespaces
* @todo create XML_Parser_Pull
* @todo Tests that need to be made:
* - mixing character encodings
* - a test using all expat handlers
* - options (folding, output charset)
* - different parsing modes
*/
class XML_Parser extends PEAR
{
// {{{ properties
/**
* XML parser handle
*
* @var resource
* @see xml_parser_create()
*/
var $parser;
/**
* File handle if parsing from a file
*
* @var resource
*/
var $fp;
/**
* Whether to do case folding
*
* If set to true, all tag and attribute names will
* be converted to UPPER CASE.
*
* @var boolean
*/
var $folding = true;
/**
* Mode of operation, one of "event" or "func"
*
* @var string
*/
var $mode;
/**
* Mapping from expat handler function to class method.
*
* @var array
*/
var $handler = array(
'character_data_handler' => 'cdataHandler',
'default_handler' => 'defaultHandler',
'processing_instruction_handler' => 'piHandler',
'unparsed_entity_decl_handler' => 'unparsedHandler',
'notation_decl_handler' => 'notationHandler',
'external_entity_ref_handler' => 'entityrefHandler'
);
/**
* source encoding
*
* @var string
*/
var $srcenc;
/**
* target encoding
*
* @var string
*/
var $tgtenc;
/**
* handler object
*
* @var object
*/
var $_handlerObj;
/**
* valid encodings
*
* @var array
*/
var $_validEncodings = array('ISO-8859-1', 'UTF-8', 'US-ASCII');
// }}}
// {{{ constructor
/**
* Creates an XML parser.
*
* This is needed for PHP4 compatibility, it will
* call the constructor, when a new instance is created.
*
* @param string $srcenc source charset encoding, use NULL (default) to use
* whatever the document specifies
* @param string $mode how this parser object should work, "event" for
* startelement/endelement-type events, "func"
* to have it call functions named after elements
* @param string $tgenc a valid target encoding
*/
function XML_Parser($srcenc = null, $mode = 'event', $tgtenc = null)
{
XML_Parser::__construct($srcenc, $mode, $tgtenc);
}
// }}}
/**
* PHP5 constructor
*
* @param string $srcenc source charset encoding, use NULL (default) to use
* whatever the document specifies
* @param string $mode how this parser object should work, "event" for
* startelement/endelement-type events, "func"
* to have it call functions named after elements
* @param string $tgenc a valid target encoding
*/
function __construct($srcenc = null, $mode = 'event', $tgtenc = null)
{
$this->PEAR('XML_Parser_Error');
$this->mode = $mode;
$this->srcenc = $srcenc;
$this->tgtenc = $tgtenc;
}
// }}}
/**
* Sets the mode of the parser.
*
* Possible modes are:
* - func
* - event
*
* You can set the mode using the second parameter
* in the constructor.
*
* This method is only needed, when switching to a new
* mode at a later point.
*
* @access public
* @param string mode, either 'func' or 'event'
* @return boolean|object true on success, PEAR_Error otherwise
*/
function setMode($mode)
{
if ($mode != 'func' && $mode != 'event') {
$this->raiseError('Unsupported mode given', XML_PARSER_ERROR_UNSUPPORTED_MODE);
}
$this->mode = $mode;
return true;
}
/**
* Sets the object, that will handle the XML events
*
* This allows you to create a handler object independent of the
* parser object that you are using and easily switch the underlying
* parser.
*
* If no object will be set, XML_Parser assumes that you
* extend this class and handle the events in $this.
*
* @access public
* @param object object to handle the events
* @return boolean will always return true
* @since v1.2.0beta3
*/
function setHandlerObj(&$obj)
{
$this->_handlerObj = &$obj;
return true;
}
/**
* Init the element handlers
*
* @access private
*/
function _initHandlers()
{
if (!is_resource($this->parser)) {
return false;
}
if (!is_object($this->_handlerObj)) {
$this->_handlerObj = &$this;
}
switch ($this->mode) {
case 'func':
xml_set_object($this->parser, $this->_handlerObj);
xml_set_element_handler($this->parser, array(&$this, 'funcStartHandler'), array(&$this, 'funcEndHandler'));
break;
case 'event':
xml_set_object($this->parser, $this->_handlerObj);
xml_set_element_handler($this->parser, 'startHandler', 'endHandler');
break;
default:
return $this->raiseError('Unsupported mode given', XML_PARSER_ERROR_UNSUPPORTED_MODE);
break;
}
/**
* set additional handlers for character data, entities, etc.
*/
foreach ($this->handler as $xml_func => $method) {
if (method_exists($this->_handlerObj, $method)) {
$xml_func = 'xml_set_' . $xml_func;
$xml_func($this->parser, $method);
}
}
}
// {{{ _create()
/**
* create the XML parser resource
*
* Has been moved from the constructor to avoid
* problems with object references.
*
* Furthermore it allows us returning an error
* if something fails.
*
* @access private
* @return boolean|object true on success, PEAR_Error otherwise
*
* @see xml_parser_create
*/
function _create()
{
if ($this->srcenc === null) {
$xp = @xml_parser_create();
} else {
$xp = @xml_parser_create($this->srcenc);
}
if (is_resource($xp)) {
if ($this->tgtenc !== null) {
if (!@xml_parser_set_option($xp, XML_OPTION_TARGET_ENCODING,
$this->tgtenc)) {
return $this->raiseError('invalid target encoding', XML_PARSER_ERROR_INVALID_ENCODING);
}
}
$this->parser = $xp;
$result = $this->_initHandlers($this->mode);
if ($this->isError($result)) {
return $result;
}
xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, $this->folding);
return true;
}
if (!in_array(strtoupper($this->srcenc), $this->_validEncodings)) {
return $this->raiseError('invalid source encoding', XML_PARSER_ERROR_INVALID_ENCODING);
}
return $this->raiseError('Unable to create XML parser resource.', XML_PARSER_ERROR_NO_RESOURCE);
}
// }}}
// {{{ reset()
/**
* Reset the parser.
*
* This allows you to use one parser instance
* to parse multiple XML documents.
*
* @access public
* @return boolean|object true on success, PEAR_Error otherwise
*/
function reset()
{
$result = $this->_create();
if ($this->isError( $result )) {
return $result;
}
return true;
}
// }}}
// {{{ setInputFile()
/**
* Sets the input xml file to be parsed
*
* @param string Filename (full path)
* @return resource fopen handle of the given file
* @throws XML_Parser_Error
* @see setInput(), setInputString(), parse()
* @access public
*/
function setInputFile($file)
{
/**
* check, if file is a remote file
*/
if (eregi('^(http|ftp)://', substr($file, 0, 10))) {
if (!ini_get('allow_url_fopen')) {
return $this->raiseError('Remote files cannot be parsed, as safe mode is enabled.', XML_PARSER_ERROR_REMOTE);
}
}
$fp = @fopen($file, 'rb');
if (is_resource($fp)) {
$this->fp = $fp;
return $fp;
}
return $this->raiseError('File could not be opened.', XML_PARSER_ERROR_FILE_NOT_READABLE);
}
// }}}
// {{{ setInputString()
/**
* XML_Parser::setInputString()
*
* Sets the xml input from a string
*
* @param string $data a string containing the XML document
* @return null
**/
function setInputString($data)
{
$this->fp = $data;
return null;
}
// }}}
// {{{ setInput()
/**
* Sets the file handle to use with parse().
*
* You should use setInputFile() or setInputString() if you
* pass a string
*
* @param mixed $fp Can be either a resource returned from fopen(),
* a URL, a local filename or a string.
* @access public
* @see parse()
* @uses setInputString(), setInputFile()
*/
function setInput($fp)
{
if (is_resource($fp)) {
$this->fp = $fp;
return true;
}
// see if it's an absolute URL (has a scheme at the beginning)
elseif (eregi('^[a-z]+://', substr($fp, 0, 10))) {
return $this->setInputFile($fp);
}
// see if it's a local file
elseif (file_exists($fp)) {
return $this->setInputFile($fp);
}
// it must be a string
else {
$this->fp = $fp;
return true;
}
return $this->raiseError('Illegal input format', XML_PARSER_ERROR_INVALID_INPUT);
}
// }}}
// {{{ parse()
/**
* Central parsing function.
*
* @return true|object PEAR error returns true on success, or a PEAR_Error otherwise
* @access public
*/
function parse()
{
/**
* reset the parser
*/
$result = $this->reset();
if ($this->isError($result)) {
return $result;
}
// if $this->fp was fopened previously
if (is_resource($this->fp)) {
while ($data = fread($this->fp, 4096)) {
if (!$this->_parseString($data, feof($this->fp))) {
$error = &$this->raiseError();
$this->free();
return $error;
}
}
// otherwise, $this->fp must be a string
} else {
if (!$this->_parseString($this->fp, true)) {
$error = &$this->raiseError();
$this->free();
return $error;
}
}
$this->free();
return true;
}
/**
* XML_Parser::_parseString()
*
* @param string $data
* @param boolean $eof
* @return bool
* @access private
* @see parseString()
**/
function _parseString($data, $eof = false)
{
return xml_parse($this->parser, $data, $eof);
}
// }}}
// {{{ parseString()
/**
* XML_Parser::parseString()
*
* Parses a string.
*
* @param string $data XML data
* @param boolean $eof If set and TRUE, data is the last piece of data sent in this parser
* @throws XML_Parser_Error
* @return Pear Error|true true on success or a PEAR Error
* @see _parseString()
*/
function parseString($data, $eof = false)
{
if (!isset($this->parser) || !is_resource($this->parser)) {
$this->reset();
}
if (!$this->_parseString($data, $eof)) {
$error = &$this->raiseError();
$this->free();
return $error;
}
if ($eof === true) {
$this->free();
}
return true;
}
/**
* XML_Parser::free()
*
* Free the internal resources associated with the parser
*
* @return null
**/
function free()
{
if (isset($this->parser) && is_resource($this->parser)) {
xml_parser_free($this->parser);
unset( $this->parser );
}
if (isset($this->fp) && is_resource($this->fp)) {
fclose($this->fp);
}
unset($this->fp);
return null;
}
/**
* XML_Parser::raiseError()
*
* Throws a XML_Parser_Error
*
* @param string $msg the error message
* @param integer $ecode the error message code
* @return XML_Parser_Error
**/
function raiseError($msg = null, $ecode = 0)
{
$msg = !is_null($msg) ? $msg : $this->parser;
$err = &new XML_Parser_Error($msg, $ecode);
return parent::raiseError($err);
}
// }}}
// {{{ funcStartHandler()
function funcStartHandler($xp, $elem, $attribs)
{
$func = 'xmltag_' . $elem;
$func = str_replace(array('.', '-', ':'), '_', $func);
if (method_exists($this->_handlerObj, $func)) {
call_user_func(array(&$this->_handlerObj, $func), $xp, $elem, $attribs);
} elseif (method_exists($this->_handlerObj, 'xmltag')) {
call_user_func(array(&$this->_handlerObj, 'xmltag'), $xp, $elem, $attribs);
}
}
// }}}
// {{{ funcEndHandler()
function funcEndHandler($xp, $elem)
{
$func = 'xmltag_' . $elem . '_';
$func = str_replace(array('.', '-', ':'), '_', $func);
if (method_exists($this->_handlerObj, $func)) {
call_user_func(array(&$this->_handlerObj, $func), $xp, $elem);
} elseif (method_exists($this->_handlerObj, 'xmltag_')) {
call_user_func(array(&$this->_handlerObj, 'xmltag_'), $xp, $elem);
}
}
// }}}
// {{{ startHandler()
/**
*
* @abstract
*/
function startHandler($xp, $elem, &$attribs)
{
return NULL;
}
// }}}
// {{{ endHandler()
/**
*
* @abstract
*/
function endHandler($xp, $elem)
{
return NULL;
}
// }}}me
}
/**
* error class, replaces PEAR_Error
*
* An instance of this class will be returned
* if an error occurs inside XML_Parser.
*
* There are three advantages over using the standard PEAR_Error:
* - All messages will be prefixed
* - check for XML_Parser error, using is_a( $error, 'XML_Parser_Error' )
* - messages can be generated from the xml_parser resource
*
* @package XML_Parser
* @access public
* @see PEAR_Error
*/
class XML_Parser_Error extends PEAR_Error
{
// {{{ properties
/**
* prefix for all messages
*
* @var string
*/
var $error_message_prefix = 'XML_Parser: ';
// }}}
// {{{ constructor()
/**
* construct a new error instance
*
* You may either pass a message or an xml_parser resource as first
* parameter. If a resource has been passed, the last error that
* happened will be retrieved and returned.
*
* @access public
* @param string|resource message or parser resource
* @param integer error code
* @param integer error handling
* @param integer error level
*/
function XML_Parser_Error($msgorparser = 'unknown error', $code = 0, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE)
{
if (is_resource($msgorparser)) {
$code = xml_get_error_code($msgorparser);
$msgorparser = sprintf('%s at XML input line %d:%d',
xml_error_string($code),
xml_get_current_line_number($msgorparser),
xml_get_current_column_number($msgorparser));
}
$this->PEAR_Error($msgorparser, $code, $mode, $level);
}
// }}}
}
?>
\ No newline at end of file
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2004 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Stephan Schmidt <schst@php-tools.net> |
// +----------------------------------------------------------------------+
//
// $Id: Simple.php,v 1.6 2005/03/25 17:13:10 schst Exp $
/**
* Simple XML parser class.
*
* This class is a simplified version of XML_Parser.
* In most XML applications the real action is executed,
* when a closing tag is found.
*
* XML_Parser_Simple allows you to just implement one callback
* for each tag that will receive the tag with its attributes
* and CData
*
* @category XML
* @package XML_Parser
* @author Stephan Schmidt <schst@php-tools.net>
*/
/**
* built on XML_Parser
*/
require_once 'XML/Parser.php';
/**
* Simple XML parser class.
*
* This class is a simplified version of XML_Parser.
* In most XML applications the real action is executed,
* when a closing tag is found.
*
* XML_Parser_Simple allows you to just implement one callback
* for each tag that will receive the tag with its attributes
* and CData.
*
* <code>
* require_once '../Parser/Simple.php';
*
* class myParser extends XML_Parser_Simple
* {
* function myParser()
* {
* $this->XML_Parser_Simple();
* }
*
* function handleElement($name, $attribs, $data)
* {
* printf('handle %s<br>', $name);
* }
* }
*
* $p = &new myParser();
*
* $result = $p->setInputFile('myDoc.xml');
* $result = $p->parse();
* </code>
*
* @category XML
* @package XML_Parser
* @author Stephan Schmidt <schst@php-tools.net>
*/
class XML_Parser_Simple extends XML_Parser
{
/**
* element stack
*
* @access private
* @var array
*/
var $_elStack = array();
/**
* all character data
*
* @access private
* @var array
*/
var $_data = array();
/**
* element depth
*
* @access private
* @var integer
*/
var $_depth = 0;
/**
* Mapping from expat handler function to class method.
*
* @var array
*/
var $handler = array(
'default_handler' => 'defaultHandler',
'processing_instruction_handler' => 'piHandler',
'unparsed_entity_decl_handler' => 'unparsedHandler',
'notation_decl_handler' => 'notationHandler',
'external_entity_ref_handler' => 'entityrefHandler'
);
/**
* Creates an XML parser.
*
* This is needed for PHP4 compatibility, it will
* call the constructor, when a new instance is created.
*
* @param string $srcenc source charset encoding, use NULL (default) to use
* whatever the document specifies
* @param string $mode how this parser object should work, "event" for
* handleElement(), "func" to have it call functions
* named after elements (handleElement_$name())
* @param string $tgenc a valid target encoding
*/
function XML_Parser_Simple($srcenc = null, $mode = 'event', $tgtenc = null)
{
$this->XML_Parser($srcenc, $mode, $tgtenc);
}
/**
* inits the handlers
*
* @access private
*/
function _initHandlers()
{
if (!is_object($this->_handlerObj)) {
$this->_handlerObj = &$this;
}
if ($this->mode != 'func' && $this->mode != 'event') {
return $this->raiseError('Unsupported mode given', XML_PARSER_ERROR_UNSUPPORTED_MODE);
}
xml_set_object($this->parser, $this->_handlerObj);
xml_set_element_handler($this->parser, array(&$this, 'startHandler'), array(&$this, 'endHandler'));
xml_set_character_data_handler($this->parser, array(&$this, 'cdataHandler'));
/**
* set additional handlers for character data, entities, etc.
*/
foreach ($this->handler as $xml_func => $method) {
if (method_exists($this->_handlerObj, $method)) {
$xml_func = 'xml_set_' . $xml_func;
$xml_func($this->parser, $method);
}
}
}
/**
* Reset the parser.
*
* This allows you to use one parser instance
* to parse multiple XML documents.
*
* @access public
* @return boolean|object true on success, PEAR_Error otherwise
*/
function reset()
{
$this->_elStack = array();
$this->_data = array();
$this->_depth = 0;
$result = $this->_create();
if ($this->isError( $result )) {
return $result;
}
return true;
}
/**
* start handler
*
* Pushes attributes and tagname onto a stack
*
* @access private
* @final
* @param resource xml parser resource
* @param string element name
* @param array attributes
*/
function startHandler($xp, $elem, &$attribs)
{
array_push($this->_elStack, array(
'name' => $elem,
'attribs' => $attribs
)
);
$this->_depth++;
$this->_data[$this->_depth] = '';
}
/**
* end handler
*
* Pulls attributes and tagname from a stack
*
* @access private
* @final
* @param resource xml parser resource
* @param string element name
*/
function endHandler($xp, $elem)
{
$el = array_pop($this->_elStack);
$data = $this->_data[$this->_depth];
$this->_depth--;
switch ($this->mode) {
case 'event':
$this->_handlerObj->handleElement($el['name'], $el['attribs'], $data);
break;
case 'func':
$func = 'handleElement_' . $elem;
if (strchr($func, '.')) {
$func = str_replace('.', '_', $func);
}
if (method_exists($this->_handlerObj, $func)) {
call_user_func(array(&$this->_handlerObj, $func), $el['name'], $el['attribs'], $data);
}
break;
}
}
/**
* handle character data
*
* @access private
* @final
* @param resource xml parser resource
* @param string data
*/
function cdataHandler($xp, $data)
{
$this->_data[$this->_depth] .= $data;
}
/**
* handle a tag
*
* Implement this in your parser
*
* @access public
* @abstract
* @param string element name
* @param array attributes
* @param string character data
*/
function handleElement($name, $attribs, $data)
{
}
/**
* get the current tag depth
*
* The root tag is in depth 0.
*
* @access public
* @return integer
*/
function getCurrentDepth()
{
return $this->_depth;
}
/**
* add some string to the current ddata.
*
* This is commonly needed, when a document is parsed recursively.
*
* @access public
* @param string data to add
* @return void
*/
function addToData( $data )
{
$this->_data[$this->_depth] .= $data;
}
}
?>
<?php
/*
* $Id: license.txt 13981 2005-03-16 08:09:28Z eespino $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
/**
* This driver file can be used to initialize and generate PHPCoverage
* report when PHPCoverage is used with a non-PHP test tool - like HttpUnit
* It can, of course, be used for PHP test tools like SimpleTest and PHPUnit
*
* Notes:
* * Option parsing courtesy of "daevid at daevid dot com" (http://php.planetmirror.com/manual/en/function.getopt.php)
* * Originally contributed by Ed Espino <eespino@spikesource.com>
*/
if(!defined("__PHPCOVERAGE_HOME")) {
define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__)));
}
require_once __PHPCOVERAGE_HOME . "/conf/phpcoverage.conf.php";
require_once __PHPCOVERAGE_HOME . "/util/Utility.php";
// ######################################################################
// ######################################################################
function usage() {
global $util;
echo "Usage: " . $_SERVER['argv'][0] . " <options>\n";
echo "\n";
echo " Options: \n";
echo " --phpcoverage-home <path> OR -p <path> Path to PHPCoverage home (defaults to PHPCOVERAGE_HOME environment property)\n";
echo " --init Initialize PHPCoverage Reporting\n";
echo " --report Generate PHPCoverage Reports\n";
echo " --cleanup Remove existing PHPCoverage data\n";
echo " init options \n";
echo " --cov-url <url> Specify application default url\n";
echo " --tmp-dir <path> Specify tmp directory location (Defaults to '" . $util->getTmpDir() . "')\n";
echo " --cov-file-name <name> Specify coverage data file name (Defaults to 'phpcoverage.data.xml')\n";
echo " report options \n";
echo " --cov-data-files <path1,path2,...> Coverage data file path [use this instead of --cov-url for a local file path]\n";
echo " --report-name <name> Report name\n";
echo " --report-dir <path> Report directory path (Defaults to 'report')\n";
echo " --appbase-path <path> Application base path (Defaults to PHPCOVERAGE_APPBASE_PATH if specified on the command line)\n";
echo " --include-paths <path1,path2,...> Comma-separated paths to include in code coverage report. (Includes appbase-path by default)\n";
echo " --exclude-paths <path1,path2,...> Comma-separated paths to exclude from code coverage report.\n";
echo " --print-summary Print coverage report summary to console.\n";
echo " other options \n";
echo " --verbose OR -v Print verbose information\n";
echo " --help OR -h Display this usage information\n";
exit;
}
//
// Setup command line argument processing
//
$OPTION["p"] = false;
$OPTION['verbose'] = false;
$OPTION['init'] = false;
$OPTION['report'] = false;
$OPTION['cleanup'] = false;
$OPTION['cov-url'] = false;
$OPTION['report-name'] = false;
$OPTION['report-dir'] = false;
$OPTION['tmp-dir'] = false;
$OPTION['cov-file-name'] = false;
$OPTION['cov-data-files'] = false;
$OPTION['appbase-path'] = false;
//
// loop through our arguments and see what the user selected
//
for ($i = 1; $i < $_SERVER["argc"]; $i++) {
switch($_SERVER["argv"][$i]) {
case "--phpcoverage-home":
case "-p":
$OPTION['p'] = $_SERVER['argv'][++$i];
break;
case "-v":
case "--verbose":
$OPTION['verbose'] = true;
break;
case "--init":
$OPTION['init'] = true;
break;
case "--report":
$OPTION['report'] = true;
break;
case "--cleanup":
$OPTION['cleanup'] = true;
break;
case "--cov-url":
$OPTION['cov-url'] = $_SERVER['argv'][++$i] . "/" . "phpcoverage.remote.top.inc.php";
break;
case "--tmp-dir":
$OPTION['tmp-dir'] = $_SERVER['argv'][++$i];
break;
case "--cov-file-name":
$OPTION['cov-file-name'] = $_SERVER['argv'][++$i];
break;
case "--cov-data-files":
$OPTION['cov-data-files'] = $_SERVER['argv'][++$i];
break;
case "--report-name":
$OPTION['report-name'] = $_SERVER['argv'][++$i];
break;
case "--report-dir":
$OPTION['report-dir'] = $_SERVER['argv'][++$i];
break;
case "--appbase-path":
$OPTION['appbase-path'] = $_SERVER['argv'][++$i];
break;
case "--include-paths":
$OPTION['include-paths'] = $_SERVER['argv'][++$i];
break;
case "--exclude-paths":
$OPTION['exclude-paths'] = $_SERVER['argv'][++$i];
break;
case "--print-summary":
$OPTION['print-summary'] = true;
break;
case "--help":
case "-h":
usage();
break;
}
}
if($OPTION['p'] == false) {
$OPTION['p'] = __PHPCOVERAGE_HOME;
if(empty($OPTION['p']) || !is_dir($OPTION['p'])) {
die("PHPCOVERAGE_HOME does not exist. [" . $OPTION['p'] . "]");
}
}
putenv("PHPCOVERAGE_HOME=" . $OPTION['p']);
require_once $OPTION['p'] . "/phpcoverage.inc.php";
require_once PHPCOVERAGE_HOME . "/remote/RemoteCoverageRecorder.php";
require_once PHPCOVERAGE_HOME . "/reporter/HtmlCoverageReporter.php";
// Initializations
$includePaths = array();
$excludePaths = array();
if (!$OPTION['cov-url']){
if(!$OPTION['report'] && !$OPTION['cov-data-files']) {
echo "ERROR: No --cov-url option specified.\n";
exit(1);
}
}
if($OPTION['init']) {
if(!$OPTION['tmp-dir']) {
$OPTION['tmp-dir'] = $util->getTmpDir();
}
if(!$OPTION['cov-file-name']) {
$OPTION['cov-file-name'] = "phpcoverage.data.xml";
}
}
if($OPTION['report']) {
if (!$OPTION['report-name']){
echo "ERROR: No --report-name option specified.\n";
exit(1);
}
if(!$OPTION['report-dir']) {
if(!empty($PHPCOVERAGE_REPORT_DIR)) {
$OPTION["report-dir"] = $PHPCOVERAGE_REPORT_DIR;
}
else {
$OPTION["report-dir"] = "report";
}
}
if(empty($OPTION['appbase-path']) && !empty($PHPCOVERAGE_APPBASE_PATH)) {
$OPTION['appbase-path'] = realpath($PHPCOVERAGE_APPBASE_PATH);
}
if(isset($OPTION['include-paths'])) {
$includePaths = explode(",", $OPTION['include-paths']);
}
if(isset($OPTION['appbase-path']) && !empty($OPTION["appbase-path"])) {
$includePaths[] = $OPTION['appbase-path'];
}
if(isset($OPTION['exclude-paths'])) {
$excludePaths = explode(",", $OPTION['exclude-paths']);
}
}
if ($OPTION['verbose']){
echo "Options: " . print_r($OPTION, true) . "\n";
echo "include-paths: " . print_r($includePaths, true) . "\n";
echo "exclude-paths: " . print_r($excludePaths, true) . "\n";
}
//
//
//
if ($OPTION['init']){
echo "PHPCoverage: init " . $OPTION['cov-url'] . "?phpcoverage-action=init&cov-file-name=". urlencode($OPTION["cov-file-name"]) . "&tmp-dir=" . urlencode($OPTION['tmp-dir']) . "\n";
//
// Initialize the PHPCoverage reporting framework
//
file_get_contents($OPTION['cov-url'] . "?phpcoverage-action=init&cov-file-name=". urlencode($OPTION["cov-file-name"]) . "&tmp-dir=" . urlencode($OPTION['tmp-dir']));
}
else if ($OPTION['report']){
//
// Retrieve coverage data (xml) from the PHPCoverage reporting framework
//
if($OPTION['cov-data-files']) {
$OPTION['cov-data-fileset'] = explode(",", $OPTION['cov-data-files']);
foreach($OPTION['cov-data-fileset'] as $covDataFile) {
if(!is_readable($covDataFile)) {
echo "Error: Cannot read cov-data-file: " . $covDataFile . "\n";
exit(1);
}
$xmlUrl[] = $covDataFile;
}
}
else {
echo "PHPCoverage: report " . $OPTION['cov-url'] . "?phpcoverage-action=get-coverage-xml" . "\n";
$xmlUrl = $OPTION['cov-url'] . "?phpcoverage-action=get-coverage-xml";
}
//
// Configure reporter, and generate the PHPCoverage report
//
$covReporter = new HtmlCoverageReporter($OPTION['report-name'], "", $OPTION["report-dir"]);
//
// Notice the coverage recorder is of type RemoteCoverageRecorder
//
$cov = new RemoteCoverageRecorder($includePaths, $excludePaths, $covReporter);
$cov->generateReport($xmlUrl, true);
$covReporter->printTextSummary($OPTION["report-dir"] . "/report.txt");
// Should the summary be printed to console ?
if(isset($OPTION['print-summary']) && $OPTION['print-summary']) {
$covReporter->printTextSummary();
}
}
else if ($OPTION['cleanup']){
echo "PHPCoverage: cleanup " . $OPTION['cov-url'] . "?phpcoverage-action=cleanup";
file_get_contents($OPTION['cov-url'] . "?phpcoverage-action=cleanup");
}
?>
<?php
/*
* $Id: instrument.php 14672 2005-03-23 21:37:47Z npac $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
#!/bin/php
if(!defined("__PHPCOVERAGE_HOME")) {
define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__)));
}
require_once __PHPCOVERAGE_HOME . "/conf/phpcoverage.conf.php";
require_once __PHPCOVERAGE_HOME . "/util/Utility.php";
## Instruments the PHP Source files
/**
* Print help message and exit
*
* @access public
*/
function help() {
echo "Usage: " . basename(__FILE__) . " -b <application-base-path> [-p <phpcoverage-home>] [-r] [-u] [-e <exclude-file-list>]"
. "[-v] [-h] [path1 [path2 [...]]]\n";
echo "\n";
echo " Options: \n";
echo " -b <application-base-path> Application directory accessible via HTTP "
. "where PHPCoverage files should be copied.\n";
echo " -p <phpcoverage-home> Path to PHPCoverage Home.\n";
echo " -r Recursively instrument PHP files.\n";
echo " -u Undo instrumentation.\n";
echo " -e <file1,file2,...> Execlude files in the file list.\n";
echo " -v Be verbose.\n";
echo " -h Print this help and exit.\n";
echo "\n";
exit(0);
}
/**
* Print error message and exit
*
* @param $msg Message to write to console.
* @access public
*/
function error($msg) {
echo basename(__FILE__) . ": [ERROR] " . $msg . "\n";
exit(1);
}
/**
* Write a information message
*
* @param $msg Message to write to console.
* @access public
*/
function writeMsg($msg) {
global $VERBOSE;
if($VERBOSE) {
echo basename(__FILE__) . ": [INFO] " . $msg . "\n";
}
}
/**
* Instrument the PHP file.
*
* @param $file File path
* @access public
*/
function instrument($file) {
global $LOCAL_PHPCOVERAGE_LOCATION, $top, $bottom;
$tmpfile = "$file.tmp";
$contents = file_get_contents($file);
$len = strlen($contents);
if(strpos($contents, $top) === 0 && strrpos($contents, $bottom) === ($len - strlen($bottom))) {
writeMsg("Skipping $file.");
return;
}
$fp = fopen($tmpfile, "w");
if(!$fp) {
error("Cannot write to file: $tmpfile");
}
fputs($fp, $top);
fwrite($fp, $contents);
fputs($fp, $bottom);
fclose($fp);
// Delete if already exists - 'rename()' on Windows will return false otherwise
if(file_exists($file)) {
unlink($file);
}
$ret = rename($tmpfile, $file);
if(!$ret) {
error("Cannot save file: $file");
}
writeMsg("Instrumented: $file.");
}
/**
* Uninstrument the PHP file
*
* @param $file File path
* @access public
*/
function uninstrument($file) {
global $LOCAL_PHPCOVERAGE_LOCATION, $top, $bottom;
$tmpfile = "$file.tmp";
$contents = file_get_contents($file);
$len = strlen($contents);
if(strpos($contents, $top) !== 0 && strrpos($contents, $bottom) !== ($len - strlen($bottom))) {
writeMsg("Skipping $file.");
return;
}
$fr = fopen($file, "r");
$fw = fopen($tmpfile, "w");
if(!$fr) {
error("Cannot read file: $file");
}
if(!$fr) {
error("Cannot write to file: $tmpfile");
}
while(!feof($fr)) {
$line = fgets($fr);
if(strpos($line, $top) === false && strpos($line, $bottom) === false) {
fputs($fw, $line);
}
}
fclose($fr);
fclose($fw);
// Delete if already exists - 'rename()' on Windows will return false otherwise
if(file_exists($file)) {
unlink($file);
}
$ret = rename($tmpfile, $file);
if(!$ret) {
error("Cannot save file: $file");
}
writeMsg("Uninstrumented: $file");
}
/**
* Retrive a list of all PHP files in the given directory
*
* @param $dir Directory to scan
* @param $recursive True is directory is scanned recursively
* @return Array List of PHP files
* @access public
*/
function get_all_php_files($dir, &$excludeFiles, $recursive) {
global $spc_config;
$phpExtensions = $spc_config["extensions"];
$dirs[] = $dir;
while(count($dirs) > 0) {
$currDir = realpath(array_pop($dirs));
if(!is_readable($currDir)) {
continue;
}
$currFiles = scandir($currDir);
for($j = 0; $j < count($currFiles); $j++) {
if($currFiles[$j] == "." || $currFiles[$j] == "..") {
continue;
}
$currFiles[$j] = $currDir . "/" . $currFiles[$j];
if(is_file($currFiles[$j])) {
$pathParts = pathinfo($currFiles[$j]);
// Ignore phpcoverage bottom and top stubs
if(strpos($pathParts['basename'], "phpcoverage.remote.") !== false) {
continue;
}
// Ignore files specified in the exclude list
if(in_array(realpath($currFiles[$j]), $excludeFiles) !== false) {
continue;
}
if(isset($pathParts['extension'])
&& in_array($pathParts['extension'], $phpExtensions)) {
$files[] = $currFiles[$j];
}
}
else if(is_dir($currFiles[$j]) && $recursive) {
$dirs[] = $currFiles[$j];
}
}
}
return $files;
}
// Initialize
$RECURSIVE = false;
$UNDO = false;
$top_file = "/phpcoverage.remote.top.inc.php";
$bottom_file = "/phpcoverage.remote.bottom.inc.php";
//print_r($argv);
for($i = 1; $i < $argc; $i++) {
switch($argv[$i]) {
case "-r":
$RECURSIVE = true;
break;
case "-p":
$PHPCOVERAGE_HOME = $argv[++$i];
break;
case "-b":
$LOCAL_PHPCOVERAGE_LOCATION = $argv[++$i];
break;
case "-u":
$UNDO = true;
break;
case "-e":
$EXCLUDE_FILES = explode(",", $argv[++$i]);
break;
case "-v":
$VERBOSE = true;
break;
case "-h":
help();
break;
default:
$paths[] = $argv[$i];
break;
}
}
if(!is_dir($LOCAL_PHPCOVERAGE_LOCATION)) {
error("LOCAL_PHPCOVERAGE_LOCATION [$LOCAL_PHPCOVERAGE_LOCATION] not found.");
}
if(empty($PHPCOVERAGE_HOME) || !is_dir($PHPCOVERAGE_HOME)) {
$PHPCOVERAGE_HOME = __PHPCOVERAGE_HOME;
if(empty($PHPCOVERAGE_HOME) || !is_dir($PHPCOVERAGE_HOME)) {
error("PHPCOVERAGE_HOME does not exist. [" . $PHPCOVERAGE_HOME . "]");
}
}
$LOCAL_PHPCOVERAGE_LOCATION = realpath($LOCAL_PHPCOVERAGE_LOCATION);
if(file_exists($LOCAL_PHPCOVERAGE_LOCATION . $top_file)) {
unlink($LOCAL_PHPCOVERAGE_LOCATION . $top_file);
}
$ret = copy($PHPCOVERAGE_HOME . $top_file, $LOCAL_PHPCOVERAGE_LOCATION . $top_file);
if(!$ret) {
error("Cannot copy to $LOCAL_PHPCOVERAGE_LOCATION");
}
if(file_exists($LOCAL_PHPCOVERAGE_LOCATION . $bottom_file)) {
unlink($LOCAL_PHPCOVERAGE_LOCATION . $bottom_file);
}
$ret = copy($PHPCOVERAGE_HOME . $bottom_file, $LOCAL_PHPCOVERAGE_LOCATION . $bottom_file);
if(!$ret) {
error("Cannot copy to $LOCAL_PHPCOVERAGE_LOCATION");
}
$top="<?php require_once \"" . $LOCAL_PHPCOVERAGE_LOCATION . $top_file ."\"; ?>\n";
$bottom="<?php require \"" . $LOCAL_PHPCOVERAGE_LOCATION . $bottom_file . "\"; ?>\n";
if(empty($paths)) {
$paths[] = getcwd();
}
if(!isset($EXCLUDE_FILES) || empty($EXCLUDE_FILES)) {
$EXCLUDE_FILES = array();
}
for($i = 0; $i < count($EXCLUDE_FILES); $i++) {
// Remove a file from the array if it does not exist
if(!file_exists($EXCLUDE_FILES[$i])) {
array_splice($EXCLUDE_FILES, $i, 1);
$i --;
continue;
}
$EXCLUDE_FILES[$i] = realpath($EXCLUDE_FILES[$i]);
}
//print_r($paths);
foreach($paths as $path) {
unset($files);
if(is_dir($path)) {
$files = get_all_php_files($path, $EXCLUDE_FILES, $RECURSIVE);
}
else if(is_file($path)) {
$files[] = $path;
}
else {
error("Unknown entity: $path");
}
//print_r($files);
foreach($files as $file) {
if($UNDO) {
uninstrument($file);
}
else {
instrument($file);
}
}
}
?>
<?php
/*
* $Id: license.txt 13981 2005-03-16 08:09:28Z eespino $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
// Set to 'LOG_DEBUG' for maximum log output
// Note that the log file size will grow rapidly
// with LOG_DEBUG
//$spc_config['log_level'] = 'LOG_NOTICE';
$spc_config['log_level'] = 'LOG_DEBUG';
// file extension to be treated as php files
// comma-separated list, no space
$spc_config['extensions'] = array('php', 'tpl', 'inc');
// temporary directory to save transient files
$spc_config['tmpdir'] = '/tmp';
// temporary directory on Windows machines
$spc_config['windows_tmpdir'] = 'C:/TMP';
?>
<?php
/*
* $Id$
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
require_once("XML/Parser.php");
if(!defined("ATTRIBUTES")) {
define("ATTRIBUTES", "__ATTRIBUTES__");
}
/**
* An XML parser that extends the functionality of PEAR XML_Parser
* module.
*
* @author Nimish Pachapurkar <npac@spikesource.com>
* @version $Revision: $
* @package SpikePHPCoverage_Parser
*/
class BasicXmlParser extends XML_Parser {
/*{{{ Members */
protected $openTags;
protected $docroot;
/*}}}*/
/*{{{ Constructor*/
/**
* Constructor
* @access public
*/
public function BasicXmlParser() {
parent::XML_Parser();
}
/*}}}*/
/*{{{ public function handleAttrTag() */
/**
* Function that handles an element with attributes.
*
* @param $name Name of the element
* @param $attrs Attributes array (name, value pairs)
* @return Array An element
* @access public
*/
public function handleAttrTag($name, $attrs) {
$tag = array();
foreach($attrs as $attr_name => $value) {
$tag[$attr_name] = $value;
}
return $tag;
}
/*}}}*/
/*{{{ public function startHandler() */
/**
* Function to handle start of an element
*
* @param $xp XMLParser handle
* @param $name Element name
* @param $attributes Attribute array
* @access public
*/
function startHandler($xp, $name, $attributes) {
$this->openTags[] = $name;
}
/*}}}*/
/*{{{ public function endHandler()*/
/**
* Function to handle end of an element
*
* @param $xp XML_Parser handle
* @param $name Name of the element
* @access public
*/
public function endHandler($xp, $name) {
// Handle error tags
$lastTag = $this->getLastOpenTag($name);
switch($name) {
case "MESSAGE":
if($lastTag == "ERROR") {
$this->docroot["ERROR"]["MESSAGE"] = $this->getCData();
}
break;
}
// Empty CData
$this->lastCData = "";
// Close tag
if($this->openTags[count($this->openTags)-1] == $name) {
array_pop($this->openTags);
}
}
/*}}}*/
/*{{{ public function cdataHandler() */
/**
* Function to handle character data
*
* @param $xp XMLParser handle
* @param $cdata Character data
* @access public
*/
public function cdataHandler($xp, $cdata) {
$this->lastCData .= $cdata;
}
/*}}}*/
/*{{{ public function getCData() */
/**
* Returns the CData collected so far.
*
* @return String Character data collected.
* @access public
*/
public function getCData() {
return $this->lastCData;
}
/*}}}*/
/*{{{ public function getLastOpenTag() */
/**
* Returns the name of parent tag of give tag
*
* @param $tag Name of a child tag
* @return String Name of the parent tag of $tag
* @access public
*/
public function getLastOpenTag($tag) {
$lastTagIndex = count($this->openTags)-1;
if($this->openTags[$lastTagIndex] == $tag) {
if($lastTagIndex > 0) {
return $this->openTags[$lastTagIndex-1];
}
}
return false;
}
/*}}}*/
/*{{{ public function getDocumentArray() */
/**
* Return the document array gathered during parsing.
* Document array is a data structure that mimics the XML
* contents.
*
* @return Array Document array
* @access public
*/
public function getDocumentArray() {
return $this->docroot;
}
/*}}}*/
}
?>
<?php
/*
* $Id: CoverageXmlParser.php 14663 2005-03-23 19:27:27Z npac $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
require_once dirname(__FILE__) . "/BasicXmlParser.php";
/**
* Special parser for SpikePHPCoverage data parsing
* Expect input in following format:
* <spike-phpcoverage>
* <file path="/complete/file/path">
* <line line-number="10" frequency="1"/>
* <line line-number="12" frequency="2"/>
* </file>
* <file path="/another/file/path">
* ...
* </file>
* </spike-phpcoverage>
*
* @author Nimish Pachapurkar <npac@spikesource.com>
* @version $Revision: $
* @package SpikePHPCoverage_Parser
*/
class CoverageXmlParser extends BasicXmlParser {
/*{{{ Members */
protected $_data = array();
protected $_lastFilePath;
/*}}}*/
/*{{{ public function startHandler() */
public function startHandler($xp, $name, $attrs) {
switch($name) {
case "FILE":
$fileAttributes = $this->handleAttrTag($name, $attrs);
$this->_lastFilePath = $fileAttributes["PATH"];
if(!isset($this->_data[$this->_lastFilePath])) {
$this->_data[$this->_lastFilePath] = array();
}
break;
case "LINE":
$lineAttributes = $this->handleAttrTag($name, $attrs);
$lineNumber = (int)$lineAttributes["LINE-NUMBER"];
if(!isset($this->_data[$this->_lastFilePath][$lineNumber])) {
$this->_data[$this->_lastFilePath][$lineNumber] = (int)$lineAttributes["FREQUENCY"];
}
else {
$this->_data[$this->_lastFilePath][$lineNumber] += (int)$lineAttributes["FREQUENCY"];
}
break;
}
}
/*}}}*/
/*{{{ public function getCoverageData() */
/**
* Returns coverage data array from the XML
* Format:
* Array
* (
* [/php/src/remote/RemoteCoverageRecorder.php] => Array
* (
* [220] => 1
* [221] => 1
* )
*
* [/opt/oss/share/apache2/htdocs/web/sample.php] => Array
* (
* [16] => 1
* [18] => 1
* )
* )
*
* @access public
*/
public function getCoverageData() {
return $this->_data;
}
/*}}}*/
}
?>
<?php
/*
* $Id: PHPParser.php 14665 2005-03-23 19:37:50Z npac $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
if(!defined("__PHPCOVERAGE_HOME")) {
define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__)));
}
require_once __PHPCOVERAGE_HOME . "/parser/Parser.php";
/**
* Parser for PHP files
*
* @author Nimish Pachapurkar (npac@spikesource.com)
* @version $Revision: 14665 $
* @package SpikePHPCoverage_Parser
*/
class PHPParser extends Parser {
/*{{{ Members */
private $inPHP = false;
private $phpStarters = array('<?php', '<?', '<?=');
private $phpFinisher = '?>';
private $inComment = false;
private $lastLineEndTokenType = "";
// If one of these tokens occur as the last token of a line
// then the next line can be treated as a continuation line
// depending on how it starts.
public static $contTypes = array(
"(",
",",
".",
"=",
T_LOGICAL_XOR,
T_LOGICAL_AND,
T_LOGICAL_OR,
T_PLUS_EQUAL,
T_MINUS_EQUAL,
T_MUL_EQUAL,
T_DIV_EQUAL,
T_CONCAT_EQUAL,
T_MOD_EQUAL,
T_AND_EQUAL,
T_OR_EQUAL,
T_XOR_EQUAL,
T_BOOLEAN_AND,
T_BOOLEAN_OR,
T_OBJECT_OPERATOR,
T_DOUBLE_ARROW,
"[",
"]",
T_LOGICAL_OR,
T_LOGICAL_XOR,
T_LOGICAL_AND
);
/*}}}*/
/*{{{ protected function processLine() */
/**
* Process a line read from the file and determine if it is an
* executable line or not.
*
* This is the work horse function that does most of the parsing.
* To parse PHP, get_all_tokens() tokenizer function is used.
*
* @param $line Line to be parsed.
* @access protected
*/
protected function processLine($line) {
// Default values
$this->lineType = LINE_TYPE_NOEXEC;
$line = trim($line);
$parseLine = $line;
$artificialStart = false;
$artificialEnd = false;
// If we are not inside PHP opening tag
if(!$this->inPHP) {
$pos = -1;
// Confirm that the line does not have T_OPEN_TAG_WITH_ECHO (< ? =)
if(strpos($line, $this->phpStarters[2]) === false) {
// If the line has PHP start tag of the first kind
if(($pos = strpos($line, $this->phpStarters[0])) !== false) {
$pos = $pos + strlen($this->phpStarters[0]);
}
// if the line has PHP start tag of the second kind.
else if(($pos = strpos($line, $this->phpStarters[1])) !== false) {
$pos = $pos + strlen($this->phpStarters[1]);
}
// $pos now points to the character after opening tag
if($pos > 0) {
$this->inPHP = true;
//echo "Going in PHP\n";
// Remove the part of the line till the PHP opening
// tag and recurse
return $this->processLine(trim(substr($line, $pos)));
}
}
}
// If we are already in PHP
else if($this->inPHP) {
// If we are inside a multi-line comment, that is not ending
// on the same line
if((strpos($line, "/*") !== false &&
strpos($line, "*/") === false) ||
(strpos($line, "/*") > strpos($line, "*/"))) {
$this->inComment = true;
}
if($this->inComment) {
// Do we need to append an artificial comment start?
// (otherwise the tokenizer might throw error.
if(strpos($line, "/*") === false) {
$line = "/*" . $line;
$artificialStart = true;
}
// Do we need to append an artificial comment end?
if(strpos($line, "*/") === false) {
$line = $line . "*/";
$artificialEnd = true;
}
}
// Since we are inside php, append php opening and closing tags
// to prevent tokenizer from mis-interpreting the line
$parseLine = "<?php " . $line . " ?>";
}
// Tokenize
$tokens = @token_get_all($parseLine);
$this->logger->debug("inPHP? " . $this->inPHP . "\nLine:" . $parseLine,
__FILE__, __LINE__);
$this->logger->debug(print_r($tokens, true), __FILE__, __LINE__);
$seenEnough = false;
$seeMore = false;
$tokenCnt = 0; //tokens in this line
$phpEnded = false;
if($this->isContinuation($this->lastLineEndTokenType)) {
$this->lineType = LINE_TYPE_CONT;
$this->logger->debug("Continuation !", __FILE__, __LINE__);
}
foreach($tokens as $token) {
$tokenCnt ++;
if($this->inPHP) {
if($tokenCnt == 2) {
if($this->isContinuation($token)) {
$this->lineType = LINE_TYPE_CONT;
$this->logger->debug("Continuation! Token: $token",
__FILE__, __LINE__);
break;
}
}
}
if(is_string($token)) {
// FIXME: Add more cases, if needed
switch($token) {
// Any of these things, are non-executable.
case '{':
case '}':
case '(':
case ')':
case ';':
if($this->lineType != LINE_TYPE_EXEC) {
$this->lineType = LINE_TYPE_NOEXEC;
}
break;
// Everything else by default is executable.
default:
$this->lineType = LINE_TYPE_EXEC;
break;
}
$this->logger->debug("Status: " . $this->getLineTypeStr($this->lineType) . "\t\tToken: $token",
__FILE__, __LINE__);
}
else {
// The token is an array
list($tokenType, $text) = $token;
switch($tokenType) {
// If it is a comment end or start, set the correct flag
// If we have put the start or end artificially, ignore!
case T_COMMENT:
case T_DOC_COMMENT:
if(strpos($text, "/*") !== false && !$artificialStart) {
$this->inComment = true;
}
if(strpos($text, "*/") !== false && !$artificialEnd) {
$this->inComment = false;
}
case T_WHITESPACE: // white space
case T_OPEN_TAG: // < ?
case T_OPEN_TAG_WITH_ECHO: // < ? =
case T_CURLY_OPEN: //
case T_INLINE_HTML: // <br/><b>jhsk</b>
//case T_STRING: //
case T_EXTENDS: // extends
case T_STATIC: // static
case T_STRING_VARNAME: // string varname?
case T_CHARACTER: // character
case T_ELSE: // else
case T_CONSTANT_ENCAPSED_STRING: // "some str"
case T_START_HEREDOC:
// Only if decision is not already made
// mark this non-executable.
if($this->lineType != LINE_TYPE_EXEC) {
$this->lineType = LINE_TYPE_NOEXEC;
}
break;
case T_PRIVATE: // private
case T_PUBLIC: // public
case T_PROTECTED: // protected
case T_VAR: // var
case T_FUNCTION: // function
case T_CLASS: // class
case T_INTERFACE: // interface
case T_REQUIRE: // require
case T_REQUIRE_ONCE: // require_once
case T_INCLUDE: // include
case T_INCLUDE_ONCE: // include_once
case T_ARRAY: // array
case T_SWITCH: // switch
case T_CONST: // const
case T_TRY: // try
$this->lineType = LINE_TYPE_NOEXEC;
// No need to see any further
$seenEnough = true;
break;
case T_VARIABLE: // $foo
$seeMore = true;
$this->lineType = LINE_TYPE_EXEC;
break;
case T_CLOSE_TAG:
if($tokenCnt != count($tokens)) {
// Token is not last (because we inserted that)
$this->logger->debug("T_CLOSE_TAG for tokenCnt " . $tokenCnt . " End of PHP code.");
$phpEnded = true; // php end tag found within the line.
}
if($this->lineType != LINE_TYPE_EXEC) {
$this->lineType = LINE_TYPE_NOEXEC;
}
break;
default:
$seeMore = false;
$this->lineType = LINE_TYPE_EXEC;
break;
}
$this->logger->debug("Status: " . $this->getLineTypeStr($this->lineType) . "\t\tToken type: $tokenType \tText: $text",
__FILE__, __LINE__);
}
if(($this->lineType == LINE_TYPE_EXEC && !$seeMore)
|| $seenEnough) {
$this->logger->debug("Made a decision! Exiting. Token Type: $tokenType & Text: $text",
__FILE__, __LINE__);
if($seenEnough) {
$this->logger->debug("Seen enough at Token Type: $tokenType & Text: $text",
__FILE__, __LINE__);
}
break;
}
} // end foreach
$this->logger->debug("Line Type: " . $this->getLineTypeStr($this->lineType),
__FILE__, __LINE__);
if($this->inPHP) {
$this->lastLineEndTokenType = $this->getLastTokenType($tokens);
}
$this->logger->debug("Last End Token: " . $this->lastLineEndTokenType,
__FILE__, __LINE__);
if($this->inPHP) {
// Check if PHP block ends on this line
if($phpEnded) {
$this->inPHP = false;
// If line is not executable so far, check for the
// remaining part
if($this->lineType != LINE_TYPE_EXEC) {
//return $this->processLine(trim(substr($line, $pos+2)));
}
}
}
}
/*}}}*/
/*{{{ public function getLineType() */
/**
* Returns the type of line just read
*
* @return Line type
* @access public
*/
public function getLineType() {
return $this->lineType;
}
/*}}}*/
/*{{{ protected function isContinuation() */
/**
* Check if a line is a continuation of the previous line
*
* @param &$token Second token in a line (after PHP start)
* @return Boolean True if the line is a continuation; false otherwise
* @access protected
*/
protected function isContinuation(&$token) {
if(is_string($token)) {
switch($token) {
case ".":
case ",";
case "]":
case "[":
case "(":
case ")":
case "=":
return true;
}
}
else {
list($tokenType, $text) = $token;
switch($tokenType) {
case T_CONSTANT_ENCAPSED_STRING:
case T_ARRAY:
case T_DOUBLE_ARROW:
case T_OBJECT_OPERATOR:
case T_LOGICAL_XOR:
case T_LOGICAL_AND:
case T_LOGICAL_OR:
case T_PLUS_EQUAL:
case T_MINUS_EQUAL:
case T_MUL_EQUAL:
case T_DIV_EQUAL:
case T_CONCAT_EQUAL:
case T_MOD_EQUAL:
case T_AND_EQUAL:
case T_OR_EQUAL:
case T_XOR_EQUAL:
case T_BOOLEAN_AND:
case T_BOOLEAN_OR:
case T_LNUMBER:
case T_DNUMBER:
return true;
case T_STRING:
case T_VARIABLE:
return in_array($this->lastLineEndTokenType, PHPParser::$contTypes);
}
}
return false;
}
/*}}}*/
/*{{{ protected function getTokenType() */
/**
* Get the token type of a token (if exists) or
* the token itself.
*
* @param $token Token
* @return Token type or token itself
* @access protected
*/
protected function getTokenType($token) {
if(is_string($token)) {
return $token;
}
else {
list($tokenType, $text) = $token;
return $tokenType;
}
}
/*}}}*/
/*{{{*/
/**
* Return the type of last non-empty token in a line
*
* @param &$tokens Array of tokens for a line
* @return mixed Last non-empty token type (or token) if exists; false otherwise
* @access protected
*/
protected function getLastTokenType(&$tokens) {
for($i = count($tokens)-2; $i > 0; $i--) {
if(empty($tokens[$i])) {
continue;
}
if(is_string($tokens[$i])) {
return $tokens[$i];
}
else {
list($tokenType, $text) = $tokens[$i];
if($tokenType != T_WHITESPACE && $tokenType != T_EMPTY) {
return $tokenType;
}
}
}
return false;
}
/*}}}*/
/*
// Main
$obj = new PHPParser();
$obj->parse("test.php");
while(($line = $obj->getLine()) !== false) {
echo "#########################\n";
echo "[" . $line . "] Type: [" . $obj->getLineTypeStr($obj->getLineType()) . "]\n";
echo "#########################\n";
}
*/
}
?>
<?php
/*
* $Id: Parser.php 14009 2005-03-16 17:33:33Z npac $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
if(!defined("__PHPCOVERAGE_HOME")) {
define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__)));
}
require_once __PHPCOVERAGE_HOME . "/conf/phpcoverage.conf.php";
require_once __PHPCOVERAGE_HOME . "/util/Utility.php";
/**
* Base class for Parsers.
*
* @author Nimish Pachapurkar (npac@spikesource.com)
* @version $Revision: 14009 $
* @package SpikePHPCoverage_Parser
*/
define("LINE_TYPE_UNKNOWN", "0");
define("LINE_TYPE_EXEC", "1");
define("LINE_TYPE_NOEXEC", "2");
define("LINE_TYPE_CONT", "3");
abstract class Parser {
/*{{{ Members */
protected $totalLines;
protected $coveredLines;
protected $uncoveredLines;
protected $fileRef;
protected $filename;
protected $line;
protected $logger;
/*}}}*/
/*{{{ public function __construct() */
/**
* Constructor
* @access public
*/
public function __construct() {
global $util;
$this->totalLines = 0;
$this->coveredLines = 0;
$this->uncoveredLines = 0;
$this->fileRef = false;
$this->line = false;
$this->lineType = false;
$this->logger = $util->getLogger();
}
/*}}}*/
/*{{{ public abstract function parse() */
/**
* Parse a given file
*
* @param $filename Full path of the file
* @return FALSE on error.
* @access public
*/
public function parse($filename) {
$this->filename = $filename;
$ret = $this->openFileReadOnly();
if(!$ret) {
die("Error: Cannot open file: $this->filename \n");
}
}
/*}}}*/
/*{{{ protected abstract function processLine() */
/**
* Process the line and classify it into either
* covered and uncovered.
*
* @param $line
* @return
* @access protected
*/
protected abstract function processLine($line);
/*}}}*/
/*{{{ public function getLine() */
/**
* Returns the next line from file.
*
* @return Next line from file
* @access public
*/
public function getLine() {
if(!feof($this->fileRef)) {
$this->line = fgets($this->fileRef);
$this->processLine($this->line);
}
else {
fclose($this->fileRef);
$this->line = false;
}
return $this->line;
}
/*}}}*/
/*{{{ public abstract function getLineType() */
/**
* Returns the type of last line read.
*
* The type can be either
* * LINE_TYPE_EXEC Line that can be executed.
* * LINE_TYPE_NOEXEC Line that cannot be executed.
* This includes the variable and function definitions
* (without initialization), blank lines, non-PHP lines,
* etc.
*
* @return Type of last line
* @access public
*/
public abstract function getLineType();
/*}}}*/
/*{{{ public function getLineTypeStr() */
/**
* Returns the string representation of LINE_TYPE
*
* @param $lineType
* @return Type of line
* @access public
*/
public function getLineTypeStr($lineType) {
if($lineType == LINE_TYPE_EXEC) {
return "LINE_TYPE_EXEC";
}
else if($lineType == LINE_TYPE_NOEXEC) {
return "LINE_TYPE_NOEXEC";
}
else if($lineType == LINE_TYPE_CONT) {
return "LINE_TYPE_CONT";
}
else {
return "LINE_TYPE_UNKNOWN";
}
}
/*}}}*/
/*{{{ protected function openFileReadOnly() */
/**
* Opens the file to be parsed in Read-only mode
*
* @return FALSE on failure.
* @access protected
*/
protected function openFileReadOnly() {
$this->fileRef = fopen($this->filename, "r");
return $this->fileRef !== false;
}
/*}}}*/
/*{{{ public function getTotalLines() */
/**
* Returns the total lines (PHP, non-PHP) from a file
*
* @return Number of lines
* @access public
*/
public function getTotalLines() {
return $this->totalLines;
}
/*}}}*/
/*{{{ public function getCoveredLines() */
/**
* Returns the number of covered PHP lines
*
* @return Number of covered lines
* @access public
*/
public function getCoveredLines() {
return $this->coveredLines;
}
/*}}}*/
/*{{{ public function getUncoveredLines() */
/**
* Returns the number of uncovered PHP lines
*
* Note that the sum of covered and uncovered
* lines may not be equal to total lines.
*
* @return Number of uncovered lines
* @access public
*/
public function getUncoveredLines() {
return $this->uncoveredLines;
}
/*}}}*/
}
?>
<?php
/*
* $Id: phpcoverage.inc.php 14670 2005-03-23 21:25:46Z npac $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
global $PHPCOVERAGE_REPORT_DIR;
global $PHPCOVERAGE_HOME;
global $PHPCOVERAGE_APPBASE_PATH;
$basedir = dirname(__FILE__);
for($ii=1; $ii < $argc; $ii++) {
if(strpos($argv[$ii], "PHPCOVERAGE_REPORT_DIR=") !== false) {
parse_str($argv[$ii]);
}
else if(strpos($argv[$ii], "PHPCOVERAGE_HOME=") !== false) {
parse_str($argv[$ii]);
}
else if(strpos($argv[$ii], "PHPCOVERAGE_APPBASE_PATH=") !== false) {
parse_str($argv[$ii]);
}
}
if(empty($PHPCOVERAGE_HOME)) {
$envvar = getenv("PHPCOVERAGE_HOME");
if(empty($envvar)) {
$share_home = getenv("LOCAL_CACHE");
$PHPCOVERAGE_HOME = $share_home . "/common/spikephpcoverage/src/";
}
else {
$PHPCOVERAGE_HOME = $envvar;
}
}
if(empty($PHPCOVERAGE_HOME) || !is_dir($PHPCOVERAGE_HOME)) {
$msg = "ERROR: Could not locate PHPCOVERAGE_HOME [$PHPCOVERAGE_HOME]. ";
$msg .= "Use 'php <filename> PHPCOVERAGE_HOME=/path/to/coverage/home'\n";
die($msg);
}
// Fallback
if(!defined("PHPCOVERAGE_HOME")) {
$include_path = get_include_path();
set_include_path($PHPCOVERAGE_HOME. PATH_SEPARATOR . $include_path);
define('PHPCOVERAGE_HOME', $PHPCOVERAGE_HOME);
}
error_log("[phpcoverage.inc.php] PHPCOVERAGE_HOME=" . $PHPCOVERAGE_HOME);
error_log("[phpcoverage.inc.php] PHPCOVERAGE_REPORT_DIR=" . $PHPCOVERAGE_REPORT_DIR);
error_log("[phpcoverage.inc.php] PHPCOVERAGE_APPBASE_PATH=" . $PHPCOVERAGE_APPBASE_PATH);
?>
<?php
/*
* $Id: phpcoverage.remote.bottom.inc.php 14665 2005-03-23 19:37:50Z npac $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
if(isset($_REQUEST)) {
global $spc_config, $util;
$logger = $util->getLogger();
// Create a distinct hash (may or may not be unique)
$session_id = md5($_SERVER["REMOTE_ADDR"] . $_SERVER["SERVER_NAME"]);
$tmpFile = $util->getTmpDir() . "/phpcoverage.session." . $session_id;
$logger->info("[phpcoverage.remote.bottom.inc.php] Session id: " . $session_id,
__FILE__, __LINE__);
if(!isset($cov)) {
if(file_exists($tmpFile)) {
$object = file_get_contents($tmpFile);
$cov = unserialize($object);
$logger->info("[phpcoverage.remote.bottom.inc.php] Coverage object found: " . $cov, __FILE__, __LINE__);
}
}
if(isset($cov)) {
// PHPCoverage bottom half
if(!isset($called_script)) {
$called_script = "";
}
$logger->info("[phpcoverage.remote.bottom.inc.php] END: " . $called_script,
__FILE__, __LINE__);
// Save the code coverage
$cov->saveCoverageXml();
$logger->info("[phpcoverage.remote.bottom.inc.php] Saved coverage xml",
__FILE__, __LINE__);
$cov->startInstrumentation();
$logger->info("[phpcoverage.remote.bottom.inc.php] Instrumentation turned on.",
__FILE__, __LINE__);
$object = serialize($cov);
file_put_contents($tmpFile, $object);
$logger->info("[phpcoverage.remote.bottom.inc.php] ################## END ###################",
__FILE__, __LINE__);
}
}
?>
<?php
/*
* $Id: phpcoverage.remote.top.inc.php 14666 2005-03-23 19:39:55Z npac $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
if(isset($_REQUEST)){
$debug = false;
// Uncomment the line below to permanently turn on debugging.
// Alternatively, export a variable called phpcoverage-debug before
// starting the web server.
$debug = true;
if(isset($_REQUEST["phpcoverage-debug"]) ||
isset($_SERVER["phpcoverage-debug"]) ||
isset($_ENV["phpcoverage-debug"])) {
$debug = true;
}
if($debug) error_log("[phpcoverage.remote.top.inc.php] ################## START ###################");
$PHPCOVERAGE_HOME = false;
global $PHPCOVERAGE_HOME;
$basedir = dirname(__FILE__);
$this_script = basename(__FILE__);
$called_script = basename($_SERVER["SCRIPT_FILENAME"]);
if(!empty($_REQUEST["PHPCOVERAGE_HOME"])) {
$PHPCOVERAGE_HOME = $_REQUEST["PHPCOVERAGE_HOME"];
}
if(empty($PHPCOVERAGE_HOME)) {
$env_var = getenv("PHPCOVERAGE_HOME");
if(empty($env_var)) {
$msg = "Could not find PHPCOVERAGE_HOME. Please either export it in your environment before starting the web server. Or include PHPCOVERAGE_HOME=<path> in your HTTP request.";
error_log("[phpcoverage.remote.top.inc.php] FATAL: " . $msg);
die($msg);
}
else {
$PHPCOVERAGE_HOME = $env_var;
}
}
if(empty($PHPCOVERAGE_HOME) || !is_dir($PHPCOVERAGE_HOME)) {
$msg = "ERROR: Could not locate PHPCOVERAGE_HOME [$PHPCOVERAGE_HOME]. ";
$msg .= "Use 'php <filename> PHPCOVERAGE_HOME=/path/to/coverage/home'\n";
die($msg);
}
// Fallback
if(!defined("PHPCOVERAGE_HOME")) {
$include_path = get_include_path();
set_include_path($PHPCOVERAGE_HOME. ":" . $include_path);
define('PHPCOVERAGE_HOME', $PHPCOVERAGE_HOME);
}
if($debug) error_log("[phpcoverage.remote.top.inc.php] PHPCOVERAGE_HOME=" . $PHPCOVERAGE_HOME);
// Register the shutdown function to get code coverage results before
// script exits abnormally.
register_shutdown_function('spikephpcoverage_before_shutdown');
require_once PHPCOVERAGE_HOME . "/conf/phpcoverage.conf.php";
require_once PHPCOVERAGE_HOME . "/util/Utility.php";
require_once PHPCOVERAGE_HOME . "/remote/RemoteCoverageRecorder.php";
require_once PHPCOVERAGE_HOME . "/reporter/HtmlCoverageReporter.php";
global $util;
$logger = $util->getLogger();
// Create a distinct hash (may or may not be unique)
$session_id = md5($_SERVER["REMOTE_ADDR"] . $_SERVER["SERVER_NAME"]);
$tmpFile = $util->getTmpDir() . "/phpcoverage.session." . $session_id;
$logger->info("[phpcoverage.remote.top.inc.php] Session id: " . $session_id . " Saved in: " . $tmpFile,
__FILE__, __LINE__);
if(file_exists($tmpFile)) {
$object = file_get_contents($tmpFile);
$cov = unserialize($object);
$logger->info("[phpcoverage.remote.top.inc.php] Coverage object found." ,
__FILE__, __LINE__);
}
else {
$covReporter = new HtmlCoverageReporter(
"PHPCoverage report",
"",
$util->getTmpDir() . "/php-coverage-report"
);
$cov = new RemoteCoverageRecorder(array(), array(), $covReporter);
$object = serialize($cov);
file_put_contents($tmpFile, $object);
$logger->info("[phpcoverage.remote.top.inc.php] Stored coverage object found",
__FILE__, __LINE__);
}
if(!empty($_REQUEST["phpcoverage-action"])) {
$logger->info("[phpcoverage.remote.top.inc.php] phpcoverage-action=" . strtolower($_REQUEST["phpcoverage-action"]),
__FILE__, __LINE__);
switch(strtolower($_REQUEST["phpcoverage-action"])) {
case "init":
if(!empty($_REQUEST["tmp-dir"])) {
$cov->setTmpDir($_REQUEST["tmp-dir"]);
}
$cov->setCoverageFileName($_REQUEST["cov-file-name"]);
if(!$cov->cleanCoverageFile()) {
die("Cannot delete existing coverage data.");
}
break;
case "instrument":
break;
case "get-coverage-xml":
$cov->getCoverageXml();
break;
case "cleanup":
if(file_exists($tmpFile) && is_writable($tmpFile)) {
unlink($tmpFile);
unset($cov);
$logger->info("[phpcoverage.remote.top.inc.php] Cleaned up!",
__FILE__, __LINE__);
return;
}
else {
$logger->error("[phpcoverage.remote.top.inc.php] Error deleting file: " . $tmpFile,
__FILE__, __LINE__);
}
break;
}
}
$cov->startInstrumentation();
$logger->info("[phpcoverage.remote.top.inc.php] Instrumentation turned on.",
__FILE__, __LINE__);
$object = serialize($cov);
file_put_contents($tmpFile, $object);
$logger->info("[phpcoverage.remote.top.inc.php] BEGIN: " . $called_script,
__FILE__, __LINE__);
}
function spikephpcoverage_before_shutdown() {
global $cov, $logger;
$logger->debug("[phpcoverage.remote.top.inc.php::before_shutdown()] Getting code coverage before shutdown: START",
__FILE__, __LINE__);
require dirname(__FILE__) . "/phpcoverage.remote.bottom.inc.php";
$logger->debug("[phpcoverage.remote.top.inc.php::before_shutdown()] Getting code coverage before shutdown: FINISH",
__FILE__, __LINE__);
}
?>
<?php
/*
* $Id: RemoteCoverageRecorder.php 14665 2005-03-23 19:37:50Z npac $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
if(!defined("__PHPCOVERAGE_HOME")) {
define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__)));
}
require_once __PHPCOVERAGE_HOME . "/util/Utility.php";
require_once __PHPCOVERAGE_HOME . "/CoverageRecorder.php";
require_once __PHPCOVERAGE_HOME . "/remote/XdebugTraceReader.php";
require_once __PHPCOVERAGE_HOME . "/parser/CoverageXmlParser.php";
/**
* A Coverage recorder extension for remote Coverage measurement.
*
* @author Nimish Pachapurkar <npac@spikesource.com>
* @version $Revision: $
* @package SpikePHPCoverage_Remote
*/
class RemoteCoverageRecorder extends CoverageRecorder {
/*{{{ Members */
protected $traceFilePath;
protected $xdebugTraceReader;
protected $tmpDir;
protected $tmpTraceFilename = "phpcoverage.xdebug.trace";
protected $coverageFileName = "phpcoverage.coverage.xml";
protected $xmlStart = "<?xml version=\"1.0\" encoding=\"utf-8\" ?><spike-phpcoverage>";
protected $xmlEnd = "</spike-phpcoverage>";
/*}}}*/
/*{{{ public function __construct() */
/**
* Constructor
*
* @access public
*/
public function __construct(
$includePaths=array("."),
$excludePaths=array(),
$reporter="new HtmlCoverageReporter()"
) {
global $util;
parent::__construct($includePaths, $excludePaths, $reporter);
$this->isRemote = true;
$this->phpCoverageFiles[] = "phpcoverage.remote.inc.php";
$this->phpCoverageFiles[] = "phpcoverage.remote.top.inc.php";
$this->phpCoverageFiles[] = "phpcoverage.remote.bottom.inc.php";
// configuration
$this->tmpDir = $util->getTmpDir();
}
/*}}}*/
/*{{{ Getters and Setters */
public function getTraceFilePath() {
return $this->traceFilePath;
}
public function setTraceFilePath($traceFilePath) {
$this->traceFilePath = $traceFilePath;
}
public function getTmpDir() {
return $this->tmpDir;
}
public function setTmpDir($tmpTraceDir) {
$this->tmpDir = $tmpTraceDir;
}
public function getCoverageFileName() {
return $this->coverageFileName;
}
public function setCoverageFileName($covFileName) {
$this->coverageFileName = $covFileName;
}
/*}}}*/
/*{{{ public function cleanCoverageFile() */
/**
* Deletes a coverage data file if one exists.
*
* @return Boolean True on success, False on failure.
* @access public
*/
public function cleanCoverageFile() {
$filepath = $this->tmpDir . "/" . $this->coverageFileName;
if(file_exists($filepath)) {
if(is_writable($filepath)) {
unlink($filepath);
}
else {
$this->logger->error("[RemoteCoverageRecorder::cleanCoverageFile()] "
. "ERROR: Cannot delete $filepath.", __FILE__, __LINE__);
return false;
}
}
return true;
}
/*}}}*/
/*{{{ protected function prepareCoverageXml() */
/**
* Convert the Coverage data into an XML.
*
* @return String XML generated from Coverage data
* @access protected
*/
protected function prepareCoverageXml() {
global $util;
$xmlString = "";
$xmlBody = "";
if(!empty($this->coverageData)) {
foreach($this->coverageData as $file => &$lines) {
$xmlBody .= "<file path=\"". $util->replaceBackslashes($file) . "\">";
foreach($lines as $linenum => &$frequency) {
$xmlBody .= "<line line-number=\"" . $linenum . "\"";
$xmlBody .= " frequency=\"" . $frequency . "\"/>";
}
$xmlBody .= "</file>\n";
}
unset($this->coverageData);
}
else {
$this->logger->info("[RemoteCoverageRecorder::prepareCoverageXml()] Coverage data is empty.",
__FILE__, __LINE__);
}
$xmlString .= $xmlBody;
$this->logger->debug("[RemoteCoverageRecorder::prepareCoverageXml()] Xml: " . $xmlString, __FILE__, __LINE__);
return $xmlString;
}
/*}}}*/
/*{{{ protected function parseCoverageXml() */
/**
* Parse coverage XML to regenerate the Coverage data array.
*
* @param $xml XML String or URL of the coverage data
* @param $stream=false Is the input a stream?
* @return
* @access protected
*/
protected function parseCoverageXml(&$xml, $stream=false) {
// Need to handle multiple xml files.
if(!is_array($xml)) {
$xml = array($xml);
}
for($i = 0; $i < count($xml); $i++) {
$xmlParser = new CoverageXmlParser();
if($stream) {
$xmlParser->setInput($xml[$i]);
}
else {
$xmlParser->setInputString($xml[$i]);
}
$xmlParser->parse();
$data =& $xmlParser->getCoverageData();
if(empty($this->coverageData)) {
$this->coverageData = $data;
}
else {
$data2 = array_merge_recursive($this->coverageData, $data);
$this->coverageData = $data2;
}
$this->logger->debug("[RemoteCoverageRecorder::prepareCoverageXml()] " . "Coverage data intermediate: " . print_r($this->coverageData, true));
}
}
/*}}}*/
/*{{{ public function getCoverageXml() */
/**
* Dumps the coverage data in XML format
*
* @access public
*/
public function getCoverageXml() {
$filepath = $this->tmpDir . "/" . $this->coverageFileName;
if(file_exists($filepath) && is_readable($filepath)) {
$fp = fopen($filepath, "r");
if($fp) {
while(!feof($fp)) {
$xml = fread($fp, 4096);
echo $xml;
}
fclose($fp);
return true;
}
else {
$this->logger->error("Could not read coverage data file.",
__FILE__, __LINE__);
}
}
else {
$this->logger->error("[RemoteCoverageRecorder::getCoverageXml()] "
. "ERROR: Cannot read file " . $filepath, __FILE__, __LINE__);
}
return false;
}
/*}}} */
/*{{{ protected function appendDataToFile() */
/**
* Append coverage data to xml file
*
* @param $newXml New xml recorded
* @return True on success; false otherwise
* @access protected
*/
protected function appendDataToFile($newXml) {
$filepath = $this->tmpDir . "/" . $this->coverageFileName;
if(!file_exists($filepath)) {
// If new file, write the xml start and end tags
$bytes = file_put_contents($filepath, $this->xmlStart . "\n" . $this->xmlEnd);
if(!$bytes) {
$this->logger->critical("[RemoteCoverageRecorder::appendDataToFile()] Could not create file: " . $filepath, __FILE__, __LINE__);
return false;
}
}
if(file_exists($filepath) && is_readable($filepath)) {
$res = fopen($filepath, "r+");
if($res) {
fseek($res, -1 * strlen($this->xmlEnd), SEEK_END);
$ret = fwrite($res, $newXml);
if(!$ret) {
$this->logger->error("[RemoteCoverageRecorder::appendDataToFile()] Could not append data to file.",
__FILE__, __LINE__);
fclose($res);
return false;
}
fwrite($res, $this->xmlEnd);
fclose($res);
}
else {
$this->logger->error("[RemoteCoverageRecorder::appendDataToFile()] Error opening file for writing: " . $filepath,
__FILE__, __LINE__);
return false;
}
}
return true;
}
/*}}}*/
/*{{{ public function saveCoverageXml() */
/**
* Append coverage xml to a xml data file.
*
* @return Boolean True on success, False on error
* @access public
*/
public function saveCoverageXml() {
$filepath = $this->tmpDir . "/" . $this->coverageFileName;
if($this->stopInstrumentation()) {
$xml = $this->prepareCoverageXml();
$ret = $this->appendDataToFile($xml);
if(!$ret) {
$this->logger->warn("[RemoteCoverageRecorder::saveCoverageXml()] "
. "ERROR: Nothing was written to " . $filepath,
__FILE__, __LINE__);
return false;
}
$this->logger->info("[RemoteCoverageRecorder::saveCoverageXml()] "
. "Saved XML to $filepath; size: [" . filesize($filepath)
. "]", __FILE__, __LINE__);
return true;
}
return false;
}
/*}}}*/
/*{{{ public function generateReport() */
/**
* Generate report from the xml coverage data
* The preferred method for usage of this function is
* passing a stream of the XML data in. This is much more
* efficient and consumes less memory.
*
* @param $xmlUrl Url where XML data is available or string
* @param $stream=false Is the xml available as stream?
* @access public
*/
public function generateReport($xmlUrl, $stream=false) {
$this->logger->debug("XML Url: " . $xmlUrl, __FILE__, __LINE__);
$this->parseCoverageXml($xmlUrl, true);
$this->logger->debug("Coverage Data final: " . print_r($this->coverageData, true));
parent::generateReport();
}
/*}}}*/
}
?>
<?php
/*
* $Id$
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
/**
* Reader that parses Xdebug Trace data.
*
* @author Nimish Pachapurkar <npac@spikesource.com>
* @version $Revision: $
* @package SpikePHPCoverage_Parser
*/
class XdebugTraceReader {
/*{{{ Members */
protected $traceFilePath;
protected $handle;
protected $coverage = array();
/*}}}*/
/*{{{ Constructor */
/**
* Constructor
*
* @param $traceFilePath Path of the Xdebug trace file
* @access public
*/
public function __construct($traceFilePath) {
$this->traceFilePath = $traceFilePath;
}
/*}}}*/
/*{{{ protected function openTraceFile() */
/**
* Opens the trace file
*
* @return Boolean True on success, false on failure.
* @access protected
*/
protected function openTraceFile() {
$this->handle = fopen($this->traceFilePath, "r");
return !empty($this->handle);
}
/*}}}*/
/*{{{ public function parseTraceFile() */
/**
* Parses the trace file
*
* @return Boolean True on success, false on failure.
* @access public
*/
public function parseTraceFile() {
if(!$this->openTraceFile()) {
error_log("[XdebugTraceReader::parseTraceFile()] Unable to read trace file.");
return false;
}
while(!feof($this->handle)) {
$line = fgets($this->handle);
// echo "Line: " . $line . "\n";
$this->processTraceLine($line);
}
fclose($this->handle);
return true;
}
/*}}}*/
/*{{{ protected function processTraceLine() */
/**
* Process a give trace line
*
* @param $line Line from a trace file
* @return Boolean True on success, false on failure
* @access protected
*/
protected function processTraceLine($line) {
$dataparts = explode("\t", $line);
// print_r($dataparts);
$cnt = count($dataparts);
if($cnt < 2) {
return false;
}
if(!file_exists($dataparts[$cnt-2])) {
// echo "No file: " . $dataparts[$cnt-2] . "\n";
return false;
}
// Trim the entries
$dataparts[$cnt-2] = trim($dataparts[$cnt-2]);
$dataparts[$cnt-1] = trim($dataparts[$cnt-1]);
if(!isset($this->coverage[$dataparts[$cnt-2]][$dataparts[$cnt-1]])) {
$this->coverage[$dataparts[$cnt-2]][$dataparts[$cnt-1]] = 1;
}
else {
$this->coverage[$dataparts[$cnt-2]][$dataparts[$cnt-1]] ++;
}
return true;
}
/*}}}*/
/*{{{ public function getCoverageData() */
/**
* Returns the coverage array
*
* @return Array Array of coverage data from parsing.
* @access public
*/
public function getCoverageData() {
return $this->coverage;
}
/*}}}*/
}
?>
<?php
/*
* $Id: CoverageReporter.php 14665 2005-03-23 19:37:50Z npac $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
if(!defined("__PHPCOVERAGE_HOME")) {
define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__)));
}
require_once __PHPCOVERAGE_HOME . "/conf/phpcoverage.conf.php";
require_once __PHPCOVERAGE_HOME . "/util/Utility.php";
/*{{{ Defines */
define("TOTAL_FILES_EXPLAIN", "count of included source code files");
define("TOTAL_LINES_EXPLAIN", "includes comments and whitespaces");
define("TOTAL_COVERED_LINES_EXPLAIN", "lines of code that were executed");
define("TOTAL_UNCOVERED_LINES_EXPLAIN", "lines of executable code that were not executed");
define ("TOTAL_LINES_OF_CODE_EXPLAIN", "lines of executable code");
/*}}}*/
/**
* The base class for reporting coverage. This is an abstract as it does not
* implement the generateReport() function. Every concrete subclass must
* implement this method to generate a report.
*
* @author Nimish Pachapurkar <npac@spikesource.com>
* @version $Revision: 14665 $
* @package SpikePHPCoverage_Reporter
*/
abstract class CoverageReporter {
// {{{ Members
protected $logger;
// Report heading - will be displayed as the title of the main page.
protected $heading;
// CSS file path to be used.
protected $style;
// Directory where the report file(s) are written.
protected $outputDir;
// Total number of lines in all the source files.
protected $grandTotalLines;
// Total number of lines covered in code coverage measurement.
protected $grandTotalCoveredLines;
// Total number of executable code lines that were left untouched.
protected $grandTotalUncoveredLines;
// Total number of files included
protected $grandTotalFiles;
protected $fileCoverage = array();
protected $recorder = false;
// }}}
/*{{{ public function __construct()*/
/**
* The constructor (PHP5 compatible)
*
* @param $heading
* @param $style
* @param $dir
* @access public
*/
public function __construct(
$heading="Coverage Report",
$style="",
$dir="report"
) {
global $util;
echo get_class($util);
$this->heading = $heading;
$this->style = $style;
$this->outputDir = $util->replaceBackslashes($dir);
// Create the directory if not there
$this->createReportDir();
$this->grandTotalFiles = 0;
$this->grandTotalLines = 0;
$this->grandTotalCoveredLines = 0;
$this->grandTotalUncoveredLines = 0;
// Configure
$this->logger = $util->getLogger();
}
/*}}}*/
/*{{{ protected function createReportDir() */
/**
* Create the report directory if it does not exists
*
* @access protected
*/
protected function createReportDir() {
global $util;
if(!file_exists($this->outputDir)) {
$util->makeDirRecursive($this->outputDir, 0755);
}
if(file_exists($this->outputDir)) {
$this->outputDir = $util->replaceBackslashes(realpath($this->outputDir));
}
}
/*}}}*/
/*{{{ protected function updateGrandTotals() */
/**
* Update the grand totals
*
* @param &$coverageCounts Coverage counts for a file
* @access protected
*/
protected function updateGrandTotals(&$coverageCounts) {
$this->grandTotalLines += $coverageCounts['total'];
$this->grandTotalCoveredLines += $coverageCounts['covered'];
$this->grandTotalUncoveredLines += $coverageCounts['uncovered'];
$this->recordFileCoverageInfo($coverageCounts);
}
/*}}}*/
/*{{{ public function getGrandCodeCoveragePercentage()*/
/**
* Returns Overall Code Coverage percentage
*
* @return double Code Coverage percentage rounded to two decimals
* @access public
*/
public function getGrandCodeCoveragePercentage() {
if($this->grandTotalCoveredLines+$this->grandTotalUncoveredLines == 0) {
return round(0, 2);
}
return round(((double)$this->grandTotalCoveredLines/((double)$this->grandTotalCoveredLines + (double)$this->grandTotalUncoveredLines)) * 100.0, 2);
}
/*}}}*/
/*{{{ public function getFileCoverageInfo() */
/**
* Return the array containing file coverage information.
*
* The array returned contains following fields
* * filename: Name of the file
* * total: Total number of lines in that file
* * covered: Total number of executed lines in that file
* * uncovered: Total number of executable lines that were not executed.
*
* @return array Array of file coverage information
* @access public
*/
public function getFileCoverageInfo() {
return $this->fileCoverage;
}
/*}}}*/
/*{{{ public function recordFileCoverageInfo() */
/**
* Record the file coverage information for a file.
*
* @param &$fileCoverage Coverage information for a file
* @access protected
*/
protected function recordFileCoverageInfo(&$fileCoverage) {
$this->fileCoverage[] = $fileCoverage;
}
/*}}}*/
/*{{{ public function printTextSummary() */
/**
* Print the coverage summary to filename (if specified) or stderr
*
* @param $filename=false Filename to write the log to
* @access public
*/
public function printTextSummary($filename=false) {
global $util;
$str = "\n";
$str .= "##############################################\n";
$str .= " Code Coverage Summary: " . $this->heading . "\n";
$str .= " Total Files: " . $this->grandTotalFiles . "\n";
$str .= " Total Lines: " . $this->grandTotalLines . "\n";
$str .= " Total Covered Lines of Code: " . $this->grandTotalCoveredLines . "\n";
$str .= " Total Missed Lines of Code: " . $this->grandTotalUncoveredLines . "\n";
$str .= " Total Lines of Code: " . ($this->grandTotalCoveredLines + $this->grandTotalUncoveredLines) . "\n";
$str .= " Code Coverage: " . $this->getGrandCodeCoveragePercentage() . "%\n";
$str .= "##############################################\n";
if(empty($filename)) {
file_put_contents("php://stdout", $str);
}
else {
$filename = $util->replaceBackslashes($filename);
if(!file_exists(dirname($filename))) {
$ret = $util->makeDirRecursive(dirname($filename), 0755);
if(!$ret) {
die ("Cannot create directory " . dirname($filename) . "\n");
}
}
file_put_contents($filename, $str);
}
}
/*}}}*/
/*{{{ protected function makeRelative() */
/**
* Convert the absolute path to PHP file markup to a path relative
* to the report dir.
*
* @param $filepath PHP markup file path
* @return Relative file path
* @access protected
*/
protected function makeRelative($filepath) {
$dirPath = realpath($this->outputDir);
$absFilePath = realpath($filepath);
if(strpos($absFilePath, $dirPath) === 0) {
$relPath = substr($absFilePath, strlen($dirPath)+1);
return $relPath;
}
return $absFilePath;
}
/*}}}*/
/*{{{ protected function getRelativeOutputDirPath() */
/**
* Get the relative path of report directory with respect to the given
* filepath
*
* @param $filepath Path of the file (relative to the report dir)
* @return String Relative path of report directory w.r.t. filepath
* @access protected
*/
protected function getRelativeOutputDirPath($filepath) {
$relPath = "";
$filepath = dirname($filepath);
while($filepath !== false && $filepath != ".") {
$relPath = "../" . $relPath;
$filepath = dirname($filepath);
}
return $relPath;
}
/*}}}*/
/*{{{ public abstract function generateReport() */
/**
*
* This function generates report using one of the concrete subclasses.
*
* @param &$data Coverage Data recorded by coverage recorder.
* @access public
*/
public abstract function generateReport(&$data);
/*}}}*/
/*{{{ Getters and Setters */
public function setHeading($heading) {
$this->heading = $heading;
}
public function getHeading() {
return $this->heading;
}
public function setStyle($style) {
$this->style = $style;
}
public function getStyle() {
return $this->style;
}
public function setOutputDir($dir) {
$this->outputDir = $dir;
}
public function getOutputDir() {
return $this->outputDir;
}
public function setCoverageRecorder(&$recorder) {
$this->recorder = $recorder;
}
/*}}}*/
}
?>
<?php
/*
* $Id: HtmlCoverageReporter.php 14665 2005-03-23 19:37:50Z npac $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
if(!defined("__PHPCOVERAGE_HOME")) {
define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__)));
}
require_once __PHPCOVERAGE_HOME . "/reporter/CoverageReporter.php";
require_once __PHPCOVERAGE_HOME . "/parser/PHPParser.php";
require_once __PHPCOVERAGE_HOME . "/util/Utility.php";
/**
* Class that implements HTML Coverage Reporter.
*
* @author Nimish Pachapurkar <npac@spikesource.com>
* @version $Revision: 14665 $
* @package SpikePHPCoverage_Reporter
*/
class HtmlCoverageReporter extends CoverageReporter {
/*{{{ Members */
private $coverageData;
private $htmlFile;
private $body;
private $header = "html/header.html";
private $footer = "html/footer.html";
private $indexHeader = "html/indexheader.html";
private $indexFooter = "html/indexfooter.html";
/*}}}*/
/*{{{ public function __construct() */
/**
* Constructor method (PHP5 only)
*
* @param $heading Heading of the report (shown as title)
* @param $style Name of the stylesheet file
* @param $dir Directory where the report files should be dumped
* @access public
*/
public function __construct(
$heading="Coverage Report",
$style="",
$dir="report"
) {
parent::__construct($heading, $style, $dir);
}
/*}}}*/
/*{{{ public function generateReport() */
/**
* Implementaion of generateReport abstract function.
* This is the only function that will be called
* by the instrumentor.
*
* @param &$data Reference to Coverage Data
* @access public
*/
public function generateReport(&$data) {
if(!file_exists($this->outputDir)) {
mkdir($this->outputDir);
}
$this->coverageData =& $data;
$this->grandTotalFiles = count($this->coverageData);
$ret = $this->writeIndexFile();
if($ret === FALSE) {
$this->logger->error("Error occured!!!", __FILE__, __LINE__);
}
$this->logger->debug(print_r($data, true), __FILE__, __LINE__);
}
/*}}}*/
/*{{{ private function writeIndexFileHeader() */
/**
* Write the index file header to a string
*
* @return string String containing HTML code for the index file header
* @access private
*/
private function writeIndexFileHeader() {
$str = false;
$dir = realpath(dirname(__FILE__));
if($dir !== false) {
$str = file_get_contents($dir . "/" . $this->indexHeader);
if($str == false) {
return $str;
}
$str = str_replace("%%heading%%", $this->heading, $str);
$str = str_replace("%%style%%", $this->style, $str);
}
return $str;
}
/*}}}*/
/*{{{ private function writeIndexFileFooter() */
/**
* Write the index file footer to a string
*
* @return string String containing HTML code for the index file footer.
* @access private
*/
private function writeIndexFileFooter() {
$str = false;
$dir = realpath(dirname(__FILE__));
if($dir !== false) {
$str = file_get_contents($dir . "/" . $this->indexFooter);
if($str == false) {
return $str;
}
}
return $str;
}
/*}}}*/
/*{{{ private function createJSDir() */
/**
* Create a directory for storing Javascript for the report
*
* @access private
*/
private function createJSDir() {
$jsDir = $this->outputDir . "/js";
if(file_exists($this->outputDir) && !file_exists($jsDir)) {
mkdir($jsDir);
}
$jsSortFile = realpath(dirname(__FILE__)) . "/js/sort_spikesource.js";
copy($jsSortFile, $jsDir . "/" . "sort_spikesource.js");
return true;
}
/*}}}*/
/*{{{ private function createImagesDir() */
/**
* Create a directory for storing images for the report
*
* @access private
*/
private function createImagesDir() {
$imagesDir = $this->outputDir . "/images";
if(file_exists($this->outputDir) && !file_exists($imagesDir)) {
mkdir($imagesDir);
}
$imagesSpikeDir = $imagesDir . "/spikesource";
if(!file_exists($imagesSpikeDir)) {
mkdir($imagesSpikeDir);
}
$imagesArrowUpFile = realpath(dirname(__FILE__)) . "/images/arrow_up.gif";
$imagesArrowDownFile = realpath(dirname(__FILE__)) . "/images/arrow_down.gif";
$imagesPHPCoverageLogoFile = realpath(dirname(__FILE__)) . "/images/spikesource/phpcoverage.gif";
$imagesSpacerFile = realpath(dirname(__FILE__)) . "/images/spacer.gif";
copy($imagesArrowUpFile, $imagesDir . "/" . "arrow_up.gif");
copy($imagesArrowDownFile, $imagesDir . "/" . "arrow_down.gif");
copy($imagesSpacerFile, $imagesDir . "/" . "spacer.gif");
copy($imagesPHPCoverageLogoFile, $imagesSpikeDir . "/" . "phpcoverage.gif");
return true;
}
/*}}}*/
/*{{{ private function createStyleDir() */
private function createStyleDir() {
if(isset($this->style)) {
$this->style = trim($this->style);
}
if(empty($this->style)) {
$this->style = "spikesource.css";
}
$styleDir = $this->outputDir . "/css";
if(file_exists($this->outputDir) && !file_exists($styleDir)) {
mkdir($styleDir);
}
$styleFile = realpath(dirname(__FILE__)) . "/css/" . $this->style;
copy($styleFile, $styleDir . "/" . $this->style);
return true;
}
/*}}}*/
/*{{{ protected function writeIndexFileTableHead() */
/**
* Writes the table heading for index.html
*
* @return string Table heading row code
* @access protected
*/
protected function writeIndexFileTableHead() {
$str = "";
$str .= '<h1>Details</h1> <table class="spikeDataTable" cellpadding="4" cellspacing="0" border="0" id="table2sort" width="800">';
$str .= '<thead>';
$str .= '<tr><td class="spikeDataTableHeadLeft" id="sortCell0" rowspan="2" style="white-space:nowrap" width="52%"><a id="sortCellLink0" class="headerlink" href="javascript:sort(0)" title="Sort Ascending">File Name </a></td>';
$str .= '<td colspan="4" class="spikeDataTableHeadCenter">Lines</td>';
$str .= '<td class="spikeDataTableHeadCenterLast" id="sortCell5" rowspan="2" width="16%" style="white-space:nowrap"><a id="sortCellLink5" class="headerlink" href="javascript:sort(5, \'percentage\')" title="Sort Ascending">Code Coverage </a></td>';
$str .= '</tr>';
// Second row - subheadings
$str .= '<tr>';
$str .= '<td class="spikeDataTableSubHeadCenter" id="sortCell1" style="white-space:nowrap" width="8%"><a id="sortCellLink1" title="Sort Ascending" class="headerlink" href="javascript:sort(1, \'number\')">Total </a></td>';
$str .= '<td class="spikeDataTableSubHeadCenter" id="sortCell2" style="white-space:nowrap" width="9%"><a id="sortCellLink2" title="Sort Ascending" class="headerlink" href="javascript:sort(2, \'number\')">Covered </a></td>';
$str .= '<td class="spikeDataTableSubHeadCenter" id="sortCell3" style="white-space:nowrap" width="8%"><a id="sortCellLink3" title="Sort Ascending" class="headerlink" href="javascript:sort(3, \'number\')">Missed </a></td>';
$str .= '<td class="spikeDataTableSubHeadCenter" id="sortCell4" style="white-space:nowrap" width="10%"><a id="sortCellLink4" title="Sort Ascending" class="headerlink" href="javascript:sort(4, \'number\')">Executable </a></td>';
$str .= '</tr>';
$str .= '</thead>';
return $str;
}
/*}}}*/
/*{{{ protected function writeIndexFileTableRow() */
/**
* Writes one row in the index.html table to display filename
* and coverage recording.
*
* @param $fileLink link to html details file.
* @param $realFile path to real PHP file.
* @param $fileCoverage Coverage recording for that file.
* @return string HTML code for a single row.
* @access protected
*/
protected function writeIndexFileTableRow($fileLink, $realFile, $fileCoverage) {
global $util;
$fileLink = $this->makeRelative($fileLink);
$realFileShort = $util->shortenFilename($realFile);
$str = "";
$str .= '<tr><td class="spikeDataTableCellLeft">';
$str .= '<a class="contentlink" href="' . $util->unixifyPath($fileLink) . '" title="'
. $realFile .'">' . $realFileShort. '</a>' . '</td>';
$str .= '<td class="spikeDataTableCellCenter">' . $fileCoverage['total'] . "</td>";
$str .= '<td class="spikeDataTableCellCenter">' . $fileCoverage['covered'] . "</td>";
$str .= '<td class="spikeDataTableCellCenter">' . $fileCoverage['uncovered'] . "</td>";
$str .= '<td class="spikeDataTableCellCenter">' . ($fileCoverage['covered']+$fileCoverage['uncovered']) . "</td>";
if($fileCoverage['uncovered'] + $fileCoverage['covered'] == 0) {
// If there are no executable lines, assume coverage to be 100%
$str .= '<td class="spikeDataTableCellCenter">100%</td></tr>';
}
else {
$str .= '<td class="spikeDataTableCellCenter">'
. round(($fileCoverage['covered']/($fileCoverage['uncovered']
+ $fileCoverage['covered']))*100.0, 2)
. '%</td></tr>';
}
return $str;
}
/*}}}*/
/*{{{ protected function writeIndexFileGrandTotalPercentage() */
/**
* Writes the grand total for coverage recordings on the index.html
*
* @return string HTML code for grand total row
* @access protected
*/
protected function writeIndexFileGrandTotalPercentage() {
$str = "";
$str .= "<br/><h1>" . $this->heading . "</h1><br/>";
$str .= '<table border="0" cellpadding="0" cellspacing="0" id="contentBox" width="800"> <tr>';
$str .= '<td align="left" valign="top"><h1>Summary</h1>';
$str .= '<table class="spikeVerticalTable" cellpadding="4" cellspacing="0" width="800" style="margin-bottom:10px" border="0">';
$str .= '<td width="380" class="spikeVerticalTableHead" style="font-size:14px">Overall Code Coverage&nbsp;</td>';
$str .= '<td class="spikeVerticalTableCell" style="font-size:14px" colspan="2"><strong>' . $this->getGrandCodeCoveragePercentage() . '%</td>';
$str .= '</tr><tr>';
$str .= '<td class="spikeVerticalTableHead">Total Covered Lines of Code&nbsp;</td>';
$str .= '<td width="30" class="spikeVerticalTableCell"><span class="emphasis">' . $this->grandTotalCoveredLines.'</span></td>';
$str .= '<td class="spikeVerticalTableCell"><span class="note">(' . TOTAL_COVERED_LINES_EXPLAIN . ')</span></td>';
$str .= '</tr><tr>';
$str .= '<td class="spikeVerticalTableHead">Total Missed Lines of Code&nbsp;</td>';
$str .= '<td class="spikeVerticalTableCell"><span class="emphasis">' . $this->grandTotalUncoveredLines.'</span></td>';
$str .= '<td class="spikeVerticalTableCell"><span class="note">(' . TOTAL_UNCOVERED_LINES_EXPLAIN . ')</span></td>';
$str .= '</tr><tr>';
$str .= '<td class="spikeVerticalTableHead">Total Lines of Code&nbsp;</td>';
$str .= '<td class="spikeVerticalTableCell"><span class="emphasis">' . ($this->grandTotalCoveredLines + $this->grandTotalUncoveredLines) .'</span></td>';
$str .= '<td class="spikeVerticalTableCell"><span class="note">(' .
TOTAL_LINES_OF_CODE_EXPLAIN . ')</span></td>';
$str .= '</tr><tr>';
$str .= '<td class="spikeVerticalTableHead" >Total Lines&nbsp;</td>';
$str .= '<td class="spikeVerticalTableCell"><span class="emphasis">' . $this->grandTotalLines.'</span></td>';
$str .= '<td class="spikeVerticalTableCell"><span class="note">(' . TOTAL_LINES_EXPLAIN . ')</span></td>';
$str .= '</tr><tr>';
$str .= '<td class="spikeVerticalTableHeadLast" >Total Files&nbsp;</td>';
$str .= '<td class="spikeVerticalTableCellLast"><span class="emphasis">' . $this->grandTotalFiles.'</span></td>';
$str .= '<td class="spikeVerticalTableCellLast"><span class="note">(' . TOTAL_FILES_EXPLAIN . ')</span></td>';
$str .= '</tr></table>';
return $str;
}
/*}}}*/
/*{{{ protected function writeIndexFile() */
/**
* Writes index.html file from all coverage recordings.
*
* @return boolean FALSE on failure
* @access protected
*/
protected function writeIndexFile() {
global $util;
$str = "";
$this->createJSDir();
$this->createImagesDir();
$this->createStyleDir();
$this->htmlFile = $this->outputDir . "/index.html";
$indexFile = fopen($this->htmlFile, "w");
if(empty($indexFile)) {
$this->logger->error("Cannot open file for writing: $this->htmlFile",
__FILE__, __LINE__);
return false;
}
$strHead = $this->writeIndexFileHeader();
if($strHead == false) {
return false;
}
$str .= $this->writeIndexFileTableHead();
$str .= '<tbody>';
if(!empty($this->coverageData)) {
foreach($this->coverageData as $filename => &$lines) {
$realFile = realpath($filename);
$fileLink = $this->outputDir . $util->unixifyPath($realFile). ".html";
$fileCoverage = $this->markFile($realFile, $fileLink, $lines);
if(empty($fileCoverage)) {
return false;
}
$this->recordFileCoverageInfo($fileCoverage);
$this->updateGrandTotals($fileCoverage);
$str .= $this->writeIndexFileTableRow($fileLink, $realFile, $fileCoverage);
unset($this->coverageData[$filename]);
}
}
$str .= '</tbody>';
$str .= "</table></td></tr>";
$str .= "<tr><td><p align=\"right\" class=\"content\">Report Generated On: " . $util->getTimeStamp() . "<br/>";
$str .= "Generated using Spike PHPCoverage " . $this->recorder->getVersion() . "</p></td></tr></table>";
// Get the summary
$strSummary = $this->writeIndexFileGrandTotalPercentage();
// Merge them - with summary on top
$str = $strHead . $strSummary . $str;
$str .= $this->writeIndexFileFooter();
fwrite($indexFile, $str);
fclose($indexFile);
return TRUE;
}
/*}}}*/
/*{{{ private function writePhpFileHeader() */
/**
* Write the header for the source file with mark-up
*
* @param $filename Name of the php file
* @return string String containing the HTML for PHP file header
* @access private
*/
private function writePhpFileHeader($filename, $fileLink) {
$fileLink = $this->makeRelative($fileLink);
$str = false;
$dir = realpath(dirname(__FILE__));
if($dir !== false) {
$str = file_get_contents($dir . "/" . $this->header);
if($str == false) {
return $str;
}
$str = str_replace("%%filename%%", $filename, $str);
// Get the path to parent CSS directory
$relativeCssPath = $this->getRelativeOutputDirPath($fileLink);
$relativeCssPath .= "/css/" . $this->style;
$str = str_replace("%%style%%", $relativeCssPath, $str);
}
return $str;
}
/*}}}*/
/*{{{ private function writePhpFileFooter() */
/**
* Write the footer for the source file with mark-up
*
* @return string String containing the HTML for PHP file footer
* @access private
*/
private function writePhpFileFooter() {
$str = false;
$dir = realpath(dirname(__FILE__));
if($dir !== false) {
$str = file_get_contents($dir . "/" . $this->footer);
if($str == false) {
return $str;
}
}
return $str;
}
/*}}}*/
/*{{{ protected function markFile() */
/**
* Mark a source code file based on the coverage data gathered
*
* @param $phpFile Name of the actual source file
* @param $fileLink Link to the html mark-up file for the $phpFile
* @param &$coverageLines Coverage recording for $phpFile
* @return boolean FALSE on failure
* @access protected
*/
protected function markFile($phpFile, $fileLink, &$coverageLines) {
global $util;
$fileLink = $util->replaceBackslashes($fileLink);
$parentDir = $util->replaceBackslashes(dirname($fileLink));
if(!file_exists($parentDir)) {
//echo "\nCreating dir: $parentDir\n";
$util->makeDirRecursive($parentDir, 0755);
}
$writer = fopen($fileLink, "w");
if(empty($writer)) {
$this->logger->error("Could not open file for writing: $fileLink",
__FILE__, __LINE__);
return false;
}
// Get the header for file
$filestr = $this->writePhpFileHeader(basename($phpFile), $fileLink);
// Add header for table
$filestr .= '<table width="100%" border="0" cellpadding="2" cellspacing="0">';
$filestr .= $this->writeFileTableHead();
$lineCnt = $coveredCnt = $uncoveredCnt = 0;
$parser = new PHPParser();
$parser->parse($phpFile);
$lastLineType = "non-exec";
$fileLines = array();
while(($line = $parser->getLine()) !== false) {
$line = substr($line, 0, strlen($line)-1);
$lineCnt++;
$coverageLineNumbers = array_keys($coverageLines);
if(in_array($lineCnt, $coverageLineNumbers)) {
$lineType = $parser->getLineType();
if($lineType == LINE_TYPE_EXEC) {
$coveredCnt ++;
$type = "covered";
}
else if($lineType == LINE_TYPE_CONT) {
// XDebug might return this as covered - when it is
// actually merely a continuation of previous line
if($lastLineType == "covered") {
unset($coverageLines[$lineCnt]);
$type = $lastLineType;
}
else {
if($lineCnt-1 >= 0 && isset($fileLines[$lineCnt-1]["type"])) {
if($fileLines[$lineCnt-1]["type"] == "uncovered") {
$uncoveredCnt --;
}
$fileLines[$lineCnt-1]["type"] = $lastLineType = "covered";
}
$coveredCnt ++;
$type = "covered";
}
}
else {
$type = "non-exec";
$coverageLines[$lineCnt] = 0;
}
}
else if($parser->getLineType() == LINE_TYPE_EXEC) {
$uncoveredCnt ++;
$type = "uncovered";
}
else if($parser->getLineType() == LINE_TYPE_CONT) {
$type = $lastLineType;
}
else {
$type = "non-exec";
}
// Save line type
$lastLineType = $type;
//echo $line . "\t[" . $type . "]\n";
if(!isset($coverageLines[$lineCnt])) {
$coverageLines[$lineCnt] = 0;
}
$fileLines[$lineCnt] = array("type" => $type, "lineCnt" => $lineCnt, "line" => $line, "coverageLines" => $coverageLines[$lineCnt]);
}
$this->logger->debug("File lines: ". print_r($fileLines, true),
__FILE__, __LINE__);
for($i = 1; $i <= count($fileLines); $i++) {
$filestr .= $this->writeFileTableRow($fileLines[$i]["type"],
$fileLines[$i]["lineCnt"],
$fileLines[$i]["line"],
$fileLines[$i]["coverageLines"]);
}
$filestr .= "</table>";
$filestr .= $this->writePhpFileFooter();
fwrite($writer, $filestr);
fclose($writer);
return array(
'filename' => $phpFile,
'covered' => $coveredCnt,
'uncovered' => $uncoveredCnt,
'total' => $lineCnt
);
}
/*}}}*/
/*{{{ protected function writeFileTableHead() */
/**
* Writes table heading for file details table.
*
* @return string HTML string representing one table row.
* @access protected
*/
protected function writeFileTableHead() {
$filestr = "";
$filestr .= '<td width="10%"class="coverageDetailsHead" >Line #</td>';
$filestr .= '<td width="10%" class="coverageDetailsHead">Frequency</td>';
$filestr .= '<td width="80%" class="coverageDetailsHead">Source Line</td>';
return $filestr;
}
/*}}}*/
/*{{{ protected function writeFileTableRow() */
/**
* Write a line for file details table.
*
* @param $color Text color
* @param $bgcolor Row bgcolor
* @param $lineCnt Line number
* @param $line The source code line
* @param $coverageLineCnt Number of time the line was executed.
* @return string HTML code for a table row.
* @access protected
*/
protected function writeFileTableRow($type, $lineCnt, $line, $coverageLineCnt) {
$spanstr = "";
if($type == "covered") {
$spanstr .= '<span class="codeExecuted">';
}
else if($type == "uncovered") {
$spanstr .= '<span class="codeMissed">';
}
else {
$spanstr .= '<span>';
}
if(empty($coverageLineCnt)) {
$coverageLineCnt = "";
}
$filestr = '<tr>';
$filestr .= '<td class="coverageDetails">' . $spanstr . $lineCnt . '</span></td>';
if(empty($coverageLineCnt)) {
$coverageLineCnt = "&nbsp;";
}
$filestr .= '<td class="coverageDetails">' . $spanstr . $coverageLineCnt . '</span></td>';
$filestr .= '<td class="coverageDetailsCode"><code>' . $spanstr . $this->preserveSpacing($line) . '</span></code></td>';
$filestr .= "</tr>";
return $filestr;
}
/*}}}*/
/*{{{ protected function preserveSpacing() */
/**
* Changes all tabs and spaces with HTML non-breakable spaces.
*
* @param $string String containing spaces and tabs.
* @return string HTML string with replacements.
* @access protected
*/
protected function preserveSpacing($string) {
$string = htmlspecialchars($string);
$string = str_replace(" ", "&nbsp;", $string);
$string = str_replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;", $string);
return $string;
}
/*}}}*/
}
?>
/* www.spikesource.com style */
/* colors */
.logoBlue{
background-color:#0066cc
}
.darkBlue{
background-color:#003399
}
.lightBlue {
background-color:#D2E7FC;
}
.faintBlue{
background-color:#efefef;
}
/* Overload html tags */
body{
background-color:#ffffff;
font-size:11px;
font-family:Arial,Helvetica,sans-serif;
margin:0px;
color:#222222;
}
/* major page sections; containers are have "Box" suffix */
#pageBox{
width:800px;
margin-left:20px;
}
#navBox{
width:800px
}
#contentBox{
width:800px;
margin-top:10px;
}
/* spikesource.com elements */
#leftBox{
width:600px;
padding-left:2px;
font-size:11px;
color:#222222;
line-height:14px
}
#rightBox{
width:190px;
margin-bottom:5px;
margin-left:15px
}
#footerBox{
border-top:2px solid #0066cc;
margin-top:15px;
width:800px;
height:22px;
}
#leftNav {
width:165px;
margin-right:10px;
}
#leftRule {
width:175px;
border-right:1px solid #000000;
}
#rightContent {
margin-left:15px;
width:600px;
}
#userInfo {
height:20px;
width:800px;
}
.navCell{
height:31px;
border-left:1px solid #000000
}
.copyright{
font-family:arial,sans-serif;
color:#666666;
font-size:10px;
font-weight:bold
}
/* Convention:Do not overload html default styles - use spike[sometag] */
h1,.spikeh1{
font-size:17px;
font-family:Arial,Helvetica,sans-serif;
font-weight:bold;
color:#000000;
margin-bottom:4px;
margin-top:0px;
}
h2,.spikeh2{
font-size:14px;
font-family:Arial,Helvetica,sans-serif;
font-weight:bold;
color:#003399;
margin-bottom:8px
}
h3,.spikeh3{
font-size:12px;
font-family:Arial,Helvetica,sans-serif;
font-weight:bold;
color:#444444;
margin-bottom:0px
}
.spikep{
margin-bottom:10px;
margin-top:5px;
padding-left:2px;
font-size:11px;
line-height:15px;
width:90%;
}
p{
margin-bottom:10px;
margin-top:5px;
padding-left:2px;
font-size:11px;
line-height:15px;
}
.emph{
font-style:oblique
}
ul,.spikeul{
margin-left:22px;
padding:0px;
margin-top:5px;
}
li,.spikeli{
list-style-position:outside;
line-height:14px;
font-size:11px;
margin-top:4px;
margin-left:2px;
}
.emphasis{
font-size:13px;
font-family:Arial,Helvetica,sans-serif;
font-weight:bold;
color:#666666
}
.rightHeader{
font-size:14px;
font-family:Arial,Helvetica,sans-serif;
color:#222222;
font-weight:bold;
padding-bottom:2px;
}
.blueRuleBox{
width:175px;
border-bottom:2px solid #0066cc;
margin-left:15px;
}
.rightRule{
border-left:1px solid #000000;
}
.rightIndent{
width:178px;
margin-left:15px;
font-size:11px;
color:#222222;
line-height:1.3em;
margin-top:7px;
text-align:left;
margin-bottom:5px
}
.newsHeader{
width:178px;
margin-left:15px;
font-size:12px;
color:#222222;
margin-bottom:2px;
margin-top:6px;
}
ul.newsBox{
margin-left:30px;
padding-bottom:5px
}
.content_small{
font-family:Arial,Helvetica,sans-serif;
font-size:10px;
color:#444444
}
.content{
font-family:Arial,Helvetica,sans-serif;
font-size:11px;
color:#444444
}
.content_gray{
font-family:Arial,Helvetica,sans-serif;
font-size:11px;
color:#000000;
background-color:#e9e9e9
}
.content_error{
font-family:Arial,Helvetica,sans-serif;
font-size:11px;
color:#0066cc;
font-weight:bold
}
.contentError{
font-family:Arial,Helvetica,sans-serif;
font-size:11px;
color:#990000;
font-weight:bold;
}
.content_required{
font-family:Arial,Helvetica,sans-serif;
font-size:11px;
color:#0066cc
}
.content_bold {
font-size:11px;
font-weight:bold;
}
/* Tables */
.spikeDataTable {
border-top:1px solid #5d6c7b;
border-right:1px solid #5d6c7b;
border-left:1px solid #5d6c7b;
}
/* DataTable headings and cell styles */
.spikeDataTableHeadRight {
font-size:12px;
font-weight:bold;
background-color:#d2e7fc;
border-right:2px solid #ffffff;
text-align:right;
padding-right:5px;
}
.spikeDataTableHeadLeft {
font-size:12px;
font-weight:bold;
background-color:#d2e7fc;
border-right:2px solid #ffffff;
text-align:left;
padding-left:5px;
}
.spikeDataTableHeadCenter {
font-size:12px;
font-weight:bold;
background-color:#d2e7fc;
border-right:2px solid #ffffff;
text-align:center;
margin-left:auto;
margin-right:auto;
}
.spikeDataTableSubHeadRight {
font-size:11px;
font-weight:bold;
background-color:#d2e7fc;
border-right:2px solid #ffffff;
border-top:2px solid #ffffff;
text-align:right;
padding-right:5px;
}
.spikeDataTableSubHeadLeft {
font-size:11px;
font-weight:bold;
background-color:#d2e7fc;
border-right:2px solid #ffffff;
border-top:2px solid #ffffff;
text-align:left;
padding-left:5px;
}
.spikeDataTableSubHeadCenter {
font-size:11px;
font-weight:bold;
background-color:#d2e7fc;
border-right:2px solid #ffffff;
border-top:2px solid #ffffff;
text-align:center;
margin-left:auto;
margin-right:auto;
}
.spikeDataTableSubHeadLeftLast{
font-size:11px;
font-weight:bold;
background-color:#d2e7fc;
border-top:2px solid #ffffff;
text-align:left;
padding-left:5px;
}
.spikeDataTableSubHeadCenterLast {
font-size:11px;
font-weight:bold;
background-color:#d2e7fc;
border-top:2px solid #ffffff;
text-align:center;
margin-left:auto;
margin-right:auto;
}
.spikeDataTableHeadRightLast {
font-size:12px;
font-weight:bold;
background-color:#d2e7fc;
text-align:right;
padding-right:5px;
}
.spikeDataTableHeadLeftLast {
font-size:12px;
font-weight:bold;
background-color:#d2e7fc;
text-align:left;
padding-left:5px;
}
.spikeDataTableHeadCenterLast {
background-color:#d2e7fc;
font-size:12px;
font-weight:bold;
text-align:center;
margin-left:auto;
margin-right:auto;
}
.spikeDataTableCellRight {
font-size:11px;
text-align:right;
border-bottom:1px solid #5d6c7b;
padding-right:5px;
}
.spikeDataTableCellRightBorder {
font-size:11px;
text-align:right;
border-bottom:1px solid #5d6c7b;
border-right:1px solid #cccccc;
padding-right:5px;
}
.spikeDataTableCellLeft {
font-size:11px;
text-align:left;
border-bottom:1px solid #5d6c7b;
padding-left:5px;
}
.spikeDataTableCellLeftBorder {
font-size:11px;
text-align:left;
border-bottom:1px solid #5d6c7b;
border-right:1px solid #cccccc;
padding-left:5px;
}
.spikeDataTableCellCenter {
font-size:11px;
text-align:center;
margin-left:auto;
margin-right:auto;
border-bottom:1px solid #5d6c7b;
}
.spikeDataTableCellCenterBorder {
font-size:11px;
text-align:center;
margin-left:auto;
margin-right:auto;
border-bottom:1px solid #5d6c7b;
border-right:1px solid #cccccc;
}
/* vertical table */
.spikeVerticalTable {
border:1px solid #5d6c7b;
}
.spikeVerticalTableHead {
font-size:12px;
font-weight:bold;
background-color:#d2e7fc;
border-bottom:2px solid #ffffff;
text-align:right;
padding-right:5px;
vertical-align:top;
}
.spikeVerticalTableHeadLast {
font-size:12px;
font-weight:bold;
background-color:#d2e7fc;
text-align:right;
padding-left:5px;
vertical-align:top;
}
.spikeVerticalTableCell {
font-size:11px;
text-align:left;
border-bottom:1px solid #5d6c7b;
padding-left:5px;
vertical-align:top;
}
.spikeVerticalTableCellBold {
font-weight:bold;
font-size:11px;
text-align:left;
border-bottom:1px solid #5d6c7b;
padding-left:5px;
vertical-align:top;
}
.spikeVerticalTableCellLast {
font-size:11px;
text-align:left;
padding-left:5px;
vertical-align:top;
}
/* Top navigation */
#navbar {
height:80px;
border-bottom:2px solid #0066cc;
width:642px;
}
.navText{
padding-left:5px;
font-size:13px;
color:#000000;
font-weight:bold;
letter-spacing:1px;
text-decoration:none
}
.navText:link,.navText:visited{
color:#0066cc
}
.navText:hover{
color:#000000
}
.navTextOn {
color:#000000;
padding-left:5px;
font-size:13px;
font-weight:bold;
letter-spacing:1px;
text-decoration:none
}
/* left side navigation */
.parent {
margin-left:15px;
width:160px;
font-weight: bold;
font-size: 11px;
font-family: Arial, Helvetica, sans-serif;
text-decoration:none;
padding-bottom:5px;
color:#0066cc;
vertical-align:top;
}
.parentChild {
margin-left:0px;
width:175px;
font-weight: bold;
font-size: 11px;
font-family: Arial, Helvetica, sans-serif;
text-decoration:none;
padding-bottom:5px;
color:#0066cc;
vertical-align:top;
}
.submenu {
color:#000000;
width:145px;
text-decoration: none;
padding-top:1px;
padding-bottom:1px;
padding-left:25px;
}
.submenu a {
white-space:normal;
}
.child {
padding-left:0px;
width:175px;
font-weight: bold;
font-size: 11px;
text-decoration:none;
display:none;
padding-bottom:2px;
}
.menuImage {
margin-right:6px;
}
.submenu a:visited {color: #0066cc; text-decoration:none; border:0px;}
.submenu a:link {color: #0066cc; text-decoration: none; border:0px;}
.submenu a:hover { color: #000000; text-decoration: none ; border:0px;}
.parentChild a:visited {
color: #0066cc;
text-decoration:none;
}
.parentChild a:link {
color: #0066cc;
text-decoration:none;
}
.parentChild a:hover {
color: #000000;
text-decoration:none
}
.parent a:visited {
color: #0066cc;
text-decoration:none;
}
.parent a:link {
color: #0066cc;
text-decoration:none;
}
.parent a:hover {
color: #000000;
text-decoration:none
}
/* links */
.footerlink:link, .footerlink:visited{
color:#003399;
font-size:10px;
text-decoration:none
}
.footerlink:hover{
color:#000000;
text-decoration:underline
}
.contentlink{
font-family:arial,sans-serif;
font-size:11px;
}
.contentlink:visited, .contentlink:link {
color:#0055bb;
text-decoration:none;
}
.contentlink:hover{
color:#000000;
text-decoration:underline
}
.contentlinkBold{
font-family:arial,sans-serif;
font-size:11px;
font-weight:bold;
}
.contentlinkBold:visited, .contentlinkBold:link {
color:#0055bb;
text-decoration:none;
}
.contentlinkBold:hover{
color:#000000;
text-decoration:underline
}
a.headerlink{
color:#000000;
text-decoration:underline;
}
a.headerlink:hover {
text-decoration:underline;
}
a.externalLink, a.tablelink {
color:#000000;
text-decoration:none;
}
a.externalLink:link,a.tablelink:link {
color:#0055bb;
}
a.externalLink:visited,a.tablelink:visited {
color:#0055bb;
}
a.externalLink:hover,a.tablelink:hover {
color:#000000;
text-decoration:underline;
}
a.tablelinkBold:link {
font-weight:bold;
text-decoration:none;
color:#0055bb;
}
a.tablelinkBold:visited {
font-weight:bold;
text-decoration:none;
color:#0066cc;
}
a.tablelinkBold:hover {
font-weight:bold;
color:#000000;
text-decoration:underline;
}
.blueHeader,.greyHeader{
font-size:12px;
font-family:Arial,Helvetica,sans-serif;
color:#0066cc;
font-weight:bold
}
/* form layout */
.spikeForm{
vertical-align:top;
margin:0px;
padding:0px;
border:1px solid #5d6c7b;
}
.spikeButton {
padding-right:10px;
}
.required {
margin-bottom:0px;
text-align:right;
}
.formRule{
border-left:1px solid #000000;
padding-top:0px;
margin-left:0px;
}
.formHeader{
padding-left:20px;
height: 22px;
background-color:#D2E7FC;
color: #000000;
font-size:12px;
font-weight:bold;
}
.formLabel{
height:14px;
font-family:Arial,Helvetica,sans-serif;
font-size:11px;
color:#000000;
text-align:right;
font-weight:bold;
white-space:nowrap;
}
.formLabelL {
font-size:11px;
font-weight:bold;
color:#000000;
white-space:nowrap;
}
.formDirection {
padding-left:20px;
height: 20px;
background-color:#ededed;
color: #000000;
font-size:11px;
}
.formNote {
color:#333333;
font-size:10px;
padding-left:2px;
}
.label,.element{
font-family:Arial,Helvetica,sans-serif;
color:#000000;
font-size:11px;
}
.checkLabel{
font-family:Arial,Helvetica,sans-serif;
font-size:10px;
color:#000000;
font-weight:bold;
margin-right:2px;
vertical-align:middle;
}
.button{
font-family:Arial,Helvetica,sans-serif;
font-size:10px;
color:#000000;
background-color:#e9e9e9;
border:1px solid #666666
}
.asterickR{
margin-right:2px
}
.asterickL{
margin-left:2px
}
.blueButton{
font-family:arial,tahoma,sans-serif;
font-size:11px;
font-weight:bold;
color:#ffffff;
background-color:#0066cc;
border-style:none;
border:none;
margin:0px;
padding:1px;
}
/* Defines input boxes,textareas,and input checkboxes */
.textfield{
margin-left:2px;
border:1px solid #3d86ce;
background-color:#E7F1FA;
font-size:11px;
color:#333333
}
input{
margin:0px;
padding:0px
}
.checkbox {
margin-right:2px;
margin-left:0px;
}
.selectMenu{
height:16px;
margin-left:4px;
background-color:#ffffff;
font-size:11px;
color:#333333
}
.optionElem{
font-size:11px;
color:#333333;
padding-bottom:2px
}
.multiBox{
height:50px;
font-size:11px
}
/* Required by HTML_QuickForm module */
.errors{
font-family:Arial,Helvetica,sans-serif;
color:#990000;
font-weight:bold
}
.note{
font-family:Arial,Helvetica,sans-serif;
font-size:9px;
color:#000000
}
/* Job Page styles */
.jobTitle {
font-family: Arial, Helvetica, sans-serif;
font-size:11px;
text-decoration:none;
line-height:12px;
font-weight:bold;
color:#0066cc;
}
a.jobTitle:link {
color:#0066cc;
}
a.jobTitle:visited {
color:#0066cc;
}
a.jobTitle:hover {
color:#000000;
text-decoration:underline;
}
.jobTable {
margin-bottom:20px;
}
.jobTable td {
padding-left:4px;
}
.jobTable .jobTitle {
font-family:Verdana, Arial, Helvetica, sans-serif;
font-weight:bold;
color:#ffffff;
background-color:#0066cc;
/*background-color:#D2E7FC; */
font-size:12px;
height:20px;
}
p.jobDescription {
margin-bottom:5px;
margin-top:2px;
}
/* old stuff */
.loginTable{
width:250px;
padding-bottom:2px;
padding-top:10px
}
.category_sublink:hover,.boxtitlelink:hover,.category_mainlink:hover{
color:#990000;
text-decoration:underline
}
.spacer,#betaTable{
background-color:#ffffff
}
.borderedbg{
background-color:#ffffff;
font-family:arial;
font-size:10px;
color:#333333
}
table.bordered{
background-color:#999999
}
td.boxoff{
background-color:#ffffff;
color:#000000;
font-size:8pt;
font-family:Arial,Helvetica,sans-serif
}
td.boxon{
background-color:#eeeeee;
color:#000000;
font-size:8pt;
font-family:Arial,Helvetica,sans-serif
}
.pagetitle{
font-family:arial;
font-weight:bold;
font-size:18pt;
color:#333333;
border-bottom:1px solid #999999
}
.boxtitlelink{
color:#336699;
font-size:9pt;
font-family:tahoma,verdana,arial;
text-decoration:none
}
.sectiontitle{
font-family:Arial,Helvetica,sans-serif;
font-size:10pt;
color:#990000;
font-weight:bold
}
.subtitle{
font-family:Arial,Helvetica,sans-serif;
font-size:11pt;
font-weight:bold;
color:#333333;
border-bottom:1px dashed #000000
}
.subcategory{
background-color:#eeeeee;
color:#000000;
font-size:8pt;
font-family:Arial,Helvetica,sans-serif;
border-left:5px solid #cccccc
}
.subcategory2{
background-color:#d9d6c5;
color:#000000;
font-size:8pt;
font-family:Arial,Helvetica,sans-serif;
border-left:5px solid #cccccc
}
.row_title{
background-color:#999999;
color:#ffffff;
font-family:Arial,Helvetica,sans-serif;
font-size:8pt;
font-weight:bold
}
.row_category{
background-color:#1D97C3;
font-family:Arial,Helvetica,sans-serif;
font-size:10pt;
font-weight:bold
}
.row1{
color:#000000;
background-color:#dadada;
font-family:Arial,Helvetica,sans-serif;
font-size:8pt
}
.row2{
color:#000000;
background-color:#e9e9e9;
font-family:Arial,Helvetica,sans-serif;
font-size:8pt
}
.row3{
color:#990000;
background-color:#e9e9e9;
font-family:Arial,Helvetica,sans-serif;
font-size:8pt
}
.row4{
color:#990000;
background-color:#ffffff;
font-family:Arial,Helvetica,sans-serif;
font-size:8pt
}
.rowsection{
color:#000000;
background-color:#f9f9f9;
font-family:Arial,Helvetica,sans-serif;
font-size:11px;
}
.category{
background-color:#ffffff;
font-family:Arial,Helvetica,sans-serif;
font-size:9pt
}
.category_mainlink{
font-family:Arial,Helvetica,sans-serif;
font-size:9pt;
color:#336699;
font-family:Arial,Helvetica,sans-serif;
text-decoration:underline;
font-weight:bold
}
.category_sublink{
font-family:Arial,Helvetica,sans-serif;
font-size:8pt;
color:#336699;
font-family:Arial,Helvetica,sans-serif;
text-decoration:underline
}
.boxed_sectionheader{
font-family:trebuchet MS,verdana,arial;
font-size:11pt;
font-weight:bold;
color:#333366;
background-color:#e9e9e9
}
.dotted_sectionheader{
font-family:trebuchet MS,verdana,arial;
font-size:11pt;
font-weight:bold;
color:#333366;
background-color:#ffffff
}
.tab_on{
background-color:#ffffff;
font-size:8pt;
font-family:Arial,Helvetica,sans-serif;
border-left:1px solid #666666;
border-right:1px solid #666666;
border-top:1px solid #666666
}
.tab_off{
background-color:#e9e9e9;
font-size:8pt;
font-family:Arial,Helvetica,sans-serif;
border-left:1px solid #666666;
border-right:1px solid #666666;
border-bottom:1px solid #666666;
border-top:1px solid #666666
}
.tab_spacer{
background-color:#ffffff;
border-bottom:1px solid #666666
}
.tab_content{
background-color:#ffffff;
border-left:1px solid #666666;
border-right:1px solid #666666;
border-bottom:1px solid #666666
}
/* PHPCoverage Specific */
.emphasis {
font-size:12px;
font-weight:bold;
color:#222222;
}
.note {
vertical-align:text-bottom
}
.coverageDetailsHead {
border-right:2px solid #eeeeee;
background-color:#C0CEDC;
color:#000000;
font-size:12px;
font-weight:bold;
white-space:nowrap;
}
.coverageDetails {
color:#666666;
font-size:13px;
border-right:1px solid #A2AFBC
}
.coverageDetailsCode {
font-weight:normal;
color:#666666;
font-size:13px;
}
.codeExecuted {
color:#003399;
font-weight:bold;
}
.codeMissed {
color:#990000;
font-weight:bold;
}
</td></tr>
</table>
</div>
</body>
</html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Spike PHPCoverage Details: %%filename%%</title>
<link href="%%style%%" rel="stylesheet" type="text/css"/>
</head>
<body>
<div id="pageBox" >
<table border="0" cellpadding="2" cellspacing="0" id="contentBox" width="100%">
<tr>
<td align="left" valign="top"><h1>Spike PHPCoverage Details: %%filename%%</h1>
</td>
</tr>
<tr>
<td>
<div id="footerBox">
<table width="800" border="0" cellspacing="0" cellpadding="0" >
<tr>
<td class="copyright" ><a class="footerlink" href="http://www.spikesource.com/projects/phpcoverage/" >Spike PHPCoverage Home</a></td>
<td class="copyright" align="right">&copy; 2004<script language="JavaScript" type="text/javascript">
var d=new Date();
yr=d.getFullYear();
if (yr!=2004) {
document.write("-"+yr);
}
</script>, SpikeSource, Inc.</td>
</tr>
</table>
<a href="mailto:ip@spikesource.com"><img src="images/spacer.gif" width="1" height="1" border="0" alt="spacer"/></a> </div>
</div>
</body>
</html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>%%heading%%</title>
<link href="css/%%style%%" rel="stylesheet" type="text/css"/>
<script language="JavaScript" type="text/javascript" src="js/sort_spikesource.js"> </script>
<script language="JavaScript" type="text/javascript">
var tableSorter = null;
function initSorting() {
if (document.getElementById("table2sort")) {
tableSorter = new TableSorter(document.getElementById("table2sort"));
// initialize table
tableSorter.initTable();
// collapse all
collapseAll();
}
}
function sort(col, type) {
if (tableSorter != null) {
tableSorter.sort(col, type);
}
}
function toggleShowChildren(id) { tableSorter.togglechildren(id); }
function collapseAll() { tableSorter.collapseAllChildren(); }
function expandAll() { tableSorter.expandAllChildren(); }
</script>
</head>
<body onload="initSorting(); sort(5, 'percentage'); sort(5,'percentage');">
<div id="pageBox" >
<table id="navBox" border="0" cellpadding="0" cellspacing="0">
<tr>
<td style=""><img align="bottom" name="logo" src="images/spikesource/phpcoverage.gif" alt="home" border="0" height="82" width="800"/></td>
</td>
</tr>
</table>
/*---------------------------------------------------\
| Table Sorter |
|----------------------------------------------------|
| Author: Vinay Srinivasaiah (vsrini@spikesource.com)|
| SpikeSource (http://www.spikesource.com) |
| - DOM 1 based script that makes the table sortable.|
| - Copyright (c) 2004 SpikeSource Inc. |
|---------------------------------------------------*/
//http://www.w3.org/TR/REC-DOM-Level-1/java-language-binding.html
var tableBody;
var table2sort;
var imgUp;
var imgDown;
function TableSorter(table) {
this.table2sort = table;
this.tableBody = this.table2sort.getElementsByTagName("tbody")[0];
this.imgUp = document.createElement("img");
this.imgUp.src = "images/arrow_up.gif";
this.imgDown = document.createElement("img");
this.imgDown.src = "images/arrow_down.gif";
}
var lastSortCol = -1;
var lastSortOrderAsc = true;
var origChildRows;
function createImgLink(row, imageSrc) {
var cell = row.cells[0];
var id = _getInnerText(cell) + "_" + imageSrc;
imgExpand = document.createElement("img");
imgExpand.src = "images" + imageSrc + ".gif";
imgExpand.border="0";
imgBlank = document.createElement("img");
imgBlank.src = "results/images/transdot.gif";
imgBlank.border="0";
imgBlank2 = imgBlank.cloneNode(false);
imgBlank3 = imgBlank.cloneNode(false);
anchorTag = document.createElement("a");
anchorTag.href="javascript:toggleShowChildren('" + id + "');"
anchorTag.appendChild(imgExpand);
anchorTag.appendChild(imgBlank);
anchorTag.appendChild(imgBlank2);
anchorTag.appendChild(imgBlank3);
anchorTag.id = id;
cell.id = id + "_cell";
row.id = id + "_row";
cell.insertBefore(anchorTag, cell.firstChild);
}
TableSorter.prototype.initTable = function () {
this.populateChildRowsMap();
for (i = 0; i < origChildRows.length; i++) {
if (origChildRows[i].id != "indented_row") {
createImgLink(origChildRows[i], "minus");
}
}
}
TableSorter.prototype.collapseAllChildren = function () {
for (i = 0; i < origChildRows.length; i++) {
if (origChildRows[i].id != "indented_row") {
id = _getInnerText(origChildRows[i].cells[0]) + "_" + "minus";
var anchorTag = document.getElementById(id);
if (anchorTag != null) {
this.togglechildren(id);
}
}
}
}
TableSorter.prototype.expandAllChildren = function () {
for (i = 0; i < origChildRows.length; i++) {
if (origChildRows[i].id != "indented_row") {
id = _getInnerText(origChildRows[i].cells[0]) + "_" + "plus";
var anchorTag = document.getElementById(id);
if (anchorTag != null) {
this.togglechildren(id);
}
}
}
}
TableSorter.prototype.togglechildren = function (id) {
anchorTag = document.getElementById(id);
anchorParent = document.getElementById((id + "_cell"));
anchorParent.removeChild(anchorTag);
row = document.getElementById((id + "_row"));
nextRow = row.nextSibling;
var addChildren = false;
if (anchorTag.firstChild.src.indexOf("plus") != -1) {
addChildren = true;
createImgLink(row, "minus");
} else if (anchorTag.firstChild.src.indexOf("minus") != -1) {
addChildren = false;
createImgLink(row, "plus");
}
for (i = 0; i < origChildRows.length; i++) {
//alert("comparing " + _getInnerText(origChildRows[i].cells[0])
// + " and " + _getInnerText(row.cells[0]));
if (_getInnerText(origChildRows[i].cells[0]) == _getInnerText(row.cells[0])) {
for (j = i + 1; j < origChildRows.length; j++) {
if (origChildRows[j].id == "indented_row") {
if (addChildren) {
this.tableBody.insertBefore(origChildRows[j], nextRow);
} else {
this.tableBody.removeChild(origChildRows[j]);
}
} else {
// done;
break;
}
}
break;
}
}
}
TableSorter.prototype.populateChildRowsMap = function () {
var rows = this.tableBody.rows;
origChildRows = new Array();
var count = 0;
var newRowsCount = 0;
for (i = 0; i < rows.length; i ++) {
if (rows[i].id == "indented_row") {
if (parentRow != null) {
origChildRows[count++] = parentRow;
parentRow = null;
}
origChildRows[count++] = rows[i];
} else {
parentRow = rows[i];
}
}
}
TableSorter.prototype.sort = function (col, type) {
if (lastSortCol != -1) {
sortCell = document.getElementById("sortCell" + lastSortCol);
if (sortCell != null) {
if (lastSortOrderAsc == true) {
sortCell.removeChild(this.imgUp);
} else {
sortCell.removeChild(this.imgDown);
}
}
sortLink = document.getElementById("sortCellLink" + lastSortCol);
if(sortLink != null) {
sortLink.title = "Sort Ascending";
}
}
if (lastSortCol == col) {
lastSortOrderAsc = !lastSortOrderAsc;
} else {
lastSortCol = col;
lastSortOrderAsc = true;
}
var rows = this.tableBody.rows;
var newRows = new Array();
var parentRow;
var childRows = new Array();
var count = 0;
var newRowsCount = 0;
for (i = 0; i < rows.length; i ++) {
if (rows[i].id == "indented_row") {
if (parentRow != null) {
childRows[count++] = parentRow;
parentRow = null;
}
childRows[count++] = rows[i];
} else {
newRows[newRowsCount++] = rows[i];
parentRow = rows[i];
}
}
// default
sortFunction = sort_caseInsensitive;
if (type == "string") sortFunction = sort_caseSensitive;
if (type == "percentage") sortFunction = sort_numericPercentage;
if (type == "number") sortFunction = sort_numeric;
newRows.sort(sortFunction);
if (lastSortOrderAsc == false) {
newRows.reverse();
}
for (i = 0; i < newRows.length; i ++) {
this.table2sort.tBodies[0].appendChild(newRows[i]);
var parentRowText = _getInnerText(newRows[i].cells[0]);
var match = -1;
for (j = 0; j < childRows.length; j++) {
var childRowText = _getInnerText(childRows[j].cells[0]);
if (childRowText == parentRowText) {
match = j;
break;
}
}
if (match != -1) {
for (j = match + 1; j < childRows.length; j++) {
if (childRows[j].id == "indented_row") {
this.table2sort.tBodies[0].appendChild(childRows[j]);
} else {
break;
}
}
}
}
sortCell = document.getElementById("sortCell" + col);
if (sortCell == null) {
} else {
if (lastSortOrderAsc == true) {
sortCell.appendChild(this.imgUp);
} else {
sortCell.appendChild(this.imgDown);
}
}
sortLink = document.getElementById("sortCellLink" + col);
if (sortLink == null) {
} else {
if (lastSortOrderAsc == true) {
sortLink.title = "Sort Descending";
} else {
sortLink.title = "Sort Ascending";
}
}
}
function sort_caseSensitive(a, b) {
aa = _getInnerText(a.cells[lastSortCol]);
bb = _getInnerText(b.cells[lastSortCol]);
return compareString(aa, bb);
}
function sort_caseInsensitive(a,b) {
aa = _getInnerText(a.cells[lastSortCol]).toLowerCase();
bb = _getInnerText(b.cells[lastSortCol]).toLowerCase();
return compareString(aa, bb);
}
function sort_numeric(a,b) {
aa = _getInnerText(a.cells[lastSortCol]);
bb = _getInnerText(b.cells[lastSortCol]);
return compareNumber(aa, bb);
}
function sort_numericPercentage(a,b) {
aa = _getInnerText(a.cells[lastSortCol]);
bb = _getInnerText(b.cells[lastSortCol]);
var aaindex = aa.indexOf("%");
var bbindex = bb.indexOf("%");
if (aaindex != -1 && bbindex != -1) {
aa = aa.substring(0, aaindex);
bb = bb.substring(0, bbindex);
return compareNumber(aa, bb);
}
return compareString(aa, bb);
}
function compareString(a, b) {
if (a == b) return 0;
if (a < b) return -1;
return 1;
}
function compareNumber(a, b) {
aa = parseFloat(a);
if (isNaN(aa)) aa = 0;
bb = parseFloat(b);
if (isNaN(bb)) bb = 0;
return aa-bb;
}
function _getInnerText(el) {
if (typeof el == "string") return el;
if (typeof el == "undefined") { return el };
if (el.innerText) return el.innerText;
var str = "";
var cs = el.childNodes;
var l = cs.length;
for (var i = 0; i < l; i++) {
switch (cs[i].nodeType) {
case 1: //ELEMENT_NODE
str += _getInnerText(cs[i]);
break;
case 3: //TEXT_NODE
str += cs[i].nodeValue;
break;
}
}
return str;
}
<?php
/*
* $Id: license.txt 13981 2005-03-16 08:09:28Z eespino $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
class CoverageLogger {
private $level;
private $logLevels = array(
"LOG_CRITICAL",
"LOG_ERROR",
"LOG_WARNING",
"LOG_NOTICE",
"LOG_INFO",
"LOG_DEBUG"
);
public function setLevel($level) {
if(!is_numeric($level)) {
for($i = 0; $i < count($this->logLevels); $i++) {
if(strcasecmp($this->logLevels[$i], $level) === 0) {
$level = $i;
break;
}
}
}
$this->level = $level;
}
public function critical($str, $file="", $line="") {
if($this->level >= 0) {
error_log("[CRITICAL] [" . $file . ":" . $line . "] " . $str);
}
}
public function error($str, $file="", $line="") {
if($this->level >= 1) {
error_log("[ERROR] [" . $file . ":" . $line . "] " . $str);
}
}
public function warn($str, $file="", $line="") {
if($this->level >= 2) {
error_log("[WARNING] [" . $file . ":" . $line . "] " . $str);
}
}
public function notice($str, $file="", $line="") {
if($this->level >= 3) {
error_log("[NOTICE] [" . $file . ":" . $line . "] " . $str);
}
}
public function info($str, $file="", $line="") {
if($this->level >= 4) {
error_log("[INFO] [" . $file . ":" . $line . "] " . $str);
}
}
public function debug($str, $file="", $line="") {
if($this->level >= 5) {
error_log("[DEBUG] [" . $file . ":" . $line . "] " . $str);
}
}
public function getLevelName($level) {
return $this->logLevels[$level];
}
}
// testing
if(isset($_SERVER["argv"][1]) && $_SERVER["argv"][1] == "__main__") {
$logger = new CoverageLogger();
for($i = 0; $i < 6; $i++) {
$logger->setLevel($i);
error_log("############## Level now: " . $i);
$logger->debug("");
$logger->info("");
$logger->notice("");
$logger->warn("");
$logger->error("");
$logger->critical("");
}
error_log("############# With Level Names");
for($i = 0; $i < 6; $i++) {
$logger->setLevel($logger->getLevelName($i));
error_log("############## Level now: " . $logger->getLevelName($i));
$logger->debug("");
$logger->info("", __FILE__, __LINE__);
$logger->notice("");
$logger->warn("");
$logger->error("");
$logger->critical("");
}
}
?>
<?php
/*
* $Id: Utility.php 14663 2005-03-23 19:27:27Z npac $
*
* Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved.
* Licensed under the Open Software License version 2.1
* (See http://www.spikesource.com/license.html)
*/
?>
<?php
if(!defined("__PHPCOVERAGE_HOME")) {
define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__)));
}
require_once __PHPCOVERAGE_HOME . "/conf/phpcoverage.conf.php";
// include our dummy implementation
require_once 'CoverageLogger.php';
/**
* Utility functions
*
* @author Nimish Pachapurkar <npac@spikesource.com>
* @version $Revision: $
* @package SpikePHPCoverage_Util
*/
class Utility {
public static $logger;
/*{{{ public function getTimeStamp() */
/**
* Return the current timestamp in human readable format.
* Thursday March 17, 2005 19:10:47
*
* @return Readable timestamp
* @access public
*/
public function getTimeStamp() {
$ts = getdate();
return $ts["weekday"] . " " . $ts["month"] . " " . $ts["mday"]
. ", " . $ts["year"] . " " . sprintf("%02d:%02d:%02d", $ts["hours"], $ts["minutes"], $ts["seconds"]);
}
/*}}}*/
/*{{{ public function shortenFilename() */
/**
* Shorten the filename to some maximum characters
*
* @param $filename Complete file path
* @param $maxlength=150 Maximum allowable length of the shortened
* filepath
* @return Shortened file path
* @access public
*/
public function shortenFilename($filename, $maxlength=80) {
$length = strlen($filename);
if($length < $maxlength) {
return $filename;
}
// trim the first few characters
$filename = substr($filename, $length-$maxlength);
// If there is a path separator slash in first n characters,
// trim upto that point.
$n = 20;
$firstSlash = strpos($filename, "/");
if($firstSlash === false || $firstSlash > $n) {
$firstSlash = strpos($filename, "\\");
if($firstSlash === false || $firstSlash > $n) {
return "..." . $filename;
}
return "..." . substr($filename, $firstSlash);
}
return "..." . substr($filename, $firstSlash);
}
/*}}}*/
/*{{{ public function writeError() */
/**
* Write error log if debug is on
*
* @param $str Error string
* @access public
*/
public function writeError($str) {
if(__PHPCOVERAGE_DEBUG) {
error_log($str);
}
}
/*}}}*/
/*{{{ public function unixifyPath() */
/**
* Convert Windows paths to Unix paths
*
* @param $path File path
* @return String Unixified file path
* @access public
*/
public function unixifyPath($path) {
// Remove the drive-letter:
if(strpos($path, ":") == 1) {
$path = substr($path, 2);
}
$path = $this->replaceBackslashes($path);
return $path;
}
/*}}}*/
/*{{{ public function replaceBackslashes() */
/**
* Convert the back slash path separators with forward slashes.
*
* @param $path Windows path with backslash path separators
* @return String Path with back slashes replaced with forward slashes.
* @access public
*/
public function replaceBackslashes($path) {
$path = str_replace("\\", "/", $path);
return $this->capitalizeDriveLetter($path);
}
/*}}}*/
/*{{{ public function capitalizeDriveLetter() */
/**
* Convert the drive letter to upper case
*
* @param $path Windows path with "c:<blah>"
* @return String Path with driver letter capitalized.
* @access public
*/
public function capitalizeDriveLetter($path) {
if(strpos($path, ":") === 1) {
$path = strtoupper(substr($path, 0, 1)) . substr($path, 1);
}
return $path;
}
/*}}}*/
/*{{{ public function makeDirRecursive() */
/**
* Make directory recursively.
* (Taken from: http://aidan.dotgeek.org/lib/?file=function.mkdirr.php)
*
* @param $dir Directory path to create
* @param $mode=0755
* @return True on success, False on failure
* @access public
*/
public function makeDirRecursive($dir, $mode=0755) {
// Check if directory already exists
if (is_dir($dir) || empty($dir)) {
return true;
}
// Ensure a file does not already exist with the same name
if (is_file($dir)) {
$this->getLogger()->debug("File already exists: " . $dir,
__FILE__, __LINE__);
return false;
}
$dir = $this->replaceBackslashes($dir);
// Crawl up the directory tree
$next_pathname = substr($dir, 0, strrpos($dir, "/"));
if ($this->makeDirRecursive($next_pathname, $mode)) {
if (!file_exists($dir)) {
return mkdir($dir, $mode);
}
}
return false;
}
/*}}}*/
/*{{{ public function getOS() */
/**
* Returns the current OS code
* WIN - Windows, LIN -Linux, etc.
*
* @return String 3 letter code for current OS
* @access public
* @since 0.6.6
*/
public function getOS() {
return strtoupper(substr(PHP_OS, 0, 3));
}
/*}}}*/
/*{{{ public function getTmpDir() */
public function getTmpDir() {
global $spc_config;
$OS = $this->getOS();
switch($OS) {
case "WIN":
return $spc_config['windows_tmpdir'];
default:
return $spc_config['tmpdir'];
}
}
/*}}}*/
/*{{{ public function getLogger() */
public function getLogger($package=false) {
global $spc_config;
if(!isset($this->logger) || $this->logger == NULL) {
$this->logger =& new CoverageLogger();
$this->logger->setLevel($spc_config["log_level"]);
}
return $this->logger;
}
/*}}}*/
}
$util = new Utility();
global $util;
?>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment