* @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 = "";
protected $xmlEnd = "";
/*}}}*/
/*{{{ 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 .= "replaceBackslashes($file) . "\">";
foreach($lines as $linenum => &$frequency) {
$xmlBody .= "";
}
$xmlBody .= "\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();
}
/*}}}*/
}
?>