1<?php 2function __autoload($class) 3{ 4 $class = str_replace("PEAR2\Pyrus\Developer\CoverageAnalyzer", "", $class); 5 include "phar://" . __FILE__ . "/" . str_replace("\\", "/", $class) . ".php"; 6} 7Phar::webPhar("pear2coverage.phar.php"); 8echo "This phar is a web application, run within your web browser to use\n"; 9exit -1; 10__HALT_COMPILER(); ?> 11� 12Web/Controller.php� �0�I� ���նWeb/View.phpS:�0�IS:Le�Web/Aggregator.php�0�Iݺ.b�Web/Exception.phph�0�IhpZ�SourceFile.php:�0�I:~��0�Aggregator.php��0�I��3n\� 12Exception.phpd�0�Id@ 12��� 13Sqlite.php0X�0�I0X�{�,�SourceFile/PerTest.php��0�I��R� index.phpF�0�IFk!�_�<?php 14namespace PEAR2\Pyrus\Developer\CoverageAnalyzer\Web { 15use PEAR2\Pyrus\Developer\CoverageAnalyzer\Sqlite; 16class Controller { 17 protected $view; 18 protected $sqlite; 19 protected $rooturl; 20 21 function __construct(View $view, $rooturl) 22 { 23 $this->view = $view; 24 $view->setController($this); 25 $this->rooturl = $rooturl; 26 } 27 28 function route() 29 { 30 if (!isset($_SESSION['fullpath'])) { 31 if (isset($_POST['setdatabase'])) { 32 if (file_exists($_POST['setdatabase'])) { 33 $this->sqlite = new \Sqlite3($_POST['setdatabase']); 34 $_SESSION['fullpath'] = $_POST['setdatabase']; 35 return $this->view->TOC($this->sqlite); 36 } 37 } 38 return $this->getDatabase(); 39 } else { 40 $this->sqlite = new \Sqlite3($_POST['setdatabase']); 41 if (isset($_GET['test'])) { 42 if ($_GET['test'] === 'TOC') { 43 return $this->view->testTOC($this->sqlite); 44 } 45 if (isset($_GET['file'])) { 46 return $this->view->fileCoverage($this->sqlite, $_GET['file'], $_GET['test']); 47 } 48 return $this->view->testTOC($this->sqlite, $_GET['test']); 49 } 50 if (isset($_GET['file'])) { 51 if (isset($_GET['line'])) { 52 return $this->view->fileLineTOC($this->sqlite, $_GET['file'], $_GET['line']); 53 } 54 return $this->view->fileCoverage($this->sqlite, $_GET['file']); 55 } 56 return $this->view->TOC($this->sqlite); 57 } 58 } 59 60 function getFileLink($file, $test = null, $line = null) 61 { 62 if ($line) { 63 return $this->rooturl . '?file=' . urlencode($file) . '&line=' . $line; 64 } 65 if ($test) { 66 return $this->rooturl . '?file=' . urlencode($file) . '&test=' . $test; 67 } 68 return $this->rooturl . '?file=' . urlencode($file); 69 } 70 71 function getTOCLink($test = false) 72 { 73 if ($test === false) { 74 return $this->rooturl; 75 } 76 if ($test === true) { 77 return $this->rooturl . '?test=TOC'; 78 } 79 if ($test) { 80 return $this->rooturl . '?test=' . urlencode($test); 81 } 82 } 83 84 function getDatabase() 85 { 86 $this->sqlite = $this->view->getDatabase(); 87 } 88} 89} 90?> 91<?php 92namespace PEAR2\Pyrus\Developer\CoverageAnalyzer\Web { 93/** 94 * Takes a source file and outputs HTML source highlighting showing the 95 * number of hits on each line, highlights un-executed lines in red 96 */ 97class View 98{ 99 protected $savePath; 100 protected $testPath; 101 protected $sourcePath; 102 protected $source; 103 protected $controller; 104 105 function getDatabase() 106 { 107 $output = new \XMLWriter; 108 if (!$output->openUri('php://output')) { 109 throw new Exception('Cannot open output - this should never happen'); 110 } 111 $output->startElement('html'); 112 $output->startElement('head'); 113 $output->writeElement('title', 'Enter a path to the database'); 114 $output->endElement(); 115 $output->startElement('body'); 116 $output->writeElement('h2', 'Please enter the path to a coverage database'); 117 $output->startElement('form'); 118 $output->writeAttribute('name', 'getdatabase'); 119 $output->writeAttribute('method', 'POST'); 120 $output->writeAttribute('action', $this->controller->getTOCLink()); 121 $output->startElement('input'); 122 $output->writeAttribute('type', 'text'); 123 $output->writeAttribute('name', 'setdatabase'); 124 $output->endElement(); 125 $output->startElement('input'); 126 $output->writeAttribute('type', 'submit'); 127 $output->endElement(); 128 $output->endElement(); 129 $output->endElement(); 130 $output->endElement(); 131 $output->endDocument(); 132 } 133 134 function setController($controller) 135 { 136 $this->controller = $controller; 137 } 138 139 function TOC($sqlite) 140 { 141 $coverage = $sqlite->retrieveProjectCoverage(); 142 $this->renderSummary($sqlite, $sqlite->retrievePaths(), false, $coverage[1], $covered[0]); 143 } 144 145 function testTOC($sqlite) 146 { 147 $this->renderTestSummary($sqlite); 148 } 149 150 function fileLineTOC($sqlite, $file, $line) 151 { 152 153 } 154 155 function fileCoverage($sqlite, $file, $test = null) 156 { 157 158 } 159 160 function mangleFile($path, $istest = false) 161 { 162 return $this->controller->getFileLink($path, $istest); 163 } 164 165 function mangleTestFile($path) 166 { 167 return $this->controller->getTOClink($path); 168 } 169 170 function getLineLink($name, $line) 171 { 172 return $this->controller->getFileLink($name, null, $line); 173 } 174 175 function renderLineSummary($name, $line, $testpath, $tests) 176 { 177 $output = new \XMLWriter; 178 if (!$output->openUri($this->getLinePath($name, $line))) { 179 throw new Exception('Cannot render ' . $name . ' line ' . $line . ', opening XML failed'); 180 } 181 $output->setIndentString(' '); 182 $output->setIndent(true); 183 $output->startElement('html'); 184 $output->startElement('head'); 185 $output->writeElement('title', 'Tests covering line ' . $line . ' of ' . $name); 186 $output->startElement('link'); 187 $output->writeAttribute('href', 'cover.css'); 188 $output->writeAttribute('rel', 'stylesheet'); 189 $output->writeAttribute('type', 'text/css'); 190 $output->endElement(); 191 $output->endElement(); 192 $output->startElement('body'); 193 $output->writeElement('h2', 'Tests covering line ' . $line . ' of ' . $name); 194 $output->startElement('p'); 195 $output->startElement('a'); 196 $output->writeAttribute('href', 'index.html'); 197 $output->text('Aggregate Code Coverage for all tests'); 198 $output->endElement(); 199 $output->endElement(); 200 $output->startElement('p'); 201 $output->startElement('a'); 202 $output->writeAttribute('href', $this->mangleFile($name)); 203 $output->text('File ' . $name . ' code coverage'); 204 $output->endElement(); 205 $output->endElement(); 206 $output->startElement('ul'); 207 foreach ($tests as $testfile) { 208 $output->startElement('li'); 209 $output->startElement('a'); 210 $output->writeAttribute('href', $this->mangleTestFile($testfile)); 211 $output->text(str_replace($testpath . '/', '', $testfile)); 212 $output->endElement(); 213 $output->endElement(); 214 } 215 $output->endElement(); 216 $output->endElement(); 217 $output->endDocument(); 218 } 219 220 /** 221 * @param PEAR2\Pyrus\Developer\CodeCoverage\SourceFile $source 222 * @param string $istest path to test file this is covering, or false for aggregate 223 */ 224 function render(SourceFile $source, $istest = false) 225 { 226 $output = new \XMLWriter; 227 if (!$output->openUri($this->manglePath($source->name(), $istest))) { 228 throw new Exception('Cannot render ' . $source->name() . ', opening XML failed'); 229 } 230 $output->setIndent(false); 231 $output->startElement('html'); 232 $output->text("\n "); 233 $output->startElement('head'); 234 $output->text("\n "); 235 if ($istest) { 236 $output->writeElement('title', 'Code Coverage for ' . $source->shortName() . ' in ' . $istest); 237 } else { 238 $output->writeElement('title', 'Code Coverage for ' . $source->shortName()); 239 } 240 $output->text("\n "); 241 $output->startElement('link'); 242 $output->writeAttribute('href', 'cover.css'); 243 $output->writeAttribute('rel', 'stylesheet'); 244 $output->writeAttribute('type', 'text/css'); 245 $output->endElement(); 246 $output->text("\n "); 247 $output->endElement(); 248 $output->text("\n "); 249 $output->startElement('body'); 250 $output->text("\n "); 251 if ($istest) { 252 $output->writeElement('h2', 'Code Coverage for ' . $source->shortName() . ' in ' . $istest); 253 } else { 254 $output->writeElement('h2', 'Code Coverage for ' . $source->shortName()); 255 } 256 $output->text("\n "); 257 $output->writeElement('h3', 'Coverage: ' . $source->coveragePercentage() . '%'); 258 $output->text("\n "); 259 $output->startElement('p'); 260 $output->startElement('a'); 261 $output->writeAttribute('href', 'index.html'); 262 $output->text('Aggregate Code Coverage for all tests'); 263 $output->endElement(); 264 $output->endElement(); 265 $output->startElement('pre'); 266 foreach ($source->source() as $num => $line) { 267 $coverage = $source->coverage($num); 268 269 $output->startElement('span'); 270 $output->writeAttribute('class', 'ln'); 271 $output->text(str_pad($num, 8, ' ', STR_PAD_LEFT)); 272 $output->endElement(); 273 274 if ($coverage === false) { 275 $output->text(str_pad(': ', 13, ' ', STR_PAD_LEFT) . $line); 276 continue; 277 } 278 279 $output->startElement('span'); 280 if ($coverage < 1) { 281 $output->writeAttribute('class', 'nc'); 282 $output->text(' '); 283 } else { 284 $output->writeAttribute('class', 'cv'); 285 if (!$istest) { 286 $output->startElement('a'); 287 $output->writeAttribute('href', $this->getLineLink($source->name(), $num)); 288 } 289 $output->text(str_pad($coverage, 10, ' ', STR_PAD_LEFT) . ' '); 290 if (!$istest) { 291 $output->endElement(); 292 $this->renderLineSummary($source->name(), $num, $source->testpath(), 293 $source->getLineLinks($num)); 294 } 295 } 296 297 $output->text(': ' . $line); 298 $output->endElement(); 299 } 300 $output->endElement(); 301 $output->text("\n "); 302 $output->endElement(); 303 $output->text("\n "); 304 $output->endElement(); 305 $output->endDocument(); 306 } 307 308 function renderSummary(Aggregator $agg, array $results, $istest = false, $total = 1, $covered = 1) 309 { 310 $output = new \XMLWriter; 311 if (!$output->openUri('php://output')) { 312 throw new Exception('Cannot render test summary, opening XML failed'); 313 } 314 $output->setIndentString(' '); 315 $output->setIndent(true); 316 $output->startElement('html'); 317 $output->startElement('head'); 318 if ($istest) { 319 $output->writeElement('title', 'Code Coverage Summary [' . $istest . ']'); 320 } else { 321 $output->writeElement('title', 'Code Coverage Summary'); 322 } 323 $output->startElement('link'); 324 $output->writeAttribute('href', 'cover.css'); 325 $output->writeAttribute('rel', 'stylesheet'); 326 $output->writeAttribute('type', 'text/css'); 327 $output->endElement(); 328 $output->endElement(); 329 $output->startElement('body'); 330 if ($istest) { 331 $output->writeElement('h2', 'Code Coverage Files for test ' . $istest); 332 } else { 333 $output->writeElement('h2', 'Code Coverage Files'); 334 $output->writeElement('h3', 'Total lines: ' . $total . ', covered lines: ' . $covered); 335 $percent = 0; 336 if ($total > 0) { 337 $percent = round(($covered / $total) * 100); 338 } 339 $output->startElement('p'); 340 if ($percent < 50) { 341 $output->writeAttribute('class', 'bad'); 342 } elseif ($percent < 75) { 343 $output->writeAttribute('class', 'ok'); 344 } else { 345 $output->writeAttribute('class', 'good'); 346 } 347 $output->text($percent . '% code coverage'); 348 $output->endElement(); 349 } 350 $output->startElement('p'); 351 $output->startElement('a'); 352 $output->writeAttribute('href', $this->controller->getTOCLink(true)); 353 $output->text('Code Coverage per PHPT test'); 354 $output->endElement(); 355 $output->endElement(); 356 $output->startElement('ul'); 357 foreach ($results as $i => $name) { 358 $source = new SourceFile($name, $agg, $this->testPath, $this->sourcePath); 359 $output->startElement('li'); 360 $percent = $source->coveragePercentage(); 361 $output->startElement('span'); 362 if ($percent < 50) { 363 $output->writeAttribute('class', 'bad'); 364 } elseif ($percent < 75) { 365 $output->writeAttribute('class', 'ok'); 366 } else { 367 $output->writeAttribute('class', 'good'); 368 } 369 $output->text(' Coverage: ' . str_pad($percent . '%', 4, ' ', STR_PAD_LEFT)); 370 $output->endElement(); 371 $output->startElement('a'); 372 $output->writeAttribute('href', $this->mangleFile($name, $istest)); 373 $output->text($source->shortName()); 374 $output->endElement(); 375 $output->endElement(); 376 } 377 $output->endElement(); 378 $output->endElement(); 379 $output->endDocument(); 380 } 381 382 function renderTestSummary(Aggregator $agg) 383 { 384 $output = new \XMLWriter; 385 if (!$output->openUri('php://output')) { 386 throw new Exception('Cannot render tests summary, opening XML failed'); 387 } 388 $output->setIndentString(' '); 389 $output->setIndent(true); 390 $output->startElement('html'); 391 $output->startElement('head'); 392 $output->writeElement('title', 'Test Summary'); 393 $output->startElement('link'); 394 $output->writeAttribute('href', 'cover.css'); 395 $output->writeAttribute('rel', 'stylesheet'); 396 $output->writeAttribute('type', 'text/css'); 397 $output->endElement(); 398 $output->endElement(); 399 $output->startElement('body'); 400 $output->writeElement('h2', 'Tests Executed, click for code coverage summary'); 401 $output->startElement('p'); 402 $output->startElement('a'); 403 $output->writeAttribute('href', $this->controller->getTOClink()); 404 $output->text('Aggregate Code Coverage for all tests'); 405 $output->endElement(); 406 $output->endElement(); 407 $output->startElement('ul'); 408 foreach ($agg->retrieveTestPaths() as $test) { 409 $output->startElement('li'); 410 $output->startElement('a'); 411 $output->writeAttribute('href', $this->mangleTestFile($test)); 412 $output->text(str_replace($agg->testpath . '/', '', $test)); 413 $output->endElement(); 414 $output->endElement(); 415 } 416 $output->endElement(); 417 $output->endElement(); 418 $output->endDocument(); 419 } 420 421 function renderTestCoverage(Aggregator $agg, $testpath, $test) 422 { 423 $reltest = str_replace($testpath . '/', '', $test); 424 $output = new \XMLWriter; 425 if (!$output->openUri('php://output')) { 426 throw new Exception('Cannot render test ' . $reltest . ' coverage, opening XML failed'); 427 } 428 $output->setIndentString(' '); 429 $output->setIndent(true); 430 $output->startElement('html'); 431 $output->startElement('head'); 432 $output->writeElement('title', 'Code Coverage Summary for test ' . $reltest); 433 $output->startElement('link'); 434 $output->writeAttribute('href', 'cover.css'); 435 $output->writeAttribute('rel', 'stylesheet'); 436 $output->writeAttribute('type', 'text/css'); 437 $output->endElement(); 438 $output->endElement(); 439 $output->startElement('body'); 440 $output->writeElement('h2', 'Code Coverage Files for test ' . $reltest); 441 $output->startElement('ul'); 442 $paths = $agg->retrievePathsForTest($test); 443 foreach ($paths as $name) { 444 echo '.'; 445 $source = new SourceFile\PerTest($name, $agg, $testpath, $basePath, $test); 446 $this->render($source, $reltest); 447 $output->startElement('li'); 448 $percent = $source->coveragePercentage(); 449 $output->startElement('span'); 450 if ($percent < 50) { 451 $output->writeAttribute('class', 'bad'); 452 } elseif ($percent < 75) { 453 $output->writeAttribute('class', 'ok'); 454 } else { 455 $output->writeAttribute('class', 'good'); 456 } 457 $output->text(' Coverage: ' . str_pad($source->coveragePercentage() . '%', 4, ' ', STR_PAD_LEFT)); 458 $output->endElement(); 459 $output->startElement('a'); 460 $output->writeAttribute('href', $this->mangleFile($name, $reltest)); 461 $output->text($source->shortName()); 462 $output->endElement(); 463 $output->endElement(); 464 } 465 echo "done\n"; 466 $output->endElement(); 467 $output->endElement(); 468 $output->endDocument(); 469 } 470 echo "done\n"; 471 } 472} 473} 474?> 475<?php 476namespace PEAR2\Pyrus\Developer\CoverageAnalyzer\Web { 477use PEAR2\Pyrus\Developer\CoverageAnalyzer; 478class Aggregator extends CoverageAnalyzer\Aggregator; 479{ 480 protected $codepath; 481 protected $testpath; 482 protected $sqlite; 483 public $totallines = 0; 484 public $totalcoveredlines = 0; 485 486 /** 487 * @var string $testpath Location of .phpt files 488 * @var string $codepath Location of code whose coverage we are testing 489 */ 490 function __construct($testpath, $codepath, $db = ':memory:') 491 { 492 $newcodepath = realpath($codepath); 493 if (!$newcodepath) { 494 if (!strpos($codepath, '://') || !file_exists($codepath)) { 495 // stream wrapper not found 496 throw new Exception('Can not find code path $codepath'); 497 } 498 } else { 499 $codepath = $newcodepath; 500 } 501 $this->sqlite = new Sqlite($codepath, $db); 502 $this->codepath = $codepath; 503 } 504 505 function retrieveLineLinks($file) 506 { 507 return $this->sqlite->retrieveLineLinks($file); 508 } 509 510 function retrievePaths() 511 { 512 return $this->sqlite->retrievePaths(); 513 } 514 515 function retrievePathsForTest($test) 516 { 517 return $this->sqlite->retrievePathsForTest($test); 518 } 519 520 function retrieveTestPaths() 521 { 522 return $this->sqlite->retrieveTestPaths(); 523 } 524 525 function coveragePercentage($sourcefile, $testfile = null) 526 { 527 return $this->sqlite->coveragePercentage($sourcefile, $testfile); 528 } 529 530 function coverageInfo($path) 531 { 532 return $this->sqlite->retrievePathCoverage($path); 533 } 534 535 function coverageInfoByTest($path, $test) 536 { 537 return $this->sqlite->retrievePathCoverageByTest($path, $test); 538 } 539 540 function retrieveCoverage($path) 541 { 542 return $this->sqlite->retrieveCoverage($path); 543 } 544 545 function retrieveCoverageByTest($path, $test) 546 { 547 return $this->sqlite->retrieveCoverageByTest($path, $test); 548 } 549 550 function retrieveXdebug($path, $testid) 551 { 552 $source = '$xdebug = ' . file_get_contents($path) . ";\n"; 553 eval($source); 554 $this->sqlite->addCoverage(str_replace('.xdebug', '.phpt', $path), $testid, $xdebug); 555 } 556 557 function scan($testpath) 558 { 559 $a = $testpath; 560 $testpath = realpath($testpath); 561 if (!$testpath) { 562 throw new Exception('Unable to process path' . $a); 563 } 564 $testpath = str_replace('\\', '/', $testpath); 565 $this->testpath = $testpath; 566 567 // first get a list of all directories 568 $dirs = $globdirs = array(); 569 $index = 0; 570 $dir = $testpath; 571 do { 572 $globdirs = glob($dir . '/*', GLOB_ONLYDIR); 573 if ($globdirs) { 574 $dirs = array_merge($dirs, $globdirs); 575 $dir = $dirs[$index++]; 576 } else { 577 while (!isset($dirs[$index++]) && $index <= count($dirs)); 578 if (isset($dirs[$index])) { 579 $dir = $dirs[$index]; 580 } 581 } 582 } while ($index <= count($dirs)); 583 584 // then find all code coverage files 585 $xdebugs = array(); 586 foreach ($dirs as $dir) { 587 $globbie = glob($dir . '/*.xdebug'); 588 $xdebugs = array_merge($xdebugs, $globbie); 589 } 590 $xdebugs = array_unique($xdebugs); 591 $modified = array(); 592 $unmodified = array(); 593 foreach ($xdebugs as $path) { 594 if ($this->sqlite->unChangedXdebug($path)) { 595 $unmodified[$path] = true; 596 continue; 597 } 598 $modified[] = $path; 599 } 600 $xdebugs = $modified; 601 sort($xdebugs); 602 // index from 1 603 array_unshift($xdebugs, ''); 604 unset($xdebugs[0]); 605 $test = array_flip($xdebugs); 606 foreach ($this->sqlite->retrieveTestPaths() as $path) { 607 $xdebugpath = str_replace('.phpt', '.xdebug', $path); 608 if (isset($test[$xdebugpath]) || isset($unmodified[$xdebugpath])) { 609 continue; 610 } 611 // remove outdated tests 612 echo "Removing results from $xdebugpath\n"; 613 $this->sqlite->removeOldTest($path, $xdebugpath); 614 } 615 return $xdebugs; 616 } 617 618 function render($toPath) 619 { 620 $decorator = new DefaultSourceDecorator($toPath, $this->testpath, $this->codepath); 621 echo "Generating project coverage data..."; 622 $coverage = $this->sqlite->retrieveProjectCoverage(); 623 echo "done\n"; 624 $decorator->renderSummary($this, $this->retrievePaths(), $this->codepath, false, $coverage[1], 625 $coverage[0]); 626 $a = $this->codepath; 627 echo "[Step 2 of 2] Rendering per-test coverage..."; 628 $decorator->renderTestCoverage($this, $this->testpath, $a); 629 echo "done\n"; 630 } 631} 632} 633?> 634<?php 635namespace PEAR2\Pyrus\Developer\CoverageAnalyzer\Web { 636class Exception extends \Exception {} 637} 638?> 639<?php 640namespace PEAR2\Pyrus\Developer\CoverageAnalyzer { 641class SourceFile 642{ 643 protected $source; 644 protected $path; 645 protected $sourcepath; 646 protected $coverage; 647 protected $aggregator; 648 protected $testpath; 649 protected $linelinks; 650 651 function __construct($path, Aggregator $agg, $testpath, $sourcepath) 652 { 653 $this->source = file($path); 654 $this->path = $path; 655 $this->sourcepath = $sourcepath; 656 657 array_unshift($this->source, ''); 658 unset($this->source[0]); // make source array indexed by line number 659 660 $this->aggregator = $agg; 661 $this->testpath = $testpath; 662 $this->setCoverage(); 663 } 664 665 function setCoverage() 666 { 667 $this->coverage = $this->aggregator->retrieveCoverage($this->path); 668 } 669 670 function aggregator() 671 { 672 return $this->aggregator; 673 } 674 675 function testpath() 676 { 677 return $this->testpath; 678 } 679 680 function render(AbstractSourceDecorator $decorator = null) 681 { 682 if ($decorator === null) { 683 $decorator = new DefaultSourceDecorator('.'); 684 } 685 return $decorator->render($this); 686 } 687 688 function coverage($line) 689 { 690 if (!isset($this->coverage[$line])) { 691 return false; 692 } 693 return $this->coverage[$line]; 694 } 695 696 function coveragePercentage() 697 { 698 return $this->aggregator->coveragePercentage($this->path); 699 } 700 701 function name() 702 { 703 return $this->path; 704 } 705 706 function shortName() 707 { 708 return str_replace($this->sourcepath . DIRECTORY_SEPARATOR, '', $this->path); 709 } 710 711 function source() 712 { 713 return $this->source; 714 } 715 716 function coveredLines() 717 { 718 $info = $this->aggregator->coverageInfo($this->path); 719 return $info[0]; 720 } 721 722 function getLineLinks($line) 723 { 724 if (!isset($this->linelinks)) { 725 $this->linelinks = $this->aggregator->retrieveLineLinks($this->path); 726 } 727 if (isset($this->linelinks[$line])) { 728 return $this->linelinks[$line]; 729 } 730 return false; 731 } 732} 733} 734?> 735<?php 736namespace PEAR2\Pyrus\Developer\CoverageAnalyzer { 737class Aggregator 738{ 739 protected $codepath; 740 protected $testpath; 741 protected $sqlite; 742 public $totallines = 0; 743 public $totalcoveredlines = 0; 744 745 /** 746 * @var string $testpath Location of .phpt files 747 * @var string $codepath Location of code whose coverage we are testing 748 */ 749 function __construct($testpath, $codepath, $db = ':memory:') 750 { 751 $newcodepath = realpath($codepath); 752 if (!$newcodepath) { 753 if (!strpos($codepath, '://') || !file_exists($codepath)) { 754 // stream wrapper not found 755 throw new Exception('Can not find code path $codepath'); 756 } 757 } else { 758 $codepath = $newcodepath; 759 } 760 $this->sqlite = new Sqlite($db, $codepath, $testpath); 761 $this->codepath = $codepath; 762 $this->sqlite->begin(); 763 echo "Scanning for xdebug coverage files..."; 764 $files = $this->scan($testpath); 765 echo "done\n"; 766 $infostring = ''; 767 echo "Parsing xdebug results\n"; 768 if (count($files)) { 769 foreach ($files as $testid => $xdebugfile) { 770 if (!file_exists(str_replace('.xdebug', '.phpt', $xdebugfile))) { 771 echo "\nWARNING: outdated .xdebug file $xdebugfile, delete this relic\n"; 772 continue; 773 } 774 $id = $this->sqlite->addTest(str_replace('.xdebug', '.phpt', $xdebugfile)); 775 echo '(' . $testid . ' of ' . count($files) . ') ' . $xdebugfile; 776 $this->retrieveXdebug($xdebugfile, $id); 777 echo "done\n"; 778 } 779 echo "done\n"; 780 $this->sqlite->updateTotalCoverage(); 781 $this->sqlite->commit(); 782 } else { 783 echo "done (no modified xdebug files)\n"; 784 } 785 } 786 787 function retrieveLineLinks($file) 788 { 789 return $this->sqlite->retrieveLineLinks($file); 790 } 791 792 function retrievePaths() 793 { 794 return $this->sqlite->retrievePaths(); 795 } 796 797 function retrievePathsForTest($test) 798 { 799 return $this->sqlite->retrievePathsForTest($test); 800 } 801 802 function retrieveTestPaths() 803 { 804 return $this->sqlite->retrieveTestPaths(); 805 } 806 807 function coveragePercentage($sourcefile, $testfile = null) 808 { 809 return $this->sqlite->coveragePercentage($sourcefile, $testfile); 810 } 811 812 function coverageInfo($path) 813 { 814 return $this->sqlite->retrievePathCoverage($path); 815 } 816 817 function coverageInfoByTest($path, $test) 818 { 819 return $this->sqlite->retrievePathCoverageByTest($path, $test); 820 } 821 822 function retrieveCoverage($path) 823 { 824 return $this->sqlite->retrieveCoverage($path); 825 } 826 827 function retrieveCoverageByTest($path, $test) 828 { 829 return $this->sqlite->retrieveCoverageByTest($path, $test); 830 } 831 832 function retrieveXdebug($path, $testid) 833 { 834 $source = '$xdebug = ' . file_get_contents($path) . ";\n"; 835 eval($source); 836 $this->sqlite->addCoverage(str_replace('.xdebug', '.phpt', $path), $testid, $xdebug); 837 } 838 839 function scan($testpath) 840 { 841 $a = $testpath; 842 $testpath = realpath($testpath); 843 if (!$testpath) { 844 throw new Exception('Unable to process path' . $a); 845 } 846 $testpath = str_replace('\\', '/', $testpath); 847 $this->testpath = $testpath; 848 849 // first get a list of all directories 850 $dirs = $globdirs = array(); 851 $index = 0; 852 $dir = $testpath; 853 do { 854 $globdirs = glob($dir . '/*', GLOB_ONLYDIR); 855 if ($globdirs) { 856 $dirs = array_merge($dirs, $globdirs); 857 $dir = $dirs[$index++]; 858 } else { 859 while (!isset($dirs[$index++]) && $index <= count($dirs)); 860 if (isset($dirs[$index])) { 861 $dir = $dirs[$index]; 862 } 863 } 864 } while ($index <= count($dirs)); 865 866 // then find all code coverage files 867 $xdebugs = array(); 868 foreach ($dirs as $dir) { 869 $globbie = glob($dir . '/*.xdebug'); 870 $xdebugs = array_merge($xdebugs, $globbie); 871 } 872 $xdebugs = array_unique($xdebugs); 873 $modified = array(); 874 $unmodified = array(); 875 foreach ($xdebugs as $path) { 876 if ($this->sqlite->unChangedXdebug($path)) { 877 $unmodified[$path] = true; 878 continue; 879 } 880 $modified[] = $path; 881 } 882 $xdebugs = $modified; 883 sort($xdebugs); 884 // index from 1 885 array_unshift($xdebugs, ''); 886 unset($xdebugs[0]); 887 $test = array_flip($xdebugs); 888 foreach ($this->sqlite->retrieveTestPaths() as $path) { 889 $xdebugpath = str_replace('.phpt', '.xdebug', $path); 890 if (isset($test[$xdebugpath]) || isset($unmodified[$xdebugpath])) { 891 continue; 892 } 893 // remove outdated tests 894 echo "Removing results from $xdebugpath\n"; 895 $this->sqlite->removeOldTest($path, $xdebugpath); 896 } 897 return $xdebugs; 898 } 899 900 function render($toPath) 901 { 902 $decorator = new DefaultSourceDecorator($toPath, $this->testpath, $this->codepath); 903 echo "Generating project coverage data..."; 904 $coverage = $this->sqlite->retrieveProjectCoverage(); 905 echo "done\n"; 906 $decorator->renderSummary($this, $this->retrievePaths(), $this->codepath, false, $coverage[1], 907 $coverage[0]); 908 $a = $this->codepath; 909 echo "[Step 2 of 2] Rendering per-test coverage..."; 910 $decorator->renderTestCoverage($this, $this->testpath, $a); 911 echo "done\n"; 912 } 913} 914} 915?> 916<?php 917namespace PEAR2\Pyrus\Developer\CoverageAnalyzer { 918class Exception extends \Exception {} 919} 920?> 921<?php 922namespace PEAR2\Pyrus\Developer\CoverageAnalyzer { 923class Sqlite 924{ 925 protected $db; 926 protected $totallines = 0; 927 protected $coveredlines = 0; 928 protected $pathCovered = array(); 929 protected $pathTotal = array(); 930 public $codepath; 931 public $testpath; 932 933 function __construct($path = ':memory:', $codepath = null, $testpath = null) 934 { 935 $this->db = new \Sqlite3($path); 936 937 $sql = 'SELECT version FROM analyzerversion'; 938 if (@$this->db->querySingle($sql) == '2.1.0') { 939 $this->codepath = $this->db->querySingle('SELECT codepath FROM paths'); 940 $this->testpath = $this->db->querySingle('SELECT testpath FROM paths'); 941 return; 942 } 943 if (!$codepath || !$testpath) { 944 throw new Exception('Both codepath and testpath must be set in ' . 945 'order to initialize a coverage database'); 946 } 947 $this->codepath = $codepath; 948 $this->testpath = $testpath; 949 // restart the database 950 echo "Upgrading database to version 2.1.0\n"; 951 $this->db->exec(' 952 DROP TABLE coverage; 953 DROP TABLE files; 954 DROP TABLE tests; 955 DROP TABLE coverage_per_file; 956 DROP TABLE xdebugs; 957 DROP TABLE analyzerversion; 958 VACUUM;'); 959 960 $this->db->exec('BEGIN'); 961 962 $query = ' 963 CREATE TABLE coverage ( 964 files_id integer NOT NULL, 965 tests_id integer NOT NULL, 966 linenumber INTEGER NOT NULL, 967 iscovered BOOL NOT NULL, 968 issource BOOL NOT NULL, 969 PRIMARY KEY (files_id, tests_id, linenumber) 970 ); 971 CREATE INDEX coverage_files_id on coverage (files_id); 972 CREATE INDEX coverage_tests_id on coverage (tests_id, issource); 973 CREATE INDEX coverage_tests_id2 on coverage (tests_id, files_id, issource); 974 CREATE INDEX coverage_linenumber on coverage (files_id, linenumber); 975 CREATE INDEX coverage_issource on coverage (issource); 976 977 CREATE TABLE coverage_per_file ( 978 files_id integer NOT NULL, 979 linenumber INTEGER NOT NULL, 980 coverage INTEGER NOT NULL, 981 PRIMARY KEY (files_id, linenumber) 982 ); 983 CREATE INDEX coverage_per_file_linenumber on coverage_per_file (linenumber); 984 '; 985 $worked = $this->db->exec($query); 986 if (!$worked) { 987 @$this->db->exec('ROLLBACK'); 988 $error = $this->db->lastErrorMsg(); 989 throw new Exception('Unable to create Code Coverage SQLite3 database: ' . $error); 990 } 991 992 $query = ' 993 CREATE TABLE files ( 994 id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 995 filepath TEXT(500) NOT NULL, 996 filepathmd5 TEXT(32) NOT NULL, 997 issource BOOL NOT NULL, 998 UNIQUE (filepath) 999 ); 1000 CREATE INDEX files_issource on files (issource); 1001 '; 1002 $worked = $this->db->exec($query); 1003 if (!$worked) { 1004 @$this->db->exec('ROLLBACK'); 1005 $error = $this->db->lastErrorMsg(); 1006 throw new Exception('Unable to create Code Coverage SQLite3 database: ' . $error); 1007 } 1008 1009 $query = ' 1010 CREATE TABLE xdebugs ( 1011 xdebugpath TEXT(500) NOT NULL, 1012 xdebugpathmd5 TEXT(32) NOT NULL, 1013 PRIMARY KEY (xdebugpath) 1014 );'; 1015 $worked = $this->db->exec($query); 1016 if (!$worked) { 1017 @$this->db->exec('ROLLBACK'); 1018 $error = $this->db->lastErrorMsg(); 1019 throw new Exception('Unable to create Code Coverage SQLite3 database: ' . $error); 1020 } 1021 1022 $query = ' 1023 CREATE TABLE tests ( 1024 id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 1025 testpath TEXT(500) NOT NULL, 1026 testpathmd5 TEXT(32) NOT NULL, 1027 UNIQUE (testpath) 1028 );'; 1029 $worked = $this->db->exec($query); 1030 if (!$worked) { 1031 @$this->db->exec('ROLLBACK'); 1032 $error = $this->db->lastErrorMsg(); 1033 throw new Exception('Unable to create Code Coverage SQLite3 database: ' . $error); 1034 } 1035 1036 $query = ' 1037 CREATE TABLE analyzerversion ( 1038 version TEXT(5) NOT NULL 1039 ); 1040 1041 INSERT INTO analyzerversion VALUES("2.0.0"); 1042 1043 CREATE TABLE paths ( 1044 codepath TEXT NOT NULL, 1045 testpath TEXT NOT NULL, 1046 ); 1047 1048 INSERT INTO paths ("' . $this->db->escapeString($codepath) . '", "' . 1049 $this->db->escapeString($testpath). '")'; 1050 $worked = $this->db->exec($query); 1051 if (!$worked) { 1052 @$this->db->exec('ROLLBACK'); 1053 $error = $this->db->lastErrorMsg(); 1054 throw new Exception('Unable to create Code Coverage SQLite3 database: ' . $error); 1055 } 1056 $this->db->exec('COMMIT'); 1057 } 1058 1059 function retrieveLineLinks($file) 1060 { 1061 $id = $this->getFileId($file); 1062 $query = 'SELECT t.testpath, c.linenumber 1063 FROM 1064 coverage c, tests t 1065 WHERE 1066 c.files_id=' . $id . ' AND t.id=c.tests_id' ; 1067 $result = $this->db->query($query); 1068 if (!$result) { 1069 $error = $this->db->lastErrorMsg(); 1070 throw new Exception('Cannot retrieve line links for ' . $file . 1071 ' line #' . $line . ': ' . $error); 1072 } 1073 1074 $ret = array(); 1075 while ($res = $result->fetchArray(SQLITE3_ASSOC)) { 1076 $ret[$res['linenumber']][] = $res['testpath']; 1077 } 1078 return $ret; 1079 } 1080 1081 function retrieveTestPaths() 1082 { 1083 $query = 'SELECT testpath from tests ORDER BY testpath'; 1084 $result = $this->db->query($query); 1085 if (!$result) { 1086 $error = $this->db->lastErrorMsg(); 1087 throw new Exception('Cannot retrieve test paths :' . $error); 1088 } 1089 $ret = array(); 1090 while ($res = $result->fetchArray(SQLITE3_NUM)) { 1091 $ret[] = $res[0]; 1092 } 1093 return $ret; 1094 } 1095 1096 function retrievePathsForTest($test, $all = 0) 1097 { 1098 $id = $this->getTestId($test); 1099 if ($all) { 1100 $query = 'SELECT DISTINCT filepath 1101 FROM coverage c, files 1102 WHERE c.tests_id=' . $id . ' 1103 AND files.id=c.files_id 1104 GROUP BY c.files_id 1105 ORDER BY filepath'; 1106 } else { 1107 $query = 'SELECT DISTINCT filepath 1108 FROM coverage c, files 1109 WHERE c.tests_id=' . $id . ' 1110 AND c.issource=1 1111 AND files.id=c.files_id 1112 GROUP BY c.files_id 1113 ORDER BY filepath'; 1114 } 1115 $result = $this->db->query($query); 1116 if (!$result) { 1117 $error = $this->db->lastErrorMsg(); 1118 throw new Exception('Cannot retrieve file paths for test ' . $test . ':' . $error); 1119 } 1120 $ret = array(); 1121 while ($res = $result->fetchArray(SQLITE3_NUM)) { 1122 $ret[] = $res[0]; 1123 } 1124 return $ret; 1125 } 1126 1127 function retrievePaths($all = 0) 1128 { 1129 if ($all) { 1130 $query = 'SELECT filepath from files ORDER BY filepath'; 1131 } else { 1132 $query = 'SELECT filepath from files WHERE issource=1 ORDER BY filepath'; 1133 } 1134 $result = $this->db->query($query); 1135 if (!$result) { 1136 $error = $this->db->lastErrorMsg(); 1137 throw new Exception('Cannot retrieve file paths :' . $error); 1138 } 1139 $ret = array(); 1140 while ($res = $result->fetchArray(SQLITE3_NUM)) { 1141 $ret[] = $res[0]; 1142 } 1143 return $ret; 1144 } 1145 1146 function coveragePercentage($sourcefile, $testfile = null) 1147 { 1148 if ($testfile) { 1149 $coverage = $this->retrievePathCoverageByTest($sourcefile, $testfile); 1150 } else { 1151 $coverage = $this->retrievePathCoverage($sourcefile); 1152 } 1153 return round(($coverage[0] / $coverage[1]) * 100); 1154 } 1155 1156 function retrieveProjectCoverage() 1157 { 1158 if ($this->totallines) { 1159 return array($this->coveredlines, $this->totallines); 1160 } 1161 $query = 'SELECT COUNT(linenumber),filepath FROM coverage_per_file, files 1162 WHERE files.id=coverage_per_file.files_id 1163 GROUP BY files_id'; 1164 $result = $this->db->query($query); 1165 if (!$result) { 1166 $error = $this->db->lastErrorMsg(); 1167 throw new Exception('Cannot retrieve coverage for ' . $path. ': ' . $error); 1168 } 1169 while ($res = $result->fetchArray(SQLITE3_NUM)) { 1170 $this->pathTotal[$res[1]] = $res[0]; 1171 $this->totallines += $res[0]; 1172 } 1173 1174 $query = 'SELECT COUNT(linenumber),filepath FROM coverage_per_file, files 1175 WHERE coverage > 0 AND files.id=coverage_per_file.files_id 1176 GROUP BY files_id'; 1177 $result = $this->db->query($query); 1178 if (!$result) { 1179 $error = $this->db->lastErrorMsg(); 1180 throw new Exception('Cannot retrieve coverage for ' . $path. ': ' . $error); 1181 } 1182 while ($res = $result->fetchArray(SQLITE3_NUM)) { 1183 $this->pathCovered[$res[1]] = $res[0]; 1184 $this->coveredlines += $res[0]; 1185 } 1186 1187 return array($this->coveredlines, $this->totallines); 1188 } 1189 1190 function retrievePathCoverage($path) 1191 { 1192 if (!$this->totallines) { 1193 // set up the cache 1194 $this->retrieveProjectCoverage(); 1195 } 1196 if (!isset($this->pathCovered[$path])) { 1197 return array(0, 0); 1198 } 1199 return array($this->pathCovered[$path], $this->pathTotal[$path]); 1200 } 1201 1202 function retrievePathCoverageByTest($path, $test) 1203 { 1204 $id = $this->getFileId($path); 1205 $testid = $this->getTestId($test); 1206 1207 $query = 'SELECT COUNT(linenumber) 1208 FROM coverage 1209 WHERE issource=1 AND files_id=' . $id . ' AND tests_id=' . $testid; 1210 $result = $this->db->query($query); 1211 if (!$result) { 1212 $error = $this->db->lastErrorMsg(); 1213 throw new Exception('Cannot retrieve path coverage for ' . $path . 1214 ' in test ' . $test . ': ' . $error); 1215 } 1216 1217 $ret = array(); 1218 while ($res = $result->fetchArray(SQLITE3_NUM)) { 1219 $total = $res[0]; 1220 } 1221 1222 $query = 'SELECT COUNT(linenumber) 1223 FROM coverage 1224 WHERE issource=1 AND iscovered AND files_id=' . $id. ' AND tests_id=' . $testid; 1225 $result = $this->db->query($query); 1226 if (!$result) { 1227 $error = $this->db->lastErrorMsg(); 1228 throw new Exception('Cannot retrieve path coverage for ' . $path . 1229 ' in test ' . $test . ': ' . $error); 1230 } 1231 1232 $ret = array(); 1233 while ($res = $result->fetchArray(SQLITE3_NUM)) { 1234 $covered = $res[0]; 1235 } 1236 return array($covered, $total); 1237 } 1238 1239 function retrieveCoverageByTest($path, $test) 1240 { 1241 $id = $this->getFileId($path); 1242 $testid = $this->getTestId($test); 1243 1244 $query = 'SELECT iscovered as coverage, linenumber FROM coverage 1245 WHERE issource=1 AND files_id=' . $id . ' AND tests_id=' . $testid; 1246 $result = $this->db->query($query); 1247 if (!$result) { 1248 $error = $this->db->lastErrorMsg(); 1249 throw new Exception('Cannot retrieve test ' . $test . 1250 ' coverage for ' . $path. ': ' . $error); 1251 } 1252 1253 $ret = array(); 1254 while ($res = $result->fetchArray(SQLITE3_ASSOC)) { 1255 $ret[$res['linenumber']] = $res['coverage']; 1256 } 1257 return $ret; 1258 } 1259 1260 function getFileId($path) 1261 { 1262 $query = 'SELECT id FROM files WHERE filepath=:filepath'; 1263 $stmt = $this->db->prepare($query); 1264 $stmt->bindValue(':filepath', $path); 1265 if (!($result = $stmt->execute())) { 1266 throw new Exception('Unable to retrieve file ' . $path . ' id from database'); 1267 } 1268 while ($id = $result->fetchArray(SQLITE3_NUM)) { 1269 return $id[0]; 1270 } 1271 throw new Exception('Unable to retrieve file ' . $path . ' id from database'); 1272 } 1273 1274 function getTestId($path) 1275 { 1276 $query = 'SELECT id FROM tests WHERE testpath=:filepath'; 1277 $stmt = $this->db->prepare($query); 1278 $stmt->bindValue(':filepath', $path); 1279 if (!($result = $stmt->execute())) { 1280 throw new Exception('Unable to retrieve test file ' . $path . ' id from database'); 1281 } 1282 while ($id = $result->fetchArray(SQLITE3_NUM)) { 1283 return $id[0]; 1284 } 1285 throw new Exception('Unable to retrieve test file ' . $path . ' id from database'); 1286 } 1287 1288 function removeOldTest($testpath, $xdebugpath) 1289 { 1290 try { 1291 $id = $this->getTestId($testpath); 1292 } catch (\Exception $e) { 1293 // get a unique ID 1294 return $this->db->querySingle('SELECT COUNT(id) from tests')+1; 1295 } 1296 $this->db->exec('DELETE FROM tests WHERE id=' . $id); 1297 $this->db->exec('DELETE FROM coverage WHERE tests_id=' . $id); 1298 $this->db->exec('DELETE FROM xdebugs WHERE xdebugpath="' . $this->db->escapeString($xdebugpath) . '"'); 1299 return $id; 1300 } 1301 1302 function addTest($testpath) 1303 { 1304 $id = $this->removeOldTest($testpath, str_replace('.phpt', '.xdebug', $testpath)); 1305 $query = 'INSERT INTO tests 1306 (testpath, testpathmd5) 1307 VALUES(:testpath, :md5)'; 1308 $stmt = $this->db->prepare($query); 1309 $stmt->bindValue(':testpath', $testpath); 1310 $md5 = md5_file($testpath); 1311 $stmt->bindValue(':md5', $md5); 1312 $stmt->execute(); 1313 $id = $this->db->lastInsertRowID(); 1314 1315 $query = 'INSERT INTO xdebugs 1316 (xdebugpath, xdebugpathmd5) 1317 VALUES(:testpath, :md5)'; 1318 $stmt = $this->db->prepare($query); 1319 $stmt->bindValue(':testpath', str_replace('.phpt', '.xdebug', $testpath)); 1320 $md5 = md5_file(str_replace('.phpt', '.xdebug', $testpath)); 1321 $stmt->bindValue(':md5', $md5); 1322 $stmt->execute(); 1323 return $id; 1324 } 1325 1326 function unChangedXdebug($path) 1327 { 1328 $query = 'SELECT xdebugpathmd5 FROM xdebugs 1329 WHERE xdebugpath=:path'; 1330 $stmt = $this->db->prepare($query); 1331 $stmt->bindValue(':path', $path); 1332 $result = $stmt->execute(); 1333 if (!$result) { 1334 return false; 1335 } 1336 $md5 = 0; 1337 while ($res = $result->fetchArray(SQLITE3_NUM)) { 1338 $md5 = $res[0]; 1339 } 1340 if (!$md5) { 1341 return false; 1342 } 1343 if ($md5 == md5_file($path)) { 1344 return true; 1345 } 1346 return false; 1347 } 1348 1349 function addFile($filepath, $issource = 0) 1350 { 1351 $query = 'SELECT id FROM files WHERE filepath=:filepath'; 1352 $stmt = $this->db->prepare($query); 1353 $stmt->bindParam(':filepath', $filepath); 1354 if (!($result = $stmt->execute())) { 1355 throw new Exception('Unable to add file ' . $filepath . ' to database'); 1356 } 1357 while ($id = $result->fetchArray(SQLITE3_NUM)) { 1358 $query = 'UPDATE files SET filepathmd5=:md5 WHERE filepath=:filepath'; 1359 $stmt = $this->db->prepare($query); 1360 $stmt->bindParam(':filepath', $filepath); 1361 $md5 = md5_file($filepath); 1362 $stmt->bindParam(':md5', $md5); 1363 if (!$stmt->execute()) { 1364 throw new Exception('Unable to update file ' . $filepath . ' md5 in database'); 1365 } 1366 return $id[0]; 1367 } 1368 $stmt->clear(); 1369 $query = 'INSERT INTO files 1370 (filepath, filepathmd5, issource) 1371 VALUES(:testpath, :md5, :issource)'; 1372 $stmt = $this->db->prepare($query); 1373 $stmt->bindValue(':testpath', $filepath); 1374 $md5 = md5_file($filepath); 1375 $stmt->bindValue(':md5', $md5); 1376 $stmt->bindValue(':issource', $issource); 1377 if (!$stmt->execute()) { 1378 throw new Exception('Unable to add file ' . $filepath . ' to database'); 1379 } 1380 return $this->db->lastInsertRowID(); 1381 } 1382 1383 function getTotalCoverage($file, $linenumber) 1384 { 1385 $query = 'SELECT coveragecount FROM coverage_per_file 1386 WHERE files_id=' . $this->getFileId($file) . ' AND linenumber=' . $linenumber; 1387 $result = $this->db->query($query); 1388 if (!$result) { 1389 return false; 1390 } 1391 $coverage = 0; 1392 while ($res = $result->fetchArray(SQLITE3_NUM)) { 1393 $coverage = $res[0]; 1394 } 1395 return $coverage; 1396 } 1397 1398 function retrieveCoverage($path) 1399 { 1400 $id = $this->getFileId($path); 1401 $query = 'SELECT coverage, linenumber FROM coverage_per_file 1402 WHERE files_id=' . $id . ' 1403 ORDER BY linenumber ASC'; 1404 $result = $this->db->query($query); 1405 if (!$result) { 1406 $error = $this->db->lastErrorMsg(); 1407 throw new Exception('Cannot retrieve coverage for ' . $path. ': ' . $error); 1408 } 1409 1410 $ret = array(); 1411 while ($res = $result->fetchArray(SQLITE3_ASSOC)) { 1412 $ret[$res['linenumber']] = $res['coverage']; 1413 } 1414 return $ret; 1415 } 1416 1417 /** 1418 * This is used to get the coverage which is then inserted into our 1419 * intermediate coverage_per_file table to speed things up at rendering 1420 */ 1421 function retrieveSlowCoverage($id) 1422 { 1423 $query = 'SELECT SUM(iscovered) as coverage, linenumber FROM coverage WHERE files_id=' . $id . ' 1424 GROUP BY linenumber'; 1425 $result = $this->db->query($query); 1426 if (!$result) { 1427 $error = $this->db->lastErrorMsg(); 1428 throw new Exception('Cannot retrieve coverage for ' . $path. ': ' . $error); 1429 } 1430 1431 $ret = array(); 1432 while ($res = $result->fetchArray(SQLITE3_ASSOC)) { 1433 $ret[$res['linenumber']] = $res['coverage']; 1434 } 1435 return $ret; 1436 } 1437 1438 function updateTotalCoverage() 1439 { 1440 $query = 'DELETE FROM coverage_per_file'; 1441 $this->db->exec($query); 1442 echo "Updating coverage per-file intermediate table\n"; 1443 foreach ($this->retrievePaths() as $path) { 1444 echo "."; 1445 $id = $this->getFileId($path); 1446 foreach ($this->retrieveSlowCoverage($id) as $linenumber => $coverage) { 1447 $query = 'INSERT INTO coverage_per_file 1448 (files_id, coverage, linenumber) 1449 VALUES(' . $id . ',' . $coverage . ',' . $linenumber .')'; 1450 $this->db->exec($query); 1451 } 1452 } 1453 echo "done\n"; 1454 } 1455 1456 function addCoverage($testpath, $testid, $xdebug) 1457 { 1458 foreach ($xdebug as $path => $results) { 1459 if (!file_exists($path)) { 1460 continue; 1461 } 1462 if (strpos($path, $this->codepath) !== 0) { 1463 $issource = 0; 1464 } else { 1465 if (strpos($path, $this->testpath) === 0) { 1466 $issource = 0; 1467 } else { 1468 $issource = 1; 1469 } 1470 } 1471 echo "."; 1472 $id = $this->addFile($path, $issource); 1473 foreach ($results as $line => $info) { 1474 if ($info > 0) { 1475 $res = 1; 1476 } else { 1477 $res = 0; 1478 } 1479 $query = 'INSERT INTO coverage 1480 (files_id, tests_id, linenumber, iscovered, issource) 1481 VALUES(' . $id . ', ' . $testid . ', ' . $line . ', ' . $res . ',' . $issource . ')'; 1482 1483 $worked = $this->db->exec($query); 1484 if (!$worked) { 1485 $error = $this->db->lastErrorMsg(); 1486 throw new Exception('Cannot add coverage for test ' . $testpath . 1487 ', covered file ' . $path . ': ' . $error); 1488 } 1489 } 1490 } 1491 } 1492 1493 function begin() 1494 { 1495 $this->db->exec('BEGIN'); 1496 } 1497 1498 function commit() 1499 { 1500 $this->db->exec('COMMIT'); 1501 } 1502 1503 /** 1504 * Retrieve a list of .phpt tests that either have been modified, 1505 * or the files they access have been modified 1506 * @return array 1507 */ 1508 function getModifiedTests() 1509 { 1510 $modifiedPaths = array(); 1511 $modifiedTests = array(); 1512 $paths = $this->retrievePaths(1); 1513 echo "Scanning ", count($paths), " source files"; 1514 foreach ($paths as $path) { 1515 echo '.'; 1516 $query = ' 1517 SELECT id, filepathmd5 FROM files where filepath="' . 1518 $this->db->escapeString($path) . '"'; 1519 $result = $this->db->query($query); 1520 while ($res = $result->fetchArray(SQLITE3_ASSOC)) { 1521 if (md5_file($path) == $res['filepathmd5']) { 1522 break; 1523 } 1524 $modifiedPaths[] = $path; 1525 // file is modified, get a list of tests that execute this file 1526 $query = ' 1527 SELECT t.testpath 1528 FROM coverage c, tests t 1529 WHERE 1530 c.files_id=' . $res['id'] . ' 1531 AND t.id=c.tests_id'; 1532 $result2 = $this->db->query($query); 1533 while ($res = $result2->fetchArray(SQLITE3_NUM)) { 1534 $modifiedTests[$res[0]] = true; 1535 } 1536 break; 1537 } 1538 } 1539 echo "done\n"; 1540 echo count($modifiedPaths), ' modified files resulting in ', 1541 count($modifiedTests), " modified tests\n"; 1542 $paths = $this->retrieveTestPaths(); 1543 echo "Scanning ", count($paths), " test paths"; 1544 foreach ($paths as $path) { 1545 echo '.'; 1546 $query = ' 1547 SELECT id, testpathmd5 FROM tests where testpath="' . 1548 $this->db->escapeString($path) . '"'; 1549 $result = $this->db->query($query); 1550 while ($res = $result->fetchArray(SQLITE3_ASSOC)) { 1551 if (md5_file($path) != $res['testpathmd5']) { 1552 $modifiedTests[$path] = true; 1553 } 1554 } 1555 } 1556 echo "done\n"; 1557 echo count($modifiedTests), " tests should be re-run\n"; 1558 return array_keys($modifiedTests); 1559 } 1560} 1561} 1562?><?php 1563namespace PEAR2\Pyrus\Developer\CoverageAnalyzer\SourceFile { 1564use PEAR2\Pyrus\Developer\CoverageAnalyzer\Aggregator, 1565 PEAR2\Pyrus\Developer\CoverageAnalyzer\AbstractSourceDecorator; 1566class PerTest extends \PEAR2\Pyrus\Developer\CoverageAnalyzer\SourceFile 1567{ 1568 protected $testname; 1569 1570 function __construct($path, Aggregator $agg, $testpath, $sourcepath, $testname) 1571 { 1572 $this->testname = $testname; 1573 parent::__construct($path, $agg, $testpath, $sourcepath); 1574 } 1575 1576 function setCoverage() 1577 { 1578 $this->coverage = $this->aggregator->retrieveCoverageByTest($this->path, $this->testname); 1579 } 1580 1581 function coveredLines() 1582 { 1583 $info = $this->aggregator->coverageInfoByTest($this->path, $this->testname); 1584 return $info[0]; 1585 } 1586 1587 function render(AbstractSourceDecorator $decorator = null) 1588 { 1589 if ($decorator === null) { 1590 $decorator = new DefaultSourceDecorator('.'); 1591 } 1592 return $decorator->render($this, $this->testname); 1593 } 1594 1595 function coveragePercentage() 1596 { 1597 return $this->aggregator->coveragePercentage($this->path, $this->testname); 1598 } 1599} 1600} 1601?> 1602<?php 1603namespace PEAR2\Pyrus\Developer\CoverageAnalyzer { 1604$class = "PEAR2\Pyrus\Developer\CoverageAnalyzer\Web\View"; 1605var_dump(str_replace("PEAR2\Pyrus\Developer\CoverageAnalyzer", "", $class)); 1606$view = new Web\View; 1607$rooturl = $_SERVER["REQUEST_URI"]; 1608$controller = new Web\Controller($view, $rooturl); 1609$controller->route(); 1610}нl�]d�nZ����G�|��GBMB