xref: /aosp_15_r20/tools/asuite/atest/coverage/coverage.py (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
1*c2e18aaaSAndroid Build Coastguard Worker# Copyright 2022, The Android Open Source Project
2*c2e18aaaSAndroid Build Coastguard Worker#
3*c2e18aaaSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*c2e18aaaSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*c2e18aaaSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*c2e18aaaSAndroid Build Coastguard Worker#
7*c2e18aaaSAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
8*c2e18aaaSAndroid Build Coastguard Worker#
9*c2e18aaaSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*c2e18aaaSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*c2e18aaaSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*c2e18aaaSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*c2e18aaaSAndroid Build Coastguard Worker# limitations under the License.
14*c2e18aaaSAndroid Build Coastguard Worker"""Code coverage instrumentation and collection functionality."""
15*c2e18aaaSAndroid Build Coastguard Worker
16*c2e18aaaSAndroid Build Coastguard Workerimport logging
17*c2e18aaaSAndroid Build Coastguard Workerimport os
18*c2e18aaaSAndroid Build Coastguard Workerfrom pathlib import Path
19*c2e18aaaSAndroid Build Coastguard Workerimport subprocess
20*c2e18aaaSAndroid Build Coastguard Workerfrom typing import List, Set
21*c2e18aaaSAndroid Build Coastguard Worker
22*c2e18aaaSAndroid Build Coastguard Workerfrom atest import atest_utils
23*c2e18aaaSAndroid Build Coastguard Workerfrom atest import constants
24*c2e18aaaSAndroid Build Coastguard Workerfrom atest import module_info
25*c2e18aaaSAndroid Build Coastguard Workerfrom atest.test_finders import test_info
26*c2e18aaaSAndroid Build Coastguard Worker
27*c2e18aaaSAndroid Build Coastguard Worker
28*c2e18aaaSAndroid Build Coastguard Workerdef build_env_vars():
29*c2e18aaaSAndroid Build Coastguard Worker  """Environment variables for building with code coverage instrumentation.
30*c2e18aaaSAndroid Build Coastguard Worker
31*c2e18aaaSAndroid Build Coastguard Worker  Returns:
32*c2e18aaaSAndroid Build Coastguard Worker      A dict with the environment variables to set.
33*c2e18aaaSAndroid Build Coastguard Worker  """
34*c2e18aaaSAndroid Build Coastguard Worker  env_vars = {
35*c2e18aaaSAndroid Build Coastguard Worker      'CLANG_COVERAGE': 'true',
36*c2e18aaaSAndroid Build Coastguard Worker      'NATIVE_COVERAGE_PATHS': '*',
37*c2e18aaaSAndroid Build Coastguard Worker      'EMMA_INSTRUMENT': 'true',
38*c2e18aaaSAndroid Build Coastguard Worker      'EMMA_INSTRUMENT_FRAMEWORK': 'true',
39*c2e18aaaSAndroid Build Coastguard Worker      'LLVM_PROFILE_FILE': '/dev/null',
40*c2e18aaaSAndroid Build Coastguard Worker  }
41*c2e18aaaSAndroid Build Coastguard Worker  return env_vars
42*c2e18aaaSAndroid Build Coastguard Worker
43*c2e18aaaSAndroid Build Coastguard Worker
44*c2e18aaaSAndroid Build Coastguard Workerdef tf_args(mod_info):
45*c2e18aaaSAndroid Build Coastguard Worker  """TradeFed command line arguments needed to collect code coverage.
46*c2e18aaaSAndroid Build Coastguard Worker
47*c2e18aaaSAndroid Build Coastguard Worker  Returns:
48*c2e18aaaSAndroid Build Coastguard Worker      A list of the command line arguments to append.
49*c2e18aaaSAndroid Build Coastguard Worker  """
50*c2e18aaaSAndroid Build Coastguard Worker  build_top = Path(os.environ.get(constants.ANDROID_BUILD_TOP))
51*c2e18aaaSAndroid Build Coastguard Worker  clang_version = _get_clang_version(build_top)
52*c2e18aaaSAndroid Build Coastguard Worker  llvm_profdata = build_top.joinpath(
53*c2e18aaaSAndroid Build Coastguard Worker      f'prebuilts/clang/host/linux-x86/{clang_version}'
54*c2e18aaaSAndroid Build Coastguard Worker  )
55*c2e18aaaSAndroid Build Coastguard Worker  jacocoagent_paths = mod_info.get_installed_paths('jacocoagent')
56*c2e18aaaSAndroid Build Coastguard Worker  return (
57*c2e18aaaSAndroid Build Coastguard Worker      '--coverage',
58*c2e18aaaSAndroid Build Coastguard Worker      '--coverage-toolchain',
59*c2e18aaaSAndroid Build Coastguard Worker      'JACOCO',
60*c2e18aaaSAndroid Build Coastguard Worker      '--coverage-toolchain',
61*c2e18aaaSAndroid Build Coastguard Worker      'CLANG',
62*c2e18aaaSAndroid Build Coastguard Worker      '--auto-collect',
63*c2e18aaaSAndroid Build Coastguard Worker      'JAVA_COVERAGE',
64*c2e18aaaSAndroid Build Coastguard Worker      '--auto-collect',
65*c2e18aaaSAndroid Build Coastguard Worker      'CLANG_COVERAGE',
66*c2e18aaaSAndroid Build Coastguard Worker      '--llvm-profdata-path',
67*c2e18aaaSAndroid Build Coastguard Worker      str(llvm_profdata),
68*c2e18aaaSAndroid Build Coastguard Worker      '--jacocoagent-path',
69*c2e18aaaSAndroid Build Coastguard Worker      str(jacocoagent_paths[0]),
70*c2e18aaaSAndroid Build Coastguard Worker  )
71*c2e18aaaSAndroid Build Coastguard Worker
72*c2e18aaaSAndroid Build Coastguard Worker
73*c2e18aaaSAndroid Build Coastguard Workerdef _get_clang_version(build_top):
74*c2e18aaaSAndroid Build Coastguard Worker  """Finds out current toolchain version."""
75*c2e18aaaSAndroid Build Coastguard Worker  version_output = subprocess.check_output(
76*c2e18aaaSAndroid Build Coastguard Worker      f'{build_top}/build/soong/scripts/get_clang_version.py', text=True
77*c2e18aaaSAndroid Build Coastguard Worker  )
78*c2e18aaaSAndroid Build Coastguard Worker  return version_output.strip()
79*c2e18aaaSAndroid Build Coastguard Worker
80*c2e18aaaSAndroid Build Coastguard Worker
81*c2e18aaaSAndroid Build Coastguard Workerdef build_modules():
82*c2e18aaaSAndroid Build Coastguard Worker  """Build modules needed for coverage report generation."""
83*c2e18aaaSAndroid Build Coastguard Worker  return ('jacoco_to_lcov_converter', 'jacocoagent')
84*c2e18aaaSAndroid Build Coastguard Worker
85*c2e18aaaSAndroid Build Coastguard Worker
86*c2e18aaaSAndroid Build Coastguard Workerdef generate_coverage_report(
87*c2e18aaaSAndroid Build Coastguard Worker    results_dir: str,
88*c2e18aaaSAndroid Build Coastguard Worker    test_infos: List[test_info.TestInfo],
89*c2e18aaaSAndroid Build Coastguard Worker    mod_info: module_info.ModuleInfo,
90*c2e18aaaSAndroid Build Coastguard Worker    is_host_enabled: bool,
91*c2e18aaaSAndroid Build Coastguard Worker    code_under_test: Set[str],
92*c2e18aaaSAndroid Build Coastguard Worker):
93*c2e18aaaSAndroid Build Coastguard Worker  """Generates HTML code coverage reports based on the test info.
94*c2e18aaaSAndroid Build Coastguard Worker
95*c2e18aaaSAndroid Build Coastguard Worker  Args:
96*c2e18aaaSAndroid Build Coastguard Worker    results_dir: The directory containing the test results
97*c2e18aaaSAndroid Build Coastguard Worker    test_infos: The TestInfo objects for this invocation
98*c2e18aaaSAndroid Build Coastguard Worker    mod_info: The ModuleInfo object containing all build module information
99*c2e18aaaSAndroid Build Coastguard Worker    is_host_enabled: True if --host was specified
100*c2e18aaaSAndroid Build Coastguard Worker    code_under_test: The set of modules to include in the coverage report
101*c2e18aaaSAndroid Build Coastguard Worker  """
102*c2e18aaaSAndroid Build Coastguard Worker  if not code_under_test:
103*c2e18aaaSAndroid Build Coastguard Worker    # No code-under-test was specified on the command line. Deduce the values
104*c2e18aaaSAndroid Build Coastguard Worker    # from module-info or from the test.
105*c2e18aaaSAndroid Build Coastguard Worker    code_under_test = _deduce_code_under_test(test_infos, mod_info)
106*c2e18aaaSAndroid Build Coastguard Worker
107*c2e18aaaSAndroid Build Coastguard Worker  logging.debug(f'Code-under-test: {code_under_test}')
108*c2e18aaaSAndroid Build Coastguard Worker
109*c2e18aaaSAndroid Build Coastguard Worker  # Collect coverage metadata files from the build for coverage report generation.
110*c2e18aaaSAndroid Build Coastguard Worker  jacoco_report_jars = _collect_java_report_jars(
111*c2e18aaaSAndroid Build Coastguard Worker      code_under_test, mod_info, is_host_enabled
112*c2e18aaaSAndroid Build Coastguard Worker  )
113*c2e18aaaSAndroid Build Coastguard Worker  unstripped_native_binaries = _collect_native_report_binaries(
114*c2e18aaaSAndroid Build Coastguard Worker      code_under_test, mod_info, is_host_enabled
115*c2e18aaaSAndroid Build Coastguard Worker  )
116*c2e18aaaSAndroid Build Coastguard Worker
117*c2e18aaaSAndroid Build Coastguard Worker  if jacoco_report_jars:
118*c2e18aaaSAndroid Build Coastguard Worker    _generate_java_coverage_report(
119*c2e18aaaSAndroid Build Coastguard Worker        jacoco_report_jars,
120*c2e18aaaSAndroid Build Coastguard Worker        _get_all_src_paths(code_under_test, mod_info),
121*c2e18aaaSAndroid Build Coastguard Worker        results_dir,
122*c2e18aaaSAndroid Build Coastguard Worker        mod_info,
123*c2e18aaaSAndroid Build Coastguard Worker    )
124*c2e18aaaSAndroid Build Coastguard Worker
125*c2e18aaaSAndroid Build Coastguard Worker  if unstripped_native_binaries:
126*c2e18aaaSAndroid Build Coastguard Worker    _generate_native_coverage_report(unstripped_native_binaries, results_dir)
127*c2e18aaaSAndroid Build Coastguard Worker
128*c2e18aaaSAndroid Build Coastguard Worker
129*c2e18aaaSAndroid Build Coastguard Workerdef _deduce_code_under_test(
130*c2e18aaaSAndroid Build Coastguard Worker    test_infos: List[test_info.TestInfo],
131*c2e18aaaSAndroid Build Coastguard Worker    mod_info: module_info.ModuleInfo,
132*c2e18aaaSAndroid Build Coastguard Worker) -> Set[str]:
133*c2e18aaaSAndroid Build Coastguard Worker  """Deduces the code-under-test from the test info and module info.
134*c2e18aaaSAndroid Build Coastguard Worker
135*c2e18aaaSAndroid Build Coastguard Worker  If the test info contains code-under-test information, that is used.
136*c2e18aaaSAndroid Build Coastguard Worker  Otherwise, the dependencies of the test are used.
137*c2e18aaaSAndroid Build Coastguard Worker
138*c2e18aaaSAndroid Build Coastguard Worker  Args:
139*c2e18aaaSAndroid Build Coastguard Worker    test_infos: The TestInfo objects for this invocation
140*c2e18aaaSAndroid Build Coastguard Worker    mod_info: The ModuleInfo object containing all build module information
141*c2e18aaaSAndroid Build Coastguard Worker
142*c2e18aaaSAndroid Build Coastguard Worker  Returns:
143*c2e18aaaSAndroid Build Coastguard Worker    The set of modules to include in the coverage report
144*c2e18aaaSAndroid Build Coastguard Worker  """
145*c2e18aaaSAndroid Build Coastguard Worker  code_under_test = set()
146*c2e18aaaSAndroid Build Coastguard Worker
147*c2e18aaaSAndroid Build Coastguard Worker  for test_info in test_infos:
148*c2e18aaaSAndroid Build Coastguard Worker    code_under_test.update(
149*c2e18aaaSAndroid Build Coastguard Worker        mod_info.get_code_under_test(test_info.raw_test_name)
150*c2e18aaaSAndroid Build Coastguard Worker    )
151*c2e18aaaSAndroid Build Coastguard Worker
152*c2e18aaaSAndroid Build Coastguard Worker  if code_under_test:
153*c2e18aaaSAndroid Build Coastguard Worker    return code_under_test
154*c2e18aaaSAndroid Build Coastguard Worker
155*c2e18aaaSAndroid Build Coastguard Worker  # No code-under-test was specified in ModuleInfo, default to using dependency
156*c2e18aaaSAndroid Build Coastguard Worker  # information of the test.
157*c2e18aaaSAndroid Build Coastguard Worker  for test_info in test_infos:
158*c2e18aaaSAndroid Build Coastguard Worker    code_under_test.update(_get_test_deps(test_info, mod_info))
159*c2e18aaaSAndroid Build Coastguard Worker
160*c2e18aaaSAndroid Build Coastguard Worker  return code_under_test
161*c2e18aaaSAndroid Build Coastguard Worker
162*c2e18aaaSAndroid Build Coastguard Worker
163*c2e18aaaSAndroid Build Coastguard Workerdef _get_test_deps(test_info, mod_info):
164*c2e18aaaSAndroid Build Coastguard Worker  """Gets all dependencies of the TestInfo, including Mainline modules."""
165*c2e18aaaSAndroid Build Coastguard Worker  deps = set()
166*c2e18aaaSAndroid Build Coastguard Worker
167*c2e18aaaSAndroid Build Coastguard Worker  deps.add(test_info.raw_test_name)
168*c2e18aaaSAndroid Build Coastguard Worker  deps |= _get_transitive_module_deps(
169*c2e18aaaSAndroid Build Coastguard Worker      mod_info.get_module_info(test_info.raw_test_name), mod_info, deps
170*c2e18aaaSAndroid Build Coastguard Worker  )
171*c2e18aaaSAndroid Build Coastguard Worker
172*c2e18aaaSAndroid Build Coastguard Worker  # Include dependencies of any Mainline modules specified as well.
173*c2e18aaaSAndroid Build Coastguard Worker  for mainline_module in test_info.mainline_modules:
174*c2e18aaaSAndroid Build Coastguard Worker    deps.add(mainline_module)
175*c2e18aaaSAndroid Build Coastguard Worker    deps |= _get_transitive_module_deps(
176*c2e18aaaSAndroid Build Coastguard Worker        mod_info.get_module_info(mainline_module), mod_info, deps
177*c2e18aaaSAndroid Build Coastguard Worker    )
178*c2e18aaaSAndroid Build Coastguard Worker
179*c2e18aaaSAndroid Build Coastguard Worker  return deps
180*c2e18aaaSAndroid Build Coastguard Worker
181*c2e18aaaSAndroid Build Coastguard Worker
182*c2e18aaaSAndroid Build Coastguard Workerdef _get_transitive_module_deps(
183*c2e18aaaSAndroid Build Coastguard Worker    info, mod_info: module_info.ModuleInfo, seen: Set[str]
184*c2e18aaaSAndroid Build Coastguard Worker) -> Set[str]:
185*c2e18aaaSAndroid Build Coastguard Worker  """Gets all dependencies of the module, including .impl versions."""
186*c2e18aaaSAndroid Build Coastguard Worker  deps = set()
187*c2e18aaaSAndroid Build Coastguard Worker
188*c2e18aaaSAndroid Build Coastguard Worker  for dep in info.get(constants.MODULE_DEPENDENCIES, []):
189*c2e18aaaSAndroid Build Coastguard Worker    if dep in seen:
190*c2e18aaaSAndroid Build Coastguard Worker      continue
191*c2e18aaaSAndroid Build Coastguard Worker
192*c2e18aaaSAndroid Build Coastguard Worker    seen.add(dep)
193*c2e18aaaSAndroid Build Coastguard Worker
194*c2e18aaaSAndroid Build Coastguard Worker    dep_info = mod_info.get_module_info(dep)
195*c2e18aaaSAndroid Build Coastguard Worker
196*c2e18aaaSAndroid Build Coastguard Worker    # Mainline modules sometimes depend on `java_sdk_library` modules that
197*c2e18aaaSAndroid Build Coastguard Worker    # generate synthetic build modules ending in `.impl` which do not appear
198*c2e18aaaSAndroid Build Coastguard Worker    # in the ModuleInfo. Strip this suffix to prevent incomplete dependency
199*c2e18aaaSAndroid Build Coastguard Worker    # information when generating coverage reports.
200*c2e18aaaSAndroid Build Coastguard Worker    # TODO(olivernguyen): Reconcile this with
201*c2e18aaaSAndroid Build Coastguard Worker    # ModuleInfo.get_module_dependency(...).
202*c2e18aaaSAndroid Build Coastguard Worker    if not dep_info:
203*c2e18aaaSAndroid Build Coastguard Worker      dep = dep.removesuffix('.impl')
204*c2e18aaaSAndroid Build Coastguard Worker      dep_info = mod_info.get_module_info(dep)
205*c2e18aaaSAndroid Build Coastguard Worker
206*c2e18aaaSAndroid Build Coastguard Worker    if not dep_info:
207*c2e18aaaSAndroid Build Coastguard Worker      continue
208*c2e18aaaSAndroid Build Coastguard Worker
209*c2e18aaaSAndroid Build Coastguard Worker    deps.add(dep)
210*c2e18aaaSAndroid Build Coastguard Worker    deps |= _get_transitive_module_deps(dep_info, mod_info, seen)
211*c2e18aaaSAndroid Build Coastguard Worker
212*c2e18aaaSAndroid Build Coastguard Worker  return deps
213*c2e18aaaSAndroid Build Coastguard Worker
214*c2e18aaaSAndroid Build Coastguard Worker
215*c2e18aaaSAndroid Build Coastguard Workerdef _collect_java_report_jars(code_under_test, mod_info, is_host_enabled):
216*c2e18aaaSAndroid Build Coastguard Worker  soong_intermediates = atest_utils.get_build_out_dir('soong/.intermediates')
217*c2e18aaaSAndroid Build Coastguard Worker  report_jars = {}
218*c2e18aaaSAndroid Build Coastguard Worker
219*c2e18aaaSAndroid Build Coastguard Worker  for module in code_under_test:
220*c2e18aaaSAndroid Build Coastguard Worker    for path in mod_info.get_paths(module):
221*c2e18aaaSAndroid Build Coastguard Worker      if not path:
222*c2e18aaaSAndroid Build Coastguard Worker        continue
223*c2e18aaaSAndroid Build Coastguard Worker      module_dir = soong_intermediates.joinpath(path, module)
224*c2e18aaaSAndroid Build Coastguard Worker      # Check for uninstrumented Java class files to report coverage.
225*c2e18aaaSAndroid Build Coastguard Worker      classfiles = list(module_dir.rglob('jacoco-report-classes/*.jar'))
226*c2e18aaaSAndroid Build Coastguard Worker      if classfiles:
227*c2e18aaaSAndroid Build Coastguard Worker        report_jars[module] = classfiles
228*c2e18aaaSAndroid Build Coastguard Worker
229*c2e18aaaSAndroid Build Coastguard Worker    # Host tests use the test itself to generate the coverage report.
230*c2e18aaaSAndroid Build Coastguard Worker    info = mod_info.get_module_info(module)
231*c2e18aaaSAndroid Build Coastguard Worker    if not info:
232*c2e18aaaSAndroid Build Coastguard Worker      continue
233*c2e18aaaSAndroid Build Coastguard Worker    if is_host_enabled or not mod_info.requires_device(info):
234*c2e18aaaSAndroid Build Coastguard Worker      installed = mod_info.get_installed_paths(module)
235*c2e18aaaSAndroid Build Coastguard Worker      installed_jars = [str(f) for f in installed if f.suffix == '.jar']
236*c2e18aaaSAndroid Build Coastguard Worker      if installed_jars:
237*c2e18aaaSAndroid Build Coastguard Worker        report_jars[module] = installed_jars
238*c2e18aaaSAndroid Build Coastguard Worker
239*c2e18aaaSAndroid Build Coastguard Worker  return report_jars
240*c2e18aaaSAndroid Build Coastguard Worker
241*c2e18aaaSAndroid Build Coastguard Worker
242*c2e18aaaSAndroid Build Coastguard Workerdef _collect_native_report_binaries(code_under_test, mod_info, is_host_enabled):
243*c2e18aaaSAndroid Build Coastguard Worker  soong_intermediates = atest_utils.get_build_out_dir('soong/.intermediates')
244*c2e18aaaSAndroid Build Coastguard Worker  report_binaries = set()
245*c2e18aaaSAndroid Build Coastguard Worker
246*c2e18aaaSAndroid Build Coastguard Worker  for module in code_under_test:
247*c2e18aaaSAndroid Build Coastguard Worker    for path in mod_info.get_paths(module):
248*c2e18aaaSAndroid Build Coastguard Worker      if not path:
249*c2e18aaaSAndroid Build Coastguard Worker        continue
250*c2e18aaaSAndroid Build Coastguard Worker      module_dir = soong_intermediates.joinpath(path, module)
251*c2e18aaaSAndroid Build Coastguard Worker      # Check for unstripped binaries to report coverage.
252*c2e18aaaSAndroid Build Coastguard Worker      report_binaries.update(module_dir.glob('*cov*/**/unstripped/*'))
253*c2e18aaaSAndroid Build Coastguard Worker
254*c2e18aaaSAndroid Build Coastguard Worker    # Host tests use the test itself to generate the coverage report.
255*c2e18aaaSAndroid Build Coastguard Worker    info = mod_info.get_module_info(module)
256*c2e18aaaSAndroid Build Coastguard Worker    if not info:
257*c2e18aaaSAndroid Build Coastguard Worker      continue
258*c2e18aaaSAndroid Build Coastguard Worker    if constants.MODULE_CLASS_NATIVE_TESTS not in info.get(
259*c2e18aaaSAndroid Build Coastguard Worker        constants.MODULE_CLASS, []
260*c2e18aaaSAndroid Build Coastguard Worker    ):
261*c2e18aaaSAndroid Build Coastguard Worker      continue
262*c2e18aaaSAndroid Build Coastguard Worker    if is_host_enabled or not mod_info.requires_device(info):
263*c2e18aaaSAndroid Build Coastguard Worker      report_binaries.update(
264*c2e18aaaSAndroid Build Coastguard Worker          str(f) for f in mod_info.get_installed_paths(module)
265*c2e18aaaSAndroid Build Coastguard Worker      )
266*c2e18aaaSAndroid Build Coastguard Worker
267*c2e18aaaSAndroid Build Coastguard Worker  return _strip_irrelevant_objects(report_binaries)
268*c2e18aaaSAndroid Build Coastguard Worker
269*c2e18aaaSAndroid Build Coastguard Worker
270*c2e18aaaSAndroid Build Coastguard Workerdef _strip_irrelevant_objects(files):
271*c2e18aaaSAndroid Build Coastguard Worker  objects = set()
272*c2e18aaaSAndroid Build Coastguard Worker  for file in files:
273*c2e18aaaSAndroid Build Coastguard Worker    cmd = ['llvm-readobj', file]
274*c2e18aaaSAndroid Build Coastguard Worker    try:
275*c2e18aaaSAndroid Build Coastguard Worker      subprocess.run(
276*c2e18aaaSAndroid Build Coastguard Worker          cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
277*c2e18aaaSAndroid Build Coastguard Worker      )
278*c2e18aaaSAndroid Build Coastguard Worker      objects.add(file)
279*c2e18aaaSAndroid Build Coastguard Worker    except subprocess.CalledProcessError:
280*c2e18aaaSAndroid Build Coastguard Worker      logging.debug(f'{file} is not a valid object file, skipping.')
281*c2e18aaaSAndroid Build Coastguard Worker  return objects
282*c2e18aaaSAndroid Build Coastguard Worker
283*c2e18aaaSAndroid Build Coastguard Worker
284*c2e18aaaSAndroid Build Coastguard Workerdef _get_all_src_paths(modules, mod_info):
285*c2e18aaaSAndroid Build Coastguard Worker  """Gets the set of directories containing any source files from the modules."""
286*c2e18aaaSAndroid Build Coastguard Worker  src_paths = set()
287*c2e18aaaSAndroid Build Coastguard Worker
288*c2e18aaaSAndroid Build Coastguard Worker  for module in modules:
289*c2e18aaaSAndroid Build Coastguard Worker    info = mod_info.get_module_info(module)
290*c2e18aaaSAndroid Build Coastguard Worker    if not info:
291*c2e18aaaSAndroid Build Coastguard Worker      continue
292*c2e18aaaSAndroid Build Coastguard Worker
293*c2e18aaaSAndroid Build Coastguard Worker    # Do not report coverage for test modules.
294*c2e18aaaSAndroid Build Coastguard Worker    if mod_info.is_testable_module(info):
295*c2e18aaaSAndroid Build Coastguard Worker      continue
296*c2e18aaaSAndroid Build Coastguard Worker
297*c2e18aaaSAndroid Build Coastguard Worker    src_paths.update(
298*c2e18aaaSAndroid Build Coastguard Worker        os.path.dirname(f) for f in info.get(constants.MODULE_SRCS, [])
299*c2e18aaaSAndroid Build Coastguard Worker    )
300*c2e18aaaSAndroid Build Coastguard Worker
301*c2e18aaaSAndroid Build Coastguard Worker  src_paths = {p for p in src_paths if not _is_generated_code(p)}
302*c2e18aaaSAndroid Build Coastguard Worker  return src_paths
303*c2e18aaaSAndroid Build Coastguard Worker
304*c2e18aaaSAndroid Build Coastguard Worker
305*c2e18aaaSAndroid Build Coastguard Workerdef _is_generated_code(path):
306*c2e18aaaSAndroid Build Coastguard Worker  return 'soong/.intermediates' in path
307*c2e18aaaSAndroid Build Coastguard Worker
308*c2e18aaaSAndroid Build Coastguard Worker
309*c2e18aaaSAndroid Build Coastguard Workerdef _generate_java_coverage_report(
310*c2e18aaaSAndroid Build Coastguard Worker    report_jars, src_paths, results_dir, mod_info
311*c2e18aaaSAndroid Build Coastguard Worker):
312*c2e18aaaSAndroid Build Coastguard Worker  build_top = os.environ.get(constants.ANDROID_BUILD_TOP)
313*c2e18aaaSAndroid Build Coastguard Worker  out_dir = os.path.join(results_dir, 'java_coverage')
314*c2e18aaaSAndroid Build Coastguard Worker  jacoco_files = atest_utils.find_files(results_dir, '*.ec')
315*c2e18aaaSAndroid Build Coastguard Worker
316*c2e18aaaSAndroid Build Coastguard Worker  os.mkdir(out_dir)
317*c2e18aaaSAndroid Build Coastguard Worker  jacoco_lcov = mod_info.get_module_info('jacoco_to_lcov_converter')
318*c2e18aaaSAndroid Build Coastguard Worker  jacoco_lcov = os.path.join(build_top, jacoco_lcov['installed'][0])
319*c2e18aaaSAndroid Build Coastguard Worker  lcov_reports = []
320*c2e18aaaSAndroid Build Coastguard Worker
321*c2e18aaaSAndroid Build Coastguard Worker  for name, classfiles in report_jars.items():
322*c2e18aaaSAndroid Build Coastguard Worker    dest = f'{out_dir}/{name}.info'
323*c2e18aaaSAndroid Build Coastguard Worker    cmd = [jacoco_lcov, '-o', dest]
324*c2e18aaaSAndroid Build Coastguard Worker    for classfile in classfiles:
325*c2e18aaaSAndroid Build Coastguard Worker      cmd.append('-classfiles')
326*c2e18aaaSAndroid Build Coastguard Worker      cmd.append(str(classfile))
327*c2e18aaaSAndroid Build Coastguard Worker    for src_path in src_paths:
328*c2e18aaaSAndroid Build Coastguard Worker      cmd.append('-sourcepath')
329*c2e18aaaSAndroid Build Coastguard Worker      cmd.append(src_path)
330*c2e18aaaSAndroid Build Coastguard Worker    cmd.extend(jacoco_files)
331*c2e18aaaSAndroid Build Coastguard Worker    logging.debug(f'Running jacoco_lcov to generate coverage report: {cmd}.')
332*c2e18aaaSAndroid Build Coastguard Worker    try:
333*c2e18aaaSAndroid Build Coastguard Worker      subprocess.run(
334*c2e18aaaSAndroid Build Coastguard Worker          cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
335*c2e18aaaSAndroid Build Coastguard Worker      )
336*c2e18aaaSAndroid Build Coastguard Worker    except subprocess.CalledProcessError as err:
337*c2e18aaaSAndroid Build Coastguard Worker      atest_utils.colorful_print(
338*c2e18aaaSAndroid Build Coastguard Worker          f'Failed to generate coverage for {name}:', constants.RED
339*c2e18aaaSAndroid Build Coastguard Worker      )
340*c2e18aaaSAndroid Build Coastguard Worker      logging.exception(err.stdout)
341*c2e18aaaSAndroid Build Coastguard Worker    atest_utils.colorful_print(
342*c2e18aaaSAndroid Build Coastguard Worker        f'Coverage for {name} written to {dest}.', constants.GREEN
343*c2e18aaaSAndroid Build Coastguard Worker    )
344*c2e18aaaSAndroid Build Coastguard Worker    lcov_reports.append(dest)
345*c2e18aaaSAndroid Build Coastguard Worker
346*c2e18aaaSAndroid Build Coastguard Worker  _generate_lcov_report(out_dir, lcov_reports, build_top)
347*c2e18aaaSAndroid Build Coastguard Worker
348*c2e18aaaSAndroid Build Coastguard Worker
349*c2e18aaaSAndroid Build Coastguard Workerdef _generate_native_coverage_report(unstripped_native_binaries, results_dir):
350*c2e18aaaSAndroid Build Coastguard Worker  build_top = os.environ.get(constants.ANDROID_BUILD_TOP)
351*c2e18aaaSAndroid Build Coastguard Worker  out_dir = os.path.join(results_dir, 'native_coverage')
352*c2e18aaaSAndroid Build Coastguard Worker  profdata_files = atest_utils.find_files(results_dir, '*.profdata')
353*c2e18aaaSAndroid Build Coastguard Worker
354*c2e18aaaSAndroid Build Coastguard Worker  os.mkdir(out_dir)
355*c2e18aaaSAndroid Build Coastguard Worker  cmd = [
356*c2e18aaaSAndroid Build Coastguard Worker      'llvm-cov',
357*c2e18aaaSAndroid Build Coastguard Worker      'show',
358*c2e18aaaSAndroid Build Coastguard Worker      '-format=html',
359*c2e18aaaSAndroid Build Coastguard Worker      f'-output-dir={out_dir}',
360*c2e18aaaSAndroid Build Coastguard Worker      f'-path-equivalence=/proc/self/cwd,{build_top}',
361*c2e18aaaSAndroid Build Coastguard Worker  ]
362*c2e18aaaSAndroid Build Coastguard Worker  for profdata in profdata_files:
363*c2e18aaaSAndroid Build Coastguard Worker    cmd.append('--instr-profile')
364*c2e18aaaSAndroid Build Coastguard Worker    cmd.append(profdata)
365*c2e18aaaSAndroid Build Coastguard Worker  for binary in unstripped_native_binaries:
366*c2e18aaaSAndroid Build Coastguard Worker    cmd.append(f'--object={str(binary)}')
367*c2e18aaaSAndroid Build Coastguard Worker
368*c2e18aaaSAndroid Build Coastguard Worker  logging.debug(f'Running llvm-cov to generate coverage report: {cmd}.')
369*c2e18aaaSAndroid Build Coastguard Worker  try:
370*c2e18aaaSAndroid Build Coastguard Worker    subprocess.run(
371*c2e18aaaSAndroid Build Coastguard Worker        cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
372*c2e18aaaSAndroid Build Coastguard Worker    )
373*c2e18aaaSAndroid Build Coastguard Worker    atest_utils.colorful_print(
374*c2e18aaaSAndroid Build Coastguard Worker        f'Native coverage written to {out_dir}.', constants.GREEN
375*c2e18aaaSAndroid Build Coastguard Worker    )
376*c2e18aaaSAndroid Build Coastguard Worker  except subprocess.CalledProcessError as err:
377*c2e18aaaSAndroid Build Coastguard Worker    atest_utils.colorful_print(
378*c2e18aaaSAndroid Build Coastguard Worker        'Failed to generate native code coverage.', constants.RED
379*c2e18aaaSAndroid Build Coastguard Worker    )
380*c2e18aaaSAndroid Build Coastguard Worker    logging.exception(err.stdout)
381*c2e18aaaSAndroid Build Coastguard Worker
382*c2e18aaaSAndroid Build Coastguard Worker
383*c2e18aaaSAndroid Build Coastguard Workerdef _generate_lcov_report(out_dir, reports, root_dir=None):
384*c2e18aaaSAndroid Build Coastguard Worker  cmd = [
385*c2e18aaaSAndroid Build Coastguard Worker      'genhtml',
386*c2e18aaaSAndroid Build Coastguard Worker      '-q',
387*c2e18aaaSAndroid Build Coastguard Worker      '-o',
388*c2e18aaaSAndroid Build Coastguard Worker      out_dir,
389*c2e18aaaSAndroid Build Coastguard Worker      # TODO(b/361334044): These errors are ignored to continue to generate a
390*c2e18aaaSAndroid Build Coastguard Worker      # flawed result but ultimately need to be resolved, see bug for details.
391*c2e18aaaSAndroid Build Coastguard Worker      '--ignore-errors',
392*c2e18aaaSAndroid Build Coastguard Worker      'unmapped,range,empty,corrupt',
393*c2e18aaaSAndroid Build Coastguard Worker  ]
394*c2e18aaaSAndroid Build Coastguard Worker  if root_dir:
395*c2e18aaaSAndroid Build Coastguard Worker    cmd.extend(['-p', root_dir])
396*c2e18aaaSAndroid Build Coastguard Worker  cmd.extend(reports)
397*c2e18aaaSAndroid Build Coastguard Worker  logging.debug(f'Running genhtml to generate coverage report: {cmd}.')
398*c2e18aaaSAndroid Build Coastguard Worker  try:
399*c2e18aaaSAndroid Build Coastguard Worker    subprocess.run(
400*c2e18aaaSAndroid Build Coastguard Worker        cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
401*c2e18aaaSAndroid Build Coastguard Worker    )
402*c2e18aaaSAndroid Build Coastguard Worker    atest_utils.colorful_print(
403*c2e18aaaSAndroid Build Coastguard Worker        f'Code coverage report written to {out_dir}.', constants.GREEN
404*c2e18aaaSAndroid Build Coastguard Worker    )
405*c2e18aaaSAndroid Build Coastguard Worker    atest_utils.colorful_print(
406*c2e18aaaSAndroid Build Coastguard Worker        f'To open, Ctrl+Click on file://{out_dir}/index.html', constants.GREEN
407*c2e18aaaSAndroid Build Coastguard Worker    )
408*c2e18aaaSAndroid Build Coastguard Worker  except subprocess.CalledProcessError as err:
409*c2e18aaaSAndroid Build Coastguard Worker    atest_utils.colorful_print(
410*c2e18aaaSAndroid Build Coastguard Worker        'Failed to generate HTML coverage report.', constants.RED
411*c2e18aaaSAndroid Build Coastguard Worker    )
412*c2e18aaaSAndroid Build Coastguard Worker    logging.exception(err.stdout)
413*c2e18aaaSAndroid Build Coastguard Worker  except FileNotFoundError:
414*c2e18aaaSAndroid Build Coastguard Worker    atest_utils.colorful_print('genhtml is not on the $PATH.', constants.RED)
415*c2e18aaaSAndroid Build Coastguard Worker    atest_utils.colorful_print(
416*c2e18aaaSAndroid Build Coastguard Worker        'Run `sudo apt-get install lcov -y` to install this tool.',
417*c2e18aaaSAndroid Build Coastguard Worker        constants.RED,
418*c2e18aaaSAndroid Build Coastguard Worker    )
419