* @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(); } /*}}}*/ } ?>