OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright 2015 The Chromium Authors. All rights reserved. | 2 # Copyright 2015 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Generates incremental code coverage reports for Java code in Chromium. | 6 """Generates incremental code coverage reports for Java code in Chromium. |
7 | 7 |
8 Usage: | 8 Usage: |
9 | 9 |
10 build/android/coverage.py -v --out <output file path> --emma-dir | 10 build/android/emma_coverage_stats.py -v --out <output file path> --emma-dir |
11 <EMMA file directory> --lines-for-coverage-file | 11 <EMMA file directory> --lines-for-coverage-file |
12 <path to file containing lines for coverage> | 12 <path to file containing lines for coverage> |
13 | 13 |
14 Creates a JSON representation of the overall and file coverage stats and saves | 14 Creates a JSON representation of the overall and file coverage stats and saves |
15 this information to the specified output file. | 15 this information to the specified output file. |
16 """ | 16 """ |
17 | 17 |
18 import argparse | 18 import argparse |
19 import collections | 19 import collections |
20 import json | 20 import json |
(...skipping 228 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
249 | 249 |
250 Returns: | 250 Returns: |
251 A dict containing coverage stats for the given dict of files and lines. | 251 A dict containing coverage stats for the given dict of files and lines. |
252 Contains absolute coverage stats for each file, coverage stats for each | 252 Contains absolute coverage stats for each file, coverage stats for each |
253 file's lines specified in |lines_for_coverage|, line by line coverage | 253 file's lines specified in |lines_for_coverage|, line by line coverage |
254 for each file, and overall coverage stats for the lines specified in | 254 for each file, and overall coverage stats for the lines specified in |
255 |lines_for_coverage|. | 255 |lines_for_coverage|. |
256 """ | 256 """ |
257 file_coverage = {} | 257 file_coverage = {} |
258 for file_path, line_numbers in lines_for_coverage.iteritems(): | 258 for file_path, line_numbers in lines_for_coverage.iteritems(): |
259 file_coverage[file_path] = self.GetCoverageDictForFile( | 259 file_coverage_dict = self.GetCoverageDictForFile(file_path, line_numbers) |
260 file_path, line_numbers) | 260 if file_coverage_dict: |
| 261 file_coverage[file_path] = file_coverage_dict |
| 262 else: |
| 263 logging.warning( |
| 264 'No code coverage data for %s, skipping.', file_path) |
261 | 265 |
262 covered_statuses = [s['incremental'] for s in file_coverage.itervalues()] | 266 covered_statuses = [s['incremental'] for s in file_coverage.itervalues()] |
263 num_covered_lines = sum(s['covered'] for s in covered_statuses) | 267 num_covered_lines = sum(s['covered'] for s in covered_statuses) |
264 num_total_lines = sum(s['total'] for s in covered_statuses) | 268 num_total_lines = sum(s['total'] for s in covered_statuses) |
265 return { | 269 return { |
266 'files': file_coverage, | 270 'files': file_coverage, |
267 'patch': { | 271 'patch': { |
268 'incremental': { | 272 'incremental': { |
269 'covered': num_covered_lines, | 273 'covered': num_covered_lines, |
270 'total': num_total_lines | 274 'total': num_total_lines |
271 } | 275 } |
272 } | 276 } |
273 } | 277 } |
274 | 278 |
275 def GetCoverageDictForFile(self, file_path, line_numbers): | 279 def GetCoverageDictForFile(self, file_path, line_numbers): |
276 """Returns a dict containing detailed coverage info for the given file. | 280 """Returns a dict containing detailed coverage info for the given file. |
277 | 281 |
278 Args: | 282 Args: |
279 file_path: The path to the Java source file that we want to create the | 283 file_path: The path to the Java source file that we want to create the |
280 coverage dict for. | 284 coverage dict for. |
281 line_numbers: A list of integer line numbers to retrieve additional stats | 285 line_numbers: A list of integer line numbers to retrieve additional stats |
282 for. | 286 for. |
283 | 287 |
284 Returns: | 288 Returns: |
285 A dict containing absolute, incremental, and line by line coverage for | 289 A dict containing absolute, incremental, and line by line coverage for |
286 a file. | 290 a file. |
287 """ | 291 """ |
288 total_line_coverage = self._GetLineCoverageForFile(file_path) | 292 if file_path not in self._source_to_emma: |
| 293 return None |
| 294 emma_file = self._source_to_emma[file_path] |
| 295 total_line_coverage = self._emma_parser.GetLineCoverage(emma_file) |
289 incremental_line_coverage = [line for line in total_line_coverage | 296 incremental_line_coverage = [line for line in total_line_coverage |
290 if line.lineno in line_numbers] | 297 if line.lineno in line_numbers] |
291 line_by_line_coverage = [ | 298 line_by_line_coverage = [ |
292 { | 299 { |
293 'line': line.source, | 300 'line': line.source, |
294 'coverage': line.covered_status, | 301 'coverage': line.covered_status, |
295 'changed': line.lineno in line_numbers, | 302 'changed': line.lineno in line_numbers, |
296 } | 303 } |
297 for line in total_line_coverage | 304 for line in total_line_coverage |
298 ] | 305 ] |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
331 if status == NOT_EXECUTABLE: | 338 if status == NOT_EXECUTABLE: |
332 continue | 339 continue |
333 covered_status_totals[status] += 1 | 340 covered_status_totals[status] += 1 |
334 if status == PARTIALLY_COVERED: | 341 if status == PARTIALLY_COVERED: |
335 partially_covered_sum += line.fractional_line_coverage | 342 partially_covered_sum += line.fractional_line_coverage |
336 | 343 |
337 total_covered = covered_status_totals[COVERED] + partially_covered_sum | 344 total_covered = covered_status_totals[COVERED] + partially_covered_sum |
338 total_lines = sum(covered_status_totals.values()) | 345 total_lines = sum(covered_status_totals.values()) |
339 return total_covered, total_lines | 346 return total_covered, total_lines |
340 | 347 |
341 def _GetLineCoverageForFile(self, file_path): | |
342 """Gets a list of LineCoverage objects corresponding to the given file path. | |
343 | |
344 Args: | |
345 file_path: String representing the path to the Java source file. | |
346 | |
347 Returns: | |
348 A list of LineCoverage objects, or None if there is no EMMA file | |
349 for the given Java source file. | |
350 """ | |
351 if file_path in self._source_to_emma: | |
352 emma_file = self._source_to_emma[file_path] | |
353 return self._emma_parser.GetLineCoverage(emma_file) | |
354 else: | |
355 logging.warning( | |
356 'No code coverage data for %s, skipping.', file_path) | |
357 return None | |
358 | |
359 def _GetSourceFileToEmmaFileDict(self, files): | 348 def _GetSourceFileToEmmaFileDict(self, files): |
360 """Gets a dict used to correlate Java source files with EMMA HTML files. | 349 """Gets a dict used to correlate Java source files with EMMA HTML files. |
361 | 350 |
362 This method gathers the information needed to correlate EMMA HTML | 351 This method gathers the information needed to correlate EMMA HTML |
363 files with Java source files. EMMA XML and plain text reports do not provide | 352 files with Java source files. EMMA XML and plain text reports do not provide |
364 line by line coverage data, so HTML reports must be used instead. | 353 line by line coverage data, so HTML reports must be used instead. |
365 Unfortunately, the HTML files that are created are given garbage names | 354 Unfortunately, the HTML files that are created are given garbage names |
366 (i.e 1.html) so we need to manually correlate EMMA HTML files | 355 (i.e 1.html) so we need to manually correlate EMMA HTML files |
367 with the original Java source files. | 356 with the original Java source files. |
368 | 357 |
(...skipping 12 matching lines...) Expand all Loading... |
381 source_to_package[file_path] = package | 370 source_to_package[file_path] = package |
382 else: | 371 else: |
383 logging.warning("Skipping %s because it doesn\'t have a package " | 372 logging.warning("Skipping %s because it doesn\'t have a package " |
384 "statement.", file_path) | 373 "statement.", file_path) |
385 | 374 |
386 # Maps package names to EMMA report HTML files. | 375 # Maps package names to EMMA report HTML files. |
387 # Example: org.chromium.file.java -> out/coverage/1a.html. | 376 # Example: org.chromium.file.java -> out/coverage/1a.html. |
388 package_to_emma = self._emma_parser.GetPackageNameToEmmaFileDict() | 377 package_to_emma = self._emma_parser.GetPackageNameToEmmaFileDict() |
389 # Finally, we have a dict mapping Java file paths to EMMA report files. | 378 # Finally, we have a dict mapping Java file paths to EMMA report files. |
390 # Example: /usr/code/file.java -> out/coverage/1a.html. | 379 # Example: /usr/code/file.java -> out/coverage/1a.html. |
391 source_to_emma = {source: package_to_emma.get(package) | 380 source_to_emma = {source: package_to_emma[package] |
392 for source, package in source_to_package.iteritems()} | 381 for source, package in source_to_package.iteritems() |
| 382 if package in package_to_emma} |
393 return source_to_emma | 383 return source_to_emma |
394 | 384 |
395 @staticmethod | 385 @staticmethod |
396 def NeedsCoverage(file_path): | 386 def NeedsCoverage(file_path): |
397 """Checks to see if the file needs to be analyzed for code coverage. | 387 """Checks to see if the file needs to be analyzed for code coverage. |
398 | 388 |
399 Args: | 389 Args: |
400 file_path: A string representing path to the file. | 390 file_path: A string representing path to the file. |
401 | 391 |
402 Returns: | 392 Returns: |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
440 Args: | 430 Args: |
441 line_coverage_file: The path to a file which contains a dict mapping file | 431 line_coverage_file: The path to a file which contains a dict mapping file |
442 names to lists of line numbers. Example: {file1: [1, 2, 3], ...} means | 432 names to lists of line numbers. Example: {file1: [1, 2, 3], ...} means |
443 that we should compute coverage information on lines 1 - 3 for file1. | 433 that we should compute coverage information on lines 1 - 3 for file1. |
444 out_file_path: A string representing the location to write the JSON report. | 434 out_file_path: A string representing the location to write the JSON report. |
445 coverage_dir: A string representing the file path where the EMMA | 435 coverage_dir: A string representing the file path where the EMMA |
446 HTML coverage files are located (i.e. folder where index.html is located). | 436 HTML coverage files are located (i.e. folder where index.html is located). |
447 """ | 437 """ |
448 with open(line_coverage_file) as f: | 438 with open(line_coverage_file) as f: |
449 potential_files_for_coverage = json.load(f) | 439 potential_files_for_coverage = json.load(f) |
| 440 |
450 files_for_coverage = {f: lines | 441 files_for_coverage = {f: lines |
451 for f, lines in potential_files_for_coverage.iteritems() | 442 for f, lines in potential_files_for_coverage.iteritems() |
452 if _EmmaCoverageStats.NeedsCoverage(f)} | 443 if _EmmaCoverageStats.NeedsCoverage(f)} |
453 | 444 |
454 coverage_results = {} | 445 coverage_results = {} |
455 if files_for_coverage: | 446 if files_for_coverage: |
456 code_coverage = _EmmaCoverageStats(coverage_dir, files_for_coverage.keys()) | 447 code_coverage = _EmmaCoverageStats(coverage_dir, files_for_coverage.keys()) |
457 coverage_results = code_coverage.GetCoverageDict( | 448 coverage_results = code_coverage.GetCoverageDict( |
458 files_for_coverage) | 449 files_for_coverage) |
459 else: | 450 else: |
(...skipping 16 matching lines...) Expand all Loading... |
476 'code for which coverage information is desired.') | 467 'code for which coverage information is desired.') |
477 argparser.add_argument('-v', '--verbose', action='count', | 468 argparser.add_argument('-v', '--verbose', action='count', |
478 help='Print verbose log information.') | 469 help='Print verbose log information.') |
479 args = argparser.parse_args() | 470 args = argparser.parse_args() |
480 run_tests_helper.SetLogLevel(args.verbose) | 471 run_tests_helper.SetLogLevel(args.verbose) |
481 GenerateCoverageReport(args.lines_for_coverage_file, args.out, args.emma_dir) | 472 GenerateCoverageReport(args.lines_for_coverage_file, args.out, args.emma_dir) |
482 | 473 |
483 | 474 |
484 if __name__ == '__main__': | 475 if __name__ == '__main__': |
485 sys.exit(main()) | 476 sys.exit(main()) |
OLD | NEW |