From b569102dd8403df4adbc0e05a96f10bcb5284dd7 Mon Sep 17 00:00:00 2001 From: Sebastian Bergmann Date: Tue, 27 Oct 2015 09:14:24 +0100 Subject: [PATCH 01/18] Merge feature/path-coverage 27c330b10133b811abc569cded263a3f2610f4e4 --- src/CodeCoverage.php | 108 +++++++++---- src/CodeCoverage/Driver/Xdebug.php | 37 +++-- src/CodeCoverage/Report/Clover.php | 6 +- .../Report/HTML/Renderer/File.php | 8 +- src/CodeCoverage/Report/Node/File.php | 4 +- src/CodeCoverage/Report/XML.php | 4 +- tests/TestCase.php | 150 ++++++++++-------- 7 files changed, 195 insertions(+), 122 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index d1ac2e9e7..4250497be 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -81,25 +81,39 @@ class PHP_CodeCoverage */ private $tests = []; + /** + * @var bool + */ + private $pathCoverage; + /** * Constructor. * - * @param PHP_CodeCoverage_Driver $driver - * @param PHP_CodeCoverage_Filter $filter - * @throws PHP_CodeCoverage_RuntimeException + * @param PHP_CodeCoverage_Driver $driver + * @param PHP_CodeCoverage_Filter $filter + * @param bool $pathCoverage + * @throws PHP_CodeCoverage_InvalidArgumentException */ - public function __construct(PHP_CodeCoverage_Driver $driver = null, PHP_CodeCoverage_Filter $filter = null) + public function __construct(PHP_CodeCoverage_Driver $driver = null, PHP_CodeCoverage_Filter $filter = null, $pathCoverage = true) { + if (!is_bool($pathCoverage)) { + throw PHP_CodeCoverage_InvalidArgumentException::create( + 3, + 'boolean' + ); + } + if ($driver === null) { - $driver = $this->selectDriver(); + $driver = $this->selectDriver($pathCoverage); } if ($filter === null) { $filter = new PHP_CodeCoverage_Filter; } - $this->driver = $driver; - $this->filter = $filter; + $this->driver = $driver; + $this->filter = $filter; + $this->pathCoverage = $pathCoverage; } /** @@ -307,15 +321,15 @@ public function append(array $data, $id = null, $append = true, $linesToBeCovere $this->tests[$id] = ['size' => $size, 'status' => $status]; - foreach ($data as $file => $lines) { + foreach ($data as $file => $fileData) { if (!$this->filter->isFile($file)) { continue; } - foreach ($lines as $k => $v) { + foreach ($fileData['lines'] as $k => $v) { if ($v == PHP_CodeCoverage_Driver::LINE_EXECUTED) { - if (empty($this->data[$file][$k]) || !in_array($id, $this->data[$file][$k])) { - $this->data[$file][$k][] = $id; + if (empty($this->data[$file]['lines'][$k]) || !in_array($id, $this->data[$file]['lines'][$k])) { + $this->data[$file]['lines'][$k][] = $id; } } } @@ -333,22 +347,22 @@ public function merge(PHP_CodeCoverage $that) array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles()) ); - foreach ($that->data as $file => $lines) { + foreach ($that->getData() as $file => $fileData) { if (!isset($this->data[$file])) { - if (!$this->filter->isFiltered($file)) { - $this->data[$file] = $lines; + if (!$that->filter()->isFiltered($file)) { + $this->data[$file] = ['lines' => $fileData['lines']]; } continue; } - foreach ($lines as $line => $data) { + foreach ($fileData['lines'] as $line => $data) { if ($data !== null) { - if (!isset($this->data[$file][$line])) { - $this->data[$file][$line] = $data; + if (!isset($this->data[$file]['lines'][$line])) { + $this->data[$file]['lines'][$line] = $data; } else { - $this->data[$file][$line] = array_unique( - array_merge($this->data[$file][$line], $data) + $this->data[$file]['lines'][$line] = array_unique( + array_merge($this->data[$file]['lines'][$line], $data) ); } } @@ -486,7 +500,7 @@ private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, ar { if ($linesToBeCovered === false || ($this->forceCoversAnnotation && empty($linesToBeCovered))) { - $data = []; + $data = ['lines' => []]; return; } @@ -508,8 +522,8 @@ private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, ar foreach (array_keys($data) as $filename) { $_linesToBeCovered = array_flip($linesToBeCovered[$filename]); - $data[$filename] = array_intersect_key( - $data[$filename], + $data[$filename]['lines'] = array_intersect_key( + $data[$filename]['lines'], $_linesToBeCovered ); } @@ -542,7 +556,7 @@ private function applyIgnoredLinesFilter(array &$data) } foreach ($this->getLinesToBeIgnored($filename) as $line) { - unset($data[$filename][$line]); + unset($data[$filename]['lines'][$line]); } } } @@ -553,12 +567,37 @@ private function applyIgnoredLinesFilter(array &$data) */ private function initializeFilesThatAreSeenTheFirstTime(array $data) { - foreach ($data as $file => $lines) { + foreach ($data as $file => $fileData) { if ($this->filter->isFile($file) && !isset($this->data[$file])) { - $this->data[$file] = []; + $this->data[$file] = ['lines' => []]; + + if ($this->pathCoverage) { + $this->data[$file]['branches'] = []; + $this->data[$file]['paths'] = []; + + foreach ($fileData['functions'] as $functionName => $functionData) { + $this->data[$file]['branches'][$functionName] = []; + $this->data[$file]['paths'][$functionName] = []; + + foreach ($functionData['branches'] as $branch) { + $this->data[$file]['branches'][$functionName][] = [ + 'line_start' => $branch['line_start'], + 'line_end' => $branch['line_end'], + 'tests' => [] + ]; + } + + foreach ($functionData['paths'] as $path) { + $this->data[$file]['paths'][$functionName][] = [ + 'path' => $path['path'], + 'tests' => [] + ]; + } + } + } - foreach ($lines as $k => $v) { - $this->data[$file][$k] = $v == -2 ? null : []; + foreach ($fileData['lines'] as $lineNumber => $flag) { + $this->data[$file]['lines'][$lineNumber] = $flag == -2 ? null : []; } } } @@ -587,12 +626,12 @@ private function addUncoveredFilesFromWhitelist() $uncoveredFiles ); } else { - $data[$uncoveredFile] = []; + $data[$uncoveredFile] = ['lines' => []]; $lines = count(file($uncoveredFile)); for ($i = 1; $i <= $lines; $i++) { - $data[$uncoveredFile][$i] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED; + $data[$uncoveredFile]['lines'][$i] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED; } } } @@ -620,7 +659,7 @@ private function processUncoveredFileFromWhitelist($uncoveredFile, array &$data, } } - $data[$file] = $fileCoverage; + $data[$file] = ['lines' => $fileCoverage]; } } } @@ -815,8 +854,8 @@ private function performUnintentionallyCoveredCodeCheck(array &$data, array $lin $message = ''; - foreach ($data as $file => $_data) { - foreach ($_data as $line => $flag) { + foreach ($data as $file => $fileData) { + foreach ($fileData['lines'] as $line => $flag) { if ($flag == 1 && (!isset($allowedLines[$file]) || !isset($allowedLines[$file][$line]))) { @@ -878,10 +917,11 @@ private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed) } /** + * @param bool $pathCoverage * @return PHP_CodeCoverage_Driver * @throws PHP_CodeCoverage_RuntimeException */ - private function selectDriver() + private function selectDriver($pathCoverage) { $runtime = new Runtime; @@ -894,7 +934,7 @@ private function selectDriver() } elseif ($runtime->isPHPDBG()) { return new PHP_CodeCoverage_Driver_PHPDBG; } else { - return new PHP_CodeCoverage_Driver_Xdebug; + return new PHP_CodeCoverage_Driver_Xdebug($pathCoverage); } } } diff --git a/src/CodeCoverage/Driver/Xdebug.php b/src/CodeCoverage/Driver/Xdebug.php index ed9a7035b..1d2a0f8ad 100644 --- a/src/CodeCoverage/Driver/Xdebug.php +++ b/src/CodeCoverage/Driver/Xdebug.php @@ -17,20 +17,33 @@ class PHP_CodeCoverage_Driver_Xdebug implements PHP_CodeCoverage_Driver { /** - * Constructor. + * @var int */ - public function __construct() + private $flags; + + /** + * @param bool $pathCoverage + */ + public function __construct($pathCoverage = true) { - if (!extension_loaded('xdebug')) { - throw new PHP_CodeCoverage_RuntimeException('This driver requires Xdebug'); + if (!extension_loaded('xdebug') || + version_compare(phpversion('xdebug'), '2.3.2', '<')) { + throw new PHP_CodeCoverage_RuntimeException( + 'This driver requires Xdebug 2.3.2 (or newer)' + ); } - if (version_compare(phpversion('xdebug'), '2.2.0-dev', '>=') && - !ini_get('xdebug.coverage_enable')) { + if (!ini_get('xdebug.coverage_enable')) { throw new PHP_CodeCoverage_RuntimeException( 'xdebug.coverage_enable=On has to be set in php.ini' ); } + + $this->flags = XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE; + + if ($pathCoverage) { + $this->flags |= XDEBUG_CC_BRANCH_CHECK; + } } /** @@ -38,7 +51,7 @@ public function __construct() */ public function start() { - xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); + xdebug_start_code_coverage($this->flags); } /** @@ -62,14 +75,18 @@ public function stop() private function cleanup(array $data) { foreach (array_keys($data) as $file) { - unset($data[$file][0]); + if (!isset($data[$file]['lines'])) { + $data[$file] = ['lines' => $data[$file]]; + } + + unset($data[$file]['lines'][0]); if ($file != 'xdebug://debug-eval' && file_exists($file)) { $numLines = $this->getNumberOfLinesInFile($file); - foreach (array_keys($data[$file]) as $line) { + foreach (array_keys($data[$file]['lines']) as $line) { if ($line > $numLines) { - unset($data[$file][$line]); + unset($data[$file]['lines'][$line]); } } } diff --git a/src/CodeCoverage/Report/Clover.php b/src/CodeCoverage/Report/Clover.php index 6c8fda190..1b595d2f4 100644 --- a/src/CodeCoverage/Report/Clover.php +++ b/src/CodeCoverage/Report/Clover.php @@ -78,8 +78,8 @@ public function process(PHP_CodeCoverage $coverage, $target = null, $name = null for ($i = $method['startLine']; $i <= $method['endLine']; $i++) { - if (isset($coverage[$i]) && ($coverage[$i] !== null)) { - $methodCount = max($methodCount, count($coverage[$i])); + if (isset($coverage['lines'][$i]) && ($coverage['lines'][$i] !== null)) { + $methodCount = max($methodCount, count($coverage['lines'][$i])); } } @@ -157,7 +157,7 @@ public function process(PHP_CodeCoverage $coverage, $target = null, $name = null $xmlClass->appendChild($xmlMetrics); } - foreach ($coverage as $line => $data) { + foreach ($coverage['lines'] as $line => $data) { if ($data === null || isset($lines[$line])) { continue; } diff --git a/src/CodeCoverage/Report/HTML/Renderer/File.php b/src/CodeCoverage/Report/HTML/Renderer/File.php index 5ab20c5bd..519544656 100644 --- a/src/CodeCoverage/Report/HTML/Renderer/File.php +++ b/src/CodeCoverage/Report/HTML/Renderer/File.php @@ -285,10 +285,10 @@ protected function renderSource(PHP_CodeCoverage_Report_Node_File $node) $popoverContent = ''; $popoverTitle = ''; - if (array_key_exists($i, $coverageData)) { - $numTests = count($coverageData[$i]); + if (array_key_exists($i, $coverageData['lines'])) { + $numTests = count($coverageData['lines'][$i]); - if ($coverageData[$i] === null) { + if ($coverageData['lines'][$i] === null) { $trClass = ' class="warning"'; } elseif ($numTests == 0) { $trClass = ' class="danger"'; @@ -302,7 +302,7 @@ protected function renderSource(PHP_CodeCoverage_Report_Node_File $node) $popoverTitle = '1 test covers line ' . $i; } - foreach ($coverageData[$i] as $test) { + foreach ($coverageData['lines'][$i] as $test) { if ($lineCss == 'covered-by-large-tests' && $testData[$test]['size'] == 'medium') { $lineCss = 'covered-by-medium-tests'; } elseif ($testData[$test]['size'] == 'small') { diff --git a/src/CodeCoverage/Report/Node/File.php b/src/CodeCoverage/Report/Node/File.php index f5b2ffb48..7d839f15f 100644 --- a/src/CodeCoverage/Report/Node/File.php +++ b/src/CodeCoverage/Report/Node/File.php @@ -390,7 +390,7 @@ protected function calculateStatistics() } } - if (isset($this->coverageData[$lineNumber])) { + if (isset($this->coverageData['lines'][$lineNumber])) { if (isset($currentClass)) { $currentClass['executableLines']++; } @@ -409,7 +409,7 @@ protected function calculateStatistics() $this->numExecutableLines++; - if (count($this->coverageData[$lineNumber]) > 0) { + if (count($this->coverageData['lines'][$lineNumber]) > 0) { if (isset($currentClass)) { $currentClass['executedLines']++; } diff --git a/src/CodeCoverage/Report/XML.php b/src/CodeCoverage/Report/XML.php index ebdd7e284..f1d70ac9d 100644 --- a/src/CodeCoverage/Report/XML.php +++ b/src/CodeCoverage/Report/XML.php @@ -114,7 +114,9 @@ private function processFile(PHP_CodeCoverage_Report_Node_File $file, PHP_CodeCo $this->processFunction($function, $fileReport); } - foreach ($file->getCoverageData() as $line => $tests) { + $fileData = $file->getCoverageData(); + + foreach ($fileData['lines'] as $line => $tests) { if (!is_array($tests) || count($tests) == 0) { continue; } diff --git a/tests/TestCase.php b/tests/TestCase.php index d03585cef..64e5490ab 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -31,48 +31,56 @@ protected function getXdebugDataForBankAccount() return [ [ TEST_FILES_PATH . 'BankAccount.php' => [ - 8 => 1, - 9 => -2, - 13 => -1, - 14 => -1, - 15 => -1, - 16 => -1, - 18 => -1, - 22 => -1, - 24 => -1, - 25 => -2, - 29 => -1, - 31 => -1, - 32 => -2 + 'lines' => [ + 8 => 1, + 9 => -2, + 13 => -1, + 14 => -1, + 15 => -1, + 16 => -1, + 18 => -1, + 22 => -1, + 24 => -1, + 25 => -2, + 29 => -1, + 31 => -1, + 32 => -2, + ], ] ], [ TEST_FILES_PATH . 'BankAccount.php' => [ - 8 => 1, - 13 => 1, - 16 => 1, - 29 => 1, + 'lines' => [ + 8 => 1, + 13 => 1, + 16 => 1, + 29 => 1, + ], ] ], [ TEST_FILES_PATH . 'BankAccount.php' => [ - 8 => 1, - 13 => 1, - 16 => 1, - 22 => 1, + 'lines' => [ + 8 => 1, + 13 => 1, + 16 => 1, + 22 => 1, + ], ] ], [ TEST_FILES_PATH . 'BankAccount.php' => [ - 8 => 1, - 13 => 1, - 14 => 1, - 15 => 1, - 18 => 1, - 22 => 1, - 24 => 1, - 29 => 1, - 31 => 1, + 'lines' => [ + 8 => 1, + 13 => 1, + 14 => 1, + 15 => 1, + 18 => 1, + 22 => 1, + 24 => 1, + 29 => 1, + 31 => 1, + ], ] ] ]; @@ -231,32 +239,34 @@ protected function getExpectedDataArrayForBankAccount() { return [ TEST_FILES_PATH . 'BankAccount.php' => [ - 8 => [ - 0 => 'BankAccountTest::testBalanceIsInitiallyZero', - 1 => 'BankAccountTest::testDepositWithdrawMoney' - ], - 9 => null, - 13 => [], - 14 => [], - 15 => [], - 16 => [], - 18 => [], - 22 => [ - 0 => 'BankAccountTest::testBalanceCannotBecomeNegative2', - 1 => 'BankAccountTest::testDepositWithdrawMoney' + 'lines' => [ + 8 => [ + 0 => 'BankAccountTest::testBalanceIsInitiallyZero', + 1 => 'BankAccountTest::testDepositWithdrawMoney', + ], + 9 => null, + 13 => [], + 14 => [], + 15 => [], + 16 => [], + 18 => [], + 22 => [ + 0 => 'BankAccountTest::testBalanceCannotBecomeNegative2', + 1 => 'BankAccountTest::testDepositWithdrawMoney', + ], + 24 => [ + 0 => 'BankAccountTest::testDepositWithdrawMoney', + ], + 25 => null, + 29 => [ + 0 => 'BankAccountTest::testBalanceCannotBecomeNegative', + 1 => 'BankAccountTest::testDepositWithdrawMoney', + ], + 31 => [ + 0 => 'BankAccountTest::testDepositWithdrawMoney', + ], + 32 => null, ], - 24 => [ - 0 => 'BankAccountTest::testDepositWithdrawMoney', - ], - 25 => null, - 29 => [ - 0 => 'BankAccountTest::testBalanceCannotBecomeNegative', - 1 => 'BankAccountTest::testDepositWithdrawMoney' - ], - 31 => [ - 0 => 'BankAccountTest::testDepositWithdrawMoney' - ], - 32 => null ] ]; } @@ -285,10 +295,12 @@ protected function setUpXdebugStubForFileWithIgnoredLines() ->will($this->returnValue( [ TEST_FILES_PATH . 'source_with_ignore.php' => [ - 2 => 1, - 4 => -1, - 6 => -1, - 7 => 1 + 'lines' => [ + 2 => 1, + 4 => -1, + 6 => -1, + 7 => 1, + ], ] ] )); @@ -320,15 +332,17 @@ protected function setUpXdebugStubForClassWithAnonymousFunction() ->will($this->returnValue( [ TEST_FILES_PATH . 'source_with_class_and_anonymous_function.php' => [ - 7 => 1, - 9 => 1, - 10 => -1, - 11 => 1, - 12 => 1, - 13 => 1, - 14 => 1, - 17 => 1, - 18 => 1 + 'lines' => [ + 7 => 1, + 9 => 1, + 10 => -1, + 11 => 1, + 12 => 1, + 13 => 1, + 14 => 1, + 17 => 1, + 18 => 1, + ], ] ] )); From 6051a1e451898b7805b5ae1b8e911119724812b6 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Tue, 27 Oct 2015 10:56:07 +0100 Subject: [PATCH 02/18] Fix tests --- src/CodeCoverage.php | 14 +- src/CodeCoverage/Driver/Xdebug.php | 3 + tests/TestCase.php | 368 +++++++++++++++++++++++++++++ 3 files changed, 381 insertions(+), 4 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 4250497be..72ab6c5ca 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -350,7 +350,7 @@ public function merge(PHP_CodeCoverage $that) foreach ($that->getData() as $file => $fileData) { if (!isset($this->data[$file])) { if (!$that->filter()->isFiltered($file)) { - $this->data[$file] = ['lines' => $fileData['lines']]; + $this->data[$file] = $fileData; } continue; @@ -500,7 +500,10 @@ private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, ar { if ($linesToBeCovered === false || ($this->forceCoversAnnotation && empty($linesToBeCovered))) { - $data = ['lines' => []]; + $data = [ + 'lines' => [], + 'functions' => [], + ]; return; } @@ -626,7 +629,10 @@ private function addUncoveredFilesFromWhitelist() $uncoveredFiles ); } else { - $data[$uncoveredFile] = ['lines' => []]; + $data[$uncoveredFile] = [ + 'lines' => [], + 'functions' => [], + ]; $lines = count(file($uncoveredFile)); @@ -659,7 +665,7 @@ private function processUncoveredFileFromWhitelist($uncoveredFile, array &$data, } } - $data[$file] = ['lines' => $fileCoverage]; + $data[$file] = $fileCoverage; } } } diff --git a/src/CodeCoverage/Driver/Xdebug.php b/src/CodeCoverage/Driver/Xdebug.php index 1d2a0f8ad..7220a9cf5 100644 --- a/src/CodeCoverage/Driver/Xdebug.php +++ b/src/CodeCoverage/Driver/Xdebug.php @@ -78,6 +78,9 @@ private function cleanup(array $data) if (!isset($data[$file]['lines'])) { $data[$file] = ['lines' => $data[$file]]; } + if (!isset($data[$file]['functions'])) { + $data[$file]['functions'] = []; + } unset($data[$file]['lines'][0]); diff --git a/tests/TestCase.php b/tests/TestCase.php index 64e5490ab..b35850192 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -28,6 +28,153 @@ public static function setUpBeforeClass() protected function getXdebugDataForBankAccount() { + $bankAccountFunctions = [ + 'BankAccount->depositMoney' => [ + 'branches' => [ + 0 => [ + 'op_start' => 0, + 'op_end' => 20, + 'line_start' => 20, + 'line_end' => 25, + 'hit' => 0, + 'out' => [ + ], + 'out_hit' => [ + ], + ], + ], + 'paths' => [ + 0 => [ + 'path' => [ + 0 => 0, + ], + 'hit' => 0, + ], + ], + ], + 'BankAccount->getBalance' => [ + 'branches' => [ + 0 => [ + 'op_start' => 0, + 'op_end' => 5, + 'line_start' => 6, + 'line_end' => 9, + 'hit' => 1, + 'out' => [ + ], + 'out_hit' => [ + ], + ], + ], + 'paths' => [ + 0 => [ + 'path' => [ + 0 => 0, + ], + 'hit' => 1, + ], + ], + ], + 'BankAccount->withdrawMoney' => [ + 'branches' => [ + 0 => [ + 'op_start' => 0, + 'op_end' => 20, + 'line_start' => 27, + 'line_end' => 32, + 'hit' => 0, + 'out' => [ + ], + 'out_hit' => [ + ], + ], + ], + 'paths' => [ + 0 => [ + 'path' => [ + 0 => 0, + ], + 'hit' => 0, + ], + ], + ], + 'BankAccount->setBalance' => [ + 'branches' => [ + 0 => [ + 'op_start' => 0, + 'op_end' => 4, + 'line_start' => 11, + 'line_end' => 13, + 'hit' => 0, + 'out' => [ + 0 => 5, + 1 => 9, + ], + 'out_hit' => [ + 0 => 0, + 1 => 0, + ], + ], + 5 => [ + 'op_start' => 5, + 'op_end' => 8, + 'line_start' => 14, + 'line_end' => 15, + 'hit' => 0, + 'out' => [ + 0 => 16, + ], + 'out_hit' => [ + 0 => 0, + ], + ], + 9 => [ + 'op_start' => 9, + 'op_end' => 15, + 'line_start' => 16, + 'line_end' => 16, + 'hit' => 0, + 'out' => [ + 0 => 2147483645, + ], + 'out_hit' => [ + 0 => 0, + ], + ], + 16 => [ + 'op_start' => 16, + 'op_end' => 17, + 'line_start' => 18, + 'line_end' => 18, + 'hit' => 0, + 'out' => [ + 0 => 2147483645, + ], + 'out_hit' => [ + 0 => 0, + ], + ], + ], + 'paths' => [ + 0 => [ + 'path' => [ + 0 => 0, + 1 => 5, + 2 => 16, + ], + 'hit' => 0, + ], + 1 => [ + 'path' => [ + 0 => 0, + 1 => 9, + ], + 'hit' => 0, + ], + ], + ], + ]; + return [ [ TEST_FILES_PATH . 'BankAccount.php' => [ @@ -46,6 +193,7 @@ protected function getXdebugDataForBankAccount() 31 => -1, 32 => -2, ], + 'functions' => $bankAccountFunctions, ] ], [ @@ -56,6 +204,7 @@ protected function getXdebugDataForBankAccount() 16 => 1, 29 => 1, ], + 'functions' => $bankAccountFunctions, ] ], [ @@ -66,6 +215,7 @@ protected function getXdebugDataForBankAccount() 16 => 1, 22 => 1, ], + 'functions' => $bankAccountFunctions, ] ], [ @@ -81,6 +231,7 @@ protected function getXdebugDataForBankAccount() 29 => 1, 31 => 1, ], + 'functions' => $bankAccountFunctions, ] ] ]; @@ -267,6 +418,94 @@ protected function getExpectedDataArrayForBankAccount() ], 32 => null, ], + 'branches' => [ + 'BankAccount->depositMoney' => [ + 0 => [ + 'line_start' => 20, + 'line_end' => 25, + 'tests' => [], + ], + ], + 'BankAccount->getBalance' => [ + 0 => [ + 'line_start' => 6, + 'line_end' => 9, + 'tests' => [], + ], + ], + 'BankAccount->withdrawMoney' => [ + 0 => [ + 'line_start' => 27, + 'line_end' => 32, + 'tests' => [], + ], + ], + 'BankAccount->setBalance' => [ + 0 => [ + 'line_start' => 11, + 'line_end' => 13, + 'tests' => [], + ], + 1 => [ + 'line_start' => 14, + 'line_end' => 15, + 'tests' => [], + ], + 2 => [ + 'line_start' => 16, + 'line_end' => 16, + 'tests' => [], + ], + 3 => [ + 'line_start' => 18, + 'line_end' => 18, + 'tests' => [], + ], + ], + ], + 'paths' => [ + 'BankAccount->depositMoney' => [ + 0 => [ + 'path' => [ + 0 => 0, + ], + 'tests' => [], + ], + ], + 'BankAccount->getBalance' => [ + 0 => [ + 'path' => [ + 0 => 0, + ], + 'tests' => [], + ], + ], + 'BankAccount->withdrawMoney' => [ + 0 => [ + 'path' => [ + 0 => 0, + ], + 'tests' => [], + ], + ], + 'BankAccount->setBalance' => [ + 0 => [ + 'path' => [ + 0 => 0, + 1 => 5, + 2 => 16, + ], + 'tests' => [], + ], + 1 => [ + 'path' => [ + 0 => 0, + 1 => 9, + ], + 'tests' => [], + ], + ], + ], ] ]; } @@ -301,6 +540,83 @@ protected function setUpXdebugStubForFileWithIgnoredLines() 6 => -1, 7 => 1, ], + 'functions' => [ + 'Bar->foo' => [ + 'branches' => [ + 0 => [ + 'op_start' => 0, + 'op_end' => 2, + 'line_start' => 23, + 'line_end' => 25, + 'hit' => 0, + 'out' => [ + 0 => 2147483645, + ], + 'out_hit' => [ + 0 => 0, + ], + ], + ], + 'paths' => [ + 0 => [ + 'path' => [ + 0 => 0, + ], + 'hit' => 0, + ], + ], + ], + 'Foo->bar' => [ + 'branches' => [ + 0 => [ + 'op_start' => 0, + 'op_end' => 2, + 'line_start' => 13, + 'line_end' => 15, + 'hit' => 0, + 'out' => [ + 0 => 2147483645, + ], + 'out_hit' => [ + 0 => 0, + ], + ], + ], + 'paths' => [ + 0 => [ + 'path' => [ + 0 => 0, + ], + 'hit' => 0, + ], + ], + ], + 'baz' => [ + 'branches' => [ + 0 => [ + 'op_start' => 0, + 'op_end' => 5, + 'line_start' => 28, + 'line_end' => 31, + 'hit' => 0, + 'out' => [ + 0 => 2147483645, + ], + 'out_hit' => [ + 0 => 0, + ], + ], + ], + 'paths' => [ + 0 => [ + 'path' => [ + 0 => 0, + ], + 'hit' => 0, + ], + ], + ], + ], ] ] )); @@ -343,6 +659,58 @@ protected function setUpXdebugStubForClassWithAnonymousFunction() 17 => 1, 18 => 1, ], + 'functions' => [ + 'CoveredClassWithAnonymousFunctionInStaticMethod->runAnonymous' => [ + 'branches' => [ + 0 => [ + 'op_start' => 0, + 'op_end' => 16, + 'line_start' => 5, + 'line_end' => 18, + 'hit' => 0, + 'out' => [ + 0 => 2147483645, + ], + 'out_hit' => [ + 0 => 0, + ], + ], + ], + 'paths' => [ + 0 => [ + 'path' => [ + 0 => 0, + ], + 'hit' => 0, + ], + ], + ], + '{closure:' . TEST_FILES_PATH . 'source_with_class_and_anonymous_function.php:11-13}' => [ + 'branches' => [ + 0 => [ + 'op_start' => 0, + 'op_end' => 12, + 'line_start' => 11, + 'line_end' => 13, + 'hit' => 0, + 'out' => [ + 0 => 2147483645, + ], + 'out_hit' => [ + 0 => 0, + ], + ], + ], + 'paths' => [ + 0 => [ + 'path' => [ + 0 => 0, + ], + 'hit' => 0, + ], + ], + ], + ], ] ] )); From 229b4ee3469a434fe81deea1dc67ad3aa125b369 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Wed, 4 Nov 2015 15:28:35 +0100 Subject: [PATCH 03/18] Workaround Travis Xdebug < 2.3 --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8bceea85b..4459eb803 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,10 @@ language: php php: - 5.6 +install: + - pecl install xdebug + - php --version + before_script: - COMPOSER_ROOT_VERSION=dev-master composer install --prefer-source From b20b5ef842ca875cdd8be471f8fa2de0d588a3e9 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Wed, 4 Nov 2015 13:42:07 +0100 Subject: [PATCH 04/18] Aggregates report --- src/CodeCoverage.php | 5 +- src/CodeCoverage/Report/Node.php | 29 +++ src/CodeCoverage/Report/Node/Directory.php | 48 +++++ src/CodeCoverage/Report/Node/File.php | 170 ++++++++++++------ tests/PHP/CodeCoverage/Report/FactoryTest.php | 10 ++ tests/TestCase.php | 10 +- 6 files changed, 204 insertions(+), 68 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 72ab6c5ca..f7ecd7544 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -591,10 +591,7 @@ private function initializeFilesThatAreSeenTheFirstTime(array $data) } foreach ($functionData['paths'] as $path) { - $this->data[$file]['paths'][$functionName][] = [ - 'path' => $path['path'], - 'tests' => [] - ]; + $this->data[$file]['paths'][$functionName][] = $path; } } } diff --git a/src/CodeCoverage/Report/Node.php b/src/CodeCoverage/Report/Node.php index e515a847a..4732f26a8 100644 --- a/src/CodeCoverage/Report/Node.php +++ b/src/CodeCoverage/Report/Node.php @@ -206,6 +206,21 @@ public function getLineExecutedPercent($asString = true) ); } + /** + * Returns the percentage of executed paths. + * + * @param bool $asString + * @return int + */ + public function getPathExecutedPercent($asString = true) + { + return PHP_CodeCoverage_Util::percent( + $this->getNumExecutedPaths(), + $this->getNumExecutablePaths(), + $asString + ); + } + /** * Returns the number of classes and traits. * @@ -281,6 +296,20 @@ abstract public function getNumExecutableLines(); */ abstract public function getNumExecutedLines(); + /** + * Returns the number of executable paths. + * + * @return int + */ + abstract public function getNumExecutablePaths(); + + /** + * Returns the number of executed paths. + * + * @return int + */ + abstract public function getNumExecutedPaths(); + /** * Returns the number of classes. * diff --git a/src/CodeCoverage/Report/Node/Directory.php b/src/CodeCoverage/Report/Node/Directory.php index b3a650a2b..9be0d535c 100644 --- a/src/CodeCoverage/Report/Node/Directory.php +++ b/src/CodeCoverage/Report/Node/Directory.php @@ -65,6 +65,16 @@ class PHP_CodeCoverage_Report_Node_Directory extends PHP_CodeCoverage_Report_Nod */ protected $numExecutedLines = -1; + /** + * @var int + */ + protected $numExecutablePaths = -1; + + /** + * @var int + */ + protected $numExecutedPaths = -1; + /** * @var int */ @@ -177,6 +187,8 @@ public function addFile($name, array $coverageData, array $testData, $cacheToken $this->numExecutableLines = -1; $this->numExecutedLines = -1; + $this->numExecutablePaths = -1; + $this->numExecutedPaths = -1; return $file; } @@ -332,6 +344,42 @@ public function getNumExecutedLines() return $this->numExecutedLines; } + /** + * Returns the number of executable paths. + * + * @return int + */ + public function getNumExecutablePaths() + { + if ($this->numExecutablePaths == -1) { + $this->numExecutablePaths = 0; + + foreach ($this->children as $child) { + $this->numExecutablePaths += $child->getNumExecutablePaths(); + } + } + + return $this->numExecutablePaths; + } + + /** + * Returns the number of executed paths. + * + * @return int + */ + public function getNumExecutedPaths() + { + if ($this->numExecutedPaths == -1) { + $this->numExecutedPaths = 0; + + foreach ($this->children as $child) { + $this->numExecutedPaths += $child->getNumExecutedPaths(); + } + } + + return $this->numExecutedPaths; + } + /** * Returns the number of classes. * diff --git a/src/CodeCoverage/Report/Node/File.php b/src/CodeCoverage/Report/Node/File.php index 7d839f15f..8ea9142ff 100644 --- a/src/CodeCoverage/Report/Node/File.php +++ b/src/CodeCoverage/Report/Node/File.php @@ -35,6 +35,16 @@ class PHP_CodeCoverage_Report_Node_File extends PHP_CodeCoverage_Report_Node */ protected $numExecutedLines = 0; + /** + * @var int + */ + protected $numExecutablePaths = 0; + + /** + * @var int + */ + protected $numExecutedPaths = 0; + /** * @var array */ @@ -213,6 +223,26 @@ public function getNumExecutedLines() return $this->numExecutedLines; } + /** + * Returns the number of executable paths. + * + * @return int + */ + public function getNumExecutablePaths() + { + return $this->numExecutablePaths; + } + + /** + * Returns the number of executed paths. + * + * @return int + */ + public function getNumExecutedPaths() + { + return $this->numExecutedPaths; + } + /** * Returns the number of classes. * @@ -461,72 +491,25 @@ protected function calculateStatistics() } } - foreach ($this->traits as &$trait) { - foreach ($trait['methods'] as &$method) { - if ($method['executableLines'] > 0) { - $method['coverage'] = ($method['executedLines'] / - $method['executableLines']) * 100; - } else { - $method['coverage'] = 100; - } - - $method['crap'] = $this->crap( - $method['ccn'], - $method['coverage'] - ); + foreach ($this->functions as &$function) { + if (isset($this->coverageData['paths'][$function['functionName']])) { + $functionPaths = $this->coverageData['paths'][$function['functionName']]; + $this->calcPathsAggregate($functionPaths, $numExecutablePaths, $numExecutedPaths); - $trait['ccn'] += $method['ccn']; - } - - if ($trait['executableLines'] > 0) { - $trait['coverage'] = ($trait['executedLines'] / - $trait['executableLines']) * 100; - } else { - $trait['coverage'] = 100; - } + $function['executablePaths'] = $numExecutablePaths; + $this->numExecutablePaths += $numExecutablePaths; - if ($trait['coverage'] == 100) { - $this->numTestedClasses++; + $function['executedPaths'] = $numExecutedPaths; + $this->numExecutedPaths += $numExecutedPaths; } + } - $trait['crap'] = $this->crap( - $trait['ccn'], - $trait['coverage'] - ); + foreach ($this->traits as &$trait) { + $this->calcAndApplyClassAggregate($trait, $trait['traitName']); } foreach ($this->classes as &$class) { - foreach ($class['methods'] as &$method) { - if ($method['executableLines'] > 0) { - $method['coverage'] = ($method['executedLines'] / - $method['executableLines']) * 100; - } else { - $method['coverage'] = 100; - } - - $method['crap'] = $this->crap( - $method['ccn'], - $method['coverage'] - ); - - $class['ccn'] += $method['ccn']; - } - - if ($class['executableLines'] > 0) { - $class['coverage'] = ($class['executedLines'] / - $class['executableLines']) * 100; - } else { - $class['coverage'] = 100; - } - - if ($class['coverage'] == 100) { - $this->numTestedClasses++; - } - - $class['crap'] = $this->crap( - $class['ccn'], - $class['coverage'] - ); + $this->calcAndApplyClassAggregate($class, $class['className']); } } @@ -547,6 +530,8 @@ protected function processClasses(PHP_Token_Stream $tokens) 'startLine' => $class['startLine'], 'executableLines' => 0, 'executedLines' => 0, + 'executablePaths' => 0, + 'executedPaths' => 0, 'ccn' => 0, 'coverage' => 0, 'crap' => 0, @@ -583,6 +568,8 @@ protected function processTraits(PHP_Token_Stream $tokens) 'startLine' => $trait['startLine'], 'executableLines' => 0, 'executedLines' => 0, + 'executablePaths' => 0, + 'executedPaths' => 0, 'ccn' => 0, 'coverage' => 0, 'crap' => 0, @@ -619,6 +606,8 @@ protected function processFunctions(PHP_Token_Stream $tokens) 'startLine' => $function['startLine'], 'executableLines' => 0, 'executedLines' => 0, + 'executablePaths' => 0, + 'executedPaths' => 0, 'ccn' => $function['ccn'], 'coverage' => 0, 'crap' => 0, @@ -672,10 +661,73 @@ private function newMethod($methodName, array $method, $link) 'endLine' => $method['endLine'], 'executableLines' => 0, 'executedLines' => 0, + 'executablePaths' => 0, + 'executedPaths' => 0, 'ccn' => $method['ccn'], 'coverage' => 0, 'crap' => 0, 'link' => $link . $method['startLine'], ]; } + + /** + * @param string $paths + * @param int $functionExecutablePaths + * @param int $functionExecutedPaths + */ + private function calcPathsAggregate($paths, &$functionExecutablePaths, &$functionExecutedPaths) + { + $functionExecutablePaths = count($paths); + $functionExecutedPaths = array_reduce( + $paths, + function ($carry, $value) { + return ($value['hit'] > 0) ? $carry + 1 : $carry; + }, + 0 + ); + } + + /** + * @param array $classOrTrait + * @param string $classOrTraitName + */ + protected function calcAndApplyClassAggregate(&$classOrTrait, $classOrTraitName) + { + foreach ($classOrTrait['methods'] as &$method) { + $methodCoveragePath = $classOrTraitName . '->' . $method['methodName']; + if (isset($this->coverageData['paths'][$methodCoveragePath])) { + $methodPaths = $this->coverageData['paths'][$methodCoveragePath]; + $this->calcPathsAggregate($methodPaths, $numExecutablePaths, $numExecutedPaths); + + $method['executablePaths'] = $numExecutablePaths; + $classOrTrait['executablePaths'] += $numExecutablePaths; + $this->numExecutablePaths += $numExecutablePaths; + + $method['executedPaths'] = $numExecutedPaths; + $classOrTrait['executedPaths'] += $numExecutedPaths; + $this->numExecutedPaths += $numExecutedPaths; + } + if ($method['executableLines'] > 0) { + $method['coverage'] = ($method['executedLines'] / $method['executableLines']) * 100; + } else { + $method['coverage'] = 100; + } + + $method['crap'] = $this->crap($method['ccn'], $method['coverage']); + + $classOrTrait['ccn'] += $method['ccn']; + } + + if ($classOrTrait['executableLines'] > 0) { + $classOrTrait['coverage'] = ($classOrTrait['executedLines'] / $classOrTrait['executableLines']) * 100; + } else { + $classOrTrait['coverage'] = 100; + } + + if ($classOrTrait['coverage'] == 100) { + $this->numTestedClasses++; + } + + $classOrTrait['crap'] = $this->crap($classOrTrait['ccn'], $classOrTrait['coverage']); + } } diff --git a/tests/PHP/CodeCoverage/Report/FactoryTest.php b/tests/PHP/CodeCoverage/Report/FactoryTest.php index 9d1aeba60..33dbe97d9 100644 --- a/tests/PHP/CodeCoverage/Report/FactoryTest.php +++ b/tests/PHP/CodeCoverage/Report/FactoryTest.php @@ -57,6 +57,8 @@ public function testSomething() 'endLine' => 9, 'executableLines' => 1, 'executedLines' => 1, + 'executablePaths' => 1, + 'executedPaths' => 1, 'ccn' => 1, 'coverage' => 100, 'crap' => '1', @@ -70,6 +72,8 @@ public function testSomething() 'endLine' => 18, 'executableLines' => 5, 'executedLines' => 0, + 'executablePaths' => 2, + 'executedPaths' => 0, 'ccn' => 2, 'coverage' => 0, 'crap' => 6, @@ -83,6 +87,8 @@ public function testSomething() 'endLine' => 25, 'executableLines' => 2, 'executedLines' => 2, + 'executablePaths' => 1, + 'executedPaths' => 0, 'ccn' => 1, 'coverage' => 100, 'crap' => '1', @@ -96,6 +102,8 @@ public function testSomething() 'endLine' => 32, 'executableLines' => 2, 'executedLines' => 2, + 'executablePaths' => 1, + 'executedPaths' => 0, 'ccn' => 1, 'coverage' => 100, 'crap' => '1', @@ -107,6 +115,8 @@ public function testSomething() 'startLine' => 2, 'executableLines' => 10, 'executedLines' => 5, + 'executablePaths' => 5, + 'executedPaths' => 1, 'ccn' => 5, 'coverage' => 50, 'crap' => '8.12', diff --git a/tests/TestCase.php b/tests/TestCase.php index b35850192..18595f1e1 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -469,7 +469,7 @@ protected function getExpectedDataArrayForBankAccount() 'path' => [ 0 => 0, ], - 'tests' => [], + 'hit' => 0, ], ], 'BankAccount->getBalance' => [ @@ -477,7 +477,7 @@ protected function getExpectedDataArrayForBankAccount() 'path' => [ 0 => 0, ], - 'tests' => [], + 'hit' => 1, ], ], 'BankAccount->withdrawMoney' => [ @@ -485,7 +485,7 @@ protected function getExpectedDataArrayForBankAccount() 'path' => [ 0 => 0, ], - 'tests' => [], + 'hit' => 0, ], ], 'BankAccount->setBalance' => [ @@ -495,14 +495,14 @@ protected function getExpectedDataArrayForBankAccount() 1 => 5, 2 => 16, ], - 'tests' => [], + 'hit' => 0, ], 1 => [ 'path' => [ 0 => 0, 1 => 9, ], - 'tests' => [], + 'hit' => 0, ], ], ], From bd4c4e8f2f0e9e8032682a76461ff94f186e2452 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Wed, 4 Nov 2015 13:42:19 +0100 Subject: [PATCH 05/18] Text report --- src/CodeCoverage/Report/Text.php | 26 +++++++++++++++++++ tests/_files/BankAccount-text.txt | 3 ++- .../class-with-anonymous-function-text.txt | 3 ++- tests/_files/ignored-lines-text.txt | 1 + 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/CodeCoverage/Report/Text.php b/src/CodeCoverage/Report/Text.php index d37a6d77c..409f209b2 100644 --- a/src/CodeCoverage/Report/Text.php +++ b/src/CodeCoverage/Report/Text.php @@ -55,6 +55,7 @@ public function process(PHP_CodeCoverage $coverage, $showColors = false) 'classes' => '', 'methods' => '', 'lines' => '', + 'paths' => '', 'reset' => '', 'eol' => '' ]; @@ -72,6 +73,10 @@ public function process(PHP_CodeCoverage $coverage, $showColors = false) $report->getNumExecutedLines(), $report->getNumExecutableLines() ); + $colors['paths'] = $this->getCoverageColor( + $report->getNumExecutedPaths(), + $report->getNumExecutablePaths() + ); $colors['reset'] = $this->colors['reset']; $colors['header'] = $this->colors['header']; $colors['eol'] = $this->colors['eol']; @@ -110,6 +115,17 @@ public function process(PHP_CodeCoverage $coverage, $showColors = false) $report->getNumExecutableLines() ); + $paths = sprintf( + ' Paths: %6s (%d/%d)', + PHP_CodeCoverage_Util::percent( + $report->getNumExecutedPaths(), + $report->getNumExecutablePaths(), + true + ), + $report->getNumExecutedPaths(), + $report->getNumExecutablePaths() + ); + $padding = max(array_map('strlen', [$classes, $methods, $lines])); if ($this->showOnlySummary) { @@ -130,6 +146,7 @@ public function process(PHP_CodeCoverage $coverage, $showColors = false) $output .= $this->format($colors['classes'], $padding, $classes); $output .= $this->format($colors['methods'], $padding, $methods); $output .= $this->format($colors['lines'], $padding, $lines); + $output .= $this->format($colors['paths'], $padding, $paths); if ($this->showOnlySummary) { return $output . PHP_EOL; @@ -147,6 +164,8 @@ public function process(PHP_CodeCoverage $coverage, $showColors = false) foreach ($classes as $className => $class) { $classStatements = 0; $coveredClassStatements = 0; + $classPaths = 0; + $coveredClassPaths = 0; $coveredMethods = 0; $classMethods = 0; @@ -158,6 +177,8 @@ public function process(PHP_CodeCoverage $coverage, $showColors = false) $classMethods++; $classStatements += $method['executableLines']; $coveredClassStatements += $method['executedLines']; + $classPaths += $method['executablePaths']; + $coveredClassPaths += $method['executedPaths']; if ($method['coverage'] == 100) { $coveredMethods++; } @@ -178,6 +199,8 @@ public function process(PHP_CodeCoverage $coverage, $showColors = false) 'methodCount' => $classMethods, 'statementsCovered' => $coveredClassStatements, 'statementCount' => $classStatements, + 'pathsCovered' => $coveredClassPaths, + 'pathCount' => $classPaths, ]; } } @@ -186,6 +209,7 @@ public function process(PHP_CodeCoverage $coverage, $showColors = false) $methodColor = ''; $linesColor = ''; + $pathsColor = ''; $resetColor = ''; foreach ($classCoverage as $fullQualifiedPath => $classInfo) { @@ -194,12 +218,14 @@ public function process(PHP_CodeCoverage $coverage, $showColors = false) if ($showColors) { $methodColor = $this->getCoverageColor($classInfo['methodsCovered'], $classInfo['methodCount']); $linesColor = $this->getCoverageColor($classInfo['statementsCovered'], $classInfo['statementCount']); + $pathsColor = $this->getCoverageColor($classInfo['pathsCovered'], $classInfo['pathCount']); $resetColor = $colors['reset']; } $output .= PHP_EOL . $fullQualifiedPath . PHP_EOL . ' ' . $methodColor . 'Methods: ' . $this->printCoverageCounts($classInfo['methodsCovered'], $classInfo['methodCount'], 2) . $resetColor . ' ' . ' ' . $linesColor . 'Lines: ' . $this->printCoverageCounts($classInfo['statementsCovered'], $classInfo['statementCount'], 3) . $resetColor + . ' ' . $pathsColor . ' Paths: ' . $this->printCoverageCounts($classInfo['pathsCovered'], $classInfo['pathCount'], 2) . $resetColor ; } } diff --git a/tests/_files/BankAccount-text.txt b/tests/_files/BankAccount-text.txt index 892d83464..31991cc7b 100644 --- a/tests/_files/BankAccount-text.txt +++ b/tests/_files/BankAccount-text.txt @@ -7,6 +7,7 @@ Code Coverage Report: Classes: 0.00% (0/1) Methods: 75.00% (3/4) Lines: 50.00% (5/10) + Paths: 20.00% (1/5) BankAccount - Methods: 75.00% ( 3/ 4) Lines: 50.00% ( 5/ 10) + Methods: 75.00% ( 3/ 4) Lines: 50.00% ( 5/ 10) Paths: 20.00% ( 1/ 5) diff --git a/tests/_files/class-with-anonymous-function-text.txt b/tests/_files/class-with-anonymous-function-text.txt index 0eb257e5f..77ed817db 100644 --- a/tests/_files/class-with-anonymous-function-text.txt +++ b/tests/_files/class-with-anonymous-function-text.txt @@ -7,6 +7,7 @@ Code Coverage Report: Classes: 0.00% (0/1) Methods: 50.00% (1/2) Lines: 87.50% (7/8) + Paths: 0.00% (0/1) CoveredClassWithAnonymousFunctionInStaticMethod - Methods: 50.00% ( 1/ 2) Lines: 80.00% ( 4/ 5) + Methods: 50.00% ( 1/ 2) Lines: 80.00% ( 4/ 5) Paths: 0.00% ( 0/ 1) diff --git a/tests/_files/ignored-lines-text.txt b/tests/_files/ignored-lines-text.txt index 428303873..5728c3b4c 100644 --- a/tests/_files/ignored-lines-text.txt +++ b/tests/_files/ignored-lines-text.txt @@ -7,4 +7,5 @@ Code Coverage Report: Classes: 100.00% (2/2) Methods: (0/0) Lines: 50.00% (1/2) + Paths: 0.00% (0/3) From 48e1ccb64e4b7e2255b540db0179c648d267c0ec Mon Sep 17 00:00:00 2001 From: Maks3w Date: Wed, 4 Nov 2015 14:33:57 +0100 Subject: [PATCH 06/18] HTML report --- src/CodeCoverage/Report/HTML/Renderer.php | 20 ++++++++ .../Report/HTML/Renderer/Directory.php | 4 ++ .../Report/HTML/Renderer/File.php | 28 ++++++++++ .../Renderer/Template/directory.html.dist | 3 +- .../Template/directory_item.html.dist | 3 ++ .../HTML/Renderer/Template/file.html.dist | 3 +- .../Renderer/Template/file_item.html.dist | 3 ++ .../Renderer/Template/method_item.html.dist | 3 ++ .../BankAccount.php.html | 51 ++++++++++++++++++- .../HTML/CoverageForBankAccount/index.html | 19 ++++++- .../index.html | 19 ++++++- ...with_class_and_anonymous_function.php.html | 35 ++++++++++++- .../index.html | 19 ++++++- .../source_with_ignore.php.html | 51 ++++++++++++++++++- 14 files changed, 253 insertions(+), 8 deletions(-) diff --git a/src/CodeCoverage/Report/HTML/Renderer.php b/src/CodeCoverage/Report/HTML/Renderer.php index e669cd789..b441281fb 100644 --- a/src/CodeCoverage/Report/HTML/Renderer.php +++ b/src/CodeCoverage/Report/HTML/Renderer.php @@ -124,6 +124,22 @@ protected function renderItemTemplate(Text_Template $template, array $data) $data['linesExecutedPercentAsString'] = '100.00%'; } + if ($data['numExecutablePaths'] > 0) { + $pathsLevel = $this->getColorLevel($data['pathsExecutedPercent']); + + $pathsNumber = $data['numExecutedPaths'] . $numSeparator . + $data['numExecutablePaths']; + + $pathsBar = $this->getCoverageBar( + $data['pathsExecutedPercent'] + ); + } else { + $pathsLevel = 'success'; + $pathsNumber = '0' . $numSeparator . '0'; + $pathsBar = $this->getCoverageBar(100); + $data['pathsExecutedPercentAsString'] = '100.00%'; + } + $template->setVar( [ 'icon' => isset($data['icon']) ? $data['icon'] : '', @@ -133,6 +149,10 @@ protected function renderItemTemplate(Text_Template $template, array $data) 'lines_executed_percent' => $data['linesExecutedPercentAsString'], 'lines_level' => $linesLevel, 'lines_number' => $linesNumber, + 'paths_bar' => $pathsBar, + 'paths_executed_percent' => $data['pathsExecutedPercentAsString'], + 'paths_level' => $pathsLevel, + 'paths_number' => $pathsNumber, 'methods_bar' => $methodsBar, 'methods_tested_percent' => $data['testedMethodsPercentAsString'], 'methods_level' => $methodsLevel, diff --git a/src/CodeCoverage/Report/HTML/Renderer/Directory.php b/src/CodeCoverage/Report/HTML/Renderer/Directory.php index b7c0d0d61..71ca727d8 100644 --- a/src/CodeCoverage/Report/HTML/Renderer/Directory.php +++ b/src/CodeCoverage/Report/HTML/Renderer/Directory.php @@ -61,6 +61,10 @@ protected function renderItem(PHP_CodeCoverage_Report_Node $item, $total = false 'linesExecutedPercentAsString' => $item->getLineExecutedPercent(), 'numExecutedLines' => $item->getNumExecutedLines(), 'numExecutableLines' => $item->getNumExecutableLines(), + 'pathsExecutedPercent' => $item->getPathExecutedPercent(false), + 'pathsExecutedPercentAsString' => $item->getPathExecutedPercent(), + 'numExecutedPaths' => $item->getNumExecutedPaths(), + 'numExecutablePaths' => $item->getNumExecutablePaths(), 'testedMethodsPercent' => $item->getTestedMethodsPercent(false), 'testedMethodsPercentAsString' => $item->getTestedMethodsPercent(), 'testedClassesPercent' => $item->getTestedClassesAndTraitsPercent(false), diff --git a/src/CodeCoverage/Report/HTML/Renderer/File.php b/src/CodeCoverage/Report/HTML/Renderer/File.php index 519544656..f59fb3a57 100644 --- a/src/CodeCoverage/Report/HTML/Renderer/File.php +++ b/src/CodeCoverage/Report/HTML/Renderer/File.php @@ -90,6 +90,10 @@ protected function renderItems(PHP_CodeCoverage_Report_Node_File $node) 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(), 'numExecutedLines' => $node->getNumExecutedLines(), 'numExecutableLines' => $node->getNumExecutableLines(), + 'pathsExecutedPercent' => $node->getPathExecutedPercent(false), + 'pathsExecutedPercentAsString' => $node->getPathExecutedPercent(), + 'numExecutedPaths' => $node->getNumExecutedPaths(), + 'numExecutablePaths' => $node->getNumExecutablePaths(), 'testedMethodsPercent' => $node->getTestedMethodsPercent(false), 'testedMethodsPercentAsString' => $node->getTestedMethodsPercent(), 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false), @@ -162,6 +166,18 @@ protected function renderTraitOrClassItems(array $items, Text_Template $template ), 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], + 'pathsExecutedPercent' => PHP_CodeCoverage_Util::percent( + $item['executedPaths'], + $item['executablePaths'], + false + ), + 'pathsExecutedPercentAsString' => PHP_CodeCoverage_Util::percent( + $item['executedPaths'], + $item['executablePaths'], + true + ), + 'numExecutedPaths' => $item['executedPaths'], + 'numExecutablePaths' => $item['executablePaths'], 'testedMethodsPercent' => PHP_CodeCoverage_Util::percent( $numTestedMethods, $numMethods, @@ -253,6 +269,18 @@ protected function renderFunctionOrMethodItem(Text_Template $template, array $it ), 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], + 'pathsExecutedPercent' => PHP_CodeCoverage_Util::percent( + $item['executedPaths'], + $item['executablePaths'], + false + ), + 'pathsExecutedPercentAsString' => PHP_CodeCoverage_Util::percent( + $item['executedPaths'], + $item['executablePaths'], + true + ), + 'numExecutedPaths' => $item['executedPaths'], + 'numExecutablePaths' => $item['executablePaths'], 'testedMethodsPercent' => PHP_CodeCoverage_Util::percent( $numTestedItems, 1, diff --git a/src/CodeCoverage/Report/HTML/Renderer/Template/directory.html.dist b/src/CodeCoverage/Report/HTML/Renderer/Template/directory.html.dist index efe743f51..8488d0eba 100644 --- a/src/CodeCoverage/Report/HTML/Renderer/Template/directory.html.dist +++ b/src/CodeCoverage/Report/HTML/Renderer/Template/directory.html.dist @@ -28,11 +28,12 @@   -
Code Coverage
+
Code Coverage
 
Lines
+
Paths
Functions and Methods
Classes and Traits
diff --git a/src/CodeCoverage/Report/HTML/Renderer/Template/directory_item.html.dist b/src/CodeCoverage/Report/HTML/Renderer/Template/directory_item.html.dist index 78dbb3565..c2e54dc30 100644 --- a/src/CodeCoverage/Report/HTML/Renderer/Template/directory_item.html.dist +++ b/src/CodeCoverage/Report/HTML/Renderer/Template/directory_item.html.dist @@ -3,6 +3,9 @@ {{lines_bar}}
{{lines_executed_percent}}
{{lines_number}}
+ {{paths_bar}} +
{{paths_executed_percent}}
+
{{paths_number}}
{{methods_bar}}
{{methods_tested_percent}}
{{methods_number}}
diff --git a/src/CodeCoverage/Report/HTML/Renderer/Template/file.html.dist b/src/CodeCoverage/Report/HTML/Renderer/Template/file.html.dist index 59a068430..4fb0ca0d0 100644 --- a/src/CodeCoverage/Report/HTML/Renderer/Template/file.html.dist +++ b/src/CodeCoverage/Report/HTML/Renderer/Template/file.html.dist @@ -28,12 +28,13 @@   -
Code Coverage
+
Code Coverage
 
Classes and Traits
Functions and Methods
+
Paths
Lines
diff --git a/src/CodeCoverage/Report/HTML/Renderer/Template/file_item.html.dist b/src/CodeCoverage/Report/HTML/Renderer/Template/file_item.html.dist index 756fdd69b..8d482aa30 100644 --- a/src/CodeCoverage/Report/HTML/Renderer/Template/file_item.html.dist +++ b/src/CodeCoverage/Report/HTML/Renderer/Template/file_item.html.dist @@ -7,6 +7,9 @@
{{methods_tested_percent}}
{{methods_number}}
{{crap}} + {{paths_bar}} +
{{paths_executed_percent}}
+
{{paths_number}}
{{lines_bar}}
{{lines_executed_percent}}
{{lines_number}}
diff --git a/src/CodeCoverage/Report/HTML/Renderer/Template/method_item.html.dist b/src/CodeCoverage/Report/HTML/Renderer/Template/method_item.html.dist index 4bb0e42cc..aca246251 100644 --- a/src/CodeCoverage/Report/HTML/Renderer/Template/method_item.html.dist +++ b/src/CodeCoverage/Report/HTML/Renderer/Template/method_item.html.dist @@ -4,6 +4,9 @@
{{methods_tested_percent}}
{{methods_number}}
{{crap}} + {{paths_bar}} +
{{paths_executed_percent}}
+
{{paths_number}}
{{lines_bar}}
{{lines_executed_percent}}
{{lines_number}}
diff --git a/tests/_files/Report/HTML/CoverageForBankAccount/BankAccount.php.html b/tests/_files/Report/HTML/CoverageForBankAccount/BankAccount.php.html index a5dcd4c2b..b3895de1f 100644 --- a/tests/_files/Report/HTML/CoverageForBankAccount/BankAccount.php.html +++ b/tests/_files/Report/HTML/CoverageForBankAccount/BankAccount.php.html @@ -30,12 +30,13 @@   -
Code Coverage
+
Code Coverage
 
Classes and Traits
Functions and Methods
+
Paths
Lines
@@ -59,6 +60,14 @@
75.00%
3 / 4
CRAP +
+
+ 20.00% covered (danger) +
+
+ +
20.00%
+
1 / 5
50.00% covered (danger) @@ -88,6 +97,14 @@
75.00%
3 / 4
8.12 +
+
+ 20.00% covered (danger) +
+
+ +
20.00%
+
1 / 5
50.00% covered (danger) @@ -114,6 +131,14 @@ 100.00% covered (success)
+ +
100.00%
+
1 / 1
+
+
+ 100.00% covered (success) +
+
100.00%
1 / 1
@@ -135,6 +160,14 @@ 0.00% covered (danger)
+ +
0.00%
+
0 / 2
+
+
+ 0.00% covered (danger) +
+
0.00%
0 / 5
@@ -151,6 +184,14 @@
100.00%
1 / 1
1 +
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 1
100.00% covered (success) @@ -172,6 +213,14 @@
100.00%
1 / 1
1 +
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 1
100.00% covered (success) diff --git a/tests/_files/Report/HTML/CoverageForBankAccount/index.html b/tests/_files/Report/HTML/CoverageForBankAccount/index.html index 5c99c2e42..ad0df9c7c 100644 --- a/tests/_files/Report/HTML/CoverageForBankAccount/index.html +++ b/tests/_files/Report/HTML/CoverageForBankAccount/index.html @@ -30,11 +30,12 @@   -
Code Coverage
+
Code Coverage
 
Lines
+
Paths
Functions and Methods
Classes and Traits
@@ -50,6 +51,14 @@
50.00%
5 / 10
+
+
+ 20.00% covered (danger) +
+
+ +
20.00%
+
1 / 5
75.00% covered (warning) @@ -78,6 +87,14 @@
50.00%
5 / 10
+
+
+ 20.00% covered (danger) +
+
+ +
20.00%
+
1 / 5
75.00% covered (warning) diff --git a/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/index.html b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/index.html index ace676be4..26e5eb522 100644 --- a/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/index.html +++ b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/index.html @@ -30,11 +30,12 @@   -
Code Coverage
+
Code Coverage
 
Lines
+
Paths
Functions and Methods
Classes and Traits
@@ -50,6 +51,14 @@
87.50%
7 / 8
+
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 1
50.00% covered (danger) @@ -78,6 +87,14 @@
87.50%
7 / 8
+
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 1
50.00% covered (danger) diff --git a/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html index ce90096b9..4dd3fba05 100644 --- a/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html +++ b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html @@ -30,12 +30,13 @@   -
Code Coverage
+
Code Coverage
 
Classes and Traits
Functions and Methods
+
Paths
Lines
@@ -59,6 +60,14 @@
50.00%
1 / 2
CRAP +
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 1
87.50% covered (warning) @@ -88,6 +97,14 @@
50.00%
1 / 2
2.01 +
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 1
87.50% covered (warning) @@ -109,6 +126,14 @@
0.00%
0 / 1
1.04 +
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 1
66.67% covered (warning) @@ -135,6 +160,14 @@ 100.00% covered (success)
+ +
100.00%
+
0 / 0
+
+
+ 100.00% covered (success) +
+
100.00%
2 / 2
diff --git a/tests/_files/Report/HTML/CoverageForFileWithIgnoredLines/index.html b/tests/_files/Report/HTML/CoverageForFileWithIgnoredLines/index.html index 2687444ff..85d69a488 100644 --- a/tests/_files/Report/HTML/CoverageForFileWithIgnoredLines/index.html +++ b/tests/_files/Report/HTML/CoverageForFileWithIgnoredLines/index.html @@ -30,11 +30,12 @@   -
Code Coverage
+
Code Coverage
 
Lines
+
Paths
Functions and Methods
Classes and Traits
@@ -50,6 +51,14 @@
50.00%
1 / 2
+
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 3
100.00% covered (success) @@ -78,6 +87,14 @@
50.00%
1 / 2
+
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 3
100.00% covered (success) diff --git a/tests/_files/Report/HTML/CoverageForFileWithIgnoredLines/source_with_ignore.php.html b/tests/_files/Report/HTML/CoverageForFileWithIgnoredLines/source_with_ignore.php.html index 251b5c309..efaa4a20f 100644 --- a/tests/_files/Report/HTML/CoverageForFileWithIgnoredLines/source_with_ignore.php.html +++ b/tests/_files/Report/HTML/CoverageForFileWithIgnoredLines/source_with_ignore.php.html @@ -30,12 +30,13 @@   -
Code Coverage
+
Code Coverage
 
Classes and Traits
Functions and Methods
+
Paths
Lines
@@ -59,6 +60,14 @@
100.00%
0 / 0
CRAP +
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 3
50.00% covered (danger) @@ -80,6 +89,14 @@
100.00%
1 / 1
0 +
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 1
100.00% covered (success) @@ -109,6 +126,14 @@
100.00%
1 / 1
1 +
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 1
100.00% covered (success) @@ -130,6 +155,14 @@
100.00%
1 / 1
1 +
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 1
100.00% covered (success) @@ -159,6 +192,14 @@
100.00%
1 / 1
1 +
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 1
100.00% covered (success) @@ -180,6 +221,14 @@
100.00%
1 / 1
1 +
+
+ 0.00% covered (danger) +
+
+ +
0.00%
+
0 / 1
100.00% covered (success) From 3adac6e4e4264db9ed5091824e0b48b192dbc306 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Wed, 4 Nov 2015 15:55:42 +0100 Subject: [PATCH 07/18] XML report --- src/CodeCoverage/Report/XML.php | 11 ++++++++-- src/CodeCoverage/Report/XML/File/Method.php | 4 +++- src/CodeCoverage/Report/XML/Totals.php | 21 +++++++++++++++++++ .../BankAccount.php.xml | 9 ++++---- .../XML/CoverageForBankAccount/index.xml | 2 ++ .../index.xml | 2 ++ ..._with_class_and_anonymous_function.php.xml | 5 +++-- .../CoverageForFileWithIgnoredLines/index.xml | 2 ++ .../source_with_ignore.php.xml | 7 ++++--- 9 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/CodeCoverage/Report/XML.php b/src/CodeCoverage/Report/XML.php index f1d70ac9d..cfdd6a1b0 100644 --- a/src/CodeCoverage/Report/XML.php +++ b/src/CodeCoverage/Report/XML.php @@ -173,7 +173,9 @@ private function processUnit($unit, PHP_CodeCoverage_Report_XML_File_Report $rep $methodObject->setTotals( $method['executableLines'], $method['executedLines'], - $method['coverage'] + $method['coverage'], + $method['executablePaths'], + $method['executedPaths'] ); } } @@ -185,7 +187,7 @@ private function processFunction($function, PHP_CodeCoverage_Report_XML_File_Rep $functionObject->setSignature($function['signature']); $functionObject->setLines($function['startLine']); $functionObject->setCrap($function['crap']); - $functionObject->setTotals($function['executableLines'], $function['executedLines'], $function['coverage']); + $functionObject->setTotals($function['executableLines'], $function['executedLines'], $function['coverage'], $function['executablePaths'], $function['executedPaths']); } private function processTests(array $tests) @@ -218,6 +220,11 @@ private function setTotals(PHP_CodeCoverage_Report_Node $node, PHP_CodeCoverage_ $node->getNumTestedClasses() ); + $totals->setNumPaths( + $node->getNumExecutablePaths(), + $node->getNumExecutedPaths() + ); + $totals->setNumTraits( $node->getNumTraits(), $node->getNumTestedTraits() diff --git a/src/CodeCoverage/Report/XML/File/Method.php b/src/CodeCoverage/Report/XML/File/Method.php index 917628fd1..08bca5bf4 100644 --- a/src/CodeCoverage/Report/XML/File/Method.php +++ b/src/CodeCoverage/Report/XML/File/Method.php @@ -44,11 +44,13 @@ public function setLines($start, $end = null) } } - public function setTotals($executable, $executed, $coverage) + public function setTotals($executable, $executed, $coverage, $executablePaths, $executedPaths) { $this->contextNode->setAttribute('executable', $executable); $this->contextNode->setAttribute('executed', $executed); $this->contextNode->setAttribute('coverage', $coverage); + $this->contextNode->setAttribute('executablePaths', $executablePaths); + $this->contextNode->setAttribute('executedPaths', $executedPaths); } public function setCrap($crap) diff --git a/src/CodeCoverage/Report/XML/Totals.php b/src/CodeCoverage/Report/XML/Totals.php index d6073d95f..30fecfb54 100644 --- a/src/CodeCoverage/Report/XML/Totals.php +++ b/src/CodeCoverage/Report/XML/Totals.php @@ -23,6 +23,11 @@ class PHP_CodeCoverage_Report_XML_Totals */ private $linesNode; + /** + * @var DOMElement + */ + private $pathsNode; + /** * @var DOMElement */ @@ -53,6 +58,11 @@ public function __construct(DOMElement $container) 'lines' ); + $this->pathsNode = $dom->createElementNS( + 'http://schema.phpunit.de/coverage/1.0', + 'paths' + ); + $this->methodsNode = $dom->createElementNS( 'http://schema.phpunit.de/coverage/1.0', 'methods' @@ -74,6 +84,7 @@ public function __construct(DOMElement $container) ); $container->appendChild($this->linesNode); + $container->appendChild($this->pathsNode); $container->appendChild($this->methodsNode); $container->appendChild($this->functionsNode); $container->appendChild($this->classesNode); @@ -98,6 +109,16 @@ public function setNumLines($loc, $cloc, $ncloc, $executable, $executed) ); } + public function setNumPaths($count, $tested) + { + $this->pathsNode->setAttribute('count', $count); + $this->pathsNode->setAttribute('tested', $tested); + $this->pathsNode->setAttribute( + 'percent', + PHP_CodeCoverage_Util::percent($tested, $count, true) + ); + } + public function setNumClasses($count, $tested) { $this->classesNode->setAttribute('count', $count); diff --git a/tests/_files/Report/XML/CoverageForBankAccount/BankAccount.php.xml b/tests/_files/Report/XML/CoverageForBankAccount/BankAccount.php.xml index d5c5d2e5f..3d7456d97 100644 --- a/tests/_files/Report/XML/CoverageForBankAccount/BankAccount.php.xml +++ b/tests/_files/Report/XML/CoverageForBankAccount/BankAccount.php.xml @@ -3,6 +3,7 @@ + @@ -11,10 +12,10 @@ - - - - + + + + diff --git a/tests/_files/Report/XML/CoverageForBankAccount/index.xml b/tests/_files/Report/XML/CoverageForBankAccount/index.xml index 27fc5b405..63f2f02d7 100644 --- a/tests/_files/Report/XML/CoverageForBankAccount/index.xml +++ b/tests/_files/Report/XML/CoverageForBankAccount/index.xml @@ -10,6 +10,7 @@ + @@ -18,6 +19,7 @@ + diff --git a/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml index 6f9cd19c4..8e9d54175 100644 --- a/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml +++ b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml @@ -7,6 +7,7 @@ + @@ -15,6 +16,7 @@ + diff --git a/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml index d42452474..ca81c7b25 100644 --- a/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml +++ b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml @@ -3,6 +3,7 @@ + @@ -11,8 +12,8 @@ - - + + diff --git a/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/index.xml b/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/index.xml index 35c0745d5..a466509e8 100644 --- a/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/index.xml +++ b/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/index.xml @@ -7,6 +7,7 @@ + @@ -15,6 +16,7 @@ + diff --git a/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/source_with_ignore.php.xml b/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/source_with_ignore.php.xml index 509990f87..1b6a2f1e1 100644 --- a/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/source_with_ignore.php.xml +++ b/tests/_files/Report/XML/CoverageForFileWithIgnoredLines/source_with_ignore.php.xml @@ -3,6 +3,7 @@ + @@ -11,14 +12,14 @@ - + - + - + From d679f67ce80f3a5c451116725760db278e87305f Mon Sep 17 00:00:00 2001 From: Maks3w Date: Wed, 4 Nov 2015 16:56:06 +0100 Subject: [PATCH 08/18] Clover report. Fill Agregate --- src/CodeCoverage/Report/Clover.php | 12 ++++++------ tests/_files/BankAccount-clover.xml | 6 +++--- .../_files/class-with-anonymous-function-clover.xml | 6 +++--- tests/_files/ignored-lines-clover.xml | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/CodeCoverage/Report/Clover.php b/src/CodeCoverage/Report/Clover.php index 1b595d2f4..ec10afed6 100644 --- a/src/CodeCoverage/Report/Clover.php +++ b/src/CodeCoverage/Report/Clover.php @@ -135,8 +135,8 @@ public function process(PHP_CodeCoverage $coverage, $target = null, $name = null $xmlMetrics->setAttribute('complexity', $class['ccn']); $xmlMetrics->setAttribute('methods', $classMethods); $xmlMetrics->setAttribute('coveredmethods', $coveredMethods); - $xmlMetrics->setAttribute('conditionals', 0); - $xmlMetrics->setAttribute('coveredconditionals', 0); + $xmlMetrics->setAttribute('conditionals', $class['executablePaths']); + $xmlMetrics->setAttribute('coveredconditionals', $class['executedPaths']); $xmlMetrics->setAttribute('statements', $classStatements); $xmlMetrics->setAttribute( 'coveredstatements', @@ -205,8 +205,8 @@ public function process(PHP_CodeCoverage $coverage, $target = null, $name = null 'coveredmethods', $item->getNumTestedMethods() ); - $xmlMetrics->setAttribute('conditionals', 0); - $xmlMetrics->setAttribute('coveredconditionals', 0); + $xmlMetrics->setAttribute('conditionals', $item->getNumExecutablePaths()); + $xmlMetrics->setAttribute('coveredconditionals', $item->getNumExecutedPaths()); $xmlMetrics->setAttribute( 'statements', $item->getNumExecutableLines() @@ -258,8 +258,8 @@ public function process(PHP_CodeCoverage $coverage, $target = null, $name = null 'coveredmethods', $report->getNumTestedMethods() ); - $xmlMetrics->setAttribute('conditionals', 0); - $xmlMetrics->setAttribute('coveredconditionals', 0); + $xmlMetrics->setAttribute('conditionals', $report->getNumExecutablePaths()); + $xmlMetrics->setAttribute('coveredconditionals', $report->getNumExecutedPaths()); $xmlMetrics->setAttribute( 'statements', $report->getNumExecutableLines() diff --git a/tests/_files/BankAccount-clover.xml b/tests/_files/BankAccount-clover.xml index 0986fdf71..b39cc51dd 100644 --- a/tests/_files/BankAccount-clover.xml +++ b/tests/_files/BankAccount-clover.xml @@ -3,7 +3,7 @@ - + @@ -19,8 +19,8 @@ - + - + diff --git a/tests/_files/class-with-anonymous-function-clover.xml b/tests/_files/class-with-anonymous-function-clover.xml index d6a8b4085..44b51122f 100644 --- a/tests/_files/class-with-anonymous-function-clover.xml +++ b/tests/_files/class-with-anonymous-function-clover.xml @@ -3,7 +3,7 @@ - + @@ -15,8 +15,8 @@ - + - + diff --git a/tests/_files/ignored-lines-clover.xml b/tests/_files/ignored-lines-clover.xml index 81a9aaa3d..bd72ae967 100644 --- a/tests/_files/ignored-lines-clover.xml +++ b/tests/_files/ignored-lines-clover.xml @@ -3,15 +3,15 @@ - + - + - + - + From e87bf4937922c8871f76e9c83e5020f3376da62c Mon Sep 17 00:00:00 2001 From: Maks3w Date: Wed, 4 Nov 2015 19:04:03 +0100 Subject: [PATCH 09/18] Support branch coverage for anonymous functions At this moment XDebug only tracks anonymous functions inside of methods or functions (not global) --- src/CodeCoverage/Report/Node/File.php | 13 ++++++++++++- .../index.html | 4 ++-- ...urce_with_class_and_anonymous_function.php.html | 14 +++++++------- .../index.xml | 4 ++-- ...ource_with_class_and_anonymous_function.php.xml | 4 ++-- .../class-with-anonymous-function-clover.xml | 6 +++--- .../_files/class-with-anonymous-function-text.txt | 4 ++-- 7 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/CodeCoverage/Report/Node/File.php b/src/CodeCoverage/Report/Node/File.php index 8ea9142ff..f919ad343 100644 --- a/src/CodeCoverage/Report/Node/File.php +++ b/src/CodeCoverage/Report/Node/File.php @@ -694,7 +694,18 @@ function ($carry, $value) { protected function calcAndApplyClassAggregate(&$classOrTrait, $classOrTraitName) { foreach ($classOrTrait['methods'] as &$method) { - $methodCoveragePath = $classOrTraitName . '->' . $method['methodName']; + if ($method['methodName'] === 'anonymous function') { + // Locate index + $methodCoveragePath = $method['methodName']; + foreach ($this->coverageData['branches'] as $index => $branch) { + if ($method['startLine'] === $branch[0]['line_start']) { + $methodCoveragePath = $index; + } + } + + } else { + $methodCoveragePath = $classOrTraitName . '->' . $method['methodName']; + } if (isset($this->coverageData['paths'][$methodCoveragePath])) { $methodPaths = $this->coverageData['paths'][$methodCoveragePath]; $this->calcPathsAggregate($methodPaths, $numExecutablePaths, $numExecutedPaths); diff --git a/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/index.html b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/index.html index 26e5eb522..9600f6d70 100644 --- a/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/index.html +++ b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/index.html @@ -58,7 +58,7 @@
0.00%
-
0 / 1
+
0 / 2
50.00% covered (danger) @@ -94,7 +94,7 @@
0.00%
-
0 / 1
+
0 / 2
50.00% covered (danger) diff --git a/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html index 4dd3fba05..ef6f4883f 100644 --- a/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html +++ b/tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.html @@ -67,7 +67,7 @@
0.00%
-
0 / 1
+
0 / 2
87.50% covered (warning) @@ -104,7 +104,7 @@
0.00%
-
0 / 1
+
0 / 2
87.50% covered (warning) @@ -155,14 +155,14 @@
100.00%
1 / 1
1 -
-
- 100.00% covered (success) +
+
+ 0.00% covered (danger)
-
100.00%
-
0 / 0
+
0.00%
+
0 / 1
100.00% covered (success) diff --git a/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml index 8e9d54175..e8b6188f0 100644 --- a/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml +++ b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/index.xml @@ -7,7 +7,7 @@ - + @@ -16,7 +16,7 @@ - + diff --git a/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml index ca81c7b25..6cfed67bf 100644 --- a/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml +++ b/tests/_files/Report/XML/CoverageForClassWithAnonymousFunction/source_with_class_and_anonymous_function.php.xml @@ -3,7 +3,7 @@ - + @@ -13,7 +13,7 @@ - + diff --git a/tests/_files/class-with-anonymous-function-clover.xml b/tests/_files/class-with-anonymous-function-clover.xml index 44b51122f..2c33d0a50 100644 --- a/tests/_files/class-with-anonymous-function-clover.xml +++ b/tests/_files/class-with-anonymous-function-clover.xml @@ -3,7 +3,7 @@ - + @@ -15,8 +15,8 @@ - + - + diff --git a/tests/_files/class-with-anonymous-function-text.txt b/tests/_files/class-with-anonymous-function-text.txt index 77ed817db..81d42abfb 100644 --- a/tests/_files/class-with-anonymous-function-text.txt +++ b/tests/_files/class-with-anonymous-function-text.txt @@ -7,7 +7,7 @@ Code Coverage Report: Classes: 0.00% (0/1) Methods: 50.00% (1/2) Lines: 87.50% (7/8) - Paths: 0.00% (0/1) + Paths: 0.00% (0/2) CoveredClassWithAnonymousFunctionInStaticMethod - Methods: 50.00% ( 1/ 2) Lines: 80.00% ( 4/ 5) Paths: 0.00% ( 0/ 1) + Methods: 50.00% ( 1/ 2) Lines: 80.00% ( 4/ 5) Paths: 0.00% ( 0/ 2) From c6e5a4b2239c3a2e71022955c70fd12f71797959 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Thu, 5 Nov 2015 00:06:23 +0100 Subject: [PATCH 10/18] Remove redundant isset && !== null --- src/CodeCoverage/Report/Clover.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CodeCoverage/Report/Clover.php b/src/CodeCoverage/Report/Clover.php index ec10afed6..d3e225728 100644 --- a/src/CodeCoverage/Report/Clover.php +++ b/src/CodeCoverage/Report/Clover.php @@ -78,7 +78,7 @@ public function process(PHP_CodeCoverage $coverage, $target = null, $name = null for ($i = $method['startLine']; $i <= $method['endLine']; $i++) { - if (isset($coverage['lines'][$i]) && ($coverage['lines'][$i] !== null)) { + if (isset($coverage['lines'][$i])) { $methodCount = max($methodCount, count($coverage['lines'][$i])); } } From 113e17b0220503287eb53f880058b351c03e9b79 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Thu, 5 Nov 2015 09:01:10 +0100 Subject: [PATCH 11/18] Prepare for extension. Add test key to line data --- src/CodeCoverage.php | 25 +++++++--- src/CodeCoverage/Report/Clover.php | 5 +- .../Report/HTML/Renderer/File.php | 9 ++-- src/CodeCoverage/Report/Node/File.php | 2 +- src/CodeCoverage/Report/XML.php | 9 +++- tests/TestCase.php | 46 +++++++++++++------ 6 files changed, 67 insertions(+), 29 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index f7ecd7544..0d99682e9 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -326,10 +326,15 @@ public function append(array $data, $id = null, $append = true, $linesToBeCovere continue; } - foreach ($fileData['lines'] as $k => $v) { - if ($v == PHP_CodeCoverage_Driver::LINE_EXECUTED) { - if (empty($this->data[$file]['lines'][$k]) || !in_array($id, $this->data[$file]['lines'][$k])) { - $this->data[$file]['lines'][$k][] = $id; + foreach ($fileData['lines'] as $line => $flag) { + if ($flag === PHP_CodeCoverage_Driver::LINE_EXECUTED) { + $lineData = &$this->data[$file]['lines'][$line]; + if ($lineData === null) { + $lineData = [ + 'tests' => [$id], + ]; + } elseif (!in_array($id, $lineData['tests'])) { + $lineData['tests'][] = $id; } } } @@ -361,8 +366,8 @@ public function merge(PHP_CodeCoverage $that) if (!isset($this->data[$file]['lines'][$line])) { $this->data[$file]['lines'][$line] = $data; } else { - $this->data[$file]['lines'][$line] = array_unique( - array_merge($this->data[$file]['lines'][$line], $data) + $this->data[$file]['lines'][$line]['tests'] = array_unique( + array_merge($this->data[$file]['lines'][$line]['tests'], $data['tests']) ); } } @@ -597,7 +602,13 @@ private function initializeFilesThatAreSeenTheFirstTime(array $data) } foreach ($fileData['lines'] as $lineNumber => $flag) { - $this->data[$file]['lines'][$lineNumber] = $flag == -2 ? null : []; + if ($flag === PHP_CodeCoverage_Driver::LINE_NOT_EXECUTABLE) { + $this->data[$file]['lines'][$lineNumber] = null; + } else { + $this->data[$file]['lines'][$lineNumber] = [ + 'tests' => [], + ]; + } } } } diff --git a/src/CodeCoverage/Report/Clover.php b/src/CodeCoverage/Report/Clover.php index d3e225728..431dc9ec8 100644 --- a/src/CodeCoverage/Report/Clover.php +++ b/src/CodeCoverage/Report/Clover.php @@ -79,7 +79,7 @@ public function process(PHP_CodeCoverage $coverage, $target = null, $name = null $i <= $method['endLine']; $i++) { if (isset($coverage['lines'][$i])) { - $methodCount = max($methodCount, count($coverage['lines'][$i])); + $methodCount = max($methodCount, count($coverage['lines'][$i]['tests'])); } } @@ -163,7 +163,8 @@ public function process(PHP_CodeCoverage $coverage, $target = null, $name = null } $lines[$line] = [ - 'count' => count($data), 'type' => 'stmt' + 'count' => count($data['tests']), + 'type' => 'stmt', ]; } diff --git a/src/CodeCoverage/Report/HTML/Renderer/File.php b/src/CodeCoverage/Report/HTML/Renderer/File.php index f59fb3a57..7e7ed40ab 100644 --- a/src/CodeCoverage/Report/HTML/Renderer/File.php +++ b/src/CodeCoverage/Report/HTML/Renderer/File.php @@ -314,23 +314,24 @@ protected function renderSource(PHP_CodeCoverage_Report_Node_File $node) $popoverTitle = ''; if (array_key_exists($i, $coverageData['lines'])) { - $numTests = count($coverageData['lines'][$i]); + $lineData = $coverageData['lines'][$i]; - if ($coverageData['lines'][$i] === null) { + if ($lineData === null) { $trClass = ' class="warning"'; - } elseif ($numTests == 0) { + } elseif (empty($lineData['tests'])) { $trClass = ' class="danger"'; } else { $lineCss = 'covered-by-large-tests'; $popoverContent = '
    '; + $numTests = count($lineData['tests']); if ($numTests > 1) { $popoverTitle = $numTests . ' tests cover line ' . $i; } else { $popoverTitle = '1 test covers line ' . $i; } - foreach ($coverageData['lines'][$i] as $test) { + foreach ($lineData['tests'] as $test) { if ($lineCss == 'covered-by-large-tests' && $testData[$test]['size'] == 'medium') { $lineCss = 'covered-by-medium-tests'; } elseif ($testData[$test]['size'] == 'small') { diff --git a/src/CodeCoverage/Report/Node/File.php b/src/CodeCoverage/Report/Node/File.php index f919ad343..c6d6efde9 100644 --- a/src/CodeCoverage/Report/Node/File.php +++ b/src/CodeCoverage/Report/Node/File.php @@ -439,7 +439,7 @@ protected function calculateStatistics() $this->numExecutableLines++; - if (count($this->coverageData['lines'][$lineNumber]) > 0) { + if (!empty($this->coverageData['lines'][$lineNumber]['tests'])) { if (isset($currentClass)) { $currentClass['executedLines']++; } diff --git a/src/CodeCoverage/Report/XML.php b/src/CodeCoverage/Report/XML.php index cfdd6a1b0..cc936cfeb 100644 --- a/src/CodeCoverage/Report/XML.php +++ b/src/CodeCoverage/Report/XML.php @@ -116,8 +116,13 @@ private function processFile(PHP_CodeCoverage_Report_Node_File $file, PHP_CodeCo $fileData = $file->getCoverageData(); - foreach ($fileData['lines'] as $line => $tests) { - if (!is_array($tests) || count($tests) == 0) { + foreach ($fileData['lines'] as $line => $lineData) { + if ($lineData === null) { + continue; + } + + $tests = $lineData['tests']; + if (empty($tests)) { continue; } diff --git a/tests/TestCase.php b/tests/TestCase.php index 18595f1e1..31b1d99b1 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -392,29 +392,49 @@ protected function getExpectedDataArrayForBankAccount() TEST_FILES_PATH . 'BankAccount.php' => [ 'lines' => [ 8 => [ - 0 => 'BankAccountTest::testBalanceIsInitiallyZero', - 1 => 'BankAccountTest::testDepositWithdrawMoney', + 'tests' => [ + 0 => 'BankAccountTest::testBalanceIsInitiallyZero', + 1 => 'BankAccountTest::testDepositWithdrawMoney', + ], ], 9 => null, - 13 => [], - 14 => [], - 15 => [], - 16 => [], - 18 => [], + 13 => [ + 'tests' => [], + ], + 14 => [ + 'tests' => [], + ], + 15 => [ + 'tests' => [], + ], + 16 => [ + 'tests' => [], + ], + 18 => [ + 'tests' => [], + ], 22 => [ - 0 => 'BankAccountTest::testBalanceCannotBecomeNegative2', - 1 => 'BankAccountTest::testDepositWithdrawMoney', + 'tests' => [ + 0 => 'BankAccountTest::testBalanceCannotBecomeNegative2', + 1 => 'BankAccountTest::testDepositWithdrawMoney', + ], ], 24 => [ - 0 => 'BankAccountTest::testDepositWithdrawMoney', + 'tests' => [ + 0 => 'BankAccountTest::testDepositWithdrawMoney', + ], ], 25 => null, 29 => [ - 0 => 'BankAccountTest::testBalanceCannotBecomeNegative', - 1 => 'BankAccountTest::testDepositWithdrawMoney', + 'tests' => [ + 0 => 'BankAccountTest::testBalanceCannotBecomeNegative', + 1 => 'BankAccountTest::testDepositWithdrawMoney', + ], ], 31 => [ - 0 => 'BankAccountTest::testDepositWithdrawMoney', + 'tests' => [ + 0 => 'BankAccountTest::testDepositWithdrawMoney', + ], ], 32 => null, ], From 735ceffa444f963b6559c2ccfef304290b26e87c Mon Sep 17 00:00:00 2001 From: Maks3w Date: Thu, 5 Nov 2015 09:52:18 +0100 Subject: [PATCH 12/18] Add path coverage to each line --- src/CodeCoverage.php | 17 +++++++++++++++-- tests/TestCase.php | 37 +++++++++++++++++++++++++++---------- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 0d99682e9..9de3df37f 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -331,7 +331,8 @@ public function append(array $data, $id = null, $append = true, $linesToBeCovere $lineData = &$this->data[$file]['lines'][$line]; if ($lineData === null) { $lineData = [ - 'tests' => [$id], + 'pathCovered' => false, + 'tests' => [$id], ]; } elseif (!in_array($id, $lineData['tests'])) { $lineData['tests'][] = $id; @@ -589,6 +590,7 @@ private function initializeFilesThatAreSeenTheFirstTime(array $data) foreach ($functionData['branches'] as $branch) { $this->data[$file]['branches'][$functionName][] = [ + 'hit' => $branch['hit'], 'line_start' => $branch['line_start'], 'line_end' => $branch['line_end'], 'tests' => [] @@ -606,10 +608,21 @@ private function initializeFilesThatAreSeenTheFirstTime(array $data) $this->data[$file]['lines'][$lineNumber] = null; } else { $this->data[$file]['lines'][$lineNumber] = [ - 'tests' => [], + 'pathCovered' => false, + 'tests' => [], ]; } } + + foreach ($this->data[$file]['branches'] as $function) { + foreach ($function as $branch) { + for ($i = $branch['line_start']; $i < $branch['line_end']; $i++) { + if (isset($this->data[$file]['lines'][$i])) { + $this->data[$file]['lines'][$i]['pathCovered'] = (bool) $branch['hit']; + } + } + } + } } } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 31b1d99b1..8e9f94d4d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -392,47 +392,57 @@ protected function getExpectedDataArrayForBankAccount() TEST_FILES_PATH . 'BankAccount.php' => [ 'lines' => [ 8 => [ - 'tests' => [ + 'pathCovered' => true, + 'tests' => [ 0 => 'BankAccountTest::testBalanceIsInitiallyZero', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], ], 9 => null, 13 => [ - 'tests' => [], + 'pathCovered' => false, + 'tests' => [], ], 14 => [ - 'tests' => [], + 'pathCovered' => false, + 'tests' => [], ], 15 => [ - 'tests' => [], + 'pathCovered' => false, + 'tests' => [], ], 16 => [ - 'tests' => [], + 'pathCovered' => false, + 'tests' => [], ], 18 => [ - 'tests' => [], + 'pathCovered' => false, + 'tests' => [], ], 22 => [ - 'tests' => [ + 'pathCovered' => false, + 'tests' => [ 0 => 'BankAccountTest::testBalanceCannotBecomeNegative2', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], ], 24 => [ - 'tests' => [ + 'pathCovered' => false, + 'tests' => [ 0 => 'BankAccountTest::testDepositWithdrawMoney', ], ], 25 => null, 29 => [ - 'tests' => [ + 'pathCovered' => false, + 'tests' => [ 0 => 'BankAccountTest::testBalanceCannotBecomeNegative', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], ], 31 => [ - 'tests' => [ + 'pathCovered' => false, + 'tests' => [ 0 => 'BankAccountTest::testDepositWithdrawMoney', ], ], @@ -444,6 +454,7 @@ protected function getExpectedDataArrayForBankAccount() 'line_start' => 20, 'line_end' => 25, 'tests' => [], + 'hit' => 0, ], ], 'BankAccount->getBalance' => [ @@ -451,6 +462,7 @@ protected function getExpectedDataArrayForBankAccount() 'line_start' => 6, 'line_end' => 9, 'tests' => [], + 'hit' => 1, ], ], 'BankAccount->withdrawMoney' => [ @@ -458,6 +470,7 @@ protected function getExpectedDataArrayForBankAccount() 'line_start' => 27, 'line_end' => 32, 'tests' => [], + 'hit' => 0, ], ], 'BankAccount->setBalance' => [ @@ -465,21 +478,25 @@ protected function getExpectedDataArrayForBankAccount() 'line_start' => 11, 'line_end' => 13, 'tests' => [], + 'hit' => 0, ], 1 => [ 'line_start' => 14, 'line_end' => 15, 'tests' => [], + 'hit' => 0, ], 2 => [ 'line_start' => 16, 'line_end' => 16, 'tests' => [], + 'hit' => 0, ], 3 => [ 'line_start' => 18, 'line_end' => 18, 'tests' => [], + 'hit' => 0, ], ], ], From 868b6111bf92a315e4328a3ee71ef7001518f858 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Thu, 5 Nov 2015 14:20:00 +0100 Subject: [PATCH 13/18] Fix class lookup --- src/CodeCoverage/Report/Node/File.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/CodeCoverage/Report/Node/File.php b/src/CodeCoverage/Report/Node/File.php index c6d6efde9..a8f9770d5 100644 --- a/src/CodeCoverage/Report/Node/File.php +++ b/src/CodeCoverage/Report/Node/File.php @@ -505,11 +505,21 @@ protected function calculateStatistics() } foreach ($this->traits as &$trait) { - $this->calcAndApplyClassAggregate($trait, $trait['traitName']); + $fqcn = $trait['traitName']; + if (!empty($trait['package']['namespace'])) { + $fqcn = $trait['package']['namespace'] . '\\' . $fqcn; + } + + $this->calcAndApplyClassAggregate($trait, $fqcn); } foreach ($this->classes as &$class) { - $this->calcAndApplyClassAggregate($class, $class['className']); + $fqcn = $class['className']; + if (!empty($class['package']['namespace'])) { + $fqcn = $class['package']['namespace'] . '\\' . $fqcn; + } + + $this->calcAndApplyClassAggregate($class, $fqcn); } } From f71b52fbe7fbb18a43709785570c7024e5e360bf Mon Sep 17 00:00:00 2001 From: Maks3w Date: Thu, 5 Nov 2015 16:13:12 +0100 Subject: [PATCH 14/18] Merge pathCovered flag --- src/CodeCoverage.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 9de3df37f..c511e1f49 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -367,6 +367,9 @@ public function merge(PHP_CodeCoverage $that) if (!isset($this->data[$file]['lines'][$line])) { $this->data[$file]['lines'][$line] = $data; } else { + if ($data['pathCovered']) { + $this->data[$file]['lines'][$line]['pathCovered'] = $data['pathCovered']; + } $this->data[$file]['lines'][$line]['tests'] = array_unique( array_merge($this->data[$file]['lines'][$line]['tests'], $data['tests']) ); From 2b0babfeffc8f8f118b30c80a112a64c572529c3 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Thu, 5 Nov 2015 17:19:07 +0100 Subject: [PATCH 15/18] Fix path coverage --- src/CodeCoverage.php | 26 +++++++++++++++++++++----- tests/PHP/CodeCoverageTest.php | 8 +++++++- tests/TestCase.php | 13 +++++++++---- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index c511e1f49..fcef9907a 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -326,9 +326,9 @@ public function append(array $data, $id = null, $append = true, $linesToBeCovere continue; } - foreach ($fileData['lines'] as $line => $flag) { - if ($flag === PHP_CodeCoverage_Driver::LINE_EXECUTED) { - $lineData = &$this->data[$file]['lines'][$line]; + foreach ($fileData['lines'] as $function => $functionCoverage) { + if ($functionCoverage === PHP_CodeCoverage_Driver::LINE_EXECUTED) { + $lineData = &$this->data[$file]['lines'][$function]; if ($lineData === null) { $lineData = [ 'pathCovered' => false, @@ -339,6 +339,22 @@ public function append(array $data, $id = null, $append = true, $linesToBeCovere } } } + + foreach ($fileData['functions'] as $function => $functionCoverage) { + foreach ($functionCoverage['branches'] as $branch => $branchCoverage) { + if ($branchCoverage['hit'] === 1){ + $this->data[$file]['branches'][$function][$branch]['hit'] = 1; + if (!in_array($id, $this->data[$file]['branches'][$function][$branch]['tests'])) { + $this->data[$file]['branches'][$function][$branch]['tests'][] = $id; + } + } + } + foreach ($functionCoverage['paths'] as $path => $pathCoverage) { + if ($pathCoverage['hit'] === 1 && $this->data[$file]['paths'][$function][$path]['hit'] === 0){ + $this->data[$file]['paths'][$function][$path]['hit'] = 1; + } + } + } } } @@ -591,8 +607,8 @@ private function initializeFilesThatAreSeenTheFirstTime(array $data) $this->data[$file]['branches'][$functionName] = []; $this->data[$file]['paths'][$functionName] = []; - foreach ($functionData['branches'] as $branch) { - $this->data[$file]['branches'][$functionName][] = [ + foreach ($functionData['branches'] as $index => $branch) { + $this->data[$file]['branches'][$functionName][$index] = [ 'hit' => $branch['hit'], 'line_start' => $branch['line_start'], 'line_end' => $branch['line_end'], diff --git a/tests/PHP/CodeCoverageTest.php b/tests/PHP/CodeCoverageTest.php index df8dfe41b..ca144bdf1 100644 --- a/tests/PHP/CodeCoverageTest.php +++ b/tests/PHP/CodeCoverageTest.php @@ -262,8 +262,14 @@ public function testMerge() $coverage = $this->getCoverageForBankAccountForFirstTwoTests(); $coverage->merge($this->getCoverageForBankAccountForLastTwoTests()); + $expectedData = $this->getExpectedDataArrayForBankAccount(); + $expectedData[TEST_FILES_PATH . 'BankAccount.php']['branches']['BankAccount->getBalance'][0]['tests'] = [ + 'BankAccountTest::testBalanceIsInitiallyZero', + 'BankAccountTest::testBalanceCannotBecomeNegative', + ]; + $this->assertEquals( - $this->getExpectedDataArrayForBankAccount(), + $expectedData, $coverage->getData() ); } diff --git a/tests/TestCase.php b/tests/TestCase.php index 8e9f94d4d..f25436583 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -461,7 +461,12 @@ protected function getExpectedDataArrayForBankAccount() 0 => [ 'line_start' => 6, 'line_end' => 9, - 'tests' => [], + 'tests' => [ + 'BankAccountTest::testBalanceIsInitiallyZero', + 'BankAccountTest::testBalanceCannotBecomeNegative', + 'BankAccountTest::testBalanceCannotBecomeNegative2', + 'BankAccountTest::testDepositWithdrawMoney', + ], 'hit' => 1, ], ], @@ -480,19 +485,19 @@ protected function getExpectedDataArrayForBankAccount() 'tests' => [], 'hit' => 0, ], - 1 => [ + 5 => [ 'line_start' => 14, 'line_end' => 15, 'tests' => [], 'hit' => 0, ], - 2 => [ + 9 => [ 'line_start' => 16, 'line_end' => 16, 'tests' => [], 'hit' => 0, ], - 3 => [ + 16 => [ 'line_start' => 18, 'line_end' => 18, 'tests' => [], From 38c014b437df3fe02491a9efa3480e7e4b151d38 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Thu, 5 Nov 2015 23:03:13 +0100 Subject: [PATCH 16/18] Loop refactor. Early continue loop --- src/CodeCoverage.php | 76 +++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index fcef9907a..73bf802fb 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -596,49 +596,51 @@ private function applyIgnoredLinesFilter(array &$data) private function initializeFilesThatAreSeenTheFirstTime(array $data) { foreach ($data as $file => $fileData) { - if ($this->filter->isFile($file) && !isset($this->data[$file])) { - $this->data[$file] = ['lines' => []]; - - if ($this->pathCoverage) { - $this->data[$file]['branches'] = []; - $this->data[$file]['paths'] = []; - - foreach ($fileData['functions'] as $functionName => $functionData) { - $this->data[$file]['branches'][$functionName] = []; - $this->data[$file]['paths'][$functionName] = []; - - foreach ($functionData['branches'] as $index => $branch) { - $this->data[$file]['branches'][$functionName][$index] = [ - 'hit' => $branch['hit'], - 'line_start' => $branch['line_start'], - 'line_end' => $branch['line_end'], - 'tests' => [] - ]; - } + if (!$this->filter->isFile($file) || isset($this->data[$file])) { + continue; + } - foreach ($functionData['paths'] as $path) { - $this->data[$file]['paths'][$functionName][] = $path; - } - } - } + $this->data[$file] = ['lines' => []]; - foreach ($fileData['lines'] as $lineNumber => $flag) { - if ($flag === PHP_CodeCoverage_Driver::LINE_NOT_EXECUTABLE) { - $this->data[$file]['lines'][$lineNumber] = null; - } else { - $this->data[$file]['lines'][$lineNumber] = [ - 'pathCovered' => false, - 'tests' => [], + if ($this->pathCoverage) { + $this->data[$file]['branches'] = []; + $this->data[$file]['paths'] = []; + + foreach ($fileData['functions'] as $functionName => $functionData) { + $this->data[$file]['branches'][$functionName] = []; + $this->data[$file]['paths'][$functionName] = []; + + foreach ($functionData['branches'] as $index => $branch) { + $this->data[$file]['branches'][$functionName][$index] = [ + 'hit' => $branch['hit'], + 'line_start' => $branch['line_start'], + 'line_end' => $branch['line_end'], + 'tests' => [] ]; } + + foreach ($functionData['paths'] as $path) { + $this->data[$file]['paths'][$functionName][] = $path; + } + } + } + + foreach ($fileData['lines'] as $lineNumber => $flag) { + if ($flag === PHP_CodeCoverage_Driver::LINE_NOT_EXECUTABLE) { + $this->data[$file]['lines'][$lineNumber] = null; + } else { + $this->data[$file]['lines'][$lineNumber] = [ + 'pathCovered' => false, + 'tests' => [], + ]; } + } - foreach ($this->data[$file]['branches'] as $function) { - foreach ($function as $branch) { - for ($i = $branch['line_start']; $i < $branch['line_end']; $i++) { - if (isset($this->data[$file]['lines'][$i])) { - $this->data[$file]['lines'][$i]['pathCovered'] = (bool) $branch['hit']; - } + foreach ($this->data[$file]['branches'] as $function) { + foreach ($function as $branch) { + for ($i = $branch['line_start']; $i < $branch['line_end']; $i++) { + if (isset($this->data[$file]['lines'][$i])) { + $this->data[$file]['lines'][$i]['pathCovered'] = (bool) $branch['hit']; } } } From 905fe1526a5543f0513ba2a3d3f7beea0aee86d7 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Thu, 5 Nov 2015 23:09:33 +0100 Subject: [PATCH 17/18] Optimize loop --- src/CodeCoverage.php | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 73bf802fb..fa61c3cf6 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -602,13 +602,24 @@ private function initializeFilesThatAreSeenTheFirstTime(array $data) $this->data[$file] = ['lines' => []]; + foreach ($fileData['lines'] as $lineNumber => $flag) { + if ($flag === PHP_CodeCoverage_Driver::LINE_NOT_EXECUTABLE) { + $this->data[$file]['lines'][$lineNumber] = null; + } else { + $this->data[$file]['lines'][$lineNumber] = [ + 'pathCovered' => false, + 'tests' => [], + ]; + } + } + if ($this->pathCoverage) { $this->data[$file]['branches'] = []; $this->data[$file]['paths'] = []; foreach ($fileData['functions'] as $functionName => $functionData) { $this->data[$file]['branches'][$functionName] = []; - $this->data[$file]['paths'][$functionName] = []; + $this->data[$file]['paths'][$functionName] = $functionData['paths']; foreach ($functionData['branches'] as $index => $branch) { $this->data[$file]['branches'][$functionName][$index] = [ @@ -617,30 +628,11 @@ private function initializeFilesThatAreSeenTheFirstTime(array $data) 'line_end' => $branch['line_end'], 'tests' => [] ]; - } - - foreach ($functionData['paths'] as $path) { - $this->data[$file]['paths'][$functionName][] = $path; - } - } - } - - foreach ($fileData['lines'] as $lineNumber => $flag) { - if ($flag === PHP_CodeCoverage_Driver::LINE_NOT_EXECUTABLE) { - $this->data[$file]['lines'][$lineNumber] = null; - } else { - $this->data[$file]['lines'][$lineNumber] = [ - 'pathCovered' => false, - 'tests' => [], - ]; - } - } - foreach ($this->data[$file]['branches'] as $function) { - foreach ($function as $branch) { - for ($i = $branch['line_start']; $i < $branch['line_end']; $i++) { - if (isset($this->data[$file]['lines'][$i])) { - $this->data[$file]['lines'][$i]['pathCovered'] = (bool) $branch['hit']; + for ($i = $branch['line_start']; $i < $branch['line_end']; $i++) { + if (isset($this->data[$file]['lines'][$i])) { + $this->data[$file]['lines'][$i]['pathCovered'] = (bool) $branch['hit']; + } } } } From 267336ffc5f332278b11defa4506a63c578eadc2 Mon Sep 17 00:00:00 2001 From: Maks3w Date: Thu, 5 Nov 2015 19:07:49 +0100 Subject: [PATCH 18/18] Restore backward compatibility Enable pathCoverage by default only when supported --- src/CodeCoverage.php | 51 ++++++++++++--------------- src/CodeCoverage/Driver/Xdebug.php | 18 ++++++---- src/CodeCoverage/Report/Node/File.php | 40 +++++++++++---------- 3 files changed, 55 insertions(+), 54 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index fa61c3cf6..a85030fb9 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -81,22 +81,19 @@ class PHP_CodeCoverage */ private $tests = []; - /** - * @var bool - */ - private $pathCoverage; - /** * Constructor. * * @param PHP_CodeCoverage_Driver $driver * @param PHP_CodeCoverage_Filter $filter - * @param bool $pathCoverage + * @param null|bool $pathCoverage `null` enables path coverage if supported. * @throws PHP_CodeCoverage_InvalidArgumentException */ - public function __construct(PHP_CodeCoverage_Driver $driver = null, PHP_CodeCoverage_Filter $filter = null, $pathCoverage = true) + public function __construct(PHP_CodeCoverage_Driver $driver = null, PHP_CodeCoverage_Filter $filter = null, $pathCoverage = null) { - if (!is_bool($pathCoverage)) { + if ($pathCoverage === null) { + $pathCoverage = version_compare(phpversion('xdebug'), '2.3.2', '>='); + } elseif (!is_bool($pathCoverage)) { throw PHP_CodeCoverage_InvalidArgumentException::create( 3, 'boolean' @@ -113,7 +110,6 @@ public function __construct(PHP_CodeCoverage_Driver $driver = null, PHP_CodeCove $this->driver = $driver; $this->filter = $filter; - $this->pathCoverage = $pathCoverage; } /** @@ -600,7 +596,11 @@ private function initializeFilesThatAreSeenTheFirstTime(array $data) continue; } - $this->data[$file] = ['lines' => []]; + $this->data[$file] = [ + 'lines' => [], + 'branches' =>[], + 'paths' => [], + ]; foreach ($fileData['lines'] as $lineNumber => $flag) { if ($flag === PHP_CodeCoverage_Driver::LINE_NOT_EXECUTABLE) { @@ -613,26 +613,21 @@ private function initializeFilesThatAreSeenTheFirstTime(array $data) } } - if ($this->pathCoverage) { - $this->data[$file]['branches'] = []; - $this->data[$file]['paths'] = []; + foreach ($fileData['functions'] as $functionName => $functionData) { + $this->data[$file]['branches'][$functionName] = []; + $this->data[$file]['paths'][$functionName] = $functionData['paths']; - foreach ($fileData['functions'] as $functionName => $functionData) { - $this->data[$file]['branches'][$functionName] = []; - $this->data[$file]['paths'][$functionName] = $functionData['paths']; - - foreach ($functionData['branches'] as $index => $branch) { - $this->data[$file]['branches'][$functionName][$index] = [ - 'hit' => $branch['hit'], - 'line_start' => $branch['line_start'], - 'line_end' => $branch['line_end'], - 'tests' => [] - ]; + foreach ($functionData['branches'] as $index => $branch) { + $this->data[$file]['branches'][$functionName][$index] = [ + 'hit' => $branch['hit'], + 'line_start' => $branch['line_start'], + 'line_end' => $branch['line_end'], + 'tests' => [] + ]; - for ($i = $branch['line_start']; $i < $branch['line_end']; $i++) { - if (isset($this->data[$file]['lines'][$i])) { - $this->data[$file]['lines'][$i]['pathCovered'] = (bool) $branch['hit']; - } + for ($i = $branch['line_start']; $i < $branch['line_end']; $i++) { + if (isset($this->data[$file]['lines'][$i])) { + $this->data[$file]['lines'][$i]['pathCovered'] = (bool) $branch['hit']; } } } diff --git a/src/CodeCoverage/Driver/Xdebug.php b/src/CodeCoverage/Driver/Xdebug.php index 7220a9cf5..5f0e4588d 100644 --- a/src/CodeCoverage/Driver/Xdebug.php +++ b/src/CodeCoverage/Driver/Xdebug.php @@ -24,16 +24,14 @@ class PHP_CodeCoverage_Driver_Xdebug implements PHP_CodeCoverage_Driver /** * @param bool $pathCoverage */ - public function __construct($pathCoverage = true) + public function __construct($pathCoverage = false) { - if (!extension_loaded('xdebug') || - version_compare(phpversion('xdebug'), '2.3.2', '<')) { - throw new PHP_CodeCoverage_RuntimeException( - 'This driver requires Xdebug 2.3.2 (or newer)' - ); + if (!extension_loaded('xdebug')) { + throw new PHP_CodeCoverage_RuntimeException('This driver requires Xdebug'); } - if (!ini_get('xdebug.coverage_enable')) { + if (version_compare(phpversion('xdebug'), '2.2.0-dev', '>=') && + !ini_get('xdebug.coverage_enable')) { throw new PHP_CodeCoverage_RuntimeException( 'xdebug.coverage_enable=On has to be set in php.ini' ); @@ -42,6 +40,12 @@ public function __construct($pathCoverage = true) $this->flags = XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE; if ($pathCoverage) { + if (version_compare(phpversion('xdebug'), '2.3.2', '<')) { + throw new PHP_CodeCoverage_RuntimeException( + 'Path coverage requires Xdebug 2.3.2 (or newer)' + ); + } + $this->flags |= XDEBUG_CC_BRANCH_CHECK; } } diff --git a/src/CodeCoverage/Report/Node/File.php b/src/CodeCoverage/Report/Node/File.php index a8f9770d5..abe994c6c 100644 --- a/src/CodeCoverage/Report/Node/File.php +++ b/src/CodeCoverage/Report/Node/File.php @@ -704,29 +704,31 @@ function ($carry, $value) { protected function calcAndApplyClassAggregate(&$classOrTrait, $classOrTraitName) { foreach ($classOrTrait['methods'] as &$method) { - if ($method['methodName'] === 'anonymous function') { - // Locate index - $methodCoveragePath = $method['methodName']; - foreach ($this->coverageData['branches'] as $index => $branch) { - if ($method['startLine'] === $branch[0]['line_start']) { - $methodCoveragePath = $index; + if (isset($this->coverageData['branches'])) { + if ($method['methodName'] === 'anonymous function') { + // Locate index + $methodCoveragePath = $method['methodName']; + foreach ($this->coverageData['branches'] as $index => $branch) { + if ($method['startLine'] === $branch[0]['line_start']) { + $methodCoveragePath = $index; + } } - } - } else { - $methodCoveragePath = $classOrTraitName . '->' . $method['methodName']; - } - if (isset($this->coverageData['paths'][$methodCoveragePath])) { - $methodPaths = $this->coverageData['paths'][$methodCoveragePath]; - $this->calcPathsAggregate($methodPaths, $numExecutablePaths, $numExecutedPaths); + } else { + $methodCoveragePath = $classOrTraitName . '->' . $method['methodName']; + } + if (isset($this->coverageData['paths'][$methodCoveragePath])) { + $methodPaths = $this->coverageData['paths'][$methodCoveragePath]; + $this->calcPathsAggregate($methodPaths, $numExecutablePaths, $numExecutedPaths); - $method['executablePaths'] = $numExecutablePaths; - $classOrTrait['executablePaths'] += $numExecutablePaths; - $this->numExecutablePaths += $numExecutablePaths; + $method['executablePaths'] = $numExecutablePaths; + $classOrTrait['executablePaths'] += $numExecutablePaths; + $this->numExecutablePaths += $numExecutablePaths; - $method['executedPaths'] = $numExecutedPaths; - $classOrTrait['executedPaths'] += $numExecutedPaths; - $this->numExecutedPaths += $numExecutedPaths; + $method['executedPaths'] = $numExecutedPaths; + $classOrTrait['executedPaths'] += $numExecutedPaths; + $this->numExecutedPaths += $numExecutedPaths; + } } if ($method['executableLines'] > 0) { $method['coverage'] = ($method['executedLines'] / $method['executableLines']) * 100;