Created
May 2, 2009 14:15
-
-
Save beberlei/105570 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env php | |
<?php | |
/** | |
* PHPUnit | |
* | |
* Copyright (c) 2002-2009, Sebastian Bergmann <[email protected]>. | |
* Copyright (c) 2009, Benjamin Eberlei <[email protected]> | |
* All rights reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions | |
* are met: | |
* | |
* * Redistributions of source code must retain the above copyright | |
* notice, this list of conditions and the following disclaimer. | |
* | |
* * Redistributions in binary form must reproduce the above copyright | |
* notice, this list of conditions and the following disclaimer in | |
* the documentation and/or other materials provided with the | |
* distribution. | |
* | |
* * Neither the name of Sebastian Bergmann nor the names of his | |
* contributors may be used to endorse or promote products derived | |
* from this software without specific prior written permission. | |
* | |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
* POSSIBILITY OF SUCH DAMAGE. | |
* | |
* @category Testing | |
* @package PHPUnit | |
* @subpackage CoverageRunner | |
* @author Benjamin Eberlei <[email protected]> | |
* @copyright 2002-2009 Sebastian Bergmann <[email protected]> | |
* @copyright 2009 Benjamin Eberlei <kontakt|beberlei.de> | |
* @license http://www.opensource.org/licenses/bsd-license.php BSD License | |
*/ | |
require_once 'PHPUnit/Util/Filter.php'; | |
PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT'); | |
require 'PHPUnit/TextUI/Command.php'; | |
define('PHPUnit_MAIN_METHOD', 'Whitewashing_TextUI_Command::main'); | |
class Whitewashing_Util_ClassCoverageReport | |
{ | |
private $classes = array(); | |
private $projectStatementsTotal = 0; | |
private $projectStatementsCovered = 0; | |
public function __construct($classes, $projectStatementsTotal, $projectCoveredStatements) | |
{ | |
$this->classes = $classes; | |
$this->projectStatementsTotal = $projectStatementsTotal; | |
$this->projectStatementsCovered = $projectCoveredStatements; | |
} | |
/** | |
* @return array('className' => '', 'statements' => 0, 'coveredStatements' => 0); | |
*/ | |
public function getCoveredClasses() | |
{ | |
return $this->classes; | |
} | |
public function getProjectStatements() | |
{ | |
return $this->projectStatementsTotal; | |
} | |
public function getProjectCoveredStatements() | |
{ | |
return $this->projectStatementsCovered; | |
} | |
} | |
class Whitewashing_Util_CoverageCalculator | |
{ | |
private $projectStatements = 0; | |
private $projectCoveredStatements = 0; | |
private $fileStatements = 0; | |
private $fileCoveredStatements = 0; | |
private $projectClassData = array(); | |
private $lines = array(); | |
/** | |
* @param array $files | |
* @return Whitewashing_Util_ClassCoverageReport | |
*/ | |
public function generateReportFromFileCoverageSummary($files) | |
{ | |
$this->resetProjectCounters(); | |
foreach ($files as $filename => $data) { | |
$this->resetFileCounters(); | |
$this->calculateCoverageOfClasses($filename, $data); | |
$this->calculateFileCoverage($data); | |
$this->addFileCoverageToProjectCoverage($filename); | |
} | |
return new Whitewashing_Util_ClassCoverageReport( | |
$this->projectClassData, $this->projectStatements, $this->projectCoveredStatements | |
); | |
} | |
private function resetProjectCounters() | |
{ | |
$this->projectClassData = array(); | |
$this->projectCoveredStatements = 0; | |
$this->projectStatements = 0; | |
} | |
private function calculateCoverageOfClasses($filename, $fileCoverageData) | |
{ | |
$classes = PHPUnit_Util_Class::getClassesInFile($filename); | |
foreach ($classes as $class) { | |
if ($class->isInterface()) { | |
continue; | |
} | |
$className = $class->getName(); | |
$methods = $class->getMethods(); | |
$classStatements = 0; | |
$classCoveredStatements = 0; | |
foreach($methods as $method) { | |
$methodName = $method->getName(); | |
$methodCount = 0; | |
if ($method->getDeclaringClass()->getName() == $class->getName()) { | |
$startLine = $method->getStartLine(); | |
$endLine = $method->getEndLine(); | |
for ($i = $startLine; $i <= $endLine; $i++) { | |
$add = TRUE; | |
$count = 0; | |
if (isset($fileCoverageData[$i])) { | |
if ($fileCoverageData[$i] != -2) { | |
$classStatements++; | |
} | |
if (is_array($fileCoverageData[$i])) { | |
$classCoveredStatements++; | |
$count = count($fileCoverageData[$i]); | |
} | |
else if ($fileCoverageData[$i] == -2) { | |
$add = FALSE; | |
} | |
} else { | |
$add = FALSE; | |
} | |
$methodCount = max($methodCount, $count); | |
if ($add) { | |
$this->lines[$i] = array( | |
'count' => $count, | |
'type' => 'stmt' | |
); | |
} | |
} | |
$this->lines[$startLine] = array( | |
'count' => $methodCount, | |
'type' => 'method' | |
); | |
} | |
} | |
$this->projectClassData[] = array( | |
'className' => $className, | |
'statements' => $classStatements, | |
'coveredStatements' => $classCoveredStatements, | |
); | |
} | |
} | |
private function resetFileCounters() | |
{ | |
$this->lines = array(); | |
$this->fileStatements = 0; | |
$this->fileCoveredStatements = 0; | |
} | |
private function calculateFileCoverage($data) | |
{ | |
foreach ($data as $_line => $_data) { | |
if (is_array($_data)) { | |
$count = count($_data); | |
} | |
else if ($_data == -1) { | |
$count = 0; | |
} | |
else if ($_data == -2) { | |
continue; | |
} | |
$this->lines[$_line] = array( | |
'count' => $count, | |
'type' => 'stmt' | |
); | |
} | |
foreach ($this->lines as $_line => $_data) { | |
if ($_data['type'] == 'stmt') { | |
if ($_data['count'] != 0) { | |
$this->fileCoveredStatements++; | |
} | |
$this->fileStatements++; | |
} | |
} | |
} | |
private function addFileCoverageToProjectCoverage($filename) | |
{ | |
if (file_exists($filename)) { | |
$this->projectStatements += $this->fileStatements; | |
$this->projectCoveredStatements += $this->fileCoveredStatements; | |
} | |
} | |
} | |
class Whitewashing_TextUI_CoveragePrinter extends PHPUnit_TextUI_ResultPrinter | |
{ | |
const COLOR_RED = "\x1b[37;41m\x1b[2K"; | |
const COLOR_YELLOW = "\x1b[30;43m\x1b[2K"; | |
const COLOR_GREEN = "\x1b[30;42m\x1b[2K"; | |
const COLOR_DEFAULT = "\x1b[0m\x1b[2K"; | |
/** | |
* Prints Code Coverage Report if no errors or failures are found, default report otherwise. | |
* | |
* @param PHPUnit_Framework_TestResult $result | |
*/ | |
public function printResult(PHPUnit_Framework_TestResult $result) | |
{ | |
if ($result->errorCount() > 0 || $result->failureCount() > 0) { | |
$this->write("Will not print Class-Level Code Coverage Report, because of failures and errors.\n\n"); | |
parent::printResult($result); | |
} else { | |
$this->printClassLevelCoverageReport($result); | |
} | |
} | |
/** | |
* @param PHPUnit_Framework_TestResult $result | |
*/ | |
private function printClassLevelCoverageReport(PHPUnit_Framework_TestResult $result) | |
{ | |
$report = $this->generateClassLevelCoverageReport($result); | |
$this->printCoverageReportHeader(); | |
$this->printCoverageReportClassList($report); | |
$this->printCoverageReportProjectOverview($report); | |
$this->flush(); | |
} | |
private function generateClassLevelCoverageReport(PHPUnit_Framework_TestResult $result) | |
{ | |
$codeCoverageInformation = $result->getCodeCoverageInformation(); | |
$files = PHPUnit_Util_CodeCoverage::getSummary($codeCoverageInformation); | |
$calculator = new Whitewashing_Util_CoverageCalculator(); | |
return $calculator->generateReportFromFileCoverageSummary($files); | |
} | |
private function printCoverageReportHeader() | |
{ | |
$this->write("\n\n"); | |
$this->write("Class-Level Code Coverage Report:\n\n"); | |
} | |
private function printCoverageReportClassList($report) | |
{ | |
$projectClassData = $report->getCoveredClasses(); | |
usort($projectClassData, array($this, 'sortByClassNameCallback')); | |
foreach($projectClassData AS $class) { | |
$this->printCoverageLine($class['className'], $class['statements'], $class['coveredStatements']); | |
} | |
$this->write("\n"); | |
} | |
private function printCoverageReportProjectOverview($report) | |
{ | |
$projectStatements = $report->getProjectStatements(); | |
$projectCoveredStatements = $report->getProjectCoveredStatements(); | |
$this->printCoverageLine("PROJECT COVERAGE", $projectStatements, $projectCoveredStatements); | |
$this->write("\n"); | |
} | |
private function sortByClassNameCallback($classA, $classB) | |
{ | |
if($classA['className'] > $classB['className']) { | |
return 1; | |
} else if($classA['className'] == $classB['className']) { | |
return 0; | |
} else { | |
return -1; | |
} | |
} | |
private function printCoverageLine($className, $statements, $coveredStatements) | |
{ | |
$len = strlen($className); | |
$coveredStatements = min($statements, $coveredStatements); | |
$percentage = 0; | |
if($statements > 0) { | |
$percentage = $coveredStatements/$statements*100; | |
} | |
$this->setCoverageLineColorBasedOnRatio($percentage); | |
$percentage = sprintf('%03.2f', $percentage); | |
$ratio = $coveredStatements."/".$statements; | |
$this->write($className); | |
$this->write(str_repeat(" ", 70-$len)); | |
$this->write($ratio); | |
$this->write(str_repeat(" ", 14-strlen($ratio))); | |
$this->write($percentage."%\n"); | |
$this->write(self::COLOR_DEFAULT); | |
} | |
private function setCoverageLineColorBasedOnRatio($percentage) | |
{ | |
if($percentage > 80) { | |
$this->write(self::COLOR_GREEN); | |
} else if($percentage > 50) { | |
$this->write(self::COLOR_YELLOW); | |
} else { | |
$this->write(self::COLOR_RED); | |
} | |
} | |
} | |
class Whitewashing_TextUI_CoverageRunner extends PHPUnit_TextUI_TestRunner | |
{ | |
/** | |
* @return PHPUnit_Framework_TestResult | |
*/ | |
protected function createTestResult() | |
{ | |
$result = new PHPUnit_Framework_TestResult; | |
$result->collectCodeCoverageInformation(true); | |
return $result; | |
} | |
} | |
class Whitewashing_TextUI_Command extends PHPUnit_TextUI_Command | |
{ | |
/** | |
*/ | |
public static function main($exit = TRUE) | |
{ | |
$arguments = self::handleArguments(); | |
$runner = new Whitewashing_TextUI_CoverageRunner; | |
$runner->setPrinter(new Whitewashing_TextUI_CoveragePrinter); | |
if (is_object($arguments['test']) && $arguments['test'] instanceof PHPUnit_Framework_Test) { | |
$suite = $arguments['test']; | |
} else { | |
$suite = $runner->getTest( | |
$arguments['test'], | |
$arguments['testFile'], | |
$arguments['syntaxCheck'] | |
); | |
} | |
if ($suite->testAt(0) instanceof PHPUnit_Framework_Warning && | |
strpos($suite->testAt(0)->getMessage(), 'No tests found in class') !== FALSE) { | |
$message = $suite->testAt(0)->getMessage(); | |
$start = strpos($message, '"') + 1; | |
$end = strpos($message, '"', $start); | |
$className = substr($message, $start, $end - $start); | |
require_once 'PHPUnit/Util/Skeleton/Test.php'; | |
$skeleton = new PHPUnit_Util_Skeleton_Test( | |
$className, | |
$arguments['testFile'] | |
); | |
$result = $skeleton->generate(TRUE); | |
if (!$result['incomplete']) { | |
eval(str_replace(array('<?php', '?>'), '', $result['code'])); | |
$suite = new PHPUnit_Framework_TestSuite($arguments['test'] . 'Test'); | |
} | |
} | |
if ($arguments['listGroups']) { | |
PHPUnit_TextUI_TestRunner::printVersionString(); | |
print "Available test group(s):\n"; | |
$groups = $suite->getGroups(); | |
sort($groups); | |
foreach ($groups as $group) { | |
print " - $group\n"; | |
} | |
exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT); | |
} | |
try { | |
$result = $runner->doRun( | |
$suite, | |
$arguments | |
); | |
} | |
catch (Exception $e) { | |
throw new RuntimeException( | |
'Could not create and run test suite: ' . $e->getMessage() | |
); | |
} | |
if ($exit) { | |
if ($result->wasSuccessful()) { | |
exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT); | |
} | |
else if ($result->errorCount() > 0) { | |
exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT); | |
} | |
else { | |
exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT); | |
} | |
} | |
} | |
} | |
Whitewashing_TextUI_Command::main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment