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���
12������������������Web/Controller.php�	���0�I�	�����ն���������Web/View.phpS:���0�IS:��Le����������Web/Aggregator.php���0�I��ݺ.b����������Web/Exception.phph����0�Ih���pZ����������SourceFile.php:���0�I:��~��0����������Aggregator.php����0�I����3n\�������
12���Exception.phpd����0�Id���@
12���������
13���Sqlite.php0X���0�I0X���{�,����������SourceFile/PerTest.php����0�I����R�������	���index.phpF���0�IF��k!�_�������<?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