Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * $Id: CoverageReportTask.php 426 2008-10-28 19:29:49Z mrook $
4
 *
5
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16
 *
17
 * This software consists of voluntary contributions made by many individuals
18
 * and is licensed under the LGPL. For more information please see
19
 * <http://phing.info>.
20
 */
21
 
22
require_once 'phing/Task.php';
23
require_once 'phing/system/io/PhingFile.php';
24
require_once 'phing/system/io/Writer.php';
25
require_once 'phing/system/util/Properties.php';
26
require_once 'phing/tasks/ext/phpunit/PHPUnitUtil.php';
27
require_once 'phing/tasks/ext/coverage/CoverageReportTransformer.php';
28
 
29
/**
30
 * Transforms information in a code coverage database to XML
31
 *
32
 * @author Michiel Rook <michiel.rook@gmail.com>
33
 * @version $Id: CoverageReportTask.php 426 2008-10-28 19:29:49Z mrook $
34
 * @package phing.tasks.ext.coverage
35
 * @since 2.1.0
36
 */
37
class CoverageReportTask extends Task
38
{
39
	private $outfile = "coverage.xml";
40
 
41
	private $transformers = array();
42
 
43
	/** the classpath to use (optional) */
44
	private $classpath = NULL;
45
 
46
	/** the path to the GeSHi library (optional) */
47
	private $geshipath = "";
48
 
49
	/** the path to the GeSHi language files (optional) */
50
	private $geshilanguagespath = "";
51
 
52
	function setClasspath(Path $classpath)
53
	{
54
		if ($this->classpath === null)
55
		{
56
			$this->classpath = $classpath;
57
		}
58
		else
59
		{
60
			$this->classpath->append($classpath);
61
		}
62
	}
63
 
64
	function createClasspath()
65
	{
66
		$this->classpath = new Path();
67
		return $this->classpath;
68
	}
69
 
70
	function setGeshiPath($path)
71
	{
72
		$this->geshipath = $path;
73
	}
74
 
75
	function setGeshiLanguagesPath($path)
76
	{
77
		$this->geshilanguagespath = $path;
78
	}
79
 
80
	function __construct()
81
	{
82
		$this->doc = new DOMDocument();
83
		$this->doc->encoding = 'UTF-8';
84
		$this->doc->formatOutput = true;
85
		$this->doc->appendChild($this->doc->createElement('snapshot'));
86
	}
87
 
88
	function setOutfile($outfile)
89
	{
90
		$this->outfile = $outfile;
91
	}
92
 
93
	/**
94
	 * Generate a report based on the XML created by this task
95
	 */
96
	function createReport()
97
	{
98
		$transformer = new CoverageReportTransformer($this);
99
		$this->transformers[] = $transformer;
100
		return $transformer;
101
	}
102
 
103
	protected function getPackageElement($packageName)
104
	{
105
		$packages = $this->doc->documentElement->getElementsByTagName('package');
106
 
107
		foreach ($packages as $package)
108
		{
109
			if ($package->getAttribute('name') == $packageName)
110
			{
111
				return $package;
112
			}
113
		}
114
 
115
		return NULL;
116
	}
117
 
118
	protected function addClassToPackage($classname, $element)
119
	{
120
		$packageName = PHPUnitUtil::getPackageName($classname);
121
 
122
		$package = $this->getPackageElement($packageName);
123
 
124
		if ($package === NULL)
125
		{
126
			$package = $this->doc->createElement('package');
127
			$package->setAttribute('name', $packageName);
128
			$this->doc->documentElement->appendChild($package);
129
		}
130
 
131
		$package->appendChild($element);
132
	}
133
 
134
	protected function stripDiv($source)
135
	{
136
		$openpos = strpos($source, "<div");
137
		$closepos = strpos($source, ">", $openpos);
138
 
139
		$line = substr($source, $closepos + 1);
140
 
141
		$tagclosepos = strpos($line, "</div>");
142
 
143
		$line = substr($line, 0, $tagclosepos);
144
 
145
		return $line;
146
	}
147
 
148
	protected function highlightSourceFile($filename)
149
	{
150
		if ($this->geshipath)
151
		{
152
			require_once $this->geshipath . '/geshi.php';
153
 
154
			$source = file_get_contents($filename);
155
 
156
			$geshi = new GeSHi($source, 'php', $this->geshilanguagespath);
157
 
158
			$geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
159
 
160
			$geshi->enable_strict_mode(true);
161
 
162
			$geshi->enable_classes(true);
163
 
164
			$geshi->set_url_for_keyword_group(3, '');
165
 
166
			$html = $geshi->parse_code();
167
 
168
			$lines = split("<li>|</li>", $html);
169
 
170
			// skip first and last line
171
			array_pop($lines);
172
			array_shift($lines);
173
 
174
			$lines = array_filter($lines);
175
 
176
			$lines = array_map(array($this, 'stripDiv'), $lines);
177
 
178
			return $lines;
179
		}
180
		else
181
		{
182
			$lines = file($filename);
183
 
184
			for ($i = 0; $i < count($lines); $i++)
185
			{
186
				$line = $lines[$i];
187
 
188
				$line = rtrim($line);
189
 
190
				if (function_exists('mb_convert_encoding'))
191
				{
192
					$lines[$i] = mb_convert_encoding($line, 'UTF-8');
193
				}
194
				else
195
				{
196
					$lines[$i] = utf8_encode($line);
197
				}
198
			}
199
 
200
			return $lines;
201
		}
202
	}
203
 
204
	protected function transformSourceFile($filename, $coverageInformation, $classStartLine = 1)
205
	{
206
		$sourceElement = $this->doc->createElement('sourcefile');
207
		$sourceElement->setAttribute('name', basename($filename));
208
 
209
		/**
210
		 * Add original/full filename to document
211
		 */
212
		$sourceElement->setAttribute('sourcefile', $filename);
213
 
214
		$filelines = $this->highlightSourceFile($filename);
215
 
216
		$linenr = 1;
217
 
218
		foreach ($filelines as $line)
219
		{
220
			$lineElement = $this->doc->createElement('sourceline');
221
			$lineElement->setAttribute('coveredcount', (isset($coverageInformation[$linenr]) ? $coverageInformation[$linenr] : '0'));
222
 
223
			if ($linenr == $classStartLine)
224
			{
225
				$lineElement->setAttribute('startclass', 1);
226
			}
227
 
228
			$textnode = $this->doc->createTextNode($line);
229
			$lineElement->appendChild($textnode);
230
 
231
			$sourceElement->appendChild($lineElement);
232
 
233
			$linenr++;
234
		}
235
 
236
		return $sourceElement;
237
	}
238
 
239
	protected function filterCovered($var)
240
	{
241
		return ($var >= 0);
242
	}
243
 
244
	protected function transformCoverageInformation($filename, $coverageInformation)
245
	{
246
		$classes = PHPUnitUtil::getDefinedClasses($filename, $this->classpath);
247
 
248
		if (is_array($classes))
249
		{
250
			foreach ($classes as $classname)
251
			{
252
				$reflection = new ReflectionClass($classname);
253
 
254
				$methods = $reflection->getMethods();
255
 
256
				$classElement = $this->doc->createElement('class');
257
				$classElement->setAttribute('name', $reflection->getName());
258
 
259
				$this->addClassToPackage($reflection->getName(), $classElement);
260
 
261
				$classStartLine = $reflection->getStartLine();
262
 
263
				$methodscovered = 0;
264
				$methodcount = 0;
265
 
266
				// Strange PHP5 reflection bug, classes without parent class or implemented interfaces seem to start one line off
267
				if ($reflection->getParentClass() == NULL && count($reflection->getInterfaces()) == 0)
268
				{
269
					unset($coverageInformation[$classStartLine + 1]);
270
				}
271
				else
272
				{
273
					unset($coverageInformation[$classStartLine]);
274
				}
275
 
276
				reset($coverageInformation);
277
 
278
				foreach ($methods as $method)
279
				{
280
					// PHP5 reflection considers methods of a parent class to be part of a subclass, we don't
281
					if ($method->getDeclaringClass()->getName() != $reflection->getName())
282
					{
283
						continue;
284
					}
285
 
286
					// small fix for XDEBUG_CC_UNUSED
287
					if (isset($coverageInformation[$method->getStartLine()]))
288
					{
289
						unset($coverageInformation[$method->getStartLine()]);
290
					}
291
 
292
					if (isset($coverageInformation[$method->getEndLine()]))
293
					{
294
						unset($coverageInformation[$method->getEndLine()]);
295
					}
296
 
297
					if ($method->isAbstract())
298
					{
299
						continue;
300
					}
301
 
302
					$linenr = key($coverageInformation);
303
 
304
					while ($linenr !== null && $linenr < $method->getStartLine())
305
					{
306
						next($coverageInformation);
307
						$linenr = key($coverageInformation);
308
					}
309
 
310
					if (current($coverageInformation) > 0 && $method->getStartLine() <= $linenr && $linenr <= $method->getEndLine())
311
					{
312
						$methodscovered++;
313
					}
314
 
315
					$methodcount++;
316
				}
317
 
318
				$statementcount = count($coverageInformation);
319
				$statementscovered = count(array_filter($coverageInformation, array($this, 'filterCovered')));
320
 
321
				$classElement->appendChild($this->transformSourceFile($filename, $coverageInformation, $classStartLine));
322
 
323
				$classElement->setAttribute('methodcount', $methodcount);
324
				$classElement->setAttribute('methodscovered', $methodscovered);
325
				$classElement->setAttribute('statementcount', $statementcount);
326
				$classElement->setAttribute('statementscovered', $statementscovered);
327
				$classElement->setAttribute('totalcount', $methodcount + $statementcount);
328
				$classElement->setAttribute('totalcovered', $methodscovered + $statementscovered);
329
			}
330
		}
331
	}
332
 
333
	protected function calculateStatistics()
334
	{
335
		$packages = $this->doc->documentElement->getElementsByTagName('package');
336
 
337
		$totalmethodcount = 0;
338
		$totalmethodscovered = 0;
339
 
340
		$totalstatementcount = 0;
341
		$totalstatementscovered = 0;
342
 
343
		foreach ($packages as $package)
344
		{
345
			$methodcount = 0;
346
			$methodscovered = 0;
347
 
348
			$statementcount = 0;
349
			$statementscovered = 0;
350
 
351
			$classes = $package->getElementsByTagName('class');
352
 
353
			foreach ($classes as $class)
354
			{
355
				$methodcount += $class->getAttribute('methodcount');
356
				$methodscovered += $class->getAttribute('methodscovered');
357
 
358
				$statementcount += $class->getAttribute('statementcount');
359
				$statementscovered += $class->getAttribute('statementscovered');
360
			}
361
 
362
			$package->setAttribute('methodcount', $methodcount);
363
			$package->setAttribute('methodscovered', $methodscovered);
364
 
365
			$package->setAttribute('statementcount', $statementcount);
366
			$package->setAttribute('statementscovered', $statementscovered);
367
 
368
			$package->setAttribute('totalcount', $methodcount + $statementcount);
369
			$package->setAttribute('totalcovered', $methodscovered + $statementscovered);
370
 
371
			$totalmethodcount += $methodcount;
372
			$totalmethodscovered += $methodscovered;
373
 
374
			$totalstatementcount += $statementcount;
375
			$totalstatementscovered += $statementscovered;
376
		}
377
 
378
		$this->doc->documentElement->setAttribute('methodcount', $totalmethodcount);
379
		$this->doc->documentElement->setAttribute('methodscovered', $totalmethodscovered);
380
 
381
		$this->doc->documentElement->setAttribute('statementcount', $totalstatementcount);
382
		$this->doc->documentElement->setAttribute('statementscovered', $totalstatementscovered);
383
 
384
		$this->doc->documentElement->setAttribute('totalcount', $totalmethodcount + $totalstatementcount);
385
		$this->doc->documentElement->setAttribute('totalcovered', $totalmethodscovered + $totalstatementscovered);
386
	}
387
 
388
	function main()
389
	{
390
		$this->log("Transforming coverage report");
391
 
392
		$database = new PhingFile($this->project->getProperty('coverage.database'));
393
 
394
		$props = new Properties();
395
		$props->load($database);
396
 
397
		foreach ($props->keys() as $filename)
398
		{
399
			$file = unserialize($props->getProperty($filename));
400
 
401
			$this->transformCoverageInformation($file['fullname'], $file['coverage']);
402
		}
403
 
404
		$this->calculateStatistics();
405
 
406
		$this->doc->save($this->outfile);
407
 
408
		foreach ($this->transformers as $transformer)
409
		{
410
			$transformer->setXmlDocument($this->doc);
411
			$transformer->transform();
412
		}
413
	}
414
}