blob: e857b2def6215072769b96378ee38c47b7f9186c [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Aggregates Jacoco coverage files to produce output."""
from __future__ import print_function
import argparse
import fnmatch
import json
import os
import sys
import devil_chromium
from devil.utils import cmd_helper
from pylib.constants import host_paths
# Source paths should be passed to Jacoco in a way that the relative file paths
# reflect the class package name.
_PARTIAL_PACKAGE_NAMES = ['com/google', 'org/chromium']
# The sources_json_file is generated by jacoco_instr.py with source directories
# and input path to non-instrumented jars.
# e.g.
# 'source_dirs': [
# "chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom",
# "chrome/android/java/src/org/chromium/chrome/browser/ui/system",
# ...]
# 'input_path':
# '$CHROMIUM_OUTPUT_DIR/\
# obj/chrome/android/features/tab_ui/java__process_prebuilt-filtered.jar'
_SOURCES_JSON_FILES_SUFFIX = '__jacoco_sources.json'
def _GetFilesWithSuffix(root_dir, suffix):
"""Gets all files with a given suffix.
Args:
root_dir: Directory in which to search for files.
suffix: Suffix to look for.
Returns:
A list of absolute paths to files that match.
"""
files = []
for root, _, filenames in os.walk(root_dir):
basenames = fnmatch.filter(filenames, '*' + suffix)
files.extend([os.path.join(root, basename) for basename in basenames])
return files
def _ParseArguments(parser):
"""Parses the command line arguments.
Args:
parser: ArgumentParser object.
Returns:
The parsed arguments.
"""
parser.add_argument(
'--format',
required=True,
choices=['html', 'xml', 'csv'],
help='Output report format. Choose one from html, xml and csv.')
parser.add_argument('--output-dir', help='html report output directory.')
parser.add_argument('--output-file', help='xml or csv report output file.')
parser.add_argument(
'--coverage-dir',
required=True,
help='Root of the directory in which to search for '
'coverage data (.exec) files.')
parser.add_argument(
'--sources-json-dir',
help='Root of the directory in which to search for '
'*__jacoco_sources.json files.')
parser.add_argument(
'--class-files',
nargs='+',
help='Location of Java non-instrumented class files. '
'Use non-instrumented jars instead of instrumented jars. '
'e.g. use chrome_java__process_prebuilt-filtered.jar instead of'
'chrome_java__process_prebuilt-instrumented.jar')
parser.add_argument(
'--sources',
nargs='+',
help='Location of the source files. '
'Specified source folders must be the direct parent of the folders '
'that define the Java packages.'
'e.g. <src_dir>/chrome/android/java/src/')
parser.add_argument(
'--cleanup',
action='store_true',
help='If set, removes coverage files generated at '
'runtime.')
args = parser.parse_args()
if args.format == 'html':
if not args.output_dir:
parser.error('--output-dir needed for html report.')
elif not args.output_file:
parser.error('--output-file needed for xml or csv report.')
if not (args.sources_json_dir or args.class_files):
parser.error('At least either --sources-json-dir or --class-files needed.')
return args
def main():
parser = argparse.ArgumentParser()
args = _ParseArguments(parser)
devil_chromium.Initialize()
coverage_files = _GetFilesWithSuffix(args.coverage_dir, '.exec')
if not coverage_files:
parser.error('No coverage file found under %s' % args.coverage_dir)
print('Found coverage files: %s' % str(coverage_files))
class_files = []
source_dirs = []
if args.sources_json_dir:
sources_json_files = _GetFilesWithSuffix(args.sources_json_dir,
_SOURCES_JSON_FILES_SUFFIX)
for f in sources_json_files:
with open(f, 'r') as json_file:
data = json.load(json_file)
class_files.extend(data['input_path'])
source_dirs.extend(data['source_dirs'])
# Fix source directories as direct parent of Java packages.
fixed_source_dirs = set()
for path in source_dirs:
for partial in _PARTIAL_PACKAGE_NAMES:
if partial in path:
fixed_dir = os.path.join(host_paths.DIR_SOURCE_ROOT,
path[:path.index(partial)])
fixed_source_dirs.add(fixed_dir)
break
if args.class_files:
class_files += args.class_files
if args.sources:
fixed_source_dirs.update(args.sources)
cmd = [
'java', '-jar',
os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party', 'jacoco', 'lib',
'jacococli.jar'), 'report'
] + coverage_files
for f in class_files:
cmd += ['--classfiles', f]
for source in fixed_source_dirs:
cmd += ['--sourcefiles', source]
if args.format == 'html':
out_cmd = ['--html', args.output_dir]
elif args.format == 'xml':
out_cmd = ['--xml', args.output_file]
else:
out_cmd = ['--csv', args.output_file]
cmd += out_cmd
exit_code = cmd_helper.RunCmd(cmd)
if args.cleanup:
for f in coverage_files:
os.remove(f)
# Command tends to exit with status 0 when it actually failed.
if not exit_code:
if args.format == 'html':
if not os.path.isdir(args.output_dir) or not os.listdir(args.output_dir):
print('No report generated at %s' % args.output_dir)
exit_code = 1
elif not os.path.isfile(args.output_file):
print('No report generated at %s' % args.output_file)
exit_code = 1
return exit_code
if __name__ == '__main__':
sys.exit(main())