| 1 |
<?php
|
| 2 |
// $Id: code_coverage.admin.inc,v 1.10 2008/06/29 03:36:20 cwgordon7 Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* Returns all the currently supported reporters.
|
| 6 |
*
|
| 7 |
* @return
|
| 8 |
* All the currently supported reports, in the format 'code' => 'readable'.
|
| 9 |
*/
|
| 10 |
function code_coverage_available_reporters() {
|
| 11 |
return array(
|
| 12 |
'csv' => t('CSV'),
|
| 13 |
'db' => t('Database dump'),
|
| 14 |
'html' => t('HTML'),
|
| 15 |
'xml' => t('XML'),
|
| 16 |
);
|
| 17 |
}
|
| 18 |
|
| 19 |
/**
|
| 20 |
* FAPI callback for the code coverage settings form.
|
| 21 |
*/
|
| 22 |
function code_coverage_settings_form() {
|
| 23 |
drupal_add_js(drupal_get_path('module', 'code_coverage') .'/code_coverage.js');
|
| 24 |
|
| 25 |
$form = array();
|
| 26 |
$form['#prefix'] = t('Changing these settings does not require regeneration of coverage statistics.');
|
| 27 |
|
| 28 |
// Generate a list of all files we /could/ generate reports for.
|
| 29 |
$result = db_query('SELECT DISTINCT(filename) FROM {code_coverage}');
|
| 30 |
$files = array();
|
| 31 |
while ($filename = db_fetch_array($result)) {
|
| 32 |
$files[$filename['filename']] = str_replace('\\', '/', substr(str_replace(getcwd() , '', $filename['filename']), 1));
|
| 33 |
}
|
| 34 |
|
| 35 |
$form['code_coverage_all'] = array(
|
| 36 |
'#type' => 'checkbox',
|
| 37 |
'#title' => t('Include all files'),
|
| 38 |
'#description' => t('If checked, all files will be included in coverage reports. Otherwise, you may chose from a list of files to include.'),
|
| 39 |
'#default_value' => variable_get('code_coverage_all', TRUE),
|
| 40 |
);
|
| 41 |
|
| 42 |
$form['code_coverage_files'] = array(
|
| 43 |
'#type' => 'select',
|
| 44 |
'#title' => t('Code coverage files'),
|
| 45 |
'#description' => t('Select the files which should be included in all code coverage reports.'),
|
| 46 |
'#multiple' => TRUE,
|
| 47 |
'#options' => $files,
|
| 48 |
'#default_value' => variable_get('code_coverage_files', array()),
|
| 49 |
);
|
| 50 |
|
| 51 |
$form['code_coverage_format'] = array(
|
| 52 |
'#type' => 'select',
|
| 53 |
'#title' => t('Available code coverage report formats.'),
|
| 54 |
'#options' => code_coverage_available_reporters(),
|
| 55 |
'#description' => t('Select the format in which code coverage reports should be allowed to be generated.'),
|
| 56 |
'#default_value' => variable_get('code_coverage_format', array('html')),
|
| 57 |
'#multiple' => TRUE,
|
| 58 |
'#required' => TRUE,
|
| 59 |
);
|
| 60 |
|
| 61 |
$form['code_coverage_covered'] = array(
|
| 62 |
'#type' => 'select',
|
| 63 |
'#title' => t('Definition of coverage'),
|
| 64 |
'#description' => t('Select the number of calls at or beyond which a line of code is considered "covered".'),
|
| 65 |
'#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 20, 25, 35, 50, 75, 100)),
|
| 66 |
'#default_value' => variable_get('code_coverage_covered', 1),
|
| 67 |
'#required' => TRUE,
|
| 68 |
);
|
| 69 |
|
| 70 |
$form['code_coverage_file_good'] = array(
|
| 71 |
'#type' => 'select',
|
| 72 |
'#title' => t('Point of "good" coverage'),
|
| 73 |
'#description' => t('Select the percentage of called lines a file needs to have to be considered to have "good" coverage.'),
|
| 74 |
'#options' => drupal_map_assoc(array(10, 25, 40, 50, 60, 65, 70, 75, 80, 85, 90, 93, 95, 96, 97, 98, 99, 100)),
|
| 75 |
'#default_value' => variable_get('code_coverage_file_good', 80),
|
| 76 |
'#required' => TRUE,
|
| 77 |
);
|
| 78 |
|
| 79 |
$form['code_coverage_file_ok'] = array(
|
| 80 |
'#type' => 'select',
|
| 81 |
'#title' => t('Point of "ok" coverage'),
|
| 82 |
'#description' => t('Select the percentage of called lines a file needs to have to be considered to have "ok" coverage.'),
|
| 83 |
'#options' => drupal_map_assoc(array(10, 25, 40, 50, 60, 65, 70, 75, 80, 85, 90, 93, 95, 96, 97, 98, 99, 100)),
|
| 84 |
'#default_value' => variable_get('code_coverage_file_ok', 60),
|
| 85 |
'#required' => TRUE,
|
| 86 |
);
|
| 87 |
|
| 88 |
$form['code_coverage_tmp_storage'] = array(
|
| 89 |
'#type' => 'textfield',
|
| 90 |
'#title' => t('Temporary storage'),
|
| 91 |
'#description' => t('A place to temporarily store data, as an absolute path. You should include a trailing slash.'),
|
| 92 |
'#default_value' => variable_get('code_coverage_tmp_storage', '/tmp/'),
|
| 93 |
'#required' => TRUE,
|
| 94 |
);
|
| 95 |
|
| 96 |
return system_settings_form($form);
|
| 97 |
}
|
| 98 |
|
| 99 |
/**
|
| 100 |
* Menu callback for the generation of the actual coverage report.
|
| 101 |
*/
|
| 102 |
function code_coverage_generate_report($reporter = NULL, $cid = NULL) {
|
| 103 |
$reporters = variable_get('code_coverage_format', array('html'));
|
| 104 |
if (is_null($cid) && count($reporters) == 1) {
|
| 105 |
$cid = $reporter;
|
| 106 |
$copy = $reporters;
|
| 107 |
$reporter = array_shift($copy);
|
| 108 |
}
|
| 109 |
if (is_null($reporter)) {
|
| 110 |
$items = array();
|
| 111 |
$reporter_names = code_coverage_available_reporters();
|
| 112 |
foreach ($reporters as $reporter) {
|
| 113 |
if (isset($reporter_names[$reporter])) {
|
| 114 |
$items[] = l($reporter_names[$reporter], 'coverage/' . $reporter);
|
| 115 |
}
|
| 116 |
}
|
| 117 |
return theme('item_list', $items, t('Choose from any of the following formats.'));
|
| 118 |
}
|
| 119 |
$callback = 'code_coverage_generate_' . $reporter . '_report';
|
| 120 |
if (!function_exists($callback) || !in_array($reporter, $reporters)) {
|
| 121 |
return t('The %format format is not available.', array('%format' => $reporter));
|
| 122 |
}
|
| 123 |
|
| 124 |
if (is_null($cid)) {
|
| 125 |
$result = db_query('SELECT DISTINCT(cid) FROM {code_coverage}');
|
| 126 |
$items = array();
|
| 127 |
while ($cid = db_fetch_array($result)) {
|
| 128 |
$items[] = l(t('Code coverage report #@num', array('@num' => $cid['cid'])), 'coverage/'. $reporter . '/' . $cid['cid']);
|
| 129 |
}
|
| 130 |
return theme('item_list', $items, t('Choose from any of the following reports.'), 'ol');
|
| 131 |
}
|
| 132 |
$covered = variable_get('code_coverage_covered', 1);
|
| 133 |
|
| 134 |
if (!isset($_GET['file'])) {
|
| 135 |
$files = variable_get('code_coverage_all', TRUE) ? NULL : variable_get('code_coverage_files', array());
|
| 136 |
if (is_array($files) && empty($files)) {
|
| 137 |
return t('No code coverage data found.');
|
| 138 |
}
|
| 139 |
}
|
| 140 |
else {
|
| 141 |
$files = $_GET['file'];
|
| 142 |
if (file_exists($files)) {
|
| 143 |
$file = $files;
|
| 144 |
}
|
| 145 |
}
|
| 146 |
$lines = code_coverage_load_lines($files, $cid);
|
| 147 |
if (isset($file) && !isset($lines[$file]) || empty($lines)) {
|
| 148 |
return drupal_not_found();
|
| 149 |
}
|
| 150 |
return $callback($lines, $covered, isset($file) ? $file : NULL, $cid);
|
| 151 |
}
|
| 152 |
|
| 153 |
/**
|
| 154 |
* Helper function for code_coverage_generate_report() - loads all of the lines
|
| 155 |
* from the database.
|
| 156 |
*
|
| 157 |
* @param $files
|
| 158 |
* The files to restrict the load to.
|
| 159 |
* @return
|
| 160 |
* An array of lines loaded from the database.
|
| 161 |
*/
|
| 162 |
function code_coverage_load_lines($files, $cid) {
|
| 163 |
if (is_array($files)) {
|
| 164 |
$placeholders = db_placeholders($files, 'varchar');
|
| 165 |
$result = db_query('SELECT * FROM {code_coverage} WHERE filename IN (' . $placeholders . ') AND cid = %d', array_merge($files, array($cid)));
|
| 166 |
}
|
| 167 |
elseif (!is_null($files)) {
|
| 168 |
$result = db_query("SELECT * FROM {code_coverage} WHERE filename = '%s' AND cid = %d", $files, $cid);
|
| 169 |
}
|
| 170 |
else {
|
| 171 |
$result = db_query('SELECT * FROM {code_coverage} WHERE cid = %d', $cid);
|
| 172 |
}
|
| 173 |
$lines = array();
|
| 174 |
while ($line = db_fetch_array($result)) {
|
| 175 |
$lines[$line['filename']][$line['line']] = $line['times'];
|
| 176 |
}
|
| 177 |
ksort($lines);
|
| 178 |
return $lines;
|
| 179 |
}
|
| 180 |
|
| 181 |
/**
|
| 182 |
* Callback for CSV reporter.
|
| 183 |
*/
|
| 184 |
function code_coverage_generate_csv_report($lines, $covered, $file, $cid) {
|
| 185 |
$csv = '';
|
| 186 |
foreach ($lines as $file => $info) {
|
| 187 |
foreach ($info as $line => $count) {
|
| 188 |
$csv .= '"' . str_replace('"', '""', $file) . '",' . $line . ',' . $count . "\n";
|
| 189 |
}
|
| 190 |
}
|
| 191 |
header('Content-Type: text/plain');
|
| 192 |
print $csv;
|
| 193 |
drupal_page_footer();
|
| 194 |
exit;
|
| 195 |
}
|
| 196 |
|
| 197 |
/**
|
| 198 |
* Callback for database reporter.
|
| 199 |
*/
|
| 200 |
function code_coverage_generate_db_report($lines, $covered, $file, $cid) {
|
| 201 |
$queries = array();
|
| 202 |
$queries[] = '<?php';
|
| 203 |
foreach ($lines as $file => $info) {
|
| 204 |
foreach ($info as $line => $count) {
|
| 205 |
$queries[] = 'db_query(\'INSERT INTO {code_coverage} (cid, filename, line, times) VALUES (%d, \\\'%s\\\' %d, %d)\', ' . $cid . ', ' . var_export($file, TRUE) . ', ' . $line . ', ' . $count . ');';
|
| 206 |
}
|
| 207 |
}
|
| 208 |
header('Content-Type: text/plain');
|
| 209 |
print implode("\n", $queries);
|
| 210 |
drupal_page_footer();
|
| 211 |
exit;
|
| 212 |
}
|
| 213 |
|
| 214 |
/**
|
| 215 |
* Callback for HTML reporter.
|
| 216 |
*/
|
| 217 |
function code_coverage_generate_html_report($lines, $covered, $file, $cid) {
|
| 218 |
drupal_add_css(drupal_get_path('module', 'code_coverage') .'/code_coverage.css');
|
| 219 |
if (empty($file)) {
|
| 220 |
$good = variable_get('code_coverage_file_good', 80);
|
| 221 |
$ok = variable_get('code_coverage_file_ok', 60);
|
| 222 |
drupal_add_js(drupal_get_path('module', 'code_coverage') .'/code_coverage.js');
|
| 223 |
$rows = array();
|
| 224 |
foreach ($lines as $file => $info) {
|
| 225 |
$covered_lines[$file] = 0;
|
| 226 |
$uncovered_lines[$file] = 0;
|
| 227 |
foreach ($info as $line => $count) {
|
| 228 |
if ($count >= $covered) {
|
| 229 |
$covered_lines[$file]++;
|
| 230 |
}
|
| 231 |
else {
|
| 232 |
$uncovered_lines[$file]++;
|
| 233 |
}
|
| 234 |
}
|
| 235 |
$row = array();
|
| 236 |
$row[] = l(str_replace('\\', '/', substr(str_replace(getcwd() , '', $file), 1)), 'coverage/html/' . $cid, array('query' => array('file' => $file)));
|
| 237 |
$row[] = $covered_lines[$file];
|
| 238 |
$row[] = $uncovered_lines[$file];
|
| 239 |
$row[] = sprintf('%04.2f%%', ($covered_lines[$file] * 100) / ($covered_lines[$file] + $uncovered_lines[$file]));
|
| 240 |
$rows[] = array('data' => $row, 'class' => ((substr(end($row), 0, -1) >= $good) ? 'code-coverage-covered' : ((substr(end($row), 0, -1) >= $ok) ? 'code-coverage-moderate' : 'code-coverage-uncovered')));
|
| 241 |
}
|
| 242 |
$total_covered = array_sum($covered_lines);
|
| 243 |
$total_uncovered = array_sum($uncovered_lines);
|
| 244 |
$row = array();
|
| 245 |
$row[] = t('Overall');
|
| 246 |
$row[] = $total_covered;
|
| 247 |
$row[] = $total_uncovered;
|
| 248 |
$row[] = sprintf('%04.2f%%', ($total_covered * 100) / ($total_covered + $total_uncovered));
|
| 249 |
$row = array('data' => $row, 'class' => 'code-coverage-overview ' . ((substr(end($row), 0, -1) > $good) ? 'code-coverage-covered' : ((substr(end($row), 0, -1) > $ok) ? 'code-coverage-moderate' : 'code-coverage-uncovered')));
|
| 250 |
array_unshift($rows, $row);
|
| 251 |
return theme('table', array(t('File !arrow', array('!arrow' => theme('image', 'misc/arrow-asc.png'))), t('Covered lines'), t('Uncovered lines'), t('Code coverage %')), $rows, array('class' => 'code-coverage-overview-report'));
|
| 252 |
}
|
| 253 |
else {
|
| 254 |
drupal_set_title(t('Code coverage for @file', array('@file' => str_replace('\\', '/', substr(str_replace(getcwd() , '', $file), 1)))));
|
| 255 |
$contents = file_get_contents($file);
|
| 256 |
$file_lines = explode("\n", $contents);
|
| 257 |
$lines = $lines[$file];
|
| 258 |
$rows = array();
|
| 259 |
foreach ($file_lines as $key => $line) {
|
| 260 |
$row = array();
|
| 261 |
$row[] = $key + 1;
|
| 262 |
$row[] = isset($lines[$key + 1]) ? $lines[$key + 1] : '';
|
| 263 |
$row[] = '<pre>' . check_plain(wordwrap($line)) . '</pre>';
|
| 264 |
$rows[] = array('data' => $row, 'id' => 'code-coverage-' . ($key + 1), 'class' => (isset($lines[$key + 1]) ? ($lines[$key + 1] >= $covered ? 'code-coverage-covered' : 'code-coverage-uncovered') : 'code-coverage-none'));
|
| 265 |
}
|
| 266 |
return theme('table', array(t('Line #'), t('Times called'), t('Code')), $rows, array('class' => 'code-coverage-file-report'));
|
| 267 |
}
|
| 268 |
}
|
| 269 |
|
| 270 |
/**
|
| 271 |
* Callback for XML reporter.
|
| 272 |
*/
|
| 273 |
function code_coverage_generate_xml_report($lines, $covered, $file, $cid) {
|
| 274 |
$xml = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
|
| 275 |
$xml .= '<coverage_report>' . "\n";
|
| 276 |
foreach ($lines as $file => $info) {
|
| 277 |
$xml .= ' <file>' . "\n";
|
| 278 |
$xml .= ' <filename>' . check_plain($file) . '</filename>' . "\n";
|
| 279 |
$xml .= ' <coverage>' . "\n";
|
| 280 |
foreach ($info as $line => $count) {
|
| 281 |
$xml .= ' <line>' . "\n";
|
| 282 |
$xml .= ' <number>' . $line . '</number>' . "\n";
|
| 283 |
$xml .= ' <count>' . $count . '</count>' . "\n";
|
| 284 |
$xml .= ' </line>' . "\n";
|
| 285 |
}
|
| 286 |
$xml .= ' </coverage>' . "\n";
|
| 287 |
$xml .= ' </file>' . "\n";
|
| 288 |
}
|
| 289 |
$xml .= '</coverage_report>';
|
| 290 |
header('Content-Type: text/xml');
|
| 291 |
print $xml;
|
| 292 |
drupal_page_footer();
|
| 293 |
exit;
|
| 294 |
}
|