5 * Copyright (c) 2002-2009, Sebastian Bergmann <sb@sebastian-bergmann.de>.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
20 * * Neither the name of Sebastian Bergmann nor the names of his
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
39 * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
40 * @copyright 2002-2009 Sebastian Bergmann <sb@sebastian-bergmann.de>
41 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
43 * @link http://www.phpunit.de/
44 * @since File available since Release 3.3.0
47 require_once 'PHPUnit/Runner/Version.php';
48 require_once 'PHPUnit/Util/Metrics/File.php';
49 require_once 'PHPUnit/Util/Class.php';
50 require_once 'PHPUnit/Util/CodeCoverage.php';
51 require_once 'PHPUnit/Util/Filter.php';
52 require_once 'PHPUnit/Util/XML.php';
54 PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT');
57 * Writes one XML file per covered PHP source file to a given directory.
58 * Each <line> element holds a line of PHP sourcecode that is annotated with
59 * code coverage information.
63 * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
64 * @copyright 2002-2009 Sebastian Bergmann <sb@sebastian-bergmann.de>
65 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
66 * @version Release: 3.3.17
67 * @link http://www.phpunit.de/
68 * @since Class available since Release 3.3.0
70 class PHPUnit_Util_Log_CodeCoverage_XML_Source
75 * @param string $directory
77 public function __construct($directory)
79 $this->directory = PHPUnit_Util_Filesystem::getDirectory($directory);
83 * @param PHPUnit_Framework_TestResult $result
85 public function process(PHPUnit_Framework_TestResult $result)
87 $sutData = $result->getCodeCoverageInformation();
88 $sutFiles = PHPUnit_Util_CodeCoverage::getSummary($sutData, TRUE);
89 $allData = $result->getCodeCoverageInformation(FALSE);
90 $allFiles = PHPUnit_Util_CodeCoverage::getSummary($allData, TRUE);
91 $testFiles = array_diff(array_keys($allFiles), array_keys($sutFiles));
93 foreach (array_keys($allFiles) as $key) {
94 if (!in_array($key, $testFiles)) {
95 unset($allFiles[$key]);
99 $allCommonPath = PHPUnit_Util_Filesystem::reducePaths($allFiles);
100 $sutCommonPath = PHPUnit_Util_Filesystem::reducePaths($sutFiles);
101 $testFiles = $allFiles;
107 $testToCoveredLinesMap = array();
110 foreach ($sutFiles as $filename => $data) {
111 $fullPath = $sutCommonPath . DIRECTORY_SEPARATOR . $filename;
113 if (file_exists($fullPath)) {
114 $fullPath = realpath($fullPath);
116 $document = new DOMDocument('1.0', 'UTF-8');
117 $document->formatOutput = TRUE;
119 $coveredFile = $document->createElement('coveredFile');
120 $coveredFile->setAttribute('fullPath', $fullPath);
121 $coveredFile->setAttribute('shortenedPath', $filename);
122 $coveredFile->setAttribute('generated', $time);
123 $coveredFile->setAttribute('phpunit', PHPUnit_Runner_Version::id());
124 $document->appendChild($coveredFile);
126 $lines = file($fullPath, FILE_IGNORE_NEW_LINES);
129 foreach ($lines as $line) {
130 if (isset($data[$lineNum])) {
131 if (is_array($data[$lineNum])) {
132 $count = count($data[$lineNum]);
134 $count = $data[$lineNum];
140 $xmlLine = $coveredFile->appendChild(
141 $document->createElement('line')
144 $xmlLine->setAttribute('lineNumber', $lineNum);
145 $xmlLine->setAttribute('executed', $count);
147 $xmlLine->appendChild(
148 $document->createElement(
151 PHPUnit_Util_XML::convertToUtf8($line),
158 if (isset($data[$lineNum]) && is_array($data[$lineNum])) {
159 $xmlTests = $document->createElement('tests');
160 $xmlLine->appendChild($xmlTests);
162 foreach ($data[$lineNum] as $test) {
163 $xmlTest = $xmlTests->appendChild(
164 $document->createElement('test')
167 if ($test instanceof PHPUnit_Framework_TestCase) {
168 $xmlTest->setAttribute('name', $test->getName());
169 $xmlTest->setAttribute('status', $test->getStatus());
171 if ($test->hasFailed()) {
172 $xmlTest->appendChild(
173 $document->createElement(
176 PHPUnit_Util_XML::convertToUtf8($test->getStatusMessage()),
184 $class = new ReflectionClass($test);
185 $testFullPath = $class->getFileName();
186 $testShortenedPath = str_replace($allCommonPath, '', $testFullPath);
187 $methodName = $test->getName(FALSE);
189 if ($class->hasMethod($methodName)) {
190 $method = $class->getMethod($methodName);
191 $startLine = $method->getStartLine();
193 $xmlTest->setAttribute('class', $class->getName());
194 $xmlTest->setAttribute('fullPath', $testFullPath);
195 $xmlTest->setAttribute('shortenedPath', $testShortenedPath);
196 $xmlTest->setAttribute('line', $startLine);
198 if (!isset($testToCoveredLinesMap[$testFullPath][$startLine])) {
199 $testToCoveredLinesMap[$testFullPath][$startLine] = array();
202 if (!isset($testToCoveredLinesMap[$testFullPath][$startLine][$fullPath])) {
203 $testToCoveredLinesMap[$testFullPath][$startLine][$fullPath] = array(
204 'coveredLines' => array($lineNum),
205 'shortenedPath' => $filename
208 $testToCoveredLinesMap[$testFullPath][$startLine][$fullPath]['coveredLines'][] = $lineNum;
223 PHPUnit_Util_Filesystem::getSafeFilename(
231 foreach ($testFiles as $filename => $data) {
232 $fullPath = $allCommonPath . DIRECTORY_SEPARATOR . $filename;
234 if (file_exists($fullPath)) {
235 $document = new DOMDocument('1.0', 'UTF-8');
236 $document->formatOutput = TRUE;
238 $testFile = $document->createElement('testFile');
239 $testFile->setAttribute('fullPath', $fullPath);
240 $testFile->setAttribute('shortenedPath', $filename);
241 $testFile->setAttribute('generated', $time);
242 $testFile->setAttribute('phpunit', PHPUnit_Runner_Version::id());
243 $document->appendChild($testFile);
245 $lines = file($fullPath, FILE_IGNORE_NEW_LINES);
248 foreach ($lines as $line) {
249 $xmlLine = $testFile->appendChild(
250 $document->createElement('line')
253 $xmlLine->setAttribute('lineNumber', $lineNum);
255 $xmlLine->appendChild(
256 $document->createElement(
259 PHPUnit_Util_XML::convertToUtf8($line),
266 if (isset($testToCoveredLinesMap[$fullPath][$lineNum])) {
267 $xmlCoveredFiles = $xmlLine->appendChild(
268 $document->createElement('coveredFiles')
271 foreach ($testToCoveredLinesMap[$fullPath][$lineNum] as $coveredFileFullPath => $coveredFileData) {
272 $xmlCoveredFile = $xmlCoveredFiles->appendChild(
273 $document->createElement('coveredFile')
276 $xmlCoveredFile->setAttribute('fullPath', $fullPath);
277 $xmlCoveredFile->setAttribute('shortenedPath', $coveredFileData['shortenedPath']);
279 foreach ($coveredFileData['coveredLines'] as $coveredLineNum) {
280 $xmlCoveredLine = $xmlCoveredFile->appendChild(
281 $document->createElement('coveredLine', $coveredLineNum)
295 PHPUnit_Util_Filesystem::getSafeFilename(