summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/build/android/generate_jacoco_report.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/build/android/generate_jacoco_report.py')
-rwxr-xr-xthird_party/libwebrtc/build/android/generate_jacoco_report.py274
1 files changed, 274 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/android/generate_jacoco_report.py b/third_party/libwebrtc/build/android/generate_jacoco_report.py
new file mode 100755
index 0000000000..da4a38e514
--- /dev/null
+++ b/third_party/libwebrtc/build/android/generate_jacoco_report.py
@@ -0,0 +1,274 @@
+#!/usr/bin/env vpython3
+
+# 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 _CreateClassfileArgs(class_files, report_type, include_substr=None):
+ """Returns a filtered list of files with classfile option.
+
+ Args:
+ class_files: A list of class files.
+ report_type: A string indicating if device or host files are desired.
+ include_substr: A substring that must be present to include the file.
+
+ Returns:
+ A list of files that don't use the suffix.
+ """
+ # These should match the jar class files generated in internal_rules.gni
+ search_jar_suffix = '%s.filter.jar' % report_type
+ result_class_files = []
+ for f in class_files:
+ include_file = False
+ if f.endswith(search_jar_suffix):
+ include_file = True
+
+ # If include_substr is specified, remove files that don't have the
+ # required substring.
+ if include_file and include_substr and include_substr not in f:
+ include_file = False
+ if include_file:
+ result_class_files += ['--classfiles', f]
+
+ return result_class_files
+
+
+def _GenerateReportOutputArgs(args, class_files, report_type):
+ cmd = _CreateClassfileArgs(class_files, report_type,
+ args.include_substr_filter)
+ if args.format == 'html':
+ report_dir = os.path.join(args.output_dir, report_type)
+ if not os.path.exists(report_dir):
+ os.makedirs(report_dir)
+ cmd += ['--html', report_dir]
+ elif args.format == 'xml':
+ cmd += ['--xml', args.output_file]
+ elif args.format == 'csv':
+ cmd += ['--csv', args.output_file]
+
+ return cmd
+
+
+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 _GetExecFiles(root_dir, exclude_substr=None):
+ """ Gets all .exec files
+
+ Args:
+ root_dir: Root directory in which to search for files.
+ exclude_substr: Substring which should be absent in filename. If None, all
+ files are selected.
+
+ Returns:
+ A list of absolute paths to .exec files
+
+ """
+ all_exec_files = _GetFilesWithSuffix(root_dir, ".exec")
+ valid_exec_files = []
+ for exec_file in all_exec_files:
+ if not exclude_substr or exclude_substr not in exec_file:
+ valid_exec_files.append(exec_file)
+ return valid_exec_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(
+ '--device-or-host',
+ choices=['device', 'host'],
+ help='Selection on whether to use the device classpath files or the '
+ 'host classpath files. Host would typically be used for junit tests '
+ ' and device for tests that run on the device. Only used for xml and csv'
+ ' reports.')
+ parser.add_argument('--include-substr-filter',
+ help='Substring that must be included in classjars.',
+ type=str,
+ default='')
+ parser.add_argument('--output-dir', help='html report output directory.')
+ parser.add_argument('--output-file',
+ help='xml file to write device coverage results.')
+ parser.add_argument(
+ '--coverage-dir',
+ required=True,
+ help='Root of the directory in which to search for '
+ 'coverage data (.exec) files.')
+ parser.add_argument('--exec-filename-excludes',
+ required=False,
+ help='Excludes .exec files which contain a particular '
+ 'substring in their name')
+ 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_(host/device)_filter.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' and not args.output_dir:
+ parser.error('--output-dir needed for report.')
+ if args.format in ('csv', 'xml'):
+ if not args.output_file:
+ parser.error('--output-file needed for xml/csv reports.')
+ if not args.device_or_host and args.sources_json_dir:
+ parser.error('--device-or-host selection needed with --sources-json-dir')
+ 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 = _GetExecFiles(args.coverage_dir, args.exec_filename_excludes)
+ 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 source in fixed_source_dirs:
+ cmd += ['--sourcefiles', source]
+
+ if args.format == 'html':
+ # Both reports are generated for html as the cq bot generates an html
+ # report and we wouldn't know which one a developer needed.
+ device_cmd = cmd + _GenerateReportOutputArgs(args, class_files, 'device')
+ host_cmd = cmd + _GenerateReportOutputArgs(args, class_files, 'host')
+
+ device_exit_code = cmd_helper.RunCmd(device_cmd)
+ host_exit_code = cmd_helper.RunCmd(host_cmd)
+ exit_code = device_exit_code or host_exit_code
+ else:
+ cmd = cmd + _GenerateReportOutputArgs(args, class_files,
+ args.device_or_host)
+ 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 device coverage report generated at %s' % args.output_file)
+ exit_code = 1
+
+ return exit_code
+
+
+if __name__ == '__main__':
+ sys.exit(main())