File: code_coverage_utils.py

package info (click to toggle)
chromium 139.0.7258.127-2
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 6,122,156 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (146 lines) | stat: -rw-r--r-- 5,485 bytes parent folder | download | duplicates (10)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Utilities for code coverage related processings."""

import logging
import os
import posixpath
import shutil
import subprocess

from devil import base_error
from pylib import constants

# These are use for code coverage.
LLVM_PROFDATA_PATH = os.path.join(constants.DIR_SOURCE_ROOT, 'third_party',
                                  'llvm-build', 'Release+Asserts', 'bin',
                                  'llvm-profdata')
# Name of the file extension for profraw data files.
_PROFRAW_FILE_EXTENSION = 'profraw'
# Name of the file where profraw data files are merged.
_MERGE_PROFDATA_FILE_NAME = 'coverage_merged.' + _PROFRAW_FILE_EXTENSION


def GetDeviceClangCoverageDir(device):
  """Gets the directory to generate clang coverage data on device.

  Args:
    device: The working device.

  Returns:
    The directory path on the device.
  """
  return posixpath.join(device.GetExternalStoragePath(), 'chrome', 'test',
                        'coverage', 'profraw')


def PullAndMaybeMergeClangCoverageFiles(device, device_coverage_dir, output_dir,
                                        output_subfolder_name):
  """Pulls and possibly merges clang coverage file to a single file.

  Only merges when llvm-profdata tool exists. If so, Merged file is at
  `output_dir/coverage_merged.profraw`and raw profraw files before merging
  are deleted.

  Args:
    device: The working device.
    device_coverage_dir: The directory storing coverage data on device.
    output_dir: The output directory on host to store the
        coverage_merged.profraw file.
    output_subfolder_name: The subfolder in |output_dir| to pull
        |device_coverage_dir| into. It will be deleted after merging if
        merging happens.
  """
  if not device.PathExists(device_coverage_dir, retries=0):
    logging.warning('Clang coverage data folder does not exist on device: %s',
                    device_coverage_dir)
    return
  # Host side dir to pull device coverage profraw folder into.
  profraw_parent_dir = os.path.join(output_dir, output_subfolder_name)
  # Note: The function pulls |device_coverage_dir| folder,
  # instead of profraw files, into |profraw_parent_dir|. the
  # function also removes |device_coverage_dir| from device.
  PullClangCoverageFiles(device, device_coverage_dir, profraw_parent_dir)
  # Merge data into one merged file if llvm-profdata tool exists.
  if os.path.isfile(LLVM_PROFDATA_PATH):
    profraw_folder_name = os.path.basename(
        os.path.normpath(device_coverage_dir))
    profraw_dir = os.path.join(profraw_parent_dir, profraw_folder_name)
    MergeClangCoverageFiles(output_dir, profraw_dir)
    shutil.rmtree(profraw_parent_dir)


def PullClangCoverageFiles(device, device_coverage_dir, output_dir):
  """Pulls clang coverage files on device to host directory.

  Args:
    device: The working device.
    device_coverage_dir: The directory to store coverage data on device.
    output_dir: The output directory on host.
  """
  try:
    if not os.path.exists(output_dir):
      os.makedirs(output_dir)
    device.PullFile(device_coverage_dir, output_dir)
    if not os.listdir(os.path.join(output_dir, 'profraw')):
      logging.warning('No clang coverage data was generated for this run')
  except (OSError, base_error.BaseError) as e:
    logging.warning('Failed to pull clang coverage data, error: %s', e)
  finally:
    device.RemovePath(device_coverage_dir, force=True, recursive=True)


def MergeClangCoverageFiles(coverage_dir, profdata_dir):
  """Merge coverage data files.

  Each instrumentation activity generates a separate profraw data file. This
  merges all profraw files in profdata_dir into a single file in
  coverage_dir. This happens after each test, rather than waiting until after
  all tests are ran to reduce the memory footprint used by all the profraw
  files.

  Args:
    coverage_dir: The path to the coverage directory.
    profdata_dir: The directory where the profraw data file(s) are located.

  Return:
    None
  """
  # profdata_dir may not exist if pulling coverage files failed.
  if not os.path.exists(profdata_dir):
    logging.debug('Profraw directory does not exist: %s', profdata_dir)
    return

  merge_file = os.path.join(coverage_dir, _MERGE_PROFDATA_FILE_NAME)
  profraw_files = [
      os.path.join(profdata_dir, f) for f in os.listdir(profdata_dir)
      if f.endswith(_PROFRAW_FILE_EXTENSION)
  ]

  try:
    logging.debug('Merging target profraw files into merged profraw file.')
    subprocess_cmd = [
        LLVM_PROFDATA_PATH,
        'merge',
        '-o',
        merge_file,
        '-sparse=true',
    ]
    # Grow the merge file by merging it with itself and the new files.
    if os.path.exists(merge_file):
      subprocess_cmd.append(merge_file)
    subprocess_cmd.extend(profraw_files)
    output = subprocess.check_output(subprocess_cmd).decode('utf8')
    logging.debug('Merge output: %s', output)

  except subprocess.CalledProcessError:
    # Don't raise error as that will kill the test run. When code coverage
    # generates a report, that will raise the error in the report generation.
    logging.error(
        'Failed to merge target profdata files to create '
        'merged profraw file for files: %s', profraw_files)

  # Free up memory space on bot as all data is in the merge file.
  for f in profraw_files:
    os.remove(f)