| 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 |