11cb0ef41Sopenharmony_ci#!/usr/bin/env python
21cb0ef41Sopenharmony_ci# Copyright 2016 the V8 project authors. All rights reserved.
31cb0ef41Sopenharmony_ci# Use of this source code is governed by a BSD-style license that can be
41cb0ef41Sopenharmony_ci# found in the LICENSE file.
51cb0ef41Sopenharmony_ci
61cb0ef41Sopenharmony_ci"""Script to transform and merge sancov files into human readable json-format.
71cb0ef41Sopenharmony_ci
81cb0ef41Sopenharmony_ciThe script supports three actions:
91cb0ef41Sopenharmony_ciall: Writes a json file with all instrumented lines of all executables.
101cb0ef41Sopenharmony_cimerge: Merges sancov files with coverage output into an existing json file.
111cb0ef41Sopenharmony_cisplit: Split json file into separate files per covered source file.
121cb0ef41Sopenharmony_ci
131cb0ef41Sopenharmony_ciThe json data is structured as follows:
141cb0ef41Sopenharmony_ci{
151cb0ef41Sopenharmony_ci  "version": 1,
161cb0ef41Sopenharmony_ci  "tests": ["executable1", "executable2", ...],
171cb0ef41Sopenharmony_ci  "files": {
181cb0ef41Sopenharmony_ci    "file1": [[<instr line 1>, <bit_mask>], [<instr line 2>, <bit_mask>], ...],
191cb0ef41Sopenharmony_ci    "file2": [...],
201cb0ef41Sopenharmony_ci    ...
211cb0ef41Sopenharmony_ci  }
221cb0ef41Sopenharmony_ci}
231cb0ef41Sopenharmony_ci
241cb0ef41Sopenharmony_ciThe executables are sorted and determine the test bit mask. Their index+1 is
251cb0ef41Sopenharmony_cithe bit, e.g. executable1 = 1, executable3 = 4, etc. Hence, a line covered by
261cb0ef41Sopenharmony_ciexecutable1 and executable3 will have bit_mask == 5 == 0b101. The number of
271cb0ef41Sopenharmony_citests is restricted to 52 in version 1, to allow javascript JSON parsing of
281cb0ef41Sopenharmony_cithe bitsets encoded as numbers. JS max safe int is (1 << 53) - 1.
291cb0ef41Sopenharmony_ci
301cb0ef41Sopenharmony_ciThe line-number-bit_mask pairs are sorted by line number and don't contain
311cb0ef41Sopenharmony_ciduplicates.
321cb0ef41Sopenharmony_ci
331cb0ef41Sopenharmony_ciSplit json data preserves the same format, but only contains one file per
341cb0ef41Sopenharmony_cijson file.
351cb0ef41Sopenharmony_ci
361cb0ef41Sopenharmony_ciThe sancov tool is expected to be in the llvm compiler-rt third-party
371cb0ef41Sopenharmony_cidirectory. It's not checked out by default and must be added as a custom deps:
381cb0ef41Sopenharmony_ci'v8/third_party/llvm/projects/compiler-rt':
391cb0ef41Sopenharmony_ci    'https://chromium.googlesource.com/external/llvm.org/compiler-rt.git'
401cb0ef41Sopenharmony_ci"""
411cb0ef41Sopenharmony_ci
421cb0ef41Sopenharmony_ci# for py2/py3 compatibility
431cb0ef41Sopenharmony_cifrom __future__ import print_function
441cb0ef41Sopenharmony_cifrom functools import reduce
451cb0ef41Sopenharmony_ci
461cb0ef41Sopenharmony_ciimport argparse
471cb0ef41Sopenharmony_ciimport json
481cb0ef41Sopenharmony_ciimport logging
491cb0ef41Sopenharmony_ciimport os
501cb0ef41Sopenharmony_ciimport re
511cb0ef41Sopenharmony_ciimport subprocess
521cb0ef41Sopenharmony_ciimport sys
531cb0ef41Sopenharmony_ci
541cb0ef41Sopenharmony_cifrom multiprocessing import Pool, cpu_count
551cb0ef41Sopenharmony_ci
561cb0ef41Sopenharmony_ci
571cb0ef41Sopenharmony_cilogging.basicConfig(level=logging.INFO)
581cb0ef41Sopenharmony_ci
591cb0ef41Sopenharmony_ci# Files to exclude from coverage. Dropping their data early adds more speed.
601cb0ef41Sopenharmony_ci# The contained cc files are already excluded from instrumentation, but inlined
611cb0ef41Sopenharmony_ci# data is referenced through v8's object files.
621cb0ef41Sopenharmony_ciEXCLUSIONS = [
631cb0ef41Sopenharmony_ci  'buildtools',
641cb0ef41Sopenharmony_ci  'src/third_party',
651cb0ef41Sopenharmony_ci  'third_party',
661cb0ef41Sopenharmony_ci  'test',
671cb0ef41Sopenharmony_ci  'testing',
681cb0ef41Sopenharmony_ci]
691cb0ef41Sopenharmony_ci
701cb0ef41Sopenharmony_ci# Executables found in the build output for which no coverage is generated.
711cb0ef41Sopenharmony_ci# Exclude them from the coverage data file.
721cb0ef41Sopenharmony_ciEXE_EXCLUSIONS = [
731cb0ef41Sopenharmony_ci  'generate-bytecode-expectations',
741cb0ef41Sopenharmony_ci  'hello-world',
751cb0ef41Sopenharmony_ci  'mksnapshot',
761cb0ef41Sopenharmony_ci  'parser-shell',
771cb0ef41Sopenharmony_ci  'process',
781cb0ef41Sopenharmony_ci  'shell',
791cb0ef41Sopenharmony_ci]
801cb0ef41Sopenharmony_ci
811cb0ef41Sopenharmony_ci# V8 checkout directory.
821cb0ef41Sopenharmony_ciBASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(
831cb0ef41Sopenharmony_ci    os.path.abspath(__file__))))
841cb0ef41Sopenharmony_ci
851cb0ef41Sopenharmony_ci# The sancov tool location.
861cb0ef41Sopenharmony_ciSANCOV_TOOL = os.path.join(
871cb0ef41Sopenharmony_ci    BASE_DIR, 'third_party', 'llvm', 'projects', 'compiler-rt',
881cb0ef41Sopenharmony_ci    'lib', 'sanitizer_common', 'scripts', 'sancov.py')
891cb0ef41Sopenharmony_ci
901cb0ef41Sopenharmony_ci# Simple script to sanitize the PCs from objdump.
911cb0ef41Sopenharmony_ciSANITIZE_PCS = os.path.join(BASE_DIR, 'tools', 'sanitizers', 'sanitize_pcs.py')
921cb0ef41Sopenharmony_ci
931cb0ef41Sopenharmony_ci# The llvm symbolizer location.
941cb0ef41Sopenharmony_ciSYMBOLIZER = os.path.join(
951cb0ef41Sopenharmony_ci    BASE_DIR, 'third_party', 'llvm-build', 'Release+Asserts', 'bin',
961cb0ef41Sopenharmony_ci    'llvm-symbolizer')
971cb0ef41Sopenharmony_ci
981cb0ef41Sopenharmony_ci# Number of cpus.
991cb0ef41Sopenharmony_ciCPUS = cpu_count()
1001cb0ef41Sopenharmony_ci
1011cb0ef41Sopenharmony_ci# Regexp to find sancov files as output by sancov_merger.py. Also grabs the
1021cb0ef41Sopenharmony_ci# executable name in group 1.
1031cb0ef41Sopenharmony_ciSANCOV_FILE_RE = re.compile(r'^(.*)\.result.sancov$')
1041cb0ef41Sopenharmony_ci
1051cb0ef41Sopenharmony_ci
1061cb0ef41Sopenharmony_cidef executables(build_dir):
1071cb0ef41Sopenharmony_ci  """Iterates over executable files in the build directory."""
1081cb0ef41Sopenharmony_ci  for f in os.listdir(build_dir):
1091cb0ef41Sopenharmony_ci    file_path = os.path.join(build_dir, f)
1101cb0ef41Sopenharmony_ci    if (os.path.isfile(file_path) and
1111cb0ef41Sopenharmony_ci        os.access(file_path, os.X_OK) and
1121cb0ef41Sopenharmony_ci        f not in EXE_EXCLUSIONS):
1131cb0ef41Sopenharmony_ci      yield file_path
1141cb0ef41Sopenharmony_ci
1151cb0ef41Sopenharmony_ci
1161cb0ef41Sopenharmony_cidef process_symbolizer_output(output, build_dir):
1171cb0ef41Sopenharmony_ci  """Post-process llvm symbolizer output.
1181cb0ef41Sopenharmony_ci
1191cb0ef41Sopenharmony_ci  Excludes files outside the v8 checkout or given in exclusion list above
1201cb0ef41Sopenharmony_ci  from further processing. Drops the character index in each line.
1211cb0ef41Sopenharmony_ci
1221cb0ef41Sopenharmony_ci  Returns: A mapping of file names to lists of line numbers. The file names
1231cb0ef41Sopenharmony_ci           have relative paths to the v8 base directory. The lists of line
1241cb0ef41Sopenharmony_ci           numbers don't contain duplicate lines and are sorted.
1251cb0ef41Sopenharmony_ci  """
1261cb0ef41Sopenharmony_ci  # Path prefix added by the llvm symbolizer including trailing slash.
1271cb0ef41Sopenharmony_ci  output_path_prefix = os.path.join(build_dir, '..', '..', '')
1281cb0ef41Sopenharmony_ci
1291cb0ef41Sopenharmony_ci  # Drop path prefix when iterating lines. The path is redundant and takes
1301cb0ef41Sopenharmony_ci  # too much space. Drop files outside that path, e.g. generated files in
1311cb0ef41Sopenharmony_ci  # the build dir and absolute paths to c++ library headers.
1321cb0ef41Sopenharmony_ci  def iter_lines():
1331cb0ef41Sopenharmony_ci    for line in output.strip().splitlines():
1341cb0ef41Sopenharmony_ci      if line.startswith(output_path_prefix):
1351cb0ef41Sopenharmony_ci        yield line[len(output_path_prefix):]
1361cb0ef41Sopenharmony_ci
1371cb0ef41Sopenharmony_ci  # Map file names to sets of instrumented line numbers.
1381cb0ef41Sopenharmony_ci  file_map = {}
1391cb0ef41Sopenharmony_ci  for line in iter_lines():
1401cb0ef41Sopenharmony_ci    # Drop character number, we only care for line numbers. Each line has the
1411cb0ef41Sopenharmony_ci    # form: <file name>:<line number>:<character number>.
1421cb0ef41Sopenharmony_ci    file_name, number, _ = line.split(':')
1431cb0ef41Sopenharmony_ci    file_map.setdefault(file_name, set([])).add(int(number))
1441cb0ef41Sopenharmony_ci
1451cb0ef41Sopenharmony_ci  # Remove exclusion patterns from file map. It's cheaper to do it after the
1461cb0ef41Sopenharmony_ci  # mapping, as there are few excluded files and we don't want to do this
1471cb0ef41Sopenharmony_ci  # check for numerous lines in ordinary files.
1481cb0ef41Sopenharmony_ci  def keep(file_name):
1491cb0ef41Sopenharmony_ci    for e in EXCLUSIONS:
1501cb0ef41Sopenharmony_ci      if file_name.startswith(e):
1511cb0ef41Sopenharmony_ci        return False
1521cb0ef41Sopenharmony_ci    return True
1531cb0ef41Sopenharmony_ci
1541cb0ef41Sopenharmony_ci  # Return in serializable form and filter.
1551cb0ef41Sopenharmony_ci  return {k: sorted(file_map[k]) for k in file_map if keep(k)}
1561cb0ef41Sopenharmony_ci
1571cb0ef41Sopenharmony_ci
1581cb0ef41Sopenharmony_cidef get_instrumented_lines(executable):
1591cb0ef41Sopenharmony_ci  """Return the instrumented lines of an executable.
1601cb0ef41Sopenharmony_ci
1611cb0ef41Sopenharmony_ci  Called trough multiprocessing pool.
1621cb0ef41Sopenharmony_ci
1631cb0ef41Sopenharmony_ci  Returns: Post-processed llvm output as returned by process_symbolizer_output.
1641cb0ef41Sopenharmony_ci  """
1651cb0ef41Sopenharmony_ci  # The first two pipes are from llvm's tool sancov.py with 0x added to the hex
1661cb0ef41Sopenharmony_ci  # numbers. The results are piped into the llvm symbolizer, which outputs for
1671cb0ef41Sopenharmony_ci  # each PC: <file name with abs path>:<line number>:<character number>.
1681cb0ef41Sopenharmony_ci  # We don't call the sancov tool to get more speed.
1691cb0ef41Sopenharmony_ci  process = subprocess.Popen(
1701cb0ef41Sopenharmony_ci      'objdump -d %s | '
1711cb0ef41Sopenharmony_ci      'grep \'^\s\+[0-9a-f]\+:.*\scall\(q\|\)\s\+[0-9a-f]\+ '
1721cb0ef41Sopenharmony_ci      '<__sanitizer_cov\(_with_check\|\|_trace_pc_guard\)\(@plt\|\)>\' | '
1731cb0ef41Sopenharmony_ci      'grep \'^\s\+[0-9a-f]\+\' -o | '
1741cb0ef41Sopenharmony_ci      '%s | '
1751cb0ef41Sopenharmony_ci      '%s --obj %s -functions=none' %
1761cb0ef41Sopenharmony_ci          (executable, SANITIZE_PCS, SYMBOLIZER, executable),
1771cb0ef41Sopenharmony_ci      stdout=subprocess.PIPE,
1781cb0ef41Sopenharmony_ci      stderr=subprocess.PIPE,
1791cb0ef41Sopenharmony_ci      stdin=subprocess.PIPE,
1801cb0ef41Sopenharmony_ci      cwd=BASE_DIR,
1811cb0ef41Sopenharmony_ci      shell=True,
1821cb0ef41Sopenharmony_ci  )
1831cb0ef41Sopenharmony_ci  output, _ = process.communicate()
1841cb0ef41Sopenharmony_ci  assert process.returncode == 0
1851cb0ef41Sopenharmony_ci  return process_symbolizer_output(output, os.path.dirname(executable))
1861cb0ef41Sopenharmony_ci
1871cb0ef41Sopenharmony_ci
1881cb0ef41Sopenharmony_cidef merge_instrumented_line_results(exe_list, results):
1891cb0ef41Sopenharmony_ci  """Merge multiprocessing results for all instrumented lines.
1901cb0ef41Sopenharmony_ci
1911cb0ef41Sopenharmony_ci  Args:
1921cb0ef41Sopenharmony_ci    exe_list: List of all executable names with absolute paths.
1931cb0ef41Sopenharmony_ci    results: List of results as returned by get_instrumented_lines.
1941cb0ef41Sopenharmony_ci
1951cb0ef41Sopenharmony_ci  Returns: Dict to be used as json data as specified on the top of this page.
1961cb0ef41Sopenharmony_ci           The dictionary contains all instrumented lines of all files
1971cb0ef41Sopenharmony_ci           referenced by all executables.
1981cb0ef41Sopenharmony_ci  """
1991cb0ef41Sopenharmony_ci  def merge_files(x, y):
2001cb0ef41Sopenharmony_ci    for file_name, lines in y.iteritems():
2011cb0ef41Sopenharmony_ci      x.setdefault(file_name, set([])).update(lines)
2021cb0ef41Sopenharmony_ci    return x
2031cb0ef41Sopenharmony_ci  result = reduce(merge_files, results, {})
2041cb0ef41Sopenharmony_ci
2051cb0ef41Sopenharmony_ci  # Return data as file->lines mapping. The lines are saved as lists
2061cb0ef41Sopenharmony_ci  # with (line number, test bits (as int)). The test bits are initialized with
2071cb0ef41Sopenharmony_ci  # 0, meaning instrumented, but no coverage.
2081cb0ef41Sopenharmony_ci  # The order of the test bits is given with key 'tests'. For now, these are
2091cb0ef41Sopenharmony_ci  # the executable names. We use a _list_ with two items instead of a tuple to
2101cb0ef41Sopenharmony_ci  # ease merging by allowing mutation of the second item.
2111cb0ef41Sopenharmony_ci  return {
2121cb0ef41Sopenharmony_ci    'version': 1,
2131cb0ef41Sopenharmony_ci    'tests': sorted(map(os.path.basename, exe_list)),
2141cb0ef41Sopenharmony_ci    'files': {f: map(lambda l: [l, 0], sorted(result[f])) for f in result},
2151cb0ef41Sopenharmony_ci  }
2161cb0ef41Sopenharmony_ci
2171cb0ef41Sopenharmony_ci
2181cb0ef41Sopenharmony_cidef write_instrumented(options):
2191cb0ef41Sopenharmony_ci  """Implements the 'all' action of this tool."""
2201cb0ef41Sopenharmony_ci  exe_list = list(executables(options.build_dir))
2211cb0ef41Sopenharmony_ci  logging.info('Reading instrumented lines from %d executables.',
2221cb0ef41Sopenharmony_ci               len(exe_list))
2231cb0ef41Sopenharmony_ci  pool = Pool(CPUS)
2241cb0ef41Sopenharmony_ci  try:
2251cb0ef41Sopenharmony_ci    results = pool.imap_unordered(get_instrumented_lines, exe_list)
2261cb0ef41Sopenharmony_ci  finally:
2271cb0ef41Sopenharmony_ci    pool.close()
2281cb0ef41Sopenharmony_ci
2291cb0ef41Sopenharmony_ci  # Merge multiprocessing results and prepare output data.
2301cb0ef41Sopenharmony_ci  data = merge_instrumented_line_results(exe_list, results)
2311cb0ef41Sopenharmony_ci
2321cb0ef41Sopenharmony_ci  logging.info('Read data from %d executables, which covers %d files.',
2331cb0ef41Sopenharmony_ci               len(data['tests']), len(data['files']))
2341cb0ef41Sopenharmony_ci  logging.info('Writing results to %s', options.json_output)
2351cb0ef41Sopenharmony_ci
2361cb0ef41Sopenharmony_ci  # Write json output.
2371cb0ef41Sopenharmony_ci  with open(options.json_output, 'w') as f:
2381cb0ef41Sopenharmony_ci    json.dump(data, f, sort_keys=True)
2391cb0ef41Sopenharmony_ci
2401cb0ef41Sopenharmony_ci
2411cb0ef41Sopenharmony_cidef get_covered_lines(args):
2421cb0ef41Sopenharmony_ci  """Return the covered lines of an executable.
2431cb0ef41Sopenharmony_ci
2441cb0ef41Sopenharmony_ci  Called trough multiprocessing pool. The args are expected to unpack to:
2451cb0ef41Sopenharmony_ci    cov_dir: Folder with sancov files merged by sancov_merger.py.
2461cb0ef41Sopenharmony_ci    executable: Absolute path to the executable that was called to produce the
2471cb0ef41Sopenharmony_ci                given coverage data.
2481cb0ef41Sopenharmony_ci    sancov_file: The merged sancov file with coverage data.
2491cb0ef41Sopenharmony_ci
2501cb0ef41Sopenharmony_ci  Returns: A tuple of post-processed llvm output as returned by
2511cb0ef41Sopenharmony_ci           process_symbolizer_output and the executable name.
2521cb0ef41Sopenharmony_ci  """
2531cb0ef41Sopenharmony_ci  cov_dir, executable, sancov_file = args
2541cb0ef41Sopenharmony_ci
2551cb0ef41Sopenharmony_ci  # Let the sancov tool print the covered PCs and pipe them through the llvm
2561cb0ef41Sopenharmony_ci  # symbolizer.
2571cb0ef41Sopenharmony_ci  process = subprocess.Popen(
2581cb0ef41Sopenharmony_ci      '%s print %s 2> /dev/null | '
2591cb0ef41Sopenharmony_ci      '%s --obj %s -functions=none' %
2601cb0ef41Sopenharmony_ci          (SANCOV_TOOL,
2611cb0ef41Sopenharmony_ci           os.path.join(cov_dir, sancov_file),
2621cb0ef41Sopenharmony_ci           SYMBOLIZER,
2631cb0ef41Sopenharmony_ci           executable),
2641cb0ef41Sopenharmony_ci      stdout=subprocess.PIPE,
2651cb0ef41Sopenharmony_ci      stderr=subprocess.PIPE,
2661cb0ef41Sopenharmony_ci      stdin=subprocess.PIPE,
2671cb0ef41Sopenharmony_ci      cwd=BASE_DIR,
2681cb0ef41Sopenharmony_ci      shell=True,
2691cb0ef41Sopenharmony_ci  )
2701cb0ef41Sopenharmony_ci  output, _ = process.communicate()
2711cb0ef41Sopenharmony_ci  assert process.returncode == 0
2721cb0ef41Sopenharmony_ci  return (
2731cb0ef41Sopenharmony_ci      process_symbolizer_output(output, os.path.dirname(executable)),
2741cb0ef41Sopenharmony_ci      os.path.basename(executable),
2751cb0ef41Sopenharmony_ci  )
2761cb0ef41Sopenharmony_ci
2771cb0ef41Sopenharmony_ci
2781cb0ef41Sopenharmony_cidef merge_covered_line_results(data, results):
2791cb0ef41Sopenharmony_ci  """Merge multiprocessing results for covered lines.
2801cb0ef41Sopenharmony_ci
2811cb0ef41Sopenharmony_ci  The data is mutated, the results are merged into it in place.
2821cb0ef41Sopenharmony_ci
2831cb0ef41Sopenharmony_ci  Args:
2841cb0ef41Sopenharmony_ci    data: Existing coverage data from json file containing all instrumented
2851cb0ef41Sopenharmony_ci          lines.
2861cb0ef41Sopenharmony_ci    results: List of results as returned by get_covered_lines.
2871cb0ef41Sopenharmony_ci  """
2881cb0ef41Sopenharmony_ci
2891cb0ef41Sopenharmony_ci  # List of executables and mapping to the test bit mask. The number of
2901cb0ef41Sopenharmony_ci  # tests is restricted to 52, to allow javascript JSON parsing of
2911cb0ef41Sopenharmony_ci  # the bitsets encoded as numbers. JS max safe int is (1 << 53) - 1.
2921cb0ef41Sopenharmony_ci  exe_list = data['tests']
2931cb0ef41Sopenharmony_ci  assert len(exe_list) <= 52, 'Max 52 different tests are supported.'
2941cb0ef41Sopenharmony_ci  test_bit_masks = {exe:1<<i for i, exe in enumerate(exe_list)}
2951cb0ef41Sopenharmony_ci
2961cb0ef41Sopenharmony_ci  def merge_lines(old_lines, new_lines, mask):
2971cb0ef41Sopenharmony_ci    """Merge the coverage data of a list of lines.
2981cb0ef41Sopenharmony_ci
2991cb0ef41Sopenharmony_ci    Args:
3001cb0ef41Sopenharmony_ci      old_lines: Lines as list of pairs with line number and test bit mask.
3011cb0ef41Sopenharmony_ci                 The new lines will be merged into the list in place.
3021cb0ef41Sopenharmony_ci      new_lines: List of new (covered) lines (sorted).
3031cb0ef41Sopenharmony_ci      mask: The bit to be set for covered lines. The bit index is the test
3041cb0ef41Sopenharmony_ci            index of the executable that covered the line.
3051cb0ef41Sopenharmony_ci    """
3061cb0ef41Sopenharmony_ci    i = 0
3071cb0ef41Sopenharmony_ci    # Iterate over old and new lines, both are sorted.
3081cb0ef41Sopenharmony_ci    for l in new_lines:
3091cb0ef41Sopenharmony_ci      while old_lines[i][0] < l:
3101cb0ef41Sopenharmony_ci        # Forward instrumented lines not present in this coverage data.
3111cb0ef41Sopenharmony_ci        i += 1
3121cb0ef41Sopenharmony_ci        # TODO: Add more context to the assert message.
3131cb0ef41Sopenharmony_ci        assert i < len(old_lines), 'Covered line %d not in input file.' % l
3141cb0ef41Sopenharmony_ci      assert old_lines[i][0] == l, 'Covered line %d not in input file.' % l
3151cb0ef41Sopenharmony_ci
3161cb0ef41Sopenharmony_ci      # Add coverage information to the line.
3171cb0ef41Sopenharmony_ci      old_lines[i][1] |= mask
3181cb0ef41Sopenharmony_ci
3191cb0ef41Sopenharmony_ci  def merge_files(data, result):
3201cb0ef41Sopenharmony_ci    """Merge result into data.
3211cb0ef41Sopenharmony_ci
3221cb0ef41Sopenharmony_ci    The data is mutated in place.
3231cb0ef41Sopenharmony_ci
3241cb0ef41Sopenharmony_ci    Args:
3251cb0ef41Sopenharmony_ci      data: Merged coverage data from the previous reduce step.
3261cb0ef41Sopenharmony_ci      result: New result to be merged in. The type is as returned by
3271cb0ef41Sopenharmony_ci              get_covered_lines.
3281cb0ef41Sopenharmony_ci    """
3291cb0ef41Sopenharmony_ci    file_map, executable = result
3301cb0ef41Sopenharmony_ci    files = data['files']
3311cb0ef41Sopenharmony_ci    for file_name, lines in file_map.iteritems():
3321cb0ef41Sopenharmony_ci      merge_lines(files[file_name], lines, test_bit_masks[executable])
3331cb0ef41Sopenharmony_ci    return data
3341cb0ef41Sopenharmony_ci
3351cb0ef41Sopenharmony_ci  reduce(merge_files, results, data)
3361cb0ef41Sopenharmony_ci
3371cb0ef41Sopenharmony_ci
3381cb0ef41Sopenharmony_cidef merge(options):
3391cb0ef41Sopenharmony_ci  """Implements the 'merge' action of this tool."""
3401cb0ef41Sopenharmony_ci
3411cb0ef41Sopenharmony_ci  # Check if folder with coverage output exists.
3421cb0ef41Sopenharmony_ci  assert (os.path.exists(options.coverage_dir) and
3431cb0ef41Sopenharmony_ci          os.path.isdir(options.coverage_dir))
3441cb0ef41Sopenharmony_ci
3451cb0ef41Sopenharmony_ci  # Inputs for multiprocessing. List of tuples of:
3461cb0ef41Sopenharmony_ci  # Coverage dir, absoluate path to executable, sancov file name.
3471cb0ef41Sopenharmony_ci  inputs = []
3481cb0ef41Sopenharmony_ci  for sancov_file in os.listdir(options.coverage_dir):
3491cb0ef41Sopenharmony_ci    match = SANCOV_FILE_RE.match(sancov_file)
3501cb0ef41Sopenharmony_ci    if match:
3511cb0ef41Sopenharmony_ci      inputs.append((
3521cb0ef41Sopenharmony_ci          options.coverage_dir,
3531cb0ef41Sopenharmony_ci          os.path.join(options.build_dir, match.group(1)),
3541cb0ef41Sopenharmony_ci          sancov_file,
3551cb0ef41Sopenharmony_ci      ))
3561cb0ef41Sopenharmony_ci
3571cb0ef41Sopenharmony_ci  logging.info('Merging %d sancov files into %s',
3581cb0ef41Sopenharmony_ci               len(inputs), options.json_input)
3591cb0ef41Sopenharmony_ci
3601cb0ef41Sopenharmony_ci  # Post-process covered lines in parallel.
3611cb0ef41Sopenharmony_ci  pool = Pool(CPUS)
3621cb0ef41Sopenharmony_ci  try:
3631cb0ef41Sopenharmony_ci    results = pool.imap_unordered(get_covered_lines, inputs)
3641cb0ef41Sopenharmony_ci  finally:
3651cb0ef41Sopenharmony_ci    pool.close()
3661cb0ef41Sopenharmony_ci
3671cb0ef41Sopenharmony_ci  # Load existing json data file for merging the results.
3681cb0ef41Sopenharmony_ci  with open(options.json_input, 'r') as f:
3691cb0ef41Sopenharmony_ci    data = json.load(f)
3701cb0ef41Sopenharmony_ci
3711cb0ef41Sopenharmony_ci  # Merge muliprocessing results. Mutates data.
3721cb0ef41Sopenharmony_ci  merge_covered_line_results(data, results)
3731cb0ef41Sopenharmony_ci
3741cb0ef41Sopenharmony_ci  logging.info('Merged data from %d executables, which covers %d files.',
3751cb0ef41Sopenharmony_ci               len(data['tests']), len(data['files']))
3761cb0ef41Sopenharmony_ci  logging.info('Writing results to %s', options.json_output)
3771cb0ef41Sopenharmony_ci
3781cb0ef41Sopenharmony_ci  # Write merged results to file.
3791cb0ef41Sopenharmony_ci  with open(options.json_output, 'w') as f:
3801cb0ef41Sopenharmony_ci    json.dump(data, f, sort_keys=True)
3811cb0ef41Sopenharmony_ci
3821cb0ef41Sopenharmony_ci
3831cb0ef41Sopenharmony_cidef split(options):
3841cb0ef41Sopenharmony_ci  """Implements the 'split' action of this tool."""
3851cb0ef41Sopenharmony_ci  # Load existing json data file for splitting.
3861cb0ef41Sopenharmony_ci  with open(options.json_input, 'r') as f:
3871cb0ef41Sopenharmony_ci    data = json.load(f)
3881cb0ef41Sopenharmony_ci
3891cb0ef41Sopenharmony_ci  logging.info('Splitting off %d coverage files from %s',
3901cb0ef41Sopenharmony_ci               len(data['files']), options.json_input)
3911cb0ef41Sopenharmony_ci
3921cb0ef41Sopenharmony_ci  for file_name, coverage in data['files'].iteritems():
3931cb0ef41Sopenharmony_ci    # Preserve relative directories that are part of the file name.
3941cb0ef41Sopenharmony_ci    file_path = os.path.join(options.output_dir, file_name + '.json')
3951cb0ef41Sopenharmony_ci    try:
3961cb0ef41Sopenharmony_ci      os.makedirs(os.path.dirname(file_path))
3971cb0ef41Sopenharmony_ci    except OSError:
3981cb0ef41Sopenharmony_ci      # Ignore existing directories.
3991cb0ef41Sopenharmony_ci      pass
4001cb0ef41Sopenharmony_ci
4011cb0ef41Sopenharmony_ci    with open(file_path, 'w') as f:
4021cb0ef41Sopenharmony_ci      # Flat-copy the old dict.
4031cb0ef41Sopenharmony_ci      new_data = dict(data)
4041cb0ef41Sopenharmony_ci
4051cb0ef41Sopenharmony_ci      # Update current file.
4061cb0ef41Sopenharmony_ci      new_data['files'] = {file_name: coverage}
4071cb0ef41Sopenharmony_ci
4081cb0ef41Sopenharmony_ci      # Write json data.
4091cb0ef41Sopenharmony_ci      json.dump(new_data, f, sort_keys=True)
4101cb0ef41Sopenharmony_ci
4111cb0ef41Sopenharmony_ci
4121cb0ef41Sopenharmony_cidef main(args=None):
4131cb0ef41Sopenharmony_ci  parser = argparse.ArgumentParser()
4141cb0ef41Sopenharmony_ci  # TODO(machenbach): Make this required and deprecate the default.
4151cb0ef41Sopenharmony_ci  parser.add_argument('--build-dir',
4161cb0ef41Sopenharmony_ci                      default=os.path.join(BASE_DIR, 'out', 'Release'),
4171cb0ef41Sopenharmony_ci                      help='Path to the build output directory.')
4181cb0ef41Sopenharmony_ci  parser.add_argument('--coverage-dir',
4191cb0ef41Sopenharmony_ci                      help='Path to the sancov output files.')
4201cb0ef41Sopenharmony_ci  parser.add_argument('--json-input',
4211cb0ef41Sopenharmony_ci                      help='Path to an existing json file with coverage data.')
4221cb0ef41Sopenharmony_ci  parser.add_argument('--json-output',
4231cb0ef41Sopenharmony_ci                      help='Path to a file to write json output to.')
4241cb0ef41Sopenharmony_ci  parser.add_argument('--output-dir',
4251cb0ef41Sopenharmony_ci                      help='Directory where to put split output files to.')
4261cb0ef41Sopenharmony_ci  parser.add_argument('action', choices=['all', 'merge', 'split'],
4271cb0ef41Sopenharmony_ci                      help='Action to perform.')
4281cb0ef41Sopenharmony_ci
4291cb0ef41Sopenharmony_ci  options = parser.parse_args(args)
4301cb0ef41Sopenharmony_ci  options.build_dir = os.path.abspath(options.build_dir)
4311cb0ef41Sopenharmony_ci  if options.action.lower() == 'all':
4321cb0ef41Sopenharmony_ci    if not options.json_output:
4331cb0ef41Sopenharmony_ci      print('--json-output is required')
4341cb0ef41Sopenharmony_ci      return 1
4351cb0ef41Sopenharmony_ci    write_instrumented(options)
4361cb0ef41Sopenharmony_ci  elif options.action.lower() == 'merge':
4371cb0ef41Sopenharmony_ci    if not options.coverage_dir:
4381cb0ef41Sopenharmony_ci      print('--coverage-dir is required')
4391cb0ef41Sopenharmony_ci      return 1
4401cb0ef41Sopenharmony_ci    if not options.json_input:
4411cb0ef41Sopenharmony_ci      print('--json-input is required')
4421cb0ef41Sopenharmony_ci      return 1
4431cb0ef41Sopenharmony_ci    if not options.json_output:
4441cb0ef41Sopenharmony_ci      print('--json-output is required')
4451cb0ef41Sopenharmony_ci      return 1
4461cb0ef41Sopenharmony_ci    merge(options)
4471cb0ef41Sopenharmony_ci  elif options.action.lower() == 'split':
4481cb0ef41Sopenharmony_ci    if not options.json_input:
4491cb0ef41Sopenharmony_ci      print('--json-input is required')
4501cb0ef41Sopenharmony_ci      return 1
4511cb0ef41Sopenharmony_ci    if not options.output_dir:
4521cb0ef41Sopenharmony_ci      print('--output-dir is required')
4531cb0ef41Sopenharmony_ci      return 1
4541cb0ef41Sopenharmony_ci    split(options)
4551cb0ef41Sopenharmony_ci  return 0
4561cb0ef41Sopenharmony_ci
4571cb0ef41Sopenharmony_ci
4581cb0ef41Sopenharmony_ciif __name__ == '__main__':
4591cb0ef41Sopenharmony_ci  sys.exit(main())
460