1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Tester\CodeCoverage;
9:
10:
11: 12: 13: 14: 15: 16: 17:
18: class ReportGenerator
19: {
20:
21: public $acceptFiles = array('php', 'phpc', 'phpt', 'phtml');
22:
23:
24: private $data;
25:
26:
27: private $sourceDir;
28:
29:
30: private $title;
31:
32:
33: private $files = array();
34:
35:
36: private $totalSum = 0;
37:
38:
39: private $coveredSum = 0;
40:
41:
42: public static $classes = array(
43: 1 => 't',
44: -1 => 'u',
45: -2 => 'dead',
46: );
47:
48:
49: 50: 51: 52:
53: public function __construct($file, $sourceDir, $title = NULL)
54: {
55: if (!is_file($file)) {
56: throw new \Exception("File '$file' is missing.");
57: }
58:
59: $this->data = @unserialize(file_get_contents($file));
60: if (!is_array($this->data)) {
61: throw new \Exception("Content of file '$file' is invalid.");
62: }
63:
64: if (!$sourceDir) {
65: $sourceDir = key($this->data);
66: for ($i = 0; $i < strlen($sourceDir); $i++) {
67: foreach ($this->data as $s => $foo) {
68: if (!isset($s[$i]) || $sourceDir[$i] !== $s[$i]) {
69: $sourceDir = substr($sourceDir, 0, $i);
70: break 2;
71: }
72: }
73: }
74: $sourceDir = dirname($sourceDir . 'x');
75:
76: } elseif (!is_dir($sourceDir)) {
77: throw new \Exception("Directory '$sourceDir' is missing.");
78: }
79:
80: $this->sourceDir = realpath($sourceDir) . DIRECTORY_SEPARATOR;
81: $this->title = $title;
82: }
83:
84:
85: public function render($file = NULL)
86: {
87: $this->setupHighlight();
88: $this->parse();
89:
90: $title = $this->title;
91: $classes = self::$classes;
92: $files = $this->files;
93: $totalSum = $this->totalSum;
94: $coveredSum = $this->coveredSum;
95:
96: $handle = $file ? @fopen($file, 'w') : STDOUT;
97: if (!$handle) {
98: throw new \Exception("Unable to write to file '$file'.");
99: }
100: ob_start(function($buffer) use ($handle) { fwrite($handle, $buffer); }, 4096);
101: include __DIR__ . '/template.phtml';
102: ob_end_flush();
103: }
104:
105:
106: private function setupHighlight()
107: {
108: ini_set('highlight.comment', '#999; font-style: italic');
109: ini_set('highlight.default', '#000');
110: ini_set('highlight.html', '#06B');
111: ini_set('highlight.keyword', '#D24; font-weight: bold');
112: ini_set('highlight.string', '#080');
113: }
114:
115:
116: private function parse()
117: {
118: if (count($this->files) > 0) {
119: return;
120: }
121:
122: $this->files = array();
123: foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->sourceDir)) as $entry) {
124: if (substr($entry->getBasename(), 0, 1) === '.'
125: || !in_array(pathinfo($entry, PATHINFO_EXTENSION), $this->acceptFiles))
126: {
127: continue;
128: }
129: $entry = (string) $entry;
130:
131: $coverage = $covered = $total = 0;
132: $loaded = isset($this->data[$entry]);
133: $lines = array();
134: if ($loaded) {
135: $lines = $this->data[$entry];
136: foreach ($lines as $flag) {
137: if ($flag >= -1) {
138: $total++;
139: }
140: if ($flag >= 1) {
141: $covered++;
142: }
143: }
144: $coverage = round($covered * 100 / $total);
145: $this->totalSum += $total;
146: $this->coveredSum += $covered;
147: }
148:
149: $light = $total ? $total < 5 : count(file($entry)) < 50;
150: $this->files[] = (object) array(
151: 'name' => str_replace($this->sourceDir, '', $entry),
152: 'file' => $entry,
153: 'lines' => $lines,
154: 'coverage' => $coverage,
155: 'total' => $total,
156: 'class' => $light ? 'light' : ($loaded ? NULL : 'not-loaded'),
157: );
158: }
159: }
160: }
161: