Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/admin/tool/unittest/simpletestcoveragelib.php
Go to the documentation of this file.
00001 <?php
00002 // This file is part of Moodle - http://moodle.org/
00003 //
00004 // Moodle is free software: you can redistribute it and/or modify
00005 // it under the terms of the GNU General Public License as published by
00006 // the Free Software Foundation, either version 3 of the License, or
00007 // (at your option) any later version.
00008 //
00009 // Moodle is distributed in the hope that it will be useful,
00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012 // GNU General Public License for more details.
00013 //
00014 // You should have received a copy of the GNU General Public License
00015 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
00016 
00030 defined('MOODLE_INTERNAL') || die();
00031 
00035 require_once($CFG->libdir.'/tablelib.php');
00036 
00037 require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/unittest/simpletestlib.php');
00038 require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/unittest/ex_simple_test.php');
00039 
00040 require_once($CFG->libdir . '/spikephpcoverage/src/CoverageRecorder.php');
00041 require_once($CFG->libdir . '/spikephpcoverage/src/reporter/HtmlCoverageReporter.php');
00042 
00055 class autogroup_test_coverage extends AutoGroupTest {
00056 
00057     private $performcoverage; // boolean
00058     private $coveragename;    // title of the coverage report
00059     private $coveragedir;     // dir, relative to dataroot/coverage where the report will be saved
00060     private $includecoverage; // paths to be analysed by the coverage report
00061     private $excludecoverage; // paths to be excluded from the coverage report
00062 
00063     function __construct($showsearch, $test_name = null,
00064                          $performcoverage = false, $coveragename = 'Code Coverage Report',
00065                          $coveragedir = 'report') {
00066         parent::__construct($showsearch, $test_name);
00067         $this->performcoverage = $performcoverage;
00068         $this->coveragename    = $coveragename;
00069         $this->coveragedir     = $coveragedir;
00070         $this->includecoverage = array();
00071         $this->excludecoverage = array();
00072     }
00073 
00074     public function addTestFile($file, $internalcall = false) {
00075         global $CFG;
00076 
00077         if ($this->performcoverage) {
00078             $refinfo = moodle_reflect_file($file);
00079             require_once($file);
00080             if ($refinfo->classes) {
00081                 foreach ($refinfo->classes as $class) {
00082                     $reflection = new ReflectionClass($class);
00083                     if ($staticprops = $reflection->getStaticProperties()) {
00084                         if (isset($staticprops['includecoverage']) && is_array($staticprops['includecoverage'])) {
00085                             foreach ($staticprops['includecoverage'] as $toinclude) {
00086                                 $this->add_coverage_include_path($toinclude);
00087                             }
00088                         }
00089                         if (isset($staticprops['excludecoverage']) && is_array($staticprops['excludecoverage'])) {
00090                             foreach ($staticprops['excludecoverage'] as $toexclude) {
00091                                 $this->add_coverage_exclude_path($toexclude);
00092                             }
00093                         }
00094                     }
00095                 }
00096                 // Automatically add the test dir itself, so nothing will be covered there
00097                 $this->add_coverage_exclude_path(dirname($file));
00098             }
00099         }
00100         parent::addTestFile($file, $internalcall);
00101     }
00102 
00103     public function add_coverage_include_path($path) {
00104         global $CFG;
00105 
00106         $path = $CFG->dirroot . '/' . $path; // Convert to full path
00107         if (!in_array($path, $this->includecoverage)) {
00108             array_push($this->includecoverage, $path);
00109         }
00110     }
00111 
00112     public function add_coverage_exclude_path($path) {
00113         global $CFG;
00114 
00115         $path = $CFG->dirroot . '/' . $path; // Convert to full path
00116         if (!in_array($path, $this->excludecoverage)) {
00117             array_push($this->excludecoverage, $path);
00118         }
00119     }
00120 
00126     public function run(&$simpletestreporter) {
00127         global $CFG;
00128 
00129         if (moodle_coverage_recorder::can_run_codecoverage() && $this->performcoverage) {
00130             // Testing with coverage
00131             $covreporter = new moodle_coverage_reporter($this->coveragename, $this->coveragedir);
00132             $covrecorder = new moodle_coverage_recorder($covreporter);
00133             $covrecorder->setIncludePaths($this->includecoverage);
00134             $covrecorder->setExcludePaths($this->excludecoverage);
00135             $covrecorder->start_instrumentation();
00136             parent::run($simpletestreporter);
00137             $covrecorder->stop_instrumentation();
00138             $covrecorder->generate_report();
00139             moodle_coverage_reporter::print_summary_info(basename($this->coveragedir));
00140         } else {
00141             // Testing without coverage
00142             parent::run($simpletestreporter);
00143         }
00144     }
00145 
00151     public function run_with_external_coverage(&$simpletestreporter, &$covrecorder) {
00152 
00153         if (moodle_coverage_recorder::can_run_codecoverage() && $this->performcoverage) {
00154             $covrecorder->setIncludePaths($this->includecoverage);
00155             $covrecorder->setExcludePaths($this->excludecoverage);
00156             $covrecorder->start_instrumentation();
00157             parent::run($simpletestreporter);
00158             $covrecorder->stop_instrumentation();
00159         } else {
00160             // Testing without coverage
00161             parent::run($simpletestreporter);
00162         }
00163     }
00164 }
00165 
00181 class moodle_coverage_recorder extends CoverageRecorder {
00182 
00183     public function __construct($reporter='new moodle_coverage_reporter()') {
00184         parent::__construct(array(), array(), $reporter);
00185     }
00186 
00190     public function stop_instrumentation() {
00191         if(extension_loaded("xdebug")) {
00192             $lastcoveragedata = xdebug_get_code_coverage(); // Get last instrumentation coverage data
00193             xdebug_stop_code_coverage(); // Stop code coverage
00194             $this->coverageData = self::merge_coverage_data($this->coverageData, $lastcoveragedata); // Append lastcoveragedata
00195             $this->logger->debug("[moodle_coverage_recorder::stopInstrumentation()] Code coverage: " . print_r($this->coverageData, true),
00196                 __FILE__, __LINE__);
00197             return true;
00198         } else {
00199             $this->logger->critical("[moodle_coverage_recorder::stopInstrumentation()] Xdebug not loaded.", __FILE__, __LINE__);
00200         }
00201         return false;
00202     }
00203 
00207     public function start_instrumentation() {
00208         $this->startInstrumentation(); 
00209     }
00210 
00214     public function generate_report() {
00215         $this->generateReport(); 
00216     }
00217 
00223     static public function can_run_codecoverage() {
00224         // Only req is xdebug loaded. PEAR XML is already in place and available
00225         if(!extension_loaded("xdebug")) {
00226             return false;
00227         }
00228         return true;
00229     }
00230 
00234     protected static function merge_coverage_data($cov1, $cov2) {
00235 
00236         $result = array();
00237 
00238         // protection against empty coverage collections
00239         if (!is_array($cov1)) {
00240             $cov1 = array();
00241         }
00242         if (!is_array($cov2)) {
00243             $cov2 = array();
00244         }
00245 
00246         // Get all the files used in both coverage datas
00247         $files = array_unique(array_merge(array_keys($cov1), array_keys($cov2)));
00248 
00249         // Iterate, getting results
00250         foreach($files as $file) {
00251             // If file exists in both coverages, let's merge their lines
00252             if (array_key_exists($file, $cov1) && array_key_exists($file, $cov2)) {
00253                 $result[$file] = self::merge_lines_coverage_data($cov1[$file], $cov2[$file]);
00254             // Only one of the coverages has the file
00255             } else if (array_key_exists($file, $cov1)) {
00256                 $result[$file] = $cov1[$file];
00257             } else {
00258                 $result[$file] = $cov2[$file];
00259             }
00260         }
00261         return $result;
00262     }
00263 
00269     protected static function merge_lines_coverage_data($lines1, $lines2) {
00270 
00271         $result = array();
00272 
00273         reset($lines1);
00274         reset($lines2);
00275 
00276         while (current($lines1) && current($lines2)) {
00277             $linenr1 = key($lines1);
00278             $linenr2 = key($lines2);
00279 
00280             if ($linenr1 < $linenr2) {
00281                 $result[$linenr1] = current($lines1);
00282                 next($lines1);
00283             } else if ($linenr2 < $linenr1) {
00284                 $result[$linenr2] = current($lines2);
00285                 next($lines2);
00286             } else {
00287                 if (current($lines1) < 0) {
00288                     $result[$linenr2] = current($lines2);
00289                 } else if (current($lines2) < 0) {
00290                     $result[$linenr2] = current($lines1);
00291                 } else {
00292                     $result[$linenr2] = current($lines1) + current($lines2);
00293                 }
00294                 next($lines1);
00295                 next($lines2);
00296             }
00297         }
00298 
00299         while (current($lines1)) {
00300             $result[key($lines1)] = current($lines1);
00301             next($lines1);
00302         }
00303 
00304         while (current($lines2)) {
00305             $result[key($lines2)] = current($lines2);
00306             next($lines2);
00307         }
00308 
00309         return $result;
00310     }
00311 }
00312 
00325 class moodle_coverage_reporter extends HtmlCoverageReporter {
00326 
00327     public function __construct($heading='Coverage Report', $dir='report') {
00328         global $CFG;
00329         parent::__construct($heading, '', $CFG->dataroot . '/codecoverage/' . $dir);
00330     }
00331 
00344     protected function writeIndexFileTableRow($fileLink, $realFile, $fileCoverage) {
00345 
00346         global $CFG;
00347 
00348         $fileLink = str_replace($CFG->dirroot, '', $fileLink);
00349         $realFile = str_replace($CFG->dirroot, '', $realFile);
00350 
00351         return parent::writeIndexFileTableRow($fileLink, $realFile, $fileCoverage);;
00352     }
00353 
00365     protected function markFile($phpFile, $fileLink, &$coverageLines) {
00366         global $CFG;
00367 
00368         $fileLink = str_replace($CFG->dirroot, '', $fileLink);
00369 
00370         return parent::markFile($phpFile, $fileLink, $coverageLines);
00371     }
00372 
00373 
00381     protected function updateGrandTotals(&$coverageCounts) {
00382         $this->grandTotalLines += $coverageCounts['total'];
00383         $this->grandTotalCoveredLines += $coverageCounts['covered'];
00384         $this->grandTotalUncoveredLines += $coverageCounts['uncovered'];
00385     }
00386 
00395     public function generateReport(&$data) {
00396         parent::generateReport($data);
00397 
00398         // head data
00399         $data = new stdClass();
00400         $data->time   = time();
00401         $data->title  = $this->heading;
00402         $data->output = $this->outputDir;
00403 
00404         // summary data
00405         $data->totalfiles       = $this->grandTotalFiles;
00406         $data->totalln          = $this->grandTotalLines;
00407         $data->totalcoveredln   = $this->grandTotalCoveredLines;
00408         $data->totaluncoveredln = $this->grandTotalUncoveredLines;
00409         $data->totalpercentage  = $this->getGrandCodeCoveragePercentage();
00410 
00411         // file details data
00412         $data->coveragedetails = $this->fileCoverage;
00413 
00414         // save serialised object
00415         file_put_contents($data->output . '/codecoverage.ser', serialize($data));
00416     }
00417 
00425     static public function get_summary_info($type) {
00426         global $CFG, $OUTPUT;
00427 
00428         $serfilepath = $CFG->dataroot . '/codecoverage/' . $type . '/codecoverage.ser';
00429         if (file_exists($serfilepath) && is_readable($serfilepath)) {
00430             if ($data = unserialize(file_get_contents($serfilepath))) {
00431                 // return one table with all the totals (we avoid individual file results here)
00432                 $result = '';
00433                 $table = new html_table();
00434                 $table->align = array('right', 'left');
00435                 $table->tablealign = 'center';
00436                 $table->attributes['class'] = 'codecoveragetable';
00437                 $table->id = 'codecoveragetable_' . $type;
00438                 $table->rowclasses = array('label', 'value');
00439                 $table->data = array(
00440                         array(get_string('date')                              , userdate($data->time)),
00441                         array(get_string('files')                             , format_float($data->totalfiles, 0)),
00442                         array(get_string('totallines', 'tool_unittest')       , format_float($data->totalln, 0)),
00443                         array(get_string('executablelines', 'tool_unittest')  , format_float($data->totalcoveredln + $data->totaluncoveredln, 0)),
00444                         array(get_string('coveredlines', 'tool_unittest')     , format_float($data->totalcoveredln, 0)),
00445                         array(get_string('uncoveredlines', 'tool_unittest')   , format_float($data->totaluncoveredln, 0)),
00446                         array(get_string('coveredpercentage', 'tool_unittest'), format_float($data->totalpercentage, 2) . '%')
00447                 );
00448 
00449                 $url = $CFG->wwwroot . '/'.$CFG->admin.'/tool/unittest/coveragefile.php/' . $type . '/index.html';
00450                 $result .= $OUTPUT->heading($data->title, 3, 'main codecoverageheading');
00451                 $result .= $OUTPUT->heading('<a href="' . $url . '" onclick="javascript:window.open(' . "'" . $url . "'" . ');return false;"' .
00452                                    ' title="">' . get_string('codecoveragecompletereport', 'tool_unittest') . '</a>', 4, 'main codecoveragelink');
00453                 $result .= html_writer::table($table);
00454 
00455                 return $OUTPUT->box($result, 'generalbox boxwidthwide boxaligncenter codecoveragebox', '', true);
00456             }
00457         }
00458         return false;
00459     }
00460 
00468     static public function print_summary_info($type) {
00469         echo self::get_summary_info($type);
00470     }
00471 
00479     static public function get_link_to_latest($type) {
00480         global $CFG, $OUTPUT;
00481 
00482         $serfilepath = $CFG->dataroot . '/codecoverage/' . $type . '/codecoverage.ser';
00483         if (file_exists($serfilepath) && is_readable($serfilepath)) {
00484             if ($data = unserialize(file_get_contents($serfilepath))) {
00485                 $info = new stdClass();
00486                 $info->date       = userdate($data->time);
00487                 $info->files      = format_float($data->totalfiles, 0);
00488                 $info->percentage = format_float($data->totalpercentage, 2) . '%';
00489 
00490                 $strlatestreport  = get_string('codecoveragelatestreport', 'tool_unittest');
00491                 $strlatestdetails = get_string('codecoveragelatestdetails', 'tool_unittest', $info);
00492 
00493                 // return one link to latest complete report
00494                 $result = '';
00495                 $url = $CFG->wwwroot . '/'.$CFG->admin.'/tool/unittest/coveragefile.php/' . $type . '/index.html';
00496                 $result .= $OUTPUT->heading('<a href="' . $url . '" onclick="javascript:window.open(' . "'" . $url . "'" . ');return false;"' .
00497                     ' title="">' . $strlatestreport . '</a>', 3, 'main codecoveragelink');
00498                 $result .= $OUTPUT->heading($strlatestdetails, 4, 'main codecoveragedetails');
00499                 return $OUTPUT->box($result, 'generalbox boxwidthwide boxaligncenter codecoveragebox', '', true);
00500             }
00501         }
00502         return false;
00503     }
00504 
00512     static public function print_link_to_latest($type) {
00513         echo self::get_link_to_latest($type);
00514     }
00515 }
00516 
00517 
00533 function moodle_reflect_file($file) {
00534 
00535     $contents = file_get_contents($file);
00536     $tokens   = token_get_all($contents);
00537 
00538     $functionTrapped = false;
00539     $classTrapped    = false;
00540     $openBraces      = 0;
00541 
00542     $classes   = array();
00543     $functions = array();
00544 
00545     foreach ($tokens as $token) {
00546         /*
00547          * Tokens are characters representing symbols or arrays
00548          * representing strings. The keys/values in the arrays are
00549          *
00550          * - 0 => token id,
00551          * - 1 => string,
00552          * - 2 => line number
00553          *
00554          * Token ID's are explained here:
00555          * http://www.php.net/manual/en/tokens.php.
00556          */
00557 
00558         if (is_array($token)) {
00559             $type    = $token[0];
00560             $value   = $token[1];
00561             $lineNum = $token[2];
00562         } else {
00563             // It's a symbol
00564             // Maintain the count of open braces
00565             if ($token == '{') {
00566                 $openBraces++;
00567             } else if ($token == '}') {
00568                 $openBraces--;
00569             }
00570 
00571             continue;
00572         }
00573 
00574         switch ($type) {
00575             // Name of something
00576             case T_STRING:
00577                 if ($functionTrapped) {
00578                     $functions[] = $value;
00579                     $functionTrapped = false;
00580                 } elseif ($classTrapped) {
00581                     $classes[] = $value;
00582                     $classTrapped = false;
00583                 }
00584                 continue;
00585 
00586             // Functions
00587             case T_FUNCTION:
00588                 if ($openBraces == 0) {
00589                     $functionTrapped = true;
00590                 }
00591                 break;
00592 
00593             // Classes
00594             case T_CLASS:
00595                 $classTrapped = true;
00596                 break;
00597 
00598             // Default case: do nothing
00599             default:
00600                 break;
00601         }
00602     }
00603 
00604     return (object)array('classes' => $classes, 'functions' => $functions);
00605 }
 All Data Structures Namespaces Files Functions Variables Enumerations