blob: a4a1eb003a062171428b6fc04f4b6269ff3f9168 [file] [log] [blame]
Lei Zhang42a5b51a2022-03-07 19:16:161#!/usr/bin/env vpython3
Avi Drissmandfd880852022-09-15 20:11:092# Copyright 2017 The Chromium Authors
Yuke Liao506e8822017-12-04 16:52:543# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
Abhishek Arya1ec832c2017-12-05 18:06:595"""This script helps to generate code coverage report.
Yuke Liao506e8822017-12-04 16:52:546
Abhishek Arya1ec832c2017-12-05 18:06:597 It uses Clang Source-based Code Coverage -
8 https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
Yuke Liao506e8822017-12-04 16:52:549
Abhishek Arya16f059a2017-12-07 17:47:3210 In order to generate code coverage report, you need to first add
Yuke Liaoab9c44e2018-02-21 00:24:4011 "use_clang_coverage=true" and "is_component_build=false" GN flags to args.gn
12 file in your build output directory (e.g. out/coverage).
Yuke Liao506e8822017-12-04 16:52:5413
Abhishek Arya03911092018-05-21 16:42:3514 * Example usage:
Abhishek Arya1ec832c2017-12-05 18:06:5915
Max Moroza5a95272018-08-31 16:20:5516 gn gen out/coverage \\
Abhishek Arya2f261182019-04-24 17:06:4517 --args="use_clang_coverage=true is_component_build=false\\
18 is_debug=false dcheck_always_on=true"
Abhishek Arya16f059a2017-12-07 17:47:3219 gclient runhooks
Fabrice de Gans0b5511e72022-09-16 22:07:2020 vpython3 tools/code_coverage/coverage.py crypto_unittests url_unittests \\
Abhishek Arya16f059a2017-12-07 17:47:3221 -b out/coverage -o out/report -c 'out/coverage/crypto_unittests' \\
22 -c 'out/coverage/url_unittests --gtest_filter=URLParser.PathURL' \\
23 -f url/ -f crypto/
Abhishek Arya1ec832c2017-12-05 18:06:5924
Abhishek Arya16f059a2017-12-07 17:47:3225 The command above builds crypto_unittests and url_unittests targets and then
26 runs them with specified command line arguments. For url_unittests, it only
27 runs the test URLParser.PathURL. The coverage report is filtered to include
28 only files and sub-directories under url/ and crypto/ directories.
Abhishek Arya1ec832c2017-12-05 18:06:5929
Yuke Liao545db322018-02-15 17:12:0130 If you want to run tests that try to draw to the screen but don't have a
31 display connected, you can run tests in headless mode with xvfb.
32
Abhishek Arya03911092018-05-21 16:42:3533 * Sample flow for running a test target with xvfb (e.g. unit_tests):
Yuke Liao545db322018-02-15 17:12:0134
Fabrice de Gans0b5511e72022-09-16 22:07:2035 vpython3 tools/code_coverage/coverage.py unit_tests -b out/coverage \\
Yuke Liao545db322018-02-15 17:12:0136 -o out/report -c 'python testing/xvfb.py out/coverage/unit_tests'
37
Julia Hansbrough570a8a82023-01-19 19:45:4838 If you are building a fuzz target, in addition to "use_clang_coverage=true"
39 and "is_component_build=false", you must have the following GN flags as well:
40 optimize_for_fuzzing=false
41 use_remoteexec=false
42 is_asan=false (ASAN & other sanitizers are incompatible with coverage)
43 use_libfuzzer=true
Abhishek Arya1ec832c2017-12-05 18:06:5944
Abhishek Arya03911092018-05-21 16:42:3545 * Sample workflow for a fuzz target (e.g. pdfium_fuzzer):
Abhishek Arya1ec832c2017-12-05 18:06:5946
Fabrice de Gans0b5511e72022-09-16 22:07:2047 vpython3 tools/code_coverage/coverage.py pdfium_fuzzer \\
Abhishek Arya16f059a2017-12-07 17:47:3248 -b out/coverage -o out/report \\
Max Moroz13c23182018-11-17 00:23:2249 -c 'out/coverage/pdfium_fuzzer -runs=0 <corpus_dir>' \\
Abhishek Arya16f059a2017-12-07 17:47:3250 -f third_party/pdfium
Abhishek Arya1ec832c2017-12-05 18:06:5951
52 where:
53 <corpus_dir> - directory containing samples files for this format.
Max Moroz13c23182018-11-17 00:23:2254
55 To learn more about generating code coverage reports for fuzz targets, see
John Palmerab8812a2021-05-21 17:03:4356 https://chromium.googlesource.com/chromium/src/+/main/testing/libfuzzer/efficient_fuzzer.md#Code-Coverage
Abhishek Arya1ec832c2017-12-05 18:06:5957
Prakhara6418512023-05-22 17:17:4558 * Sample workflow for running Blink web platform tests:
Abhishek Arya03911092018-05-21 16:42:3559
Fabrice de Gans0b5511e72022-09-16 22:07:2060 vpython3 tools/code_coverage/coverage.py blink_tests \\
Prakhara6418512023-05-22 17:17:4561 -b out/coverage -o out/report -f third_party/blink -wt
Abhishek Arya03911092018-05-21 16:42:3562
Prakhara6418512023-05-22 17:17:4563 -wt flag tells coverage script that it is a web test, and can also be
64 used to pass arguments to run_web_tests.py
65
66 vpython3 tools/code_coverage/coverage.py blink_wpt_tests \\
67 -b out/Release -o out/report
68 -wt external/wpt/webcodecs/per-frame-qp-encoding.https.any.js
Abhishek Arya03911092018-05-21 16:42:3569
Abhishek Arya1ec832c2017-12-05 18:06:5970 For more options, please refer to tools/code_coverage/coverage.py -h.
Yuke Liao8e209fe82018-04-18 20:36:3871
72 For an overview of how code coverage works in Chromium, please refer to
John Palmerab8812a2021-05-21 17:03:4373 https://chromium.googlesource.com/chromium/src/+/main/docs/testing/code_coverage.md
Yuke Liao506e8822017-12-04 16:52:5474"""
75
76from __future__ import print_function
77
78import sys
79
80import argparse
Julia Hansbrough58aa7b0a2023-01-17 21:08:4181import glob
Yuke Liaoea228d02018-01-05 19:10:3382import json
Yuke Liao481d3482018-01-29 19:17:1083import logging
Abhishek Arya03911092018-05-21 16:42:3584import multiprocessing
Yuke Liao506e8822017-12-04 16:52:5485import os
Sajjad Mirza0b96e002020-11-10 19:32:5586import platform
Yuke Liaob2926832018-03-02 17:34:2987import re
88import shlex
Max Moroz025d8952018-05-03 16:33:3489import shutil
Yuke Liao506e8822017-12-04 16:52:5490import subprocess
Choongwoo Hanbd1aa952021-06-09 22:25:3891
Lei Zhang20e2ab752022-10-11 22:11:0092from urllib.request import urlopen
Choongwoo Hanbd1aa952021-06-09 22:25:3893
Abhishek Arya1ec832c2017-12-05 18:06:5994sys.path.append(
95 os.path.join(
Yuke Liaoea228d02018-01-05 19:10:3396 os.path.dirname(__file__), os.path.pardir, os.path.pardir,
97 'third_party'))
Yuke Liaoea228d02018-01-05 19:10:3398from collections import defaultdict
99
Max Moroz1de68d72018-08-21 13:38:18100import coverage_utils
101
Yuke Liao082e99632018-05-18 15:40:40102# Absolute path to the code coverage tools binary. These paths can be
103# overwritten by user specified coverage tool paths.
pasthanab37d5bfd2020-05-28 12:18:31104# Absolute path to the root of the checkout.
105SRC_ROOT_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)),
106 os.path.pardir, os.path.pardir)
107LLVM_BIN_DIR = os.path.join(
108 os.path.join(SRC_ROOT_PATH, 'third_party', 'llvm-build', 'Release+Asserts'),
109 'bin')
Abhishek Arya1c97ea542018-05-10 03:53:19110LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
111LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:54112
Abhishek Arya03911092018-05-21 16:42:35113
Yuke Liao506e8822017-12-04 16:52:54114# Build directory, the value is parsed from command line arguments.
115BUILD_DIR = None
116
117# Output directory for generated artifacts, the value is parsed from command
118# line arguemnts.
119OUTPUT_DIR = None
120
Yuke Liao506e8822017-12-04 16:52:54121# Name of the file extension for profraw data files.
122PROFRAW_FILE_EXTENSION = 'profraw'
123
124# Name of the final profdata file, and this file needs to be passed to
125# "llvm-cov" command in order to call "llvm-cov show" to inspect the
126# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48127PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
128
129# Name of the file with summary information generated by llvm-cov export.
130SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54131
Akekawit Jitprasertf9cb6622021-08-24 17:48:02132# Name of the coverage file in lcov format generated by llvm-cov export.
133LCOV_FILE_NAME = os.extsep.join(['coverage', 'lcov'])
134
Yuke Liao506e8822017-12-04 16:52:54135# Build arg required for generating code coverage data.
136CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
137
Max Moroz7c5354f2018-05-06 00:03:48138LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37139
140# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19141COMPONENT_MAPPING_URL = (
142 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37143
Yuke Liao80afff32018-03-07 01:26:20144# Caches the results returned by _GetBuildArgs, don't use this variable
145# directly, call _GetBuildArgs instead.
146_BUILD_ARGS = None
147
Abhishek Aryac19bc5ef2018-05-04 22:10:02148# Retry failed merges.
149MERGE_RETRIES = 3
150
Abhishek Aryad35de7e2018-05-10 22:23:04151# Message to guide user to file a bug when everything else fails.
152FILE_BUG_MESSAGE = (
153 'If it persists, please file a bug with the command you used, git revision '
154 'and args.gn config here: '
155 'https://bugs.chromium.org/p/chromium/issues/entry?'
Yuke Liao03c644072019-07-30 18:33:40156 'components=Infra%3ETest%3ECodeCoverage')
Abhishek Aryad35de7e2018-05-10 22:23:04157
Abhishek Aryabd0655d2018-05-21 19:55:24158# String to replace with actual llvm profile path.
159LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
160
Yuke Liao082e99632018-05-18 15:40:40161def _ConfigureLLVMCoverageTools(args):
162 """Configures llvm coverage tools."""
163 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18164 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40165 global LLVM_COV_PATH
166 global LLVM_PROFDATA_PATH
167 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
168 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
169 else:
Choongwoo Hanbd1aa952021-06-09 22:25:38170 subprocess.check_call([
Akekawit Jitprasert928671e2021-09-20 18:40:58171 sys.executable, 'tools/clang/scripts/update.py', '--package',
172 'coverage_tools'
Choongwoo Hanbd1aa952021-06-09 22:25:38173 ])
Brent McBrideb25b177a42020-05-11 18:13:06174
175 if coverage_utils.GetHostPlatform() == 'win':
176 LLVM_COV_PATH += '.exe'
177 LLVM_PROFDATA_PATH += '.exe'
Yuke Liao082e99632018-05-18 15:40:40178
179 coverage_tools_exist = (
180 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
181 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
182 'both \'%s\' and \'%s\' exist.') % (
183 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
184
Abhishek Arya2f261182019-04-24 17:06:45185
Abhishek Arya1c97ea542018-05-10 03:53:19186def _GetPathWithLLVMSymbolizerDir():
187 """Add llvm-symbolizer directory to path for symbolized stacks."""
188 path = os.getenv('PATH')
189 dirs = path.split(os.pathsep)
190 if LLVM_BIN_DIR in dirs:
191 return path
192
193 return path + os.pathsep + LLVM_BIN_DIR
194
195
Yuke Liaoc60b2d02018-03-02 21:40:43196def _GetTargetOS():
197 """Returns the target os specified in args.gn file.
198
199 Returns an empty string is target_os is not specified.
200 """
Yuke Liao80afff32018-03-07 01:26:20201 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43202 return build_args['target_os'] if 'target_os' in build_args else ''
203
204
Ben Joyce88282362021-01-29 23:53:31205def _IsAndroid():
206 """Returns true if the target_os specified in args.gn file is android"""
207 return _GetTargetOS() == 'android'
208
209
Yuke Liaob2926832018-03-02 17:34:29210def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10211 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43212 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10213
214
Sahel Sharify38cabdc2020-01-16 00:40:01215def _GeneratePerFileLineByLineCoverageInFormat(binary_paths, profdata_file_path,
216 filters, ignore_filename_regex,
217 output_format):
218 """Generates per file line-by-line coverage in html or text using
219 'llvm-cov show'.
Yuke Liao506e8822017-12-04 16:52:54220
Sahel Sharify38cabdc2020-01-16 00:40:01221 For a file with absolute path /a/b/x.cc, a html/txt report is generated as:
222 OUTPUT_DIR/coverage/a/b/x.cc.[html|txt]. For html format, an index html file
223 is also generated as: OUTPUT_DIR/index.html.
Yuke Liao506e8822017-12-04 16:52:54224
225 Args:
226 binary_paths: A list of paths to the instrumented binaries.
227 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42228 filters: A list of directories and files to get coverage for.
Sahel Sharify38cabdc2020-01-16 00:40:01229 ignore_filename_regex: A regular expression for skipping source code files
230 with certain file paths.
231 output_format: The output format of generated report files.
Yuke Liao506e8822017-12-04 16:52:54232 """
Yuke Liao506e8822017-12-04 16:52:54233 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
234 # [[-object BIN]] [SOURCES]
235 # NOTE: For object files, the first one is specified as a positional argument,
236 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10237 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40238 '"llvm-cov show" command.')
Sahel Sharify38cabdc2020-01-16 00:40:01239
Abhishek Arya1ec832c2017-12-05 18:06:59240 subprocess_cmd = [
Sahel Sharify38cabdc2020-01-16 00:40:01241 LLVM_COV_PATH, 'show', '-format={}'.format(output_format),
Choongwoo Han56752522021-06-10 17:38:34242 '-compilation-dir={}'.format(BUILD_DIR),
Abhishek Arya1ec832c2017-12-05 18:06:59243 '-output-dir={}'.format(OUTPUT_DIR),
244 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
245 ]
246 subprocess_cmd.extend(
247 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29248 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18249 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17250 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42251 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59252 if ignore_filename_regex:
253 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
254
Yuke Liao506e8822017-12-04 16:52:54255 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34256
Abhishek Aryafb70b532018-05-06 17:47:40257 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54258
259
Lei Zhang42a5b51a2022-03-07 19:16:16260def _GeneratePerFileLineByLineCoverageInLcov(binary_paths, profdata_file_path,
261 filters, ignore_filename_regex):
Akekawit Jitprasertf9cb6622021-08-24 17:48:02262 """Generates per file line-by-line coverage using "llvm-cov export".
263
264 Args:
265 binary_paths: A list of paths to the instrumented binaries.
266 profdata_file_path: A path to the profdata file.
267 filters: A list of directories and files to get coverage for.
268 ignore_filename_regex: A regular expression for skipping source code files
269 with certain file paths.
270 """
271 logging.debug('Generating per file line by line coverage reports using '
272 '"llvm-cov export" command.')
273 for path in binary_paths:
274 if not os.path.exists(path):
275 logging.error("Binary %s does not exist", path)
276 subprocess_cmd = [
277 LLVM_COV_PATH, 'export', '-format=lcov',
278 '-instr-profile=' + profdata_file_path, binary_paths[0]
279 ]
280 subprocess_cmd.extend(
281 ['-object=' + binary_path for binary_path in binary_paths[1:]])
282 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
283 subprocess_cmd.extend(filters)
284 if ignore_filename_regex:
285 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
286
287 # Write output on the disk to be used by code coverage bot.
288 with open(_GetLcovFilePath(), 'w') as f:
289 subprocess.check_call(subprocess_cmd, stdout=f)
290
291 logging.debug('Finished running "llvm-cov export" command.')
292
293
Max Moroz7c5354f2018-05-06 00:03:48294def _GetLogsDirectoryPath():
295 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18296 return os.path.join(
297 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48298
299
300def _GetProfdataFilePath():
301 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18302 return os.path.join(
303 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
304 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48305
306
307def _GetSummaryFilePath():
308 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18309 return os.path.join(
310 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
311 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33312
313
Akekawit Jitprasertf9cb6622021-08-24 17:48:02314def _GetLcovFilePath():
315 """The LCOV file that contains coverage data written by llvm-cov export."""
316 return os.path.join(
317 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
318 LCOV_FILE_NAME)
319
320
Yuke Liao506e8822017-12-04 16:52:54321def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
322 """Builds and runs target to generate the coverage profile data.
323
324 Args:
325 targets: A list of targets to build with coverage instrumentation.
326 commands: A list of commands used to run the targets.
327 jobs_count: Number of jobs to run in parallel for building. If None, a
328 default value is derived based on CPUs availability.
329
330 Returns:
331 A relative path to the generated profdata file.
332 """
333 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02334 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59335 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02336 coverage_profdata_file_path = (
337 _CreateCoverageProfileDataFromTargetProfDataFiles(
338 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54339
Abhishek Aryac19bc5ef2018-05-04 22:10:02340 for target_profdata_file_path in target_profdata_file_paths:
341 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52342
Abhishek Aryac19bc5ef2018-05-04 22:10:02343 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54344
345
346def _BuildTargets(targets, jobs_count):
347 """Builds target with Clang coverage instrumentation.
348
349 This function requires current working directory to be the root of checkout.
350
351 Args:
352 targets: A list of targets to build with coverage instrumentation.
353 jobs_count: Number of jobs to run in parallel for compilation. If None, a
354 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54355 """
Abhishek Aryafb70b532018-05-06 17:47:40356 logging.info('Building %s.', str(targets))
Brent McBrideb25b177a42020-05-11 18:13:06357 autoninja = 'autoninja'
358 if coverage_utils.GetHostPlatform() == 'win':
359 autoninja += '.bat'
Yuke Liao506e8822017-12-04 16:52:54360
Brent McBrideb25b177a42020-05-11 18:13:06361 subprocess_cmd = [autoninja, '-C', BUILD_DIR]
Yuke Liao506e8822017-12-04 16:52:54362 if jobs_count is not None:
363 subprocess_cmd.append('-j' + str(jobs_count))
364
365 subprocess_cmd.extend(targets)
Arthur Wang06bd4df2024-05-20 16:42:06366 # subprocess.check_call(subprocess_cmd, shell=os.name == 'nt')
367 # RBE enabled autoninja run returns non-zero exit code
368 subprocess.call(subprocess_cmd, shell=os.name == 'nt')
Abhishek Aryafb70b532018-05-06 17:47:40369 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54370
371
Abhishek Aryac19bc5ef2018-05-04 22:10:02372def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54373 """Runs commands and returns the relative paths to the profraw data files.
374
375 Args:
376 targets: A list of targets built with coverage instrumentation.
377 commands: A list of commands used to run the targets.
378
379 Returns:
380 A list of relative paths to the generated profraw data files.
381 """
Abhishek Aryafb70b532018-05-06 17:47:40382 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10383
Yuke Liao506e8822017-12-04 16:52:54384 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18385 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
386 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54387 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18388 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48389
390 # Ensure that logs directory exists.
391 if not os.path.exists(_GetLogsDirectoryPath()):
392 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54393
Abhishek Aryac19bc5ef2018-05-04 22:10:02394 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10395
Yuke Liaod4a9865202018-01-12 23:17:52396 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54397 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48398 output_file_name = os.extsep.join([target + '_output', 'log'])
399 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10400
Abhishek Aryac19bc5ef2018-05-04 22:10:02401 profdata_file_path = None
Prakhar65d63832021-06-16 23:01:37402 for _ in range(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40403 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02404 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10405
Abhishek Aryac19bc5ef2018-05-04 22:10:02406 if _IsIOSCommand(command):
407 # On iOS platform, due to lack of write permissions, profraw files are
408 # generated outside of the OUTPUT_DIR, and the exact paths are contained
409 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35410 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02411 else:
412 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35413 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02414
415 profraw_file_paths = []
416 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57417 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Ben Joyce88282362021-01-29 23:53:31418 elif _IsAndroid():
419 android_coverage_dir = os.path.join(BUILD_DIR, 'coverage')
420 for r, _, files in os.walk(android_coverage_dir):
421 for f in files:
422 if f.endswith(PROFRAW_FILE_EXTENSION):
423 profraw_file_paths.append(os.path.join(r, f))
Abhishek Aryac19bc5ef2018-05-04 22:10:02424 else:
Max Moroz1de68d72018-08-21 13:38:18425 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02426 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48427 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18428 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02429
430 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40431 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04432 'please make sure the binary exists, is properly instrumented and '
433 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02434
Yuke Liao9c2c70b2018-05-23 15:37:57435 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18436 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
437 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57438
Abhishek Aryac19bc5ef2018-05-04 22:10:02439 try:
440 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
441 target, profraw_file_paths)
442 break
443 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04444 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02445 finally:
446 # Remove profraw files now so that they are not used in next iteration.
447 for profraw_file_path in profraw_file_paths:
448 os.remove(profraw_file_path)
449
450 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04451 'Failed to merge target "%s" profraw files after %d retries. %s' %
452 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02453 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54454
Abhishek Aryafb70b532018-05-06 17:47:40455 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10456
Abhishek Aryac19bc5ef2018-05-04 22:10:02457 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54458
459
Abhishek Arya03911092018-05-21 16:42:35460def _GetEnvironmentVars(profraw_file_path):
461 """Return environment vars for subprocess, given a profraw file path."""
462 env = os.environ.copy()
463 env.update({
464 'LLVM_PROFILE_FILE': profraw_file_path,
465 'PATH': _GetPathWithLLVMSymbolizerDir()
466 })
467 return env
468
469
Sajjad Mirza0b96e002020-11-10 19:32:55470def _SplitCommand(command):
471 """Split a command string into parts in a platform-specific way."""
472 if coverage_utils.GetHostPlatform() == 'win':
473 return command.split()
Julia Hansbrough58aa7b0a2023-01-17 21:08:41474 split_command = shlex.split(command)
475 # Python's subprocess does not do glob expansion, so we expand it out here.
476 new_command = []
477 for item in split_command:
Kevin Babbitt4f6d96fb2024-07-29 18:18:29478 if '*' in item and not item.startswith('--gtest_filter'):
Julia Hansbrough58aa7b0a2023-01-17 21:08:41479 files = glob.glob(item)
480 for file in files:
481 new_command.append(file)
482 else:
483 new_command.append(item)
484 return new_command
Sajjad Mirza0b96e002020-11-10 19:32:55485
486
Abhishek Arya03911092018-05-21 16:42:35487def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10488 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52489 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01490 #
Max Morozd73e45f2018-04-24 18:32:47491 # "%p" expands out to the process ID. It's not used by this scripts due to:
492 # 1) If a target program spawns too many processess, it may exhaust all disk
493 # space available. For example, unit_tests writes thousands of .profraw
494 # files each of size 1GB+.
495 # 2) If a target binary uses shared libraries, coverage profile data for them
496 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01497 #
Yuke Liaod4a9865202018-01-12 23:17:52498 # "%Nm" expands out to the instrumented binary's signature. When this pattern
499 # is specified, the runtime creates a pool of N raw profiles which are used
500 # for on-line profile merging. The runtime takes care of selecting a raw
501 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52502 # N must be between 1 and 9. The merge pool specifier can only occur once per
503 # filename pattern.
504 #
Max Morozd73e45f2018-04-24 18:32:47505 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01506 #
Max Morozd73e45f2018-04-24 18:32:47507 # For other cases, "%4m" is chosen as it creates some level of parallelism,
508 # but it's not too big to consume too much computing resource or disk space.
509 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59510 expected_profraw_file_name = os.extsep.join(
Nico Weber51e61c7d2023-12-12 17:32:46511 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18512 expected_profraw_file_path = os.path.join(
513 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
514 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24515 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
516 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54517
Yuke Liaoa0c8c2f2018-02-28 20:14:10518 try:
Max Moroz7c5354f2018-05-06 00:03:48519 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35520 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55521 subprocess.check_call(_SplitCommand(command),
522 stdout=output_file_handle,
523 stderr=subprocess.STDOUT,
524 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10525 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35526 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10527
Abhishek Arya03911092018-05-21 16:42:35528 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10529
530
Yuke Liao27349c92018-03-22 21:10:01531def _IsFuzzerTarget(target):
532 """Returns true if the target is a fuzzer target."""
533 build_args = _GetBuildArgs()
534 use_libfuzzer = ('use_libfuzzer' in build_args and
535 build_args['use_libfuzzer'] == 'true')
Adrian Taylor9470000f2023-03-10 16:18:25536 use_centipede = ('use_centipede' in build_args
537 and build_args['use_centipede'] == 'true')
538 return (use_libfuzzer or use_centipede) and target.endswith('_fuzzer')
Yuke Liao27349c92018-03-22 21:10:01539
540
Abhishek Arya03911092018-05-21 16:42:35541def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10542 """Runs a single iOS command and generates a profraw data file.
543
544 iOS application doesn't have write access to folders outside of the app, so
545 it's impossible to instruct the app to flush the profraw data file to the
546 desired location. The profraw data file will be generated somewhere within the
547 application's Documents folder, and the full path can be obtained by parsing
548 the output.
549 """
Yuke Liaob2926832018-03-02 17:34:29550 assert _IsIOSCommand(command)
551
552 # After running tests, iossim generates a profraw data file, it won't be
553 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
554 # checkout.
555 iossim_profraw_file_path = os.path.join(
556 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24557 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
558 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10559
560 try:
Abhishek Arya03911092018-05-21 16:42:35561 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55562 subprocess.check_call(_SplitCommand(command),
563 stdout=output_file_handle,
564 stderr=subprocess.STDOUT,
565 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10566 except subprocess.CalledProcessError as e:
567 # iossim emits non-zero return code even if tests run successfully, so
568 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35569 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10570
Abhishek Arya03911092018-05-21 16:42:35571 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10572
573
574def _GetProfrawDataFileByParsingOutput(output):
575 """Returns the path to the profraw data file obtained by parsing the output.
576
577 The output of running the test target has no format, but it is guaranteed to
578 have a single line containing the path to the generated profraw data file.
579 NOTE: This should only be called when target os is iOS.
580 """
Yuke Liaob2926832018-03-02 17:34:29581 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10582
Yuke Liaob2926832018-03-02 17:34:29583 output_by_lines = ''.join(output).splitlines()
584 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10585
586 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29587 result = profraw_file_pattern.match(line)
588 if result:
589 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10590
591 assert False, ('No profraw data file was generated, did you call '
592 'coverage_util::ConfigureCoverageReportPath() in test setup? '
593 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54594
595
Abhishek Aryac19bc5ef2018-05-04 22:10:02596def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
597 """Returns a relative path to coverage profdata file by merging target
598 profdata files.
Yuke Liao506e8822017-12-04 16:52:54599
600 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02601 profdata_file_paths: A list of relative paths to the profdata data files
602 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54603
604 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02605 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54606
607 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02608 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54609 """
Abhishek Aryafb70b532018-05-06 17:47:40610 logging.info('Creating the coverage profile data file.')
611 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48612 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54613 try:
Abhishek Arya1ec832c2017-12-05 18:06:59614 subprocess_cmd = [
615 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
616 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02617 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01618
619 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18620 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02621 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04622 logging.error(
623 'Failed to merge target profdata files to create coverage profdata. %s',
624 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02625 raise error
626
Abhishek Aryafb70b532018-05-06 17:47:40627 logging.debug('Finished merging target profdata files.')
628 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02629 profdata_file_path)
630 return profdata_file_path
631
632
633def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
634 """Returns a relative path to target profdata file by merging target
635 profraw files.
636
637 Args:
638 profraw_file_paths: A list of relative paths to the profdata data files
639 that are to be merged.
640
641 Returns:
642 A relative path to the merged coverage profdata file.
643
644 Raises:
645 CalledProcessError: An error occurred merging profdata files.
646 """
Abhishek Aryafb70b532018-05-06 17:47:40647 logging.info('Creating target profile data file.')
648 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02649 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
650
651 try:
652 subprocess_cmd = [
653 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
654 ]
Yuke Liao506e8822017-12-04 16:52:54655 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01656 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18657 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54658 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04659 logging.error(
660 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54661 raise error
662
Abhishek Aryafb70b532018-05-06 17:47:40663 logging.debug('Finished merging target profraw files.')
664 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10665 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54666 return profdata_file_path
667
668
Yuke Liao0e4c8682018-04-18 21:06:59669def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
670 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33671 """Generates per file coverage summary using "llvm-cov export" command."""
672 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
673 # [[-object BIN]] [SOURCES].
674 # NOTE: For object files, the first one is specified as a positional argument,
675 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10676 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40677 'export -summary-only" command.')
Sajjad Mirza07f52332020-11-11 01:50:47678 for path in binary_paths:
679 if not os.path.exists(path):
680 logging.error("Binary %s does not exist", path)
Yuke Liaoea228d02018-01-05 19:10:33681 subprocess_cmd = [
682 LLVM_COV_PATH, 'export', '-summary-only',
Choongwoo Han56752522021-06-10 17:38:34683 '-compilation-dir={}'.format(BUILD_DIR),
Yuke Liaoea228d02018-01-05 19:10:33684 '-instr-profile=' + profdata_file_path, binary_paths[0]
685 ]
686 subprocess_cmd.extend(
687 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29688 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33689 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59690 if ignore_filename_regex:
691 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33692
Max Moroz7c5354f2018-05-06 00:03:48693 export_output = subprocess.check_output(subprocess_cmd)
694
695 # Write output on the disk to be used by code coverage bot.
Prakhar65d63832021-06-16 23:01:37696 with open(_GetSummaryFilePath(), 'wb') as f:
Max Moroz7c5354f2018-05-06 00:03:48697 f.write(export_output)
698
Max Moroz1de68d72018-08-21 13:38:18699 return export_output
Yuke Liaoea228d02018-01-05 19:10:33700
701
Yuke Liaob2926832018-03-02 17:34:29702def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
703 """Appends -arch arguments to the command list if it's ios platform.
704
705 iOS binaries are universal binaries, and require specifying the architecture
706 to use, and one architecture needs to be specified for each binary.
707 """
708 if _IsIOS():
709 cmd_list.extend(['-arch=x86_64'] * num_archs)
710
711
Yuke Liao506e8822017-12-04 16:52:54712def _GetBinaryPath(command):
713 """Returns a relative path to the binary to be run by the command.
714
Yuke Liao545db322018-02-15 17:12:01715 Currently, following types of commands are supported (e.g. url_unittests):
716 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
717 2. Use xvfb.
718 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
719 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37720 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
721 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10722 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37723 <iossim_arguments> -c <app_arguments>
724 out/Coverage-iphonesimulator/url_unittests.app"
725
Yuke Liao506e8822017-12-04 16:52:54726 Args:
727 command: A command used to run a target.
728
729 Returns:
730 A relative path to the binary.
731 """
Yuke Liao545db322018-02-15 17:12:01732 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
733
Sajjad Mirza0b96e002020-11-10 19:32:55734 command_parts = _SplitCommand(command)
Yuke Liao545db322018-02-15 17:12:01735 if os.path.basename(command_parts[0]) == 'python':
736 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40737 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01738 return command_parts[2]
739
740 if os.path.basename(command_parts[0]) == xvfb_script_name:
741 return command_parts[1]
742
Yuke Liaob2926832018-03-02 17:34:29743 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10744 # For a given application bundle, the binary resides in the bundle and has
745 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02746 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10747 app_name = os.path.splitext(os.path.basename(app_path))[0]
748 return os.path.join(app_path, app_name)
749
Sajjad Mirza07f52332020-11-11 01:50:47750 if coverage_utils.GetHostPlatform() == 'win' \
751 and not command_parts[0].endswith('.exe'):
752 return command_parts[0] + '.exe'
753
Yuke Liaob2926832018-03-02 17:34:29754 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54755
756
Yuke Liaob2926832018-03-02 17:34:29757def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10758 """Returns true if command is used to run tests on iOS platform."""
Sajjad Mirza0b96e002020-11-10 19:32:55759 return os.path.basename(_SplitCommand(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10760
761
Yuke Liao95d13d72017-12-07 18:18:50762def _VerifyTargetExecutablesAreInBuildDirectory(commands):
763 """Verifies that the target executables specified in the commands are inside
764 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54765 for command in commands:
766 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18767 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35768 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50769 'Target executable "%s" in command: "%s" is outside of '
770 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54771
772
773def _ValidateBuildingWithClangCoverage():
774 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20775 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54776
777 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
778 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59779 assert False, ('\'{} = true\' is required in args.gn.'
780 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54781
782
Yuke Liaoc60b2d02018-03-02 21:40:43783def _ValidateCurrentPlatformIsSupported():
784 """Asserts that this script suports running on the current platform"""
785 target_os = _GetTargetOS()
786 if target_os:
787 current_platform = target_os
788 else:
Max Moroz1de68d72018-08-21 13:38:18789 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43790
Ben Joyce88282362021-01-29 23:53:31791 supported_platforms = ['android', 'chromeos', 'ios', 'linux', 'mac', 'win']
792 assert current_platform in supported_platforms, ('Coverage is only'
793 'supported on %s' %
794 supported_platforms)
Yuke Liaoc60b2d02018-03-02 21:40:43795
796
Yuke Liao80afff32018-03-07 01:26:20797def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54798 """Parses args.gn file and returns results as a dictionary.
799
800 Returns:
801 A dictionary representing the build args.
802 """
Yuke Liao80afff32018-03-07 01:26:20803 global _BUILD_ARGS
804 if _BUILD_ARGS is not None:
805 return _BUILD_ARGS
806
807 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54808 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
809 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
810 'missing args.gn file.' % BUILD_DIR)
811 with open(build_args_path) as build_args_file:
812 build_args_lines = build_args_file.readlines()
813
Yuke Liao506e8822017-12-04 16:52:54814 for build_arg_line in build_args_lines:
815 build_arg_without_comments = build_arg_line.split('#')[0]
816 key_value_pair = build_arg_without_comments.split('=')
817 if len(key_value_pair) != 2:
818 continue
819
820 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43821
822 # Values are wrapped within a pair of double-quotes, so remove the leading
823 # and trailing double-quotes.
824 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20825 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54826
Yuke Liao80afff32018-03-07 01:26:20827 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54828
829
Abhishek Arya16f059a2017-12-07 17:47:32830def _VerifyPathsAndReturnAbsolutes(paths):
831 """Verifies that the paths specified in |paths| exist and returns absolute
832 versions.
Yuke Liao66da1732017-12-05 22:19:42833
834 Args:
835 paths: A list of files or directories.
836 """
Abhishek Arya16f059a2017-12-07 17:47:32837 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42838 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32839 absolute_path = os.path.join(SRC_ROOT_PATH, path)
840 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
841
842 absolute_paths.append(absolute_path)
843
844 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42845
846
Abhishek Arya64636af2018-05-04 14:42:13847def _GetBinaryPathsFromTargets(targets, build_dir):
848 """Return binary paths from target names."""
Alison Gale4d9c2312024-04-26 19:15:24849 # TODO(crbug.com/41423295): Derive output binary from target build definitions
Ben Joyce88282362021-01-29 23:53:31850 # rather than assuming that it is always the same name.
Abhishek Arya64636af2018-05-04 14:42:13851 binary_paths = []
852 for target in targets:
853 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18854 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13855 binary_path += '.exe'
856
857 if os.path.exists(binary_path):
858 binary_paths.append(binary_path)
859 else:
860 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40861 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13862 os.path.basename(binary_path))
863
864 return binary_paths
865
866
Prakhara6418512023-05-22 17:17:45867def _GetCommandForWebTests(targets, arguments):
Abhishek Arya03911092018-05-21 16:42:35868 """Return command to run for blink web tests."""
Prakhara6418512023-05-22 17:17:45869 assert len(targets) == 1, "Only one wpt target can be run"
870 target = targets[0]
871 expected_profraw_file_name = os.extsep.join(
Nico Weber51e61c7d2023-12-12 17:32:46872 [target, '%2m', PROFRAW_FILE_EXTENSION])
Prakhara6418512023-05-22 17:17:45873 expected_profraw_file_path = os.path.join(
874 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
875 expected_profraw_file_name)
876
Dirk Pranke34c093a42021-03-25 19:19:05877 cpu_count = multiprocessing.cpu_count()
878 if sys.platform == 'win32':
Alison Gale4d9c2312024-04-26 19:15:24879 # TODO(crbug.com/40755900) - we can't use more than 56
Dirk Pranke34c093a42021-03-25 19:19:05880 # cores on Windows or Python3 may hang.
881 cpu_count = min(cpu_count, 56)
882 cpu_count = max(1, cpu_count // 2)
883
Abhishek Arya03911092018-05-21 16:42:35884 command_list = [
Abhishek Arya03911092018-05-21 16:42:35885 'third_party/blink/tools/run_web_tests.py',
886 '--additional-driver-flag=--no-sandbox',
Prakhara6418512023-05-22 17:17:45887 '--additional-env-var=LLVM_PROFILE_FILE=%s' % expected_profraw_file_path,
Dirk Pranke34c093a42021-03-25 19:19:05888 '--child-processes=%d' % cpu_count, '--disable-breakpad',
889 '--no-show-results', '--skip-failing-tests',
Weizhong Xia91b53362022-01-05 17:13:35890 '--target=%s' % os.path.basename(BUILD_DIR), '--timeout-ms=30000'
Abhishek Arya03911092018-05-21 16:42:35891 ]
892 if arguments.strip():
893 command_list.append(arguments)
894 return ' '.join(command_list)
895
896
Ben Joyce88282362021-01-29 23:53:31897def _GetBinaryPathsForAndroid(targets):
898 """Return binary paths used when running android tests."""
Alison Gale4d9c2312024-04-26 19:15:24899 # TODO(crbug.com/41423295): Implement approach that doesn't assume .so file is
Ben Joyce88282362021-01-29 23:53:31900 # based on the target's name.
901 android_binaries = set()
902 for target in targets:
903 so_library_path = os.path.join(BUILD_DIR, 'lib.unstripped',
904 'lib%s__library.so' % target)
905 if os.path.exists(so_library_path):
906 android_binaries.add(so_library_path)
907
908 return list(android_binaries)
909
910
Abhishek Arya03911092018-05-21 16:42:35911def _GetBinaryPathForWebTests():
912 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18913 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35914 if host_platform == 'win':
915 return os.path.join(BUILD_DIR, 'content_shell.exe')
916 elif host_platform == 'linux':
917 return os.path.join(BUILD_DIR, 'content_shell')
918 elif host_platform == 'mac':
919 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
920 'Content Shell')
921 else:
922 assert False, 'This platform is not supported for web tests.'
923
924
Arthur Wang06bd4df2024-05-20 16:42:06925def _GenerateCoverageReport(args, binary_paths, profdata_file_path,
926 absolute_filter_paths):
927 """Generate the coverage report in the supported format."""
928 assert args.format in [
929 'html', 'lcov', 'text'
930 ], ('%s is not a valid output format for "llvm-cov show/export". Only '
931 '"text", "html" and "lcov" formats are supported.' % (args.format))
932 logging.info('Generating code coverage report in %s (this can take a while '
933 'depending on size of target!).' % (args.format))
934 per_file_summary_data = _GeneratePerFileCoverageSummary(
935 binary_paths, profdata_file_path, absolute_filter_paths,
936 args.ignore_filename_regex)
937
938 if args.format == 'lcov':
939 _GeneratePerFileLineByLineCoverageInLcov(binary_paths, profdata_file_path,
940 absolute_filter_paths,
941 args.ignore_filename_regex)
942 return
943
944 _GeneratePerFileLineByLineCoverageInFormat(binary_paths, profdata_file_path,
945 absolute_filter_paths,
946 args.ignore_filename_regex,
947 args.format)
948 component_mappings = None
949 if not args.no_component_view:
950 component_mappings = json.load(urlopen(COMPONENT_MAPPING_URL))
951
952 # Call prepare here.
953 processor = coverage_utils.CoverageReportPostProcessor(
954 OUTPUT_DIR,
955 SRC_ROOT_PATH,
956 per_file_summary_data,
957 no_component_view=args.no_component_view,
958 no_file_view=args.no_file_view,
959 component_mappings=component_mappings)
960
961 if args.format == 'html':
962 processor.PrepareHtmlReport()
963
964
Abhishek Aryae5811afa2018-05-24 03:56:01965def _SetupOutputDir():
966 """Setup output directory."""
967 if os.path.exists(OUTPUT_DIR):
968 shutil.rmtree(OUTPUT_DIR)
969
970 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18971 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01972
973
Yuke Liaoabfbba42019-06-11 16:03:59974def _SetMacXcodePath():
975 """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
976 if sys.platform != 'darwin':
977 return
978
979 xcode_path = os.path.join(SRC_ROOT_PATH, 'build', 'mac_files', 'Xcode.app')
980 if os.path.exists(xcode_path):
981 os.environ['DEVELOPER_DIR'] = xcode_path
982
983
Yuke Liao506e8822017-12-04 16:52:54984def _ParseCommandArguments():
985 """Adds and parses relevant arguments for tool comands.
986
987 Returns:
988 A dictionary representing the arguments.
989 """
990 arg_parser = argparse.ArgumentParser()
991 arg_parser.usage = __doc__
992
Abhishek Arya1ec832c2017-12-05 18:06:59993 arg_parser.add_argument(
994 '-b',
995 '--build-dir',
996 type=str,
997 required=True,
998 help='The build directory, the path needs to be relative to the root of '
999 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:541000
Abhishek Arya1ec832c2017-12-05 18:06:591001 arg_parser.add_argument(
1002 '-o',
1003 '--output-dir',
1004 type=str,
1005 required=True,
1006 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:541007
Abhishek Arya1ec832c2017-12-05 18:06:591008 arg_parser.add_argument(
1009 '-c',
1010 '--command',
1011 action='append',
Abhishek Arya64636af2018-05-04 14:42:131012 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:591013 help='Commands used to run test targets, one test target needs one and '
1014 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:131015 'current working directory is the root of the checkout. This option is '
1016 'incompatible with -p/--profdata-file option.')
1017
1018 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:351019 '-wt',
1020 '--web-tests',
1021 nargs='?',
1022 type=str,
1023 const=' ',
1024 required=False,
1025 help='Run blink web tests. Support passing arguments to run_web_tests.py')
1026
1027 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:131028 '-p',
1029 '--profdata-file',
1030 type=str,
Prakharb8527802023-04-20 09:48:461031 action='append',
Abhishek Arya64636af2018-05-04 14:42:131032 required=False,
Prakharb8527802023-04-20 09:48:461033 help=
Arthur Wang06bd4df2024-05-20 16:42:061034 'Path(s) to profdata file(s) to use for generating code coverage reports.'
Prakharb8527802023-04-20 09:48:461035 'This can be useful if you generated the profdata file seperately in '
1036 'your own test harness. This option is ignored if run command(s) are '
1037 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:541038
Abhishek Arya1ec832c2017-12-05 18:06:591039 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:421040 '-f',
1041 '--filters',
1042 action='append',
Abhishek Arya16f059a2017-12-07 17:47:321043 required=False,
Yuke Liao66da1732017-12-05 22:19:421044 help='Directories or files to get code coverage for, and all files under '
1045 'the directories are included recursively.')
1046
1047 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:591048 '-i',
1049 '--ignore-filename-regex',
1050 type=str,
1051 help='Skip source code files with file paths that match the given '
1052 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
1053 'to exclude files in third_party/ and out/ folders from the report.')
1054
1055 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:321056 '--no-file-view',
1057 action='store_true',
1058 help='Don\'t generate the file view in the coverage report. When there '
1059 'are large number of html files, the file view becomes heavy and may '
1060 'cause the browser to freeze, and this argument comes handy.')
1061
1062 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:181063 '--no-component-view',
1064 action='store_true',
1065 help='Don\'t generate the component view in the coverage report.')
1066
1067 arg_parser.add_argument(
Arthur Wang06bd4df2024-05-20 16:42:061068 '--no-report',
1069 action='store_true',
1070 help='Don\'t generate the final coverage report. This option is '
1071 'incompatible with -p/--profdata-file, --format, --no-file-view, and'
1072 '--no-component-view option flags.')
1073
1074 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:401075 '--coverage-tools-dir',
1076 type=str,
1077 help='Path of the directory where LLVM coverage tools (llvm-cov, '
1078 'llvm-profdata) exist. This should be only needed if you are testing '
1079 'against a custom built clang revision. Otherwise, we pick coverage '
1080 'tools automatically from your current source checkout.')
1081
1082 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:591083 '-j',
1084 '--jobs',
1085 type=int,
1086 default=None,
1087 help='Run N jobs to build in parallel. If not specified, a default value '
Takuto Ikutae2188ad2024-06-06 09:27:231088 'will be derived based on CPUs and reclient/siso availability. Please '
1089 'refer to \'autoninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:541090
Abhishek Arya1ec832c2017-12-05 18:06:591091 arg_parser.add_argument(
Sahel Sharify38cabdc2020-01-16 00:40:011092 '--format',
1093 type=str,
1094 default='html',
Akekawit Jitprasertf9cb6622021-08-24 17:48:021095 help='Output format of the "llvm-cov show/export" command. The '
1096 'supported formats are "text", "html" and "lcov".')
Sahel Sharify38cabdc2020-01-16 00:40:011097
1098 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:101099 '-v',
1100 '--verbose',
1101 action='store_true',
1102 help='Prints additional output for diagnostics.')
1103
1104 arg_parser.add_argument(
1105 '-l', '--log_file', type=str, help='Redirects logs to a file.')
1106
1107 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:021108 'targets',
1109 nargs='+',
1110 help='The names of the test targets to run. If multiple run commands are '
1111 'specified using the -c/--command option, then the order of targets and '
1112 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:541113
1114 args = arg_parser.parse_args()
1115 return args
1116
1117
1118def Main():
1119 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:401120
Abhishek Arya64636af2018-05-04 14:42:131121 # Change directory to source root to aid in relative paths calculations.
1122 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:111123
pasthanaa4844112020-05-21 18:03:551124 # Setup coverage binaries even when script is called with empty params. This
1125 # is used by coverage bot for initial setup.
1126 if len(sys.argv) == 1:
Choongwoo Hanbd1aa952021-06-09 22:25:381127 subprocess.check_call([
Akekawit Jitprasert928671e2021-09-20 18:40:581128 sys.executable, 'tools/clang/scripts/update.py', '--package',
1129 'coverage_tools'
Choongwoo Hanbd1aa952021-06-09 22:25:381130 ])
pasthanaa4844112020-05-21 18:03:551131 print(__doc__)
1132 return
1133
Yuke Liao506e8822017-12-04 16:52:541134 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:181135 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:401136 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:131137
Yuke Liao506e8822017-12-04 16:52:541138 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:181139 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:011140
Yuke Liao506e8822017-12-04 16:52:541141 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:181142 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:541143
Abhishek Arya03911092018-05-21 16:42:351144 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:131145 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:351146 'provide prof-data file as input using -p/--profdata-file option OR '
1147 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:431148
Abhishek Arya64636af2018-05-04 14:42:131149 assert not args.command or (len(args.targets) == len(args.command)), (
1150 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:431151
Abhishek Arya1ec832c2017-12-05 18:06:591152 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:401153 'Build directory: "%s" doesn\'t exist. '
1154 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:131155
Yuke Liaoc60b2d02018-03-02 21:40:431156 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:541157 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:321158
1159 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:421160 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:321161 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:421162
Abhishek Aryae5811afa2018-05-24 03:56:011163 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:541164
Abhishek Arya03911092018-05-21 16:42:351165 # Get .profdata file and list of binary paths.
1166 if args.web_tests:
Prakhara6418512023-05-22 17:17:451167 commands = [_GetCommandForWebTests(args.targets, args.web_tests)]
Abhishek Arya03911092018-05-21 16:42:351168 profdata_file_path = _CreateCoverageProfileDataForTargets(
1169 args.targets, commands, args.jobs)
1170 binary_paths = [_GetBinaryPathForWebTests()]
1171 elif args.command:
1172 for i in range(len(args.command)):
1173 assert not 'run_web_tests.py' in args.command[i], (
1174 'run_web_tests.py is not supported via --command argument. '
1175 'Please use --run-web-tests argument instead.')
1176
Abhishek Arya64636af2018-05-04 14:42:131177 # A list of commands are provided. Run them to generate profdata file, and
1178 # create a list of binary paths from parsing commands.
1179 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1180 profdata_file_path = _CreateCoverageProfileDataForTargets(
1181 args.targets, args.command, args.jobs)
1182 binary_paths = [_GetBinaryPath(command) for command in args.command]
1183 else:
Julia Hansbrough57bd3fc2023-03-30 01:47:231184 # An input prof-data file(s) is already provided.
1185 if len(args.profdata_file) == 1:
1186 # If it's just one input file, use as-is.
Prakharf851c562023-05-23 19:32:401187 profdata_file_path = args.profdata_file[0]
Julia Hansbrough57bd3fc2023-03-30 01:47:231188 else:
1189 # Otherwise, there are multiple profdata files and we need to merge them.
Arthur Wang06bd4df2024-05-20 16:42:061190 profdata_file_path = _CreateCoverageProfileDataFromTargetProfDataFiles(
1191 args.profdata_file)
Julia Hansbrough57bd3fc2023-03-30 01:47:231192 # Since input prof-data files were provided, we only need to calculate the
1193 # binary paths from here.
Abhishek Arya64636af2018-05-04 14:42:131194 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331195
Erik Chen283b92c72019-07-22 16:37:391196 # If the checkout uses the hermetic xcode binaries, then otool must be
1197 # directly invoked. The indirection via /usr/bin/otool won't work unless
1198 # there's an actual system install of Xcode.
1199 otool_path = None
1200 if sys.platform == 'darwin':
1201 hermetic_otool_path = os.path.join(
1202 SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents',
1203 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin',
1204 'otool')
1205 if os.path.exists(hermetic_otool_path):
1206 otool_path = hermetic_otool_path
Ben Joyce88282362021-01-29 23:53:311207
1208 if _IsAndroid():
1209 binary_paths = _GetBinaryPathsForAndroid(args.targets)
1210 elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
Brent McBrideb25b177a42020-05-11 18:13:061211 binary_paths.extend(
1212 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
Abhishek Arya78120bc2018-05-07 20:53:541213
Arthur Wang06bd4df2024-05-20 16:42:061214 # Skip generating coverage summary for possible offline processing
1215 if args.no_report:
1216 logging.info('Skip generating coverage report due to --no-report flag.')
Akekawit Jitprasertf9cb6622021-08-24 17:48:021217 return
1218
Arthur Wang06bd4df2024-05-20 16:42:061219 _GenerateCoverageReport(args, binary_paths, profdata_file_path,
1220 absolute_filter_paths)
Yuke Liao506e8822017-12-04 16:52:541221
Abhishek Arya1ec832c2017-12-05 18:06:591222
Yuke Liao506e8822017-12-04 16:52:541223if __name__ == '__main__':
1224 sys.exit(Main())