Back to index

php5  5.3.10
pear2coverage.phar.php
Go to the documentation of this file.
00001 <?php
00002 function __autoload($class)
00003 {
00004     $class = str_replace("PEAR2\Pyrus\Developer\CoverageAnalyzer", "", $class);
00005     include "phar://" . __FILE__ . "/" . str_replace("\\", "/", $class) . ".php";
00006 }
00007 Phar::webPhar("pear2coverage.phar.php");
00008 echo "This phar is a web application, run within your web browser to use\n";
00009 exit -1;
00010 __HALT_COMPILER(); ?>
00011   
00012                   Web/Controller.php    0I         ն         Web/View.phpS:  0IS:  Le         Web/Aggregator.php  0I  ݺ.b         Web/Exception.phph   0Ih   pZ         SourceFile.php:  0I:  ~0         Aggregator.php  0I  3n\      
00013    Exception.phpd   0Id   @
00014       
00015    Sqlite.php0X  0I0X  {,         SourceFile/PerTest.php  0I  R             index.phpF  0IF  k!_      <?php
00016 namespace PEAR2\Pyrus\Developer\CoverageAnalyzer\Web {
00017 use PEAR2\Pyrus\Developer\CoverageAnalyzer\Sqlite;
00018 class Controller {
00019     protected $view;
00020     protected $sqlite;
00021     protected $rooturl;
00022 
00023     function __construct(View $view, $rooturl)
00024     {
00025         $this->view = $view;
00026         $view->setController($this);
00027         $this->rooturl = $rooturl;
00028     }
00029 
00030     function route()
00031     {
00032         if (!isset($_SESSION['fullpath'])) {
00033             if (isset($_POST['setdatabase'])) {
00034                 if (file_exists($_POST['setdatabase'])) {
00035                     $this->sqlite = new \Sqlite3($_POST['setdatabase']);
00036                     $_SESSION['fullpath'] = $_POST['setdatabase'];
00037                     return $this->view->TOC($this->sqlite);
00038                 }
00039             }
00040             return $this->getDatabase();
00041         } else {
00042             $this->sqlite = new \Sqlite3($_POST['setdatabase']);
00043             if (isset($_GET['test'])) {
00044                 if ($_GET['test'] === 'TOC') {
00045                     return $this->view->testTOC($this->sqlite);
00046                 }
00047                 if (isset($_GET['file'])) {
00048                     return $this->view->fileCoverage($this->sqlite, $_GET['file'], $_GET['test']);
00049                 }
00050                 return $this->view->testTOC($this->sqlite, $_GET['test']);
00051             }
00052             if (isset($_GET['file'])) {
00053                 if (isset($_GET['line'])) {
00054                     return $this->view->fileLineTOC($this->sqlite, $_GET['file'], $_GET['line']);
00055                 }
00056                 return $this->view->fileCoverage($this->sqlite, $_GET['file']);
00057             }
00058             return $this->view->TOC($this->sqlite);
00059         }
00060     }
00061 
00062     function getFileLink($file, $test = null, $line = null)
00063     {
00064         if ($line) {
00065             return $this->rooturl . '?file=' . urlencode($file) . '&line=' . $line;
00066         }
00067         if ($test) {
00068             return $this->rooturl . '?file=' . urlencode($file) . '&test=' . $test;
00069         }
00070         return $this->rooturl . '?file=' . urlencode($file);
00071     }
00072 
00073     function getTOCLink($test = false)
00074     {
00075         if ($test === false) {
00076             return $this->rooturl;
00077         }
00078         if ($test === true) {
00079             return $this->rooturl . '?test=TOC';
00080         }
00081         if ($test) {
00082             return $this->rooturl . '?test=' . urlencode($test);
00083         }
00084     }
00085 
00086     function getDatabase()
00087     {
00088         $this->sqlite = $this->view->getDatabase();
00089     }
00090 }
00091 }
00092 ?>
00093 <?php
00094 namespace PEAR2\Pyrus\Developer\CoverageAnalyzer\Web {
00099 class View
00100 {
00101     protected $savePath;
00102     protected $testPath;
00103     protected $sourcePath;
00104     protected $source;
00105     protected $controller;
00106 
00107     function getDatabase()
00108     {
00109         $output = new \XMLWriter;
00110         if (!$output->openUri('php://output')) {
00111             throw new Exception('Cannot open output - this should never happen');
00112         }
00113         $output->startElement('html');
00114          $output->startElement('head');
00115           $output->writeElement('title', 'Enter a path to the database');
00116          $output->endElement();
00117          $output->startElement('body');
00118           $output->writeElement('h2', 'Please enter the path to a coverage database');
00119           $output->startElement('form');
00120            $output->writeAttribute('name', 'getdatabase');
00121            $output->writeAttribute('method', 'POST');
00122            $output->writeAttribute('action', $this->controller->getTOCLink());
00123            $output->startElement('input');
00124             $output->writeAttribute('type', 'text');
00125             $output->writeAttribute('name', 'setdatabase');
00126            $output->endElement();
00127            $output->startElement('input');
00128             $output->writeAttribute('type', 'submit');
00129            $output->endElement();
00130           $output->endElement();
00131          $output->endElement();
00132         $output->endElement();
00133         $output->endDocument();
00134     }
00135 
00136     function setController($controller)
00137     {
00138         $this->controller = $controller;
00139     }
00140 
00141     function TOC($sqlite)
00142     {
00143         $coverage = $sqlite->retrieveProjectCoverage();
00144         $this->renderSummary($sqlite, $sqlite->retrievePaths(), false, $coverage[1], $covered[0]);
00145     }
00146 
00147     function testTOC($sqlite)
00148     {
00149         $this->renderTestSummary($sqlite);
00150     }
00151 
00152     function fileLineTOC($sqlite, $file, $line)
00153     {
00154         
00155     }
00156 
00157     function fileCoverage($sqlite, $file, $test = null)
00158     {
00159         
00160     }
00161 
00162     function mangleFile($path, $istest = false)
00163     {
00164         return $this->controller->getFileLink($path, $istest);
00165     }
00166 
00167     function mangleTestFile($path)
00168     {
00169         return $this->controller->getTOClink($path);
00170     }
00171 
00172     function getLineLink($name, $line)
00173     {
00174         return $this->controller->getFileLink($name, null, $line);
00175     }
00176 
00177     function renderLineSummary($name, $line, $testpath, $tests)
00178     {
00179         $output = new \XMLWriter;
00180         if (!$output->openUri($this->getLinePath($name, $line))) {
00181             throw new Exception('Cannot render ' . $name . ' line ' . $line . ', opening XML failed');
00182         }
00183         $output->setIndentString(' ');
00184         $output->setIndent(true);
00185         $output->startElement('html');
00186         $output->startElement('head');
00187         $output->writeElement('title', 'Tests covering line ' . $line . ' of ' . $name);
00188         $output->startElement('link');
00189         $output->writeAttribute('href', 'cover.css');
00190         $output->writeAttribute('rel', 'stylesheet');
00191         $output->writeAttribute('type', 'text/css');
00192         $output->endElement();
00193         $output->endElement();
00194         $output->startElement('body');
00195         $output->writeElement('h2', 'Tests covering line ' . $line . ' of ' . $name);
00196         $output->startElement('p');
00197         $output->startElement('a');
00198         $output->writeAttribute('href', 'index.html');
00199         $output->text('Aggregate Code Coverage for all tests');
00200         $output->endElement();
00201         $output->endElement();
00202         $output->startElement('p');
00203         $output->startElement('a');
00204         $output->writeAttribute('href', $this->mangleFile($name));
00205         $output->text('File ' . $name . ' code coverage');
00206         $output->endElement();
00207         $output->endElement();
00208         $output->startElement('ul');
00209         foreach ($tests as $testfile) {
00210             $output->startElement('li');
00211             $output->startElement('a');
00212             $output->writeAttribute('href', $this->mangleTestFile($testfile));
00213             $output->text(str_replace($testpath . '/', '', $testfile));
00214             $output->endElement();
00215             $output->endElement();
00216         }
00217         $output->endElement();
00218         $output->endElement();
00219         $output->endDocument();
00220     }
00221 
00226     function render(SourceFile $source, $istest = false)
00227     {
00228         $output = new \XMLWriter;
00229         if (!$output->openUri($this->manglePath($source->name(), $istest))) {
00230             throw new Exception('Cannot render ' . $source->name() . ', opening XML failed');
00231         }
00232         $output->setIndent(false);
00233         $output->startElement('html');
00234         $output->text("\n ");
00235         $output->startElement('head');
00236         $output->text("\n  ");
00237         if ($istest) {
00238             $output->writeElement('title', 'Code Coverage for ' . $source->shortName() . ' in ' . $istest);
00239         } else {
00240             $output->writeElement('title', 'Code Coverage for ' . $source->shortName());
00241         }
00242         $output->text("\n  ");
00243         $output->startElement('link');
00244         $output->writeAttribute('href', 'cover.css');
00245         $output->writeAttribute('rel', 'stylesheet');
00246         $output->writeAttribute('type', 'text/css');
00247         $output->endElement();
00248         $output->text("\n  ");
00249         $output->endElement();
00250         $output->text("\n ");
00251         $output->startElement('body');
00252         $output->text("\n ");
00253         if ($istest) {
00254             $output->writeElement('h2', 'Code Coverage for ' . $source->shortName() . ' in ' . $istest);
00255         } else {
00256             $output->writeElement('h2', 'Code Coverage for ' . $source->shortName());
00257         }
00258         $output->text("\n ");
00259         $output->writeElement('h3', 'Coverage: ' . $source->coveragePercentage() . '%');
00260         $output->text("\n ");
00261         $output->startElement('p');
00262         $output->startElement('a');
00263         $output->writeAttribute('href', 'index.html');
00264         $output->text('Aggregate Code Coverage for all tests');
00265         $output->endElement();
00266         $output->endElement();
00267         $output->startElement('pre');
00268         foreach ($source->source() as $num => $line) {
00269             $coverage = $source->coverage($num);
00270 
00271             $output->startElement('span');
00272             $output->writeAttribute('class', 'ln');
00273             $output->text(str_pad($num, 8, ' ', STR_PAD_LEFT));
00274             $output->endElement();
00275 
00276             if ($coverage === false) {
00277                 $output->text(str_pad(': ', 13, ' ', STR_PAD_LEFT) . $line);
00278                 continue;
00279             }
00280 
00281             $output->startElement('span');
00282             if ($coverage < 1) {
00283                 $output->writeAttribute('class', 'nc');
00284                 $output->text('           ');
00285             } else {
00286                 $output->writeAttribute('class', 'cv');
00287                 if (!$istest) {
00288                     $output->startElement('a');
00289                     $output->writeAttribute('href', $this->getLineLink($source->name(), $num));
00290                 }
00291                 $output->text(str_pad($coverage, 10, ' ', STR_PAD_LEFT) . ' ');
00292                 if (!$istest) {
00293                     $output->endElement();
00294                     $this->renderLineSummary($source->name(), $num, $source->testpath(),
00295                                              $source->getLineLinks($num));
00296                 }
00297             }
00298 
00299             $output->text(': ' .  $line);
00300             $output->endElement();
00301         }
00302         $output->endElement();
00303         $output->text("\n ");
00304         $output->endElement();
00305         $output->text("\n ");
00306         $output->endElement();
00307         $output->endDocument();
00308     }
00309 
00310     function renderSummary(Aggregator $agg, array $results, $istest = false, $total = 1, $covered = 1)
00311     {
00312         $output = new \XMLWriter;
00313         if (!$output->openUri('php://output')) {
00314             throw new Exception('Cannot render test summary, opening XML failed');
00315         }
00316         $output->setIndentString(' ');
00317         $output->setIndent(true);
00318         $output->startElement('html');
00319         $output->startElement('head');
00320         if ($istest) {
00321             $output->writeElement('title', 'Code Coverage Summary [' . $istest . ']');
00322         } else {
00323             $output->writeElement('title', 'Code Coverage Summary');
00324         }
00325         $output->startElement('link');
00326         $output->writeAttribute('href', 'cover.css');
00327         $output->writeAttribute('rel', 'stylesheet');
00328         $output->writeAttribute('type', 'text/css');
00329         $output->endElement();
00330         $output->endElement();
00331         $output->startElement('body');
00332         if ($istest) {
00333             $output->writeElement('h2', 'Code Coverage Files for test ' . $istest);
00334         } else {
00335             $output->writeElement('h2', 'Code Coverage Files');
00336             $output->writeElement('h3', 'Total lines: ' . $total . ', covered lines: ' . $covered);
00337             $percent = 0;
00338             if ($total > 0) {
00339                 $percent = round(($covered / $total) * 100);
00340             }
00341             $output->startElement('p');
00342             if ($percent < 50) {
00343                 $output->writeAttribute('class', 'bad');
00344             } elseif ($percent < 75) {
00345                 $output->writeAttribute('class', 'ok');
00346             } else {
00347                 $output->writeAttribute('class', 'good');
00348             }
00349             $output->text($percent . '% code coverage');
00350             $output->endElement();
00351         }
00352         $output->startElement('p');
00353         $output->startElement('a');
00354         $output->writeAttribute('href', $this->controller->getTOCLink(true));
00355         $output->text('Code Coverage per PHPT test');
00356         $output->endElement();
00357         $output->endElement();
00358         $output->startElement('ul');
00359         foreach ($results as $i => $name) {
00360             $source = new SourceFile($name, $agg, $this->testPath, $this->sourcePath);
00361             $output->startElement('li');
00362             $percent = $source->coveragePercentage();
00363             $output->startElement('span');
00364             if ($percent < 50) {
00365                 $output->writeAttribute('class', 'bad');
00366             } elseif ($percent < 75) {
00367                 $output->writeAttribute('class', 'ok');
00368             } else {
00369                 $output->writeAttribute('class', 'good');
00370             }
00371             $output->text(' Coverage: ' . str_pad($percent . '%', 4, ' ', STR_PAD_LEFT));
00372             $output->endElement();
00373             $output->startElement('a');
00374             $output->writeAttribute('href', $this->mangleFile($name, $istest));
00375             $output->text($source->shortName());
00376             $output->endElement();
00377             $output->endElement();
00378         }
00379         $output->endElement();
00380         $output->endElement();
00381         $output->endDocument();
00382     }
00383 
00384     function renderTestSummary(Aggregator $agg)
00385     {
00386         $output = new \XMLWriter;
00387         if (!$output->openUri('php://output')) {
00388                 throw new Exception('Cannot render tests summary, opening XML failed');
00389         }
00390         $output->setIndentString(' ');
00391         $output->setIndent(true);
00392         $output->startElement('html');
00393         $output->startElement('head');
00394         $output->writeElement('title', 'Test Summary');
00395         $output->startElement('link');
00396         $output->writeAttribute('href', 'cover.css');
00397         $output->writeAttribute('rel', 'stylesheet');
00398         $output->writeAttribute('type', 'text/css');
00399         $output->endElement();
00400         $output->endElement();
00401         $output->startElement('body');
00402         $output->writeElement('h2', 'Tests Executed, click for code coverage summary');
00403         $output->startElement('p');
00404         $output->startElement('a');
00405         $output->writeAttribute('href', $this->controller->getTOClink());
00406         $output->text('Aggregate Code Coverage for all tests');
00407         $output->endElement();
00408         $output->endElement();
00409         $output->startElement('ul');
00410         foreach ($agg->retrieveTestPaths() as $test) {
00411             $output->startElement('li');
00412             $output->startElement('a');
00413             $output->writeAttribute('href', $this->mangleTestFile($test));
00414             $output->text(str_replace($agg->testpath . '/', '', $test));
00415             $output->endElement();
00416             $output->endElement();
00417         }
00418         $output->endElement();
00419         $output->endElement();
00420         $output->endDocument();
00421     }
00422 
00423     function renderTestCoverage(Aggregator $agg, $testpath, $test)
00424     {
00425         $reltest = str_replace($testpath . '/', '', $test);
00426         $output = new \XMLWriter;
00427         if (!$output->openUri('php://output')) {
00428             throw new Exception('Cannot render test ' . $reltest . ' coverage, opening XML failed');
00429         }
00430             $output->setIndentString(' ');
00431             $output->setIndent(true);
00432             $output->startElement('html');
00433             $output->startElement('head');
00434             $output->writeElement('title', 'Code Coverage Summary for test ' . $reltest);
00435             $output->startElement('link');
00436             $output->writeAttribute('href', 'cover.css');
00437             $output->writeAttribute('rel', 'stylesheet');
00438             $output->writeAttribute('type', 'text/css');
00439             $output->endElement();
00440             $output->endElement();
00441             $output->startElement('body');
00442             $output->writeElement('h2', 'Code Coverage Files for test ' . $reltest);
00443             $output->startElement('ul');
00444             $paths = $agg->retrievePathsForTest($test);
00445             foreach ($paths as $name) {
00446                 echo '.';
00447                 $source = new SourceFile\PerTest($name, $agg, $testpath, $basePath, $test);
00448                 $this->render($source, $reltest);
00449                 $output->startElement('li');
00450                 $percent = $source->coveragePercentage();
00451                 $output->startElement('span');
00452                 if ($percent < 50) {
00453                     $output->writeAttribute('class', 'bad');
00454                 } elseif ($percent < 75) {
00455                     $output->writeAttribute('class', 'ok');
00456                 } else {
00457                     $output->writeAttribute('class', 'good');
00458                 }
00459                 $output->text(' Coverage: ' . str_pad($source->coveragePercentage() . '%', 4, ' ', STR_PAD_LEFT));
00460                 $output->endElement();
00461                 $output->startElement('a');
00462                 $output->writeAttribute('href', $this->mangleFile($name, $reltest));
00463                 $output->text($source->shortName());
00464                 $output->endElement();
00465                 $output->endElement();
00466             }
00467             echo "done\n";
00468             $output->endElement();
00469             $output->endElement();
00470             $output->endDocument();
00471         }
00472         echo "done\n";
00473     }
00474 }
00475 }
00476 ?>
00477 <?php
00478 namespace PEAR2\Pyrus\Developer\CoverageAnalyzer\Web {
00479 use PEAR2\Pyrus\Developer\CoverageAnalyzer;
00480 class Aggregator extends CoverageAnalyzer\Aggregator;
00481 {
00482     protected $codepath;
00483     protected $testpath;
00484     protected $sqlite;
00485     public $totallines = 0;
00486     public $totalcoveredlines = 0;
00487 
00492     function __construct($testpath, $codepath, $db = ':memory:')
00493     {
00494         $newcodepath = realpath($codepath);
00495         if (!$newcodepath) {
00496             if (!strpos($codepath, '://') || !file_exists($codepath)) {
00497                 // stream wrapper not found
00498                 throw new Exception('Can not find code path $codepath');
00499             }
00500         } else {
00501             $codepath = $newcodepath;
00502         }
00503         $this->sqlite = new Sqlite($codepath, $db);
00504         $this->codepath = $codepath;
00505     }
00506 
00507     function retrieveLineLinks($file)
00508     {
00509         return $this->sqlite->retrieveLineLinks($file);
00510     }
00511 
00512     function retrievePaths()
00513     {
00514         return $this->sqlite->retrievePaths();
00515     }
00516 
00517     function retrievePathsForTest($test)
00518     {
00519         return $this->sqlite->retrievePathsForTest($test);
00520     }
00521 
00522     function retrieveTestPaths()
00523     {
00524         return $this->sqlite->retrieveTestPaths();
00525     }
00526 
00527     function coveragePercentage($sourcefile, $testfile = null)
00528     {
00529         return $this->sqlite->coveragePercentage($sourcefile, $testfile);
00530     }
00531 
00532     function coverageInfo($path)
00533     {
00534         return $this->sqlite->retrievePathCoverage($path);
00535     }
00536 
00537     function coverageInfoByTest($path, $test)
00538     {
00539         return $this->sqlite->retrievePathCoverageByTest($path, $test);
00540     }
00541 
00542     function retrieveCoverage($path)
00543     {
00544         return $this->sqlite->retrieveCoverage($path);
00545     }
00546 
00547     function retrieveCoverageByTest($path, $test)
00548     {
00549         return $this->sqlite->retrieveCoverageByTest($path, $test);
00550     }
00551 
00552     function retrieveXdebug($path, $testid)
00553     {
00554         $source = '$xdebug = ' . file_get_contents($path) . ";\n";
00555         eval($source);
00556         $this->sqlite->addCoverage(str_replace('.xdebug', '.phpt', $path), $testid, $xdebug);
00557     }
00558 
00559     function scan($testpath)
00560     {
00561         $a = $testpath;
00562         $testpath = realpath($testpath);
00563         if (!$testpath) {
00564             throw new Exception('Unable to process path' . $a);
00565         }
00566         $testpath = str_replace('\\', '/', $testpath);
00567         $this->testpath = $testpath;
00568 
00569         // first get a list of all directories
00570         $dirs = $globdirs = array();
00571         $index = 0;
00572         $dir = $testpath;
00573         do {
00574             $globdirs = glob($dir . '/*', GLOB_ONLYDIR);
00575             if ($globdirs) {
00576                 $dirs = array_merge($dirs, $globdirs);
00577                 $dir = $dirs[$index++];
00578             } else {
00579                 while (!isset($dirs[$index++]) && $index <= count($dirs));
00580                 if (isset($dirs[$index])) {
00581                     $dir = $dirs[$index];
00582                 }
00583             }
00584         } while ($index <= count($dirs));
00585 
00586         // then find all code coverage files
00587         $xdebugs = array();
00588         foreach ($dirs as $dir) {
00589             $globbie = glob($dir . '/*.xdebug');
00590             $xdebugs = array_merge($xdebugs, $globbie);
00591         }
00592         $xdebugs = array_unique($xdebugs);
00593         $modified = array();
00594         $unmodified = array();
00595         foreach ($xdebugs as $path) {
00596             if ($this->sqlite->unChangedXdebug($path)) {
00597                 $unmodified[$path] = true;
00598                 continue;
00599             }
00600             $modified[] = $path;
00601         }
00602         $xdebugs = $modified;
00603         sort($xdebugs);
00604         // index from 1
00605         array_unshift($xdebugs, '');
00606         unset($xdebugs[0]);
00607         $test = array_flip($xdebugs);
00608         foreach ($this->sqlite->retrieveTestPaths() as $path) {
00609             $xdebugpath = str_replace('.phpt', '.xdebug', $path);
00610             if (isset($test[$xdebugpath]) || isset($unmodified[$xdebugpath])) {
00611                 continue;
00612             }
00613             // remove outdated tests
00614             echo "Removing results from $xdebugpath\n";
00615             $this->sqlite->removeOldTest($path, $xdebugpath);
00616         }
00617         return $xdebugs;
00618     }
00619 
00620     function render($toPath)
00621     {
00622         $decorator = new DefaultSourceDecorator($toPath, $this->testpath, $this->codepath);
00623         echo "Generating project coverage data...";
00624         $coverage = $this->sqlite->retrieveProjectCoverage();
00625         echo "done\n";
00626         $decorator->renderSummary($this, $this->retrievePaths(), $this->codepath, false, $coverage[1], 
00627                                   $coverage[0]);
00628         $a = $this->codepath;
00629         echo "[Step 2 of 2] Rendering per-test coverage...";
00630         $decorator->renderTestCoverage($this, $this->testpath, $a);
00631         echo "done\n";
00632     }
00633 }
00634 }
00635 ?>
00636 <?php
00637 namespace PEAR2\Pyrus\Developer\CoverageAnalyzer\Web {
00638 class Exception extends \Exception {}
00639 }
00640 ?>
00641 <?php
00642 namespace PEAR2\Pyrus\Developer\CoverageAnalyzer {
00643 class SourceFile
00644 {
00645     protected $source;
00646     protected $path;
00647     protected $sourcepath;
00648     protected $coverage;
00649     protected $aggregator;
00650     protected $testpath;
00651     protected $linelinks;
00652 
00653     function __construct($path, Aggregator $agg, $testpath, $sourcepath)
00654     {
00655         $this->source = file($path);
00656         $this->path = $path;
00657         $this->sourcepath = $sourcepath;
00658 
00659         array_unshift($this->source, '');
00660         unset($this->source[0]); // make source array indexed by line number
00661 
00662         $this->aggregator = $agg;
00663         $this->testpath = $testpath;
00664         $this->setCoverage();
00665     }
00666 
00667     function setCoverage()
00668     {
00669         $this->coverage = $this->aggregator->retrieveCoverage($this->path);
00670     }
00671 
00672     function aggregator()
00673     {
00674         return $this->aggregator;
00675     }
00676 
00677     function testpath()
00678     {
00679         return $this->testpath;
00680     }
00681 
00682     function render(AbstractSourceDecorator $decorator = null)
00683     {
00684         if ($decorator === null) {
00685             $decorator = new DefaultSourceDecorator('.');
00686         }
00687         return $decorator->render($this);
00688     }
00689 
00690     function coverage($line)
00691     {
00692         if (!isset($this->coverage[$line])) {
00693             return false;
00694         }
00695         return $this->coverage[$line];
00696     }
00697 
00698     function coveragePercentage()
00699     {
00700         return $this->aggregator->coveragePercentage($this->path);
00701     }
00702 
00703     function name()
00704     {
00705         return $this->path;
00706     }
00707 
00708     function shortName()
00709     {
00710         return str_replace($this->sourcepath . DIRECTORY_SEPARATOR, '', $this->path);
00711     }
00712 
00713     function source()
00714     {
00715         return $this->source;
00716     }
00717 
00718     function coveredLines()
00719     {
00720         $info = $this->aggregator->coverageInfo($this->path);
00721         return $info[0];
00722     }
00723 
00724     function getLineLinks($line)
00725     {
00726         if (!isset($this->linelinks)) {
00727             $this->linelinks = $this->aggregator->retrieveLineLinks($this->path);
00728         }
00729         if (isset($this->linelinks[$line])) {
00730             return $this->linelinks[$line];
00731         }
00732         return false;
00733     }
00734 }
00735 }
00736 ?>
00737 <?php
00738 namespace PEAR2\Pyrus\Developer\CoverageAnalyzer {
00739 class Aggregator
00740 {
00741     protected $codepath;
00742     protected $testpath;
00743     protected $sqlite;
00744     public $totallines = 0;
00745     public $totalcoveredlines = 0;
00746 
00751     function __construct($testpath, $codepath, $db = ':memory:')
00752     {
00753         $newcodepath = realpath($codepath);
00754         if (!$newcodepath) {
00755             if (!strpos($codepath, '://') || !file_exists($codepath)) {
00756                 // stream wrapper not found
00757                 throw new Exception('Can not find code path $codepath');
00758             }
00759         } else {
00760             $codepath = $newcodepath;
00761         }
00762         $this->sqlite = new Sqlite($db, $codepath, $testpath);
00763         $this->codepath = $codepath;
00764         $this->sqlite->begin();
00765         echo "Scanning for xdebug coverage files...";
00766         $files = $this->scan($testpath);
00767         echo "done\n";
00768         $infostring = '';
00769         echo "Parsing xdebug results\n";
00770         if (count($files)) {
00771             foreach ($files as $testid => $xdebugfile) {
00772                 if (!file_exists(str_replace('.xdebug', '.phpt', $xdebugfile))) {
00773                     echo "\nWARNING: outdated .xdebug file $xdebugfile, delete this relic\n";
00774                     continue;
00775                 }
00776                 $id = $this->sqlite->addTest(str_replace('.xdebug', '.phpt', $xdebugfile));
00777                 echo '(' . $testid . ' of ' . count($files) . ') ' . $xdebugfile;
00778                 $this->retrieveXdebug($xdebugfile, $id);
00779                 echo "done\n";
00780             }
00781             echo "done\n";
00782             $this->sqlite->updateTotalCoverage();
00783             $this->sqlite->commit();
00784         } else {
00785             echo "done (no modified xdebug files)\n";
00786         }
00787     }
00788 
00789     function retrieveLineLinks($file)
00790     {
00791         return $this->sqlite->retrieveLineLinks($file);
00792     }
00793 
00794     function retrievePaths()
00795     {
00796         return $this->sqlite->retrievePaths();
00797     }
00798 
00799     function retrievePathsForTest($test)
00800     {
00801         return $this->sqlite->retrievePathsForTest($test);
00802     }
00803 
00804     function retrieveTestPaths()
00805     {
00806         return $this->sqlite->retrieveTestPaths();
00807     }
00808 
00809     function coveragePercentage($sourcefile, $testfile = null)
00810     {
00811         return $this->sqlite->coveragePercentage($sourcefile, $testfile);
00812     }
00813 
00814     function coverageInfo($path)
00815     {
00816         return $this->sqlite->retrievePathCoverage($path);
00817     }
00818 
00819     function coverageInfoByTest($path, $test)
00820     {
00821         return $this->sqlite->retrievePathCoverageByTest($path, $test);
00822     }
00823 
00824     function retrieveCoverage($path)
00825     {
00826         return $this->sqlite->retrieveCoverage($path);
00827     }
00828 
00829     function retrieveCoverageByTest($path, $test)
00830     {
00831         return $this->sqlite->retrieveCoverageByTest($path, $test);
00832     }
00833 
00834     function retrieveXdebug($path, $testid)
00835     {
00836         $source = '$xdebug = ' . file_get_contents($path) . ";\n";
00837         eval($source);
00838         $this->sqlite->addCoverage(str_replace('.xdebug', '.phpt', $path), $testid, $xdebug);
00839     }
00840 
00841     function scan($testpath)
00842     {
00843         $a = $testpath;
00844         $testpath = realpath($testpath);
00845         if (!$testpath) {
00846             throw new Exception('Unable to process path' . $a);
00847         }
00848         $testpath = str_replace('\\', '/', $testpath);
00849         $this->testpath = $testpath;
00850 
00851         // first get a list of all directories
00852         $dirs = $globdirs = array();
00853         $index = 0;
00854         $dir = $testpath;
00855         do {
00856             $globdirs = glob($dir . '/*', GLOB_ONLYDIR);
00857             if ($globdirs) {
00858                 $dirs = array_merge($dirs, $globdirs);
00859                 $dir = $dirs[$index++];
00860             } else {
00861                 while (!isset($dirs[$index++]) && $index <= count($dirs));
00862                 if (isset($dirs[$index])) {
00863                     $dir = $dirs[$index];
00864                 }
00865             }
00866         } while ($index <= count($dirs));
00867 
00868         // then find all code coverage files
00869         $xdebugs = array();
00870         foreach ($dirs as $dir) {
00871             $globbie = glob($dir . '/*.xdebug');
00872             $xdebugs = array_merge($xdebugs, $globbie);
00873         }
00874         $xdebugs = array_unique($xdebugs);
00875         $modified = array();
00876         $unmodified = array();
00877         foreach ($xdebugs as $path) {
00878             if ($this->sqlite->unChangedXdebug($path)) {
00879                 $unmodified[$path] = true;
00880                 continue;
00881             }
00882             $modified[] = $path;
00883         }
00884         $xdebugs = $modified;
00885         sort($xdebugs);
00886         // index from 1
00887         array_unshift($xdebugs, '');
00888         unset($xdebugs[0]);
00889         $test = array_flip($xdebugs);
00890         foreach ($this->sqlite->retrieveTestPaths() as $path) {
00891             $xdebugpath = str_replace('.phpt', '.xdebug', $path);
00892             if (isset($test[$xdebugpath]) || isset($unmodified[$xdebugpath])) {
00893                 continue;
00894             }
00895             // remove outdated tests
00896             echo "Removing results from $xdebugpath\n";
00897             $this->sqlite->removeOldTest($path, $xdebugpath);
00898         }
00899         return $xdebugs;
00900     }
00901 
00902     function render($toPath)
00903     {
00904         $decorator = new DefaultSourceDecorator($toPath, $this->testpath, $this->codepath);
00905         echo "Generating project coverage data...";
00906         $coverage = $this->sqlite->retrieveProjectCoverage();
00907         echo "done\n";
00908         $decorator->renderSummary($this, $this->retrievePaths(), $this->codepath, false, $coverage[1], 
00909                                   $coverage[0]);
00910         $a = $this->codepath;
00911         echo "[Step 2 of 2] Rendering per-test coverage...";
00912         $decorator->renderTestCoverage($this, $this->testpath, $a);
00913         echo "done\n";
00914     }
00915 }
00916 }
00917 ?>
00918 <?php
00919 namespace PEAR2\Pyrus\Developer\CoverageAnalyzer {
00920 class Exception extends \Exception {}
00921 }
00922 ?>
00923 <?php
00924 namespace PEAR2\Pyrus\Developer\CoverageAnalyzer {
00925 class Sqlite
00926 {
00927     protected $db;
00928     protected $totallines = 0;
00929     protected $coveredlines = 0;
00930     protected $pathCovered = array();
00931     protected $pathTotal = array();
00932     public $codepath;
00933     public $testpath;
00934 
00935     function __construct($path = ':memory:', $codepath = null, $testpath = null)
00936     {
00937         $this->db = new \Sqlite3($path);
00938 
00939         $sql = 'SELECT version FROM analyzerversion';
00940         if (@$this->db->querySingle($sql) == '2.1.0') {
00941             $this->codepath = $this->db->querySingle('SELECT codepath FROM paths');
00942             $this->testpath = $this->db->querySingle('SELECT testpath FROM paths');
00943             return;
00944         }
00945         if (!$codepath || !$testpath) {
00946             throw new Exception('Both codepath and testpath must be set in ' .
00947                                 'order to initialize a coverage database');
00948         }
00949         $this->codepath = $codepath;
00950         $this->testpath = $testpath;
00951         // restart the database
00952         echo "Upgrading database to version 2.1.0\n";
00953         $this->db->exec('
00954             DROP TABLE coverage;
00955             DROP TABLE files;
00956             DROP TABLE tests;
00957             DROP TABLE coverage_per_file;
00958             DROP TABLE xdebugs;
00959             DROP TABLE analyzerversion;
00960             VACUUM;');
00961 
00962         $this->db->exec('BEGIN');
00963 
00964         $query = '
00965           CREATE TABLE coverage (
00966             files_id integer NOT NULL,
00967             tests_id integer NOT NULL,
00968             linenumber INTEGER NOT NULL,
00969             iscovered BOOL NOT NULL,
00970             issource BOOL NOT NULL,
00971             PRIMARY KEY (files_id, tests_id, linenumber)
00972           );
00973           CREATE INDEX coverage_files_id on coverage (files_id);
00974           CREATE INDEX coverage_tests_id on coverage (tests_id, issource);
00975           CREATE INDEX coverage_tests_id2 on coverage (tests_id, files_id, issource);
00976           CREATE INDEX coverage_linenumber on coverage (files_id, linenumber);
00977           CREATE INDEX coverage_issource on coverage (issource);
00978 
00979           CREATE TABLE coverage_per_file (
00980             files_id integer NOT NULL,
00981             linenumber INTEGER NOT NULL,
00982             coverage INTEGER NOT NULL,
00983             PRIMARY KEY (files_id, linenumber)
00984           );
00985           CREATE INDEX coverage_per_file_linenumber on coverage_per_file (linenumber);
00986           ';
00987         $worked = $this->db->exec($query);
00988         if (!$worked) {
00989             @$this->db->exec('ROLLBACK');
00990             $error = $this->db->lastErrorMsg();
00991             throw new Exception('Unable to create Code Coverage SQLite3 database: ' . $error);
00992         }
00993 
00994         $query = '
00995           CREATE TABLE files (
00996             id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
00997             filepath TEXT(500) NOT NULL,
00998             filepathmd5 TEXT(32) NOT NULL,
00999             issource BOOL NOT NULL,
01000             UNIQUE (filepath)
01001           );
01002           CREATE INDEX files_issource on files (issource);
01003           ';
01004         $worked = $this->db->exec($query);
01005         if (!$worked) {
01006             @$this->db->exec('ROLLBACK');
01007             $error = $this->db->lastErrorMsg();
01008             throw new Exception('Unable to create Code Coverage SQLite3 database: ' . $error);
01009         }
01010 
01011         $query = '
01012           CREATE TABLE xdebugs (
01013             xdebugpath TEXT(500) NOT NULL,
01014             xdebugpathmd5 TEXT(32) NOT NULL,
01015             PRIMARY KEY (xdebugpath)
01016           );';
01017         $worked = $this->db->exec($query);
01018         if (!$worked) {
01019             @$this->db->exec('ROLLBACK');
01020             $error = $this->db->lastErrorMsg();
01021             throw new Exception('Unable to create Code Coverage SQLite3 database: ' . $error);
01022         }
01023 
01024         $query = '
01025           CREATE TABLE tests (
01026             id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
01027             testpath TEXT(500) NOT NULL,
01028             testpathmd5 TEXT(32) NOT NULL,
01029             UNIQUE (testpath)
01030           );';
01031         $worked = $this->db->exec($query);
01032         if (!$worked) {
01033             @$this->db->exec('ROLLBACK');
01034             $error = $this->db->lastErrorMsg();
01035             throw new Exception('Unable to create Code Coverage SQLite3 database: ' . $error);
01036         }
01037 
01038         $query = '
01039           CREATE TABLE analyzerversion (
01040             version TEXT(5) NOT NULL
01041           );
01042 
01043           INSERT INTO analyzerversion VALUES("2.0.0");
01044 
01045           CREATE TABLE paths (
01046             codepath TEXT NOT NULL,
01047             testpath TEXT NOT NULL,
01048           );
01049           
01050           INSERT INTO paths ("' . $this->db->escapeString($codepath) . '", "' .
01051           $this->db->escapeString($testpath). '")';
01052         $worked = $this->db->exec($query);
01053         if (!$worked) {
01054             @$this->db->exec('ROLLBACK');
01055             $error = $this->db->lastErrorMsg();
01056             throw new Exception('Unable to create Code Coverage SQLite3 database: ' . $error);
01057         }
01058         $this->db->exec('COMMIT');
01059     }
01060 
01061     function retrieveLineLinks($file)
01062     {
01063         $id = $this->getFileId($file);
01064         $query = 'SELECT t.testpath, c.linenumber
01065             FROM
01066                 coverage c, tests t
01067             WHERE
01068                 c.files_id=' . $id . ' AND t.id=c.tests_id' ;
01069         $result = $this->db->query($query);
01070         if (!$result) {
01071             $error = $this->db->lastErrorMsg();
01072             throw new Exception('Cannot retrieve line links for ' . $file .
01073                                 ' line #' . $line .  ': ' . $error);
01074         }
01075 
01076         $ret = array();
01077         while ($res = $result->fetchArray(SQLITE3_ASSOC)) {
01078             $ret[$res['linenumber']][] = $res['testpath'];
01079         }
01080         return $ret;
01081     }
01082 
01083     function retrieveTestPaths()
01084     {
01085         $query = 'SELECT testpath from tests ORDER BY testpath';
01086         $result = $this->db->query($query);
01087         if (!$result) {
01088             $error = $this->db->lastErrorMsg();
01089             throw new Exception('Cannot retrieve test paths :' . $error);
01090         }
01091         $ret = array();
01092         while ($res = $result->fetchArray(SQLITE3_NUM)) {
01093             $ret[] = $res[0];
01094         }
01095         return $ret;
01096     }
01097 
01098     function retrievePathsForTest($test, $all = 0)
01099     {
01100         $id = $this->getTestId($test);
01101         if ($all) {
01102             $query = 'SELECT DISTINCT filepath
01103                 FROM coverage c, files
01104                 WHERE c.tests_id=' . $id . '
01105                     AND files.id=c.files_id
01106                 GROUP BY c.files_id
01107                 ORDER BY filepath';
01108         } else {
01109             $query = 'SELECT DISTINCT filepath
01110                 FROM coverage c, files
01111                 WHERE c.tests_id=' . $id . '
01112                     AND c.issource=1
01113                     AND files.id=c.files_id
01114                 GROUP BY c.files_id
01115                 ORDER BY filepath';
01116         }
01117         $result = $this->db->query($query);
01118         if (!$result) {
01119             $error = $this->db->lastErrorMsg();
01120             throw new Exception('Cannot retrieve file paths for test ' . $test . ':' . $error);
01121         }
01122         $ret = array();
01123         while ($res = $result->fetchArray(SQLITE3_NUM)) {
01124             $ret[] = $res[0];
01125         }
01126         return $ret;
01127     }
01128 
01129     function retrievePaths($all = 0)
01130     {
01131         if ($all) {
01132             $query = 'SELECT filepath from files ORDER BY filepath';
01133         } else {
01134             $query = 'SELECT filepath from files WHERE issource=1 ORDER BY filepath';
01135         }
01136         $result = $this->db->query($query);
01137         if (!$result) {
01138             $error = $this->db->lastErrorMsg();
01139             throw new Exception('Cannot retrieve file paths :' . $error);
01140         }
01141         $ret = array();
01142         while ($res = $result->fetchArray(SQLITE3_NUM)) {
01143             $ret[] = $res[0];
01144         }
01145         return $ret;
01146     }
01147 
01148     function coveragePercentage($sourcefile, $testfile = null)
01149     {
01150         if ($testfile) {
01151             $coverage = $this->retrievePathCoverageByTest($sourcefile, $testfile);
01152         } else {
01153             $coverage = $this->retrievePathCoverage($sourcefile);
01154         }
01155         return round(($coverage[0] / $coverage[1]) * 100);
01156     }
01157 
01158     function retrieveProjectCoverage()
01159     {
01160         if ($this->totallines) {
01161             return array($this->coveredlines, $this->totallines);
01162         }
01163         $query = 'SELECT COUNT(linenumber),filepath FROM coverage_per_file, files
01164                 WHERE files.id=coverage_per_file.files_id
01165                 GROUP BY files_id';
01166         $result = $this->db->query($query);
01167         if (!$result) {
01168             $error = $this->db->lastErrorMsg();
01169             throw new Exception('Cannot retrieve coverage for ' . $path.  ': ' . $error);
01170         }
01171         while ($res = $result->fetchArray(SQLITE3_NUM)) {
01172             $this->pathTotal[$res[1]] = $res[0];
01173             $this->totallines += $res[0];
01174         }
01175 
01176         $query = 'SELECT COUNT(linenumber),filepath FROM coverage_per_file, files
01177                 WHERE coverage > 0 AND files.id=coverage_per_file.files_id
01178                 GROUP BY files_id';
01179         $result = $this->db->query($query);
01180         if (!$result) {
01181             $error = $this->db->lastErrorMsg();
01182             throw new Exception('Cannot retrieve coverage for ' . $path.  ': ' . $error);
01183         }
01184         while ($res = $result->fetchArray(SQLITE3_NUM)) {
01185             $this->pathCovered[$res[1]] = $res[0];
01186             $this->coveredlines += $res[0];
01187         }
01188 
01189         return array($this->coveredlines, $this->totallines);
01190     }
01191 
01192     function retrievePathCoverage($path)
01193     {
01194         if (!$this->totallines) {
01195             // set up the cache
01196             $this->retrieveProjectCoverage();
01197         }
01198         if (!isset($this->pathCovered[$path])) {
01199             return array(0, 0);
01200         }
01201         return array($this->pathCovered[$path], $this->pathTotal[$path]);
01202     }
01203 
01204     function retrievePathCoverageByTest($path, $test)
01205     {
01206         $id = $this->getFileId($path);
01207         $testid = $this->getTestId($test);
01208 
01209         $query = 'SELECT COUNT(linenumber)
01210             FROM coverage
01211             WHERE issource=1 AND files_id=' . $id . ' AND tests_id=' . $testid;
01212         $result = $this->db->query($query);
01213         if (!$result) {
01214             $error = $this->db->lastErrorMsg();
01215             throw new Exception('Cannot retrieve path coverage for ' . $path .
01216                                 ' in test ' . $test . ': ' . $error);
01217         }
01218 
01219         $ret = array();
01220         while ($res = $result->fetchArray(SQLITE3_NUM)) {
01221             $total = $res[0];
01222         }
01223 
01224         $query = 'SELECT COUNT(linenumber)
01225             FROM coverage
01226             WHERE issource=1 AND iscovered AND files_id=' . $id. ' AND tests_id=' . $testid;
01227         $result = $this->db->query($query);
01228         if (!$result) {
01229             $error = $this->db->lastErrorMsg();
01230             throw new Exception('Cannot retrieve path coverage for ' . $path .
01231                                 ' in test ' . $test . ': ' . $error);
01232         }
01233 
01234         $ret = array();
01235         while ($res = $result->fetchArray(SQLITE3_NUM)) {
01236             $covered = $res[0];
01237         }
01238         return array($covered, $total);
01239     }
01240 
01241     function retrieveCoverageByTest($path, $test)
01242     {
01243         $id = $this->getFileId($path);
01244         $testid = $this->getTestId($test);
01245 
01246         $query = 'SELECT iscovered as coverage, linenumber FROM coverage
01247                     WHERE issource=1 AND files_id=' . $id . ' AND tests_id=' . $testid;
01248         $result = $this->db->query($query);
01249         if (!$result) {
01250             $error = $this->db->lastErrorMsg();
01251             throw new Exception('Cannot retrieve test ' . $test .
01252                                 ' coverage for ' . $path.  ': ' . $error);
01253         }
01254 
01255         $ret = array();
01256         while ($res = $result->fetchArray(SQLITE3_ASSOC)) {
01257             $ret[$res['linenumber']] = $res['coverage'];
01258         }
01259         return $ret;
01260     }
01261 
01262     function getFileId($path)
01263     {
01264         $query = 'SELECT id FROM files WHERE filepath=:filepath';
01265         $stmt = $this->db->prepare($query);
01266         $stmt->bindValue(':filepath', $path);
01267         if (!($result = $stmt->execute())) {
01268             throw new Exception('Unable to retrieve file ' . $path . ' id from database');
01269         }
01270         while ($id = $result->fetchArray(SQLITE3_NUM)) {
01271             return $id[0];
01272         }
01273         throw new Exception('Unable to retrieve file ' . $path . ' id from database');
01274     }
01275 
01276     function getTestId($path)
01277     {
01278         $query = 'SELECT id FROM tests WHERE testpath=:filepath';
01279         $stmt = $this->db->prepare($query);
01280         $stmt->bindValue(':filepath', $path);
01281         if (!($result = $stmt->execute())) {
01282             throw new Exception('Unable to retrieve test file ' . $path . ' id from database');
01283         }
01284         while ($id = $result->fetchArray(SQLITE3_NUM)) {
01285             return $id[0];
01286         }
01287         throw new Exception('Unable to retrieve test file ' . $path . ' id from database');
01288     }
01289 
01290     function removeOldTest($testpath, $xdebugpath)
01291     {
01292         try {
01293             $id = $this->getTestId($testpath);
01294         } catch (\Exception $e) {
01295             // get a unique ID
01296             return $this->db->querySingle('SELECT COUNT(id) from tests')+1;
01297         }
01298         $this->db->exec('DELETE FROM tests WHERE id=' . $id);
01299         $this->db->exec('DELETE FROM coverage WHERE tests_id=' . $id);
01300         $this->db->exec('DELETE FROM xdebugs WHERE xdebugpath="' . $this->db->escapeString($xdebugpath) . '"');
01301         return $id;
01302     }
01303 
01304     function addTest($testpath)
01305     {
01306         $id = $this->removeOldTest($testpath, str_replace('.phpt', '.xdebug', $testpath));
01307         $query = 'INSERT INTO tests
01308             (testpath, testpathmd5)
01309             VALUES(:testpath, :md5)';
01310         $stmt = $this->db->prepare($query);
01311         $stmt->bindValue(':testpath', $testpath);
01312         $md5 = md5_file($testpath);
01313         $stmt->bindValue(':md5', $md5);
01314         $stmt->execute();
01315         $id = $this->db->lastInsertRowID();
01316 
01317         $query = 'INSERT INTO xdebugs
01318             (xdebugpath, xdebugpathmd5)
01319             VALUES(:testpath, :md5)';
01320         $stmt = $this->db->prepare($query);
01321         $stmt->bindValue(':testpath', str_replace('.phpt', '.xdebug', $testpath));
01322         $md5 = md5_file(str_replace('.phpt', '.xdebug', $testpath));
01323         $stmt->bindValue(':md5', $md5);
01324         $stmt->execute();
01325         return $id;
01326     }
01327 
01328     function unChangedXdebug($path)
01329     {
01330         $query = 'SELECT xdebugpathmd5 FROM xdebugs
01331                     WHERE xdebugpath=:path';
01332         $stmt = $this->db->prepare($query);
01333         $stmt->bindValue(':path', $path);
01334         $result = $stmt->execute();
01335         if (!$result) {
01336             return false;
01337         }
01338         $md5 = 0;
01339         while ($res = $result->fetchArray(SQLITE3_NUM)) {
01340             $md5 = $res[0];
01341         }
01342         if (!$md5) {
01343             return false;
01344         }
01345         if ($md5 == md5_file($path)) {
01346             return true;
01347         }
01348         return false;
01349     }
01350 
01351     function addFile($filepath, $issource = 0)
01352     {
01353         $query = 'SELECT id FROM files WHERE filepath=:filepath';
01354         $stmt = $this->db->prepare($query);
01355         $stmt->bindParam(':filepath', $filepath);
01356         if (!($result = $stmt->execute())) {
01357             throw new Exception('Unable to add file ' . $filepath . ' to database');
01358         }
01359         while ($id = $result->fetchArray(SQLITE3_NUM)) {
01360             $query = 'UPDATE files SET filepathmd5=:md5 WHERE filepath=:filepath';
01361             $stmt = $this->db->prepare($query);
01362             $stmt->bindParam(':filepath', $filepath);
01363             $md5 = md5_file($filepath);
01364             $stmt->bindParam(':md5', $md5);
01365             if (!$stmt->execute()) {
01366                 throw new Exception('Unable to update file ' . $filepath . ' md5 in database');
01367             }
01368             return $id[0];
01369         }
01370         $stmt->clear();
01371         $query = 'INSERT INTO files
01372             (filepath, filepathmd5, issource)
01373             VALUES(:testpath, :md5, :issource)';
01374         $stmt = $this->db->prepare($query);
01375         $stmt->bindValue(':testpath', $filepath);
01376         $md5 = md5_file($filepath);
01377         $stmt->bindValue(':md5', $md5);
01378         $stmt->bindValue(':issource', $issource);
01379         if (!$stmt->execute()) {
01380             throw new Exception('Unable to add file ' . $filepath . ' to database');
01381         }
01382         return $this->db->lastInsertRowID();
01383     }
01384 
01385     function getTotalCoverage($file, $linenumber)
01386     {
01387         $query = 'SELECT coveragecount FROM coverage_per_file
01388                     WHERE files_id=' . $this->getFileId($file) . ' AND linenumber=' . $linenumber;
01389         $result = $this->db->query($query);
01390         if (!$result) {
01391             return false;
01392         }
01393         $coverage = 0;
01394         while ($res = $result->fetchArray(SQLITE3_NUM)) {
01395             $coverage = $res[0];
01396         }
01397         return $coverage;
01398     }
01399 
01400     function retrieveCoverage($path)
01401     {
01402         $id = $this->getFileId($path);
01403         $query = 'SELECT coverage, linenumber FROM coverage_per_file
01404                     WHERE files_id=' . $id . '
01405                     ORDER BY linenumber ASC';
01406         $result = $this->db->query($query);
01407         if (!$result) {
01408             $error = $this->db->lastErrorMsg();
01409             throw new Exception('Cannot retrieve coverage for ' . $path.  ': ' . $error);
01410         }
01411 
01412         $ret = array();
01413         while ($res = $result->fetchArray(SQLITE3_ASSOC)) {
01414             $ret[$res['linenumber']] = $res['coverage'];
01415         }
01416         return $ret;
01417     }
01418 
01423     function retrieveSlowCoverage($id)
01424     {
01425         $query = 'SELECT SUM(iscovered) as coverage, linenumber FROM coverage WHERE files_id=' . $id . '
01426                     GROUP BY linenumber';
01427         $result = $this->db->query($query);
01428         if (!$result) {
01429             $error = $this->db->lastErrorMsg();
01430             throw new Exception('Cannot retrieve coverage for ' . $path.  ': ' . $error);
01431         }
01432 
01433         $ret = array();
01434         while ($res = $result->fetchArray(SQLITE3_ASSOC)) {
01435             $ret[$res['linenumber']] = $res['coverage'];
01436         }
01437         return $ret;
01438     }
01439 
01440     function updateTotalCoverage()
01441     {
01442         $query = 'DELETE FROM coverage_per_file';
01443         $this->db->exec($query);
01444         echo "Updating coverage per-file intermediate table\n";
01445         foreach ($this->retrievePaths() as $path) {
01446             echo ".";
01447             $id = $this->getFileId($path);
01448             foreach ($this->retrieveSlowCoverage($id) as $linenumber => $coverage) {
01449                 $query = 'INSERT INTO coverage_per_file
01450                     (files_id, coverage, linenumber)
01451                     VALUES(' . $id . ',' . $coverage . ',' . $linenumber .')';
01452                 $this->db->exec($query);
01453             }
01454         }
01455         echo "done\n";
01456     }
01457 
01458     function addCoverage($testpath, $testid, $xdebug)
01459     {
01460         foreach ($xdebug as $path => $results) {
01461             if (!file_exists($path)) {
01462                 continue;
01463             }
01464             if (strpos($path, $this->codepath) !== 0) {
01465                 $issource = 0;
01466             } else {
01467                 if (strpos($path, $this->testpath) === 0) {
01468                     $issource = 0;
01469                 } else {
01470                     $issource = 1;
01471                 }
01472             }
01473             echo ".";
01474             $id = $this->addFile($path, $issource);
01475             foreach ($results as $line => $info) {
01476                 if ($info > 0) {
01477                     $res = 1;
01478                 } else {
01479                     $res = 0;
01480                 }
01481                 $query = 'INSERT INTO coverage
01482                     (files_id, tests_id, linenumber, iscovered, issource)
01483                     VALUES(' . $id . ', ' . $testid . ', ' . $line . ', ' . $res . ',' . $issource . ')';
01484 
01485                 $worked = $this->db->exec($query);
01486                 if (!$worked) {
01487                     $error = $this->db->lastErrorMsg();
01488                     throw new Exception('Cannot add coverage for test ' . $testpath .
01489                                         ', covered file ' . $path . ': ' . $error);
01490                 }
01491             }
01492         }
01493     }
01494 
01495     function begin()
01496     {
01497         $this->db->exec('BEGIN');
01498     }
01499 
01500     function commit()
01501     {
01502         $this->db->exec('COMMIT');
01503     }
01504 
01510     function getModifiedTests()
01511     {
01512         $modifiedPaths = array();
01513         $modifiedTests = array();
01514         $paths = $this->retrievePaths(1);
01515         echo "Scanning ", count($paths), " source files";
01516         foreach ($paths as $path) {
01517             echo '.';
01518             $query = '
01519                 SELECT id, filepathmd5 FROM files where filepath="' .
01520                 $this->db->escapeString($path) . '"';
01521             $result = $this->db->query($query);
01522             while ($res = $result->fetchArray(SQLITE3_ASSOC)) {
01523                 if (md5_file($path) == $res['filepathmd5']) {
01524                     break;
01525                 }
01526                 $modifiedPaths[] = $path;
01527                 // file is modified, get a list of tests that execute this file
01528                 $query = '
01529                     SELECT t.testpath
01530                     FROM coverage c, tests t
01531                     WHERE
01532                         c.files_id=' . $res['id'] . '
01533                         AND t.id=c.tests_id';
01534                 $result2 = $this->db->query($query);
01535                 while ($res = $result2->fetchArray(SQLITE3_NUM)) {
01536                     $modifiedTests[$res[0]] = true;
01537                 }
01538                 break;
01539             }
01540         }
01541         echo "done\n";
01542         echo count($modifiedPaths), ' modified files resulting in ',
01543             count($modifiedTests), " modified tests\n";
01544         $paths = $this->retrieveTestPaths();
01545         echo "Scanning ", count($paths), " test paths";
01546         foreach ($paths as $path) {
01547             echo '.';
01548             $query = '
01549                 SELECT id, testpathmd5 FROM tests where testpath="' .
01550                 $this->db->escapeString($path) . '"';
01551             $result = $this->db->query($query);
01552             while ($res = $result->fetchArray(SQLITE3_ASSOC)) {
01553                 if (md5_file($path) != $res['testpathmd5']) {
01554                     $modifiedTests[$path] = true;
01555                 }
01556             }
01557         }
01558         echo "done\n";
01559         echo count($modifiedTests), " tests should be re-run\n";
01560         return array_keys($modifiedTests);
01561     }
01562 }
01563 }
01564 ?><?php
01565 namespace PEAR2\Pyrus\Developer\CoverageAnalyzer\SourceFile {
01566 use PEAR2\Pyrus\Developer\CoverageAnalyzer\Aggregator,
01567     PEAR2\Pyrus\Developer\CoverageAnalyzer\AbstractSourceDecorator;
01568 class PerTest extends \PEAR2\Pyrus\Developer\CoverageAnalyzer\SourceFile
01569 {
01570     protected $testname;
01571 
01572     function __construct($path, Aggregator $agg, $testpath, $sourcepath, $testname)
01573     {
01574         $this->testname = $testname;
01575         parent::__construct($path, $agg, $testpath, $sourcepath);
01576     }
01577 
01578     function setCoverage()
01579     {
01580         $this->coverage = $this->aggregator->retrieveCoverageByTest($this->path, $this->testname);
01581     }
01582 
01583     function coveredLines()
01584     {
01585         $info = $this->aggregator->coverageInfoByTest($this->path, $this->testname);
01586         return $info[0];
01587     }
01588 
01589     function render(AbstractSourceDecorator $decorator = null)
01590     {
01591         if ($decorator === null) {
01592             $decorator = new DefaultSourceDecorator('.');
01593         }
01594         return $decorator->render($this, $this->testname);
01595     }
01596 
01597     function coveragePercentage()
01598     {
01599         return $this->aggregator->coveragePercentage($this->path, $this->testname);
01600     }
01601 }
01602 }
01603 ?>
01604 <?php
01605 namespace PEAR2\Pyrus\Developer\CoverageAnalyzer {
01606 $class = "PEAR2\Pyrus\Developer\CoverageAnalyzer\Web\View";
01607 var_dump(str_replace("PEAR2\Pyrus\Developer\CoverageAnalyzer", "", $class));
01608 $view = new Web\View;
01609 $rooturl = $_SERVER["REQUEST_URI"];
01610 $controller = new Web\Controller($view, $rooturl);
01611 $controller->route();
01612 }нl]dnZG|   GBMB