tor-browser

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

code_coverage_utils.py (5485B)


      1 # Copyright 2023 The Chromium Authors
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 """Utilities for code coverage related processings."""
      5 
      6 import logging
      7 import os
      8 import posixpath
      9 import shutil
     10 import subprocess
     11 
     12 from devil import base_error
     13 from pylib import constants
     14 
     15 # These are use for code coverage.
     16 LLVM_PROFDATA_PATH = os.path.join(constants.DIR_SOURCE_ROOT, 'third_party',
     17                                  'llvm-build', 'Release+Asserts', 'bin',
     18                                  'llvm-profdata')
     19 # Name of the file extension for profraw data files.
     20 _PROFRAW_FILE_EXTENSION = 'profraw'
     21 # Name of the file where profraw data files are merged.
     22 _MERGE_PROFDATA_FILE_NAME = 'coverage_merged.' + _PROFRAW_FILE_EXTENSION
     23 
     24 
     25 def GetDeviceClangCoverageDir(device):
     26  """Gets the directory to generate clang coverage data on device.
     27 
     28  Args:
     29    device: The working device.
     30 
     31  Returns:
     32    The directory path on the device.
     33  """
     34  return posixpath.join(device.GetExternalStoragePath(), 'chrome', 'test',
     35                        'coverage', 'profraw')
     36 
     37 
     38 def PullAndMaybeMergeClangCoverageFiles(device, device_coverage_dir, output_dir,
     39                                        output_subfolder_name):
     40  """Pulls and possibly merges clang coverage file to a single file.
     41 
     42  Only merges when llvm-profdata tool exists. If so, Merged file is at
     43  `output_dir/coverage_merged.profraw`and raw profraw files before merging
     44  are deleted.
     45 
     46  Args:
     47    device: The working device.
     48    device_coverage_dir: The directory storing coverage data on device.
     49    output_dir: The output directory on host to store the
     50        coverage_merged.profraw file.
     51    output_subfolder_name: The subfolder in |output_dir| to pull
     52        |device_coverage_dir| into. It will be deleted after merging if
     53        merging happens.
     54  """
     55  if not device.PathExists(device_coverage_dir, retries=0):
     56    logging.warning('Clang coverage data folder does not exist on device: %s',
     57                    device_coverage_dir)
     58    return
     59  # Host side dir to pull device coverage profraw folder into.
     60  profraw_parent_dir = os.path.join(output_dir, output_subfolder_name)
     61  # Note: The function pulls |device_coverage_dir| folder,
     62  # instead of profraw files, into |profraw_parent_dir|. the
     63  # function also removes |device_coverage_dir| from device.
     64  PullClangCoverageFiles(device, device_coverage_dir, profraw_parent_dir)
     65  # Merge data into one merged file if llvm-profdata tool exists.
     66  if os.path.isfile(LLVM_PROFDATA_PATH):
     67    profraw_folder_name = os.path.basename(
     68        os.path.normpath(device_coverage_dir))
     69    profraw_dir = os.path.join(profraw_parent_dir, profraw_folder_name)
     70    MergeClangCoverageFiles(output_dir, profraw_dir)
     71    shutil.rmtree(profraw_parent_dir)
     72 
     73 
     74 def PullClangCoverageFiles(device, device_coverage_dir, output_dir):
     75  """Pulls clang coverage files on device to host directory.
     76 
     77  Args:
     78    device: The working device.
     79    device_coverage_dir: The directory to store coverage data on device.
     80    output_dir: The output directory on host.
     81  """
     82  try:
     83    if not os.path.exists(output_dir):
     84      os.makedirs(output_dir)
     85    device.PullFile(device_coverage_dir, output_dir)
     86    if not os.listdir(os.path.join(output_dir, 'profraw')):
     87      logging.warning('No clang coverage data was generated for this run')
     88  except (OSError, base_error.BaseError) as e:
     89    logging.warning('Failed to pull clang coverage data, error: %s', e)
     90  finally:
     91    device.RemovePath(device_coverage_dir, force=True, recursive=True)
     92 
     93 
     94 def MergeClangCoverageFiles(coverage_dir, profdata_dir):
     95  """Merge coverage data files.
     96 
     97  Each instrumentation activity generates a separate profraw data file. This
     98  merges all profraw files in profdata_dir into a single file in
     99  coverage_dir. This happens after each test, rather than waiting until after
    100  all tests are ran to reduce the memory footprint used by all the profraw
    101  files.
    102 
    103  Args:
    104    coverage_dir: The path to the coverage directory.
    105    profdata_dir: The directory where the profraw data file(s) are located.
    106 
    107  Return:
    108    None
    109  """
    110  # profdata_dir may not exist if pulling coverage files failed.
    111  if not os.path.exists(profdata_dir):
    112    logging.debug('Profraw directory does not exist: %s', profdata_dir)
    113    return
    114 
    115  merge_file = os.path.join(coverage_dir, _MERGE_PROFDATA_FILE_NAME)
    116  profraw_files = [
    117      os.path.join(profdata_dir, f) for f in os.listdir(profdata_dir)
    118      if f.endswith(_PROFRAW_FILE_EXTENSION)
    119  ]
    120 
    121  try:
    122    logging.debug('Merging target profraw files into merged profraw file.')
    123    subprocess_cmd = [
    124        LLVM_PROFDATA_PATH,
    125        'merge',
    126        '-o',
    127        merge_file,
    128        '-sparse=true',
    129    ]
    130    # Grow the merge file by merging it with itself and the new files.
    131    if os.path.exists(merge_file):
    132      subprocess_cmd.append(merge_file)
    133    subprocess_cmd.extend(profraw_files)
    134    output = subprocess.check_output(subprocess_cmd).decode('utf8')
    135    logging.debug('Merge output: %s', output)
    136 
    137  except subprocess.CalledProcessError:
    138    # Don't raise error as that will kill the test run. When code coverage
    139    # generates a report, that will raise the error in the report generation.
    140    logging.error(
    141        'Failed to merge target profdata files to create '
    142        'merged profraw file for files: %s', profraw_files)
    143 
    144  # Free up memory space on bot as all data is in the merge file.
    145  for f in profraw_files:
    146    os.remove(f)