tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

generate_jacoco_report.py (9093B)


      1 #!/usr/bin/env vpython3
      2 
      3 # Copyright 2013 The Chromium Authors
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 """Aggregates Jacoco coverage files to produce output."""
      8 
      9 
     10 import argparse
     11 import fnmatch
     12 import json
     13 import os
     14 import sys
     15 
     16 import devil_chromium
     17 from devil.utils import cmd_helper
     18 from pylib.constants import host_paths
     19 
     20 # Source paths should be passed to Jacoco in a way that the relative file paths
     21 # reflect the class package name.
     22 _PARTIAL_PACKAGE_NAMES = ['com/google', 'org/chromium']
     23 
     24 # The sources_json_file is generated by jacoco_instr.py with source directories
     25 # and input path to non-instrumented jars.
     26 # e.g.
     27 # 'source_dirs': [
     28 #   "chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom",
     29 #   "chrome/android/java/src/org/chromium/chrome/browser/ui/system",
     30 # ...]
     31 # 'input_path':
     32 #   '$CHROMIUM_OUTPUT_DIR/\
     33 #    obj/chrome/android/features/tab_ui/java__process_prebuilt-filtered.jar'
     34 
     35 _SOURCES_JSON_FILES_SUFFIX = '__jacoco_sources.json'
     36 
     37 
     38 def _CreateClassfileArgs(class_files, report_type, include_substr=None):
     39  """Returns a filtered list of files with classfile option.
     40 
     41  Args:
     42    class_files: A list of class files.
     43    report_type: A string indicating if device or host files are desired.
     44    include_substr: A substring that must be present to include the file.
     45 
     46  Returns:
     47    A list of files that don't use the suffix.
     48  """
     49  # These should match the jar class files generated in internal_rules.gni
     50  search_jar_suffix = '%s.filter.jar' % report_type
     51  result_class_files = []
     52  for f in class_files:
     53    include_file = False
     54    if f.endswith(search_jar_suffix):
     55      include_file = True
     56 
     57    # If include_substr is specified, remove files that don't have the
     58    # required substring.
     59    if include_file and include_substr and include_substr not in f:
     60      include_file = False
     61    if include_file:
     62      result_class_files += ['--classfiles', f]
     63 
     64  return result_class_files
     65 
     66 
     67 def _GenerateReportOutputArgs(args, class_files, report_type):
     68  cmd = _CreateClassfileArgs(class_files, report_type,
     69                             args.include_substr_filter)
     70  if args.format == 'html':
     71    report_dir = os.path.join(args.output_dir, report_type)
     72    if not os.path.exists(report_dir):
     73      os.makedirs(report_dir)
     74    cmd += ['--html', report_dir]
     75  elif args.format == 'xml':
     76    cmd += ['--xml', args.output_file]
     77  elif args.format == 'csv':
     78    cmd += ['--csv', args.output_file]
     79 
     80  return cmd
     81 
     82 
     83 def _GetFilesWithSuffix(root_dir, suffix):
     84  """Gets all files with a given suffix.
     85 
     86  Args:
     87    root_dir: Directory in which to search for files.
     88    suffix: Suffix to look for.
     89 
     90  Returns:
     91    A list of absolute paths to files that match.
     92  """
     93  files = []
     94  for root, _, filenames in os.walk(root_dir):
     95    basenames = fnmatch.filter(filenames, '*' + suffix)
     96    files.extend([os.path.join(root, basename) for basename in basenames])
     97 
     98  return files
     99 
    100 
    101 def _GetExecFiles(root_dir, exclude_substr=None):
    102  """ Gets all .exec files
    103 
    104  Args:
    105    root_dir: Root directory in which to search for files.
    106    exclude_substr: Substring which should be absent in filename. If None, all
    107      files are selected.
    108 
    109  Returns:
    110    A list of absolute paths to .exec files
    111 
    112  """
    113  all_exec_files = _GetFilesWithSuffix(root_dir, ".exec")
    114  valid_exec_files = []
    115  for exec_file in all_exec_files:
    116    if not exclude_substr or exclude_substr not in exec_file:
    117      valid_exec_files.append(exec_file)
    118  return valid_exec_files
    119 
    120 
    121 def _ParseArguments(parser):
    122  """Parses the command line arguments.
    123 
    124  Args:
    125    parser: ArgumentParser object.
    126 
    127  Returns:
    128    The parsed arguments.
    129  """
    130  parser.add_argument(
    131      '--format',
    132      required=True,
    133      choices=['html', 'xml', 'csv'],
    134      help='Output report format. Choose one from html, xml and csv.')
    135  parser.add_argument(
    136      '--device-or-host',
    137      choices=['device', 'host'],
    138      help='Selection on whether to use the device classpath files or the '
    139      'host classpath files. Host would typically be used for junit tests '
    140      ' and device for tests that run on the device. Only used for xml and csv'
    141      ' reports.')
    142  parser.add_argument('--include-substr-filter',
    143                      help='Substring that must be included in classjars.',
    144                      type=str,
    145                      default='')
    146  parser.add_argument('--output-dir', help='html report output directory.')
    147  parser.add_argument('--output-file',
    148                      help='xml file to write device coverage results.')
    149  parser.add_argument(
    150      '--coverage-dir',
    151      required=True,
    152      help='Root of the directory in which to search for '
    153      'coverage data (.exec) files.')
    154  parser.add_argument('--exec-filename-excludes',
    155                      required=False,
    156                      help='Excludes .exec files which contain a particular '
    157                      'substring in their name')
    158  parser.add_argument(
    159      '--sources-json-dir',
    160      help='Root of the directory in which to search for '
    161      '*__jacoco_sources.json files.')
    162  parser.add_argument(
    163      '--class-files',
    164      nargs='+',
    165      help='Location of Java non-instrumented class files. '
    166      'Use non-instrumented jars instead of instrumented jars. '
    167      'e.g. use chrome_java__process_prebuilt_(host/device)_filter.jar instead'
    168      'of chrome_java__process_prebuilt-instrumented.jar')
    169  parser.add_argument(
    170      '--sources',
    171      nargs='+',
    172      help='Location of the source files. '
    173      'Specified source folders must be the direct parent of the folders '
    174      'that define the Java packages.'
    175      'e.g. <src_dir>/chrome/android/java/src/')
    176  parser.add_argument(
    177      '--cleanup',
    178      action='store_true',
    179      help='If set, removes coverage files generated at '
    180      'runtime.')
    181  args = parser.parse_args()
    182 
    183  if args.format == 'html' and not args.output_dir:
    184    parser.error('--output-dir needed for report.')
    185  if args.format in ('csv', 'xml'):
    186    if not args.output_file:
    187      parser.error('--output-file needed for xml/csv reports.')
    188    if not args.device_or_host and args.sources_json_dir:
    189      parser.error('--device-or-host selection needed with --sources-json-dir')
    190  if not (args.sources_json_dir or args.class_files):
    191    parser.error('At least either --sources-json-dir or --class-files needed.')
    192  return args
    193 
    194 
    195 def main():
    196  parser = argparse.ArgumentParser()
    197  args = _ParseArguments(parser)
    198 
    199  devil_chromium.Initialize()
    200 
    201  coverage_files = _GetExecFiles(args.coverage_dir, args.exec_filename_excludes)
    202  if not coverage_files:
    203    parser.error('No coverage file found under %s' % args.coverage_dir)
    204  print('Found coverage files: %s' % str(coverage_files))
    205 
    206  class_files = []
    207  source_dirs = []
    208  if args.sources_json_dir:
    209    sources_json_files = _GetFilesWithSuffix(args.sources_json_dir,
    210                                             _SOURCES_JSON_FILES_SUFFIX)
    211    for f in sources_json_files:
    212      with open(f, 'r') as json_file:
    213        data = json.load(json_file)
    214        class_files.extend(data['input_path'])
    215        source_dirs.extend(data['source_dirs'])
    216 
    217  # Fix source directories as direct parent of Java packages.
    218  fixed_source_dirs = set()
    219  for path in source_dirs:
    220    for partial in _PARTIAL_PACKAGE_NAMES:
    221      if partial in path:
    222        fixed_dir = os.path.join(host_paths.DIR_SOURCE_ROOT,
    223                                 path[:path.index(partial)])
    224        fixed_source_dirs.add(fixed_dir)
    225        break
    226 
    227  if args.class_files:
    228    class_files += args.class_files
    229  if args.sources:
    230    fixed_source_dirs.update(args.sources)
    231 
    232  cmd = [
    233      'java', '-jar',
    234      os.path.join(host_paths.DIR_SOURCE_ROOT, 'third_party', 'jacoco', 'cipd',
    235                   'lib', 'jacococli.jar'), 'report'
    236  ] + coverage_files
    237 
    238  for source in fixed_source_dirs:
    239    cmd += ['--sourcefiles', source]
    240 
    241  if args.format == 'html':
    242    # Both reports are generated for html as the cq bot generates an html
    243    # report and we wouldn't know which one a developer needed.
    244    device_cmd = cmd + _GenerateReportOutputArgs(args, class_files, 'device')
    245    host_cmd = cmd + _GenerateReportOutputArgs(args, class_files, 'host')
    246 
    247    device_exit_code = cmd_helper.RunCmd(device_cmd)
    248    host_exit_code = cmd_helper.RunCmd(host_cmd)
    249    exit_code = device_exit_code or host_exit_code
    250  else:
    251    cmd = cmd + _GenerateReportOutputArgs(args, class_files,
    252                                          args.device_or_host)
    253    exit_code = cmd_helper.RunCmd(cmd)
    254 
    255  if args.cleanup:
    256    for f in coverage_files:
    257      os.remove(f)
    258 
    259  # Command tends to exit with status 0 when it actually failed.
    260  if not exit_code:
    261    if args.format == 'html':
    262      if not os.path.isdir(args.output_dir) or not os.listdir(args.output_dir):
    263        print('No report generated at %s' % args.output_dir)
    264        exit_code = 1
    265    elif not os.path.isfile(args.output_file):
    266      print('No device coverage report generated at %s' % args.output_file)
    267      exit_code = 1
    268 
    269  return exit_code
    270 
    271 
    272 if __name__ == '__main__':
    273  sys.exit(main())