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