blob: d25edf6f0d7fef8f96d675cd360ef683e394a2c3 [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
Abhishek Arya03911092018-05-21 16:42:3558 * Sample workflow for running Blink web tests:
59
Fabrice de Gans0b5511e72022-09-16 22:07:2060 vpython3 tools/code_coverage/coverage.py blink_tests \\
Abhishek Arya03911092018-05-21 16:42:3561 -wt -b out/coverage -o out/report -f third_party/blink
62
63 If you need to pass arguments to run_web_tests.py, use
64 -wt='arguments to run_web_tests.py e.g. test directories'
65
Abhishek Arya1ec832c2017-12-05 18:06:5966 For more options, please refer to tools/code_coverage/coverage.py -h.
Yuke Liao8e209fe82018-04-18 20:36:3867
68 For an overview of how code coverage works in Chromium, please refer to
John Palmerab8812a2021-05-21 17:03:4369 https://chromium.googlesource.com/chromium/src/+/main/docs/testing/code_coverage.md
Yuke Liao506e8822017-12-04 16:52:5470"""
71
72from __future__ import print_function
73
74import sys
75
76import argparse
Julia Hansbrough58aa7b0a2023-01-17 21:08:4177import glob
Yuke Liaoea228d02018-01-05 19:10:3378import json
Yuke Liao481d3482018-01-29 19:17:1079import logging
Abhishek Arya03911092018-05-21 16:42:3580import multiprocessing
Yuke Liao506e8822017-12-04 16:52:5481import os
Sajjad Mirza0b96e002020-11-10 19:32:5582import platform
Yuke Liaob2926832018-03-02 17:34:2983import re
84import shlex
Max Moroz025d8952018-05-03 16:33:3485import shutil
Yuke Liao506e8822017-12-04 16:52:5486import subprocess
Choongwoo Hanbd1aa952021-06-09 22:25:3887
Lei Zhang20e2ab752022-10-11 22:11:0088from urllib.request import urlopen
Choongwoo Hanbd1aa952021-06-09 22:25:3889
Abhishek Arya1ec832c2017-12-05 18:06:5990sys.path.append(
91 os.path.join(
Yuke Liaoea228d02018-01-05 19:10:3392 os.path.dirname(__file__), os.path.pardir, os.path.pardir,
93 'third_party'))
Yuke Liaoea228d02018-01-05 19:10:3394from collections import defaultdict
95
Max Moroz1de68d72018-08-21 13:38:1896import coverage_utils
97
Yuke Liao082e99632018-05-18 15:40:4098# Absolute path to the code coverage tools binary. These paths can be
99# overwritten by user specified coverage tool paths.
pasthanab37d5bfd2020-05-28 12:18:31100# Absolute path to the root of the checkout.
101SRC_ROOT_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)),
102 os.path.pardir, os.path.pardir)
103LLVM_BIN_DIR = os.path.join(
104 os.path.join(SRC_ROOT_PATH, 'third_party', 'llvm-build', 'Release+Asserts'),
105 'bin')
Abhishek Arya1c97ea542018-05-10 03:53:19106LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
107LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:54108
Abhishek Arya03911092018-05-21 16:42:35109
Yuke Liao506e8822017-12-04 16:52:54110# Build directory, the value is parsed from command line arguments.
111BUILD_DIR = None
112
113# Output directory for generated artifacts, the value is parsed from command
114# line arguemnts.
115OUTPUT_DIR = None
116
Yuke Liao506e8822017-12-04 16:52:54117# Name of the file extension for profraw data files.
118PROFRAW_FILE_EXTENSION = 'profraw'
119
120# Name of the final profdata file, and this file needs to be passed to
121# "llvm-cov" command in order to call "llvm-cov show" to inspect the
122# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48123PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
124
125# Name of the file with summary information generated by llvm-cov export.
126SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54127
Akekawit Jitprasertf9cb6622021-08-24 17:48:02128# Name of the coverage file in lcov format generated by llvm-cov export.
129LCOV_FILE_NAME = os.extsep.join(['coverage', 'lcov'])
130
Yuke Liao506e8822017-12-04 16:52:54131# Build arg required for generating code coverage data.
132CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
133
Max Moroz7c5354f2018-05-06 00:03:48134LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37135
136# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19137COMPONENT_MAPPING_URL = (
138 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37139
Yuke Liao80afff32018-03-07 01:26:20140# Caches the results returned by _GetBuildArgs, don't use this variable
141# directly, call _GetBuildArgs instead.
142_BUILD_ARGS = None
143
Abhishek Aryac19bc5ef2018-05-04 22:10:02144# Retry failed merges.
145MERGE_RETRIES = 3
146
Abhishek Aryad35de7e2018-05-10 22:23:04147# Message to guide user to file a bug when everything else fails.
148FILE_BUG_MESSAGE = (
149 'If it persists, please file a bug with the command you used, git revision '
150 'and args.gn config here: '
151 'https://bugs.chromium.org/p/chromium/issues/entry?'
Yuke Liao03c644072019-07-30 18:33:40152 'components=Infra%3ETest%3ECodeCoverage')
Abhishek Aryad35de7e2018-05-10 22:23:04153
Abhishek Aryabd0655d2018-05-21 19:55:24154# String to replace with actual llvm profile path.
155LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
156
Yuke Liao082e99632018-05-18 15:40:40157def _ConfigureLLVMCoverageTools(args):
158 """Configures llvm coverage tools."""
159 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18160 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40161 global LLVM_COV_PATH
162 global LLVM_PROFDATA_PATH
163 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
164 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
165 else:
Choongwoo Hanbd1aa952021-06-09 22:25:38166 subprocess.check_call([
Akekawit Jitprasert928671e2021-09-20 18:40:58167 sys.executable, 'tools/clang/scripts/update.py', '--package',
168 'coverage_tools'
Choongwoo Hanbd1aa952021-06-09 22:25:38169 ])
Brent McBrideb25b177a42020-05-11 18:13:06170
171 if coverage_utils.GetHostPlatform() == 'win':
172 LLVM_COV_PATH += '.exe'
173 LLVM_PROFDATA_PATH += '.exe'
Yuke Liao082e99632018-05-18 15:40:40174
175 coverage_tools_exist = (
176 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
177 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
178 'both \'%s\' and \'%s\' exist.') % (
179 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
180
Abhishek Arya2f261182019-04-24 17:06:45181
Abhishek Arya1c97ea542018-05-10 03:53:19182def _GetPathWithLLVMSymbolizerDir():
183 """Add llvm-symbolizer directory to path for symbolized stacks."""
184 path = os.getenv('PATH')
185 dirs = path.split(os.pathsep)
186 if LLVM_BIN_DIR in dirs:
187 return path
188
189 return path + os.pathsep + LLVM_BIN_DIR
190
191
Yuke Liaoc60b2d02018-03-02 21:40:43192def _GetTargetOS():
193 """Returns the target os specified in args.gn file.
194
195 Returns an empty string is target_os is not specified.
196 """
Yuke Liao80afff32018-03-07 01:26:20197 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43198 return build_args['target_os'] if 'target_os' in build_args else ''
199
200
Ben Joyce88282362021-01-29 23:53:31201def _IsAndroid():
202 """Returns true if the target_os specified in args.gn file is android"""
203 return _GetTargetOS() == 'android'
204
205
Yuke Liaob2926832018-03-02 17:34:29206def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10207 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43208 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10209
210
Sahel Sharify38cabdc2020-01-16 00:40:01211def _GeneratePerFileLineByLineCoverageInFormat(binary_paths, profdata_file_path,
212 filters, ignore_filename_regex,
213 output_format):
214 """Generates per file line-by-line coverage in html or text using
215 'llvm-cov show'.
Yuke Liao506e8822017-12-04 16:52:54216
Sahel Sharify38cabdc2020-01-16 00:40:01217 For a file with absolute path /a/b/x.cc, a html/txt report is generated as:
218 OUTPUT_DIR/coverage/a/b/x.cc.[html|txt]. For html format, an index html file
219 is also generated as: OUTPUT_DIR/index.html.
Yuke Liao506e8822017-12-04 16:52:54220
221 Args:
222 binary_paths: A list of paths to the instrumented binaries.
223 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42224 filters: A list of directories and files to get coverage for.
Sahel Sharify38cabdc2020-01-16 00:40:01225 ignore_filename_regex: A regular expression for skipping source code files
226 with certain file paths.
227 output_format: The output format of generated report files.
Yuke Liao506e8822017-12-04 16:52:54228 """
Yuke Liao506e8822017-12-04 16:52:54229 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
230 # [[-object BIN]] [SOURCES]
231 # NOTE: For object files, the first one is specified as a positional argument,
232 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10233 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40234 '"llvm-cov show" command.')
Sahel Sharify38cabdc2020-01-16 00:40:01235
Abhishek Arya1ec832c2017-12-05 18:06:59236 subprocess_cmd = [
Sahel Sharify38cabdc2020-01-16 00:40:01237 LLVM_COV_PATH, 'show', '-format={}'.format(output_format),
Choongwoo Han56752522021-06-10 17:38:34238 '-compilation-dir={}'.format(BUILD_DIR),
Abhishek Arya1ec832c2017-12-05 18:06:59239 '-output-dir={}'.format(OUTPUT_DIR),
240 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
241 ]
242 subprocess_cmd.extend(
243 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29244 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18245 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17246 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42247 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59248 if ignore_filename_regex:
249 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
250
Yuke Liao506e8822017-12-04 16:52:54251 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34252
Abhishek Aryafb70b532018-05-06 17:47:40253 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54254
255
Lei Zhang42a5b51a2022-03-07 19:16:16256def _GeneratePerFileLineByLineCoverageInLcov(binary_paths, profdata_file_path,
257 filters, ignore_filename_regex):
Akekawit Jitprasertf9cb6622021-08-24 17:48:02258 """Generates per file line-by-line coverage using "llvm-cov export".
259
260 Args:
261 binary_paths: A list of paths to the instrumented binaries.
262 profdata_file_path: A path to the profdata file.
263 filters: A list of directories and files to get coverage for.
264 ignore_filename_regex: A regular expression for skipping source code files
265 with certain file paths.
266 """
267 logging.debug('Generating per file line by line coverage reports using '
268 '"llvm-cov export" command.')
269 for path in binary_paths:
270 if not os.path.exists(path):
271 logging.error("Binary %s does not exist", path)
272 subprocess_cmd = [
273 LLVM_COV_PATH, 'export', '-format=lcov',
274 '-instr-profile=' + profdata_file_path, binary_paths[0]
275 ]
276 subprocess_cmd.extend(
277 ['-object=' + binary_path for binary_path in binary_paths[1:]])
278 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
279 subprocess_cmd.extend(filters)
280 if ignore_filename_regex:
281 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
282
283 # Write output on the disk to be used by code coverage bot.
284 with open(_GetLcovFilePath(), 'w') as f:
285 subprocess.check_call(subprocess_cmd, stdout=f)
286
287 logging.debug('Finished running "llvm-cov export" command.')
288
289
Max Moroz7c5354f2018-05-06 00:03:48290def _GetLogsDirectoryPath():
291 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18292 return os.path.join(
293 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48294
295
296def _GetProfdataFilePath():
297 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18298 return os.path.join(
299 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
300 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48301
302
303def _GetSummaryFilePath():
304 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18305 return os.path.join(
306 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
307 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33308
309
Akekawit Jitprasertf9cb6622021-08-24 17:48:02310def _GetLcovFilePath():
311 """The LCOV file that contains coverage data written by llvm-cov export."""
312 return os.path.join(
313 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
314 LCOV_FILE_NAME)
315
316
Yuke Liao506e8822017-12-04 16:52:54317def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
318 """Builds and runs target to generate the coverage profile data.
319
320 Args:
321 targets: A list of targets to build with coverage instrumentation.
322 commands: A list of commands used to run the targets.
323 jobs_count: Number of jobs to run in parallel for building. If None, a
324 default value is derived based on CPUs availability.
325
326 Returns:
327 A relative path to the generated profdata file.
328 """
329 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02330 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59331 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02332 coverage_profdata_file_path = (
333 _CreateCoverageProfileDataFromTargetProfDataFiles(
334 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54335
Abhishek Aryac19bc5ef2018-05-04 22:10:02336 for target_profdata_file_path in target_profdata_file_paths:
337 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52338
Abhishek Aryac19bc5ef2018-05-04 22:10:02339 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54340
341
342def _BuildTargets(targets, jobs_count):
343 """Builds target with Clang coverage instrumentation.
344
345 This function requires current working directory to be the root of checkout.
346
347 Args:
348 targets: A list of targets to build with coverage instrumentation.
349 jobs_count: Number of jobs to run in parallel for compilation. If None, a
350 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54351 """
Abhishek Aryafb70b532018-05-06 17:47:40352 logging.info('Building %s.', str(targets))
Brent McBrideb25b177a42020-05-11 18:13:06353 autoninja = 'autoninja'
354 if coverage_utils.GetHostPlatform() == 'win':
355 autoninja += '.bat'
Yuke Liao506e8822017-12-04 16:52:54356
Brent McBrideb25b177a42020-05-11 18:13:06357 subprocess_cmd = [autoninja, '-C', BUILD_DIR]
Yuke Liao506e8822017-12-04 16:52:54358 if jobs_count is not None:
359 subprocess_cmd.append('-j' + str(jobs_count))
360
361 subprocess_cmd.extend(targets)
362 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40363 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54364
365
Abhishek Aryac19bc5ef2018-05-04 22:10:02366def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54367 """Runs commands and returns the relative paths to the profraw data files.
368
369 Args:
370 targets: A list of targets built with coverage instrumentation.
371 commands: A list of commands used to run the targets.
372
373 Returns:
374 A list of relative paths to the generated profraw data files.
375 """
Abhishek Aryafb70b532018-05-06 17:47:40376 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10377
Yuke Liao506e8822017-12-04 16:52:54378 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18379 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
380 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54381 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18382 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48383
384 # Ensure that logs directory exists.
385 if not os.path.exists(_GetLogsDirectoryPath()):
386 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54387
Abhishek Aryac19bc5ef2018-05-04 22:10:02388 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10389
Yuke Liaod4a9865202018-01-12 23:17:52390 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54391 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48392 output_file_name = os.extsep.join([target + '_output', 'log'])
393 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10394
Abhishek Aryac19bc5ef2018-05-04 22:10:02395 profdata_file_path = None
Prakhar65d63832021-06-16 23:01:37396 for _ in range(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40397 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02398 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10399
Abhishek Aryac19bc5ef2018-05-04 22:10:02400 if _IsIOSCommand(command):
401 # On iOS platform, due to lack of write permissions, profraw files are
402 # generated outside of the OUTPUT_DIR, and the exact paths are contained
403 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35404 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02405 else:
406 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35407 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02408
409 profraw_file_paths = []
410 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57411 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Ben Joyce88282362021-01-29 23:53:31412 elif _IsAndroid():
413 android_coverage_dir = os.path.join(BUILD_DIR, 'coverage')
414 for r, _, files in os.walk(android_coverage_dir):
415 for f in files:
416 if f.endswith(PROFRAW_FILE_EXTENSION):
417 profraw_file_paths.append(os.path.join(r, f))
Abhishek Aryac19bc5ef2018-05-04 22:10:02418 else:
Max Moroz1de68d72018-08-21 13:38:18419 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02420 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48421 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18422 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02423
424 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40425 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04426 'please make sure the binary exists, is properly instrumented and '
427 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02428
Yuke Liao9c2c70b2018-05-23 15:37:57429 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18430 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
431 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57432
Abhishek Aryac19bc5ef2018-05-04 22:10:02433 try:
434 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
435 target, profraw_file_paths)
436 break
437 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04438 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02439 finally:
440 # Remove profraw files now so that they are not used in next iteration.
441 for profraw_file_path in profraw_file_paths:
442 os.remove(profraw_file_path)
443
444 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04445 'Failed to merge target "%s" profraw files after %d retries. %s' %
446 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02447 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54448
Abhishek Aryafb70b532018-05-06 17:47:40449 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10450
Abhishek Aryac19bc5ef2018-05-04 22:10:02451 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54452
453
Abhishek Arya03911092018-05-21 16:42:35454def _GetEnvironmentVars(profraw_file_path):
455 """Return environment vars for subprocess, given a profraw file path."""
456 env = os.environ.copy()
457 env.update({
458 'LLVM_PROFILE_FILE': profraw_file_path,
459 'PATH': _GetPathWithLLVMSymbolizerDir()
460 })
461 return env
462
463
Sajjad Mirza0b96e002020-11-10 19:32:55464def _SplitCommand(command):
465 """Split a command string into parts in a platform-specific way."""
466 if coverage_utils.GetHostPlatform() == 'win':
467 return command.split()
Julia Hansbrough58aa7b0a2023-01-17 21:08:41468 split_command = shlex.split(command)
469 # Python's subprocess does not do glob expansion, so we expand it out here.
470 new_command = []
471 for item in split_command:
472 if '*' in item:
473 files = glob.glob(item)
474 for file in files:
475 new_command.append(file)
476 else:
477 new_command.append(item)
478 return new_command
Sajjad Mirza0b96e002020-11-10 19:32:55479
480
Abhishek Arya03911092018-05-21 16:42:35481def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10482 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52483 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01484 #
Max Morozd73e45f2018-04-24 18:32:47485 # "%p" expands out to the process ID. It's not used by this scripts due to:
486 # 1) If a target program spawns too many processess, it may exhaust all disk
487 # space available. For example, unit_tests writes thousands of .profraw
488 # files each of size 1GB+.
489 # 2) If a target binary uses shared libraries, coverage profile data for them
490 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01491 #
Yuke Liaod4a9865202018-01-12 23:17:52492 # "%Nm" expands out to the instrumented binary's signature. When this pattern
493 # is specified, the runtime creates a pool of N raw profiles which are used
494 # for on-line profile merging. The runtime takes care of selecting a raw
495 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52496 # N must be between 1 and 9. The merge pool specifier can only occur once per
497 # filename pattern.
498 #
Max Morozd73e45f2018-04-24 18:32:47499 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01500 #
Max Morozd73e45f2018-04-24 18:32:47501 # For other cases, "%4m" is chosen as it creates some level of parallelism,
502 # but it's not too big to consume too much computing resource or disk space.
503 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59504 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01505 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18506 expected_profraw_file_path = os.path.join(
507 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
508 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24509 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
510 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54511
Yuke Liaoa0c8c2f2018-02-28 20:14:10512 try:
Max Moroz7c5354f2018-05-06 00:03:48513 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35514 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55515 subprocess.check_call(_SplitCommand(command),
516 stdout=output_file_handle,
517 stderr=subprocess.STDOUT,
518 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10519 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35520 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10521
Abhishek Arya03911092018-05-21 16:42:35522 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10523
524
Yuke Liao27349c92018-03-22 21:10:01525def _IsFuzzerTarget(target):
526 """Returns true if the target is a fuzzer target."""
527 build_args = _GetBuildArgs()
528 use_libfuzzer = ('use_libfuzzer' in build_args and
529 build_args['use_libfuzzer'] == 'true')
Adrian Taylor9470000f2023-03-10 16:18:25530 use_centipede = ('use_centipede' in build_args
531 and build_args['use_centipede'] == 'true')
532 return (use_libfuzzer or use_centipede) and target.endswith('_fuzzer')
Yuke Liao27349c92018-03-22 21:10:01533
534
Abhishek Arya03911092018-05-21 16:42:35535def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10536 """Runs a single iOS command and generates a profraw data file.
537
538 iOS application doesn't have write access to folders outside of the app, so
539 it's impossible to instruct the app to flush the profraw data file to the
540 desired location. The profraw data file will be generated somewhere within the
541 application's Documents folder, and the full path can be obtained by parsing
542 the output.
543 """
Yuke Liaob2926832018-03-02 17:34:29544 assert _IsIOSCommand(command)
545
546 # After running tests, iossim generates a profraw data file, it won't be
547 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
548 # checkout.
549 iossim_profraw_file_path = os.path.join(
550 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24551 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
552 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10553
554 try:
Abhishek Arya03911092018-05-21 16:42:35555 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55556 subprocess.check_call(_SplitCommand(command),
557 stdout=output_file_handle,
558 stderr=subprocess.STDOUT,
559 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10560 except subprocess.CalledProcessError as e:
561 # iossim emits non-zero return code even if tests run successfully, so
562 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35563 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10564
Abhishek Arya03911092018-05-21 16:42:35565 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10566
567
568def _GetProfrawDataFileByParsingOutput(output):
569 """Returns the path to the profraw data file obtained by parsing the output.
570
571 The output of running the test target has no format, but it is guaranteed to
572 have a single line containing the path to the generated profraw data file.
573 NOTE: This should only be called when target os is iOS.
574 """
Yuke Liaob2926832018-03-02 17:34:29575 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10576
Yuke Liaob2926832018-03-02 17:34:29577 output_by_lines = ''.join(output).splitlines()
578 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10579
580 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29581 result = profraw_file_pattern.match(line)
582 if result:
583 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10584
585 assert False, ('No profraw data file was generated, did you call '
586 'coverage_util::ConfigureCoverageReportPath() in test setup? '
587 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54588
589
Abhishek Aryac19bc5ef2018-05-04 22:10:02590def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
591 """Returns a relative path to coverage profdata file by merging target
592 profdata files.
Yuke Liao506e8822017-12-04 16:52:54593
594 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02595 profdata_file_paths: A list of relative paths to the profdata data files
596 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54597
598 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02599 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54600
601 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02602 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54603 """
Abhishek Aryafb70b532018-05-06 17:47:40604 logging.info('Creating the coverage profile data file.')
605 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48606 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54607 try:
Abhishek Arya1ec832c2017-12-05 18:06:59608 subprocess_cmd = [
609 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
610 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02611 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01612
613 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18614 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02615 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04616 logging.error(
617 'Failed to merge target profdata files to create coverage profdata. %s',
618 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02619 raise error
620
Abhishek Aryafb70b532018-05-06 17:47:40621 logging.debug('Finished merging target profdata files.')
622 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02623 profdata_file_path)
624 return profdata_file_path
625
626
627def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
628 """Returns a relative path to target profdata file by merging target
629 profraw files.
630
631 Args:
632 profraw_file_paths: A list of relative paths to the profdata data files
633 that are to be merged.
634
635 Returns:
636 A relative path to the merged coverage profdata file.
637
638 Raises:
639 CalledProcessError: An error occurred merging profdata files.
640 """
Abhishek Aryafb70b532018-05-06 17:47:40641 logging.info('Creating target profile data file.')
642 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02643 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
644
645 try:
646 subprocess_cmd = [
647 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
648 ]
Yuke Liao506e8822017-12-04 16:52:54649 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01650 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18651 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54652 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04653 logging.error(
654 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54655 raise error
656
Abhishek Aryafb70b532018-05-06 17:47:40657 logging.debug('Finished merging target profraw files.')
658 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10659 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54660 return profdata_file_path
661
662
Yuke Liao0e4c8682018-04-18 21:06:59663def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
664 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33665 """Generates per file coverage summary using "llvm-cov export" command."""
666 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
667 # [[-object BIN]] [SOURCES].
668 # NOTE: For object files, the first one is specified as a positional argument,
669 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10670 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40671 'export -summary-only" command.')
Sajjad Mirza07f52332020-11-11 01:50:47672 for path in binary_paths:
673 if not os.path.exists(path):
674 logging.error("Binary %s does not exist", path)
Yuke Liaoea228d02018-01-05 19:10:33675 subprocess_cmd = [
676 LLVM_COV_PATH, 'export', '-summary-only',
Choongwoo Han56752522021-06-10 17:38:34677 '-compilation-dir={}'.format(BUILD_DIR),
Yuke Liaoea228d02018-01-05 19:10:33678 '-instr-profile=' + profdata_file_path, binary_paths[0]
679 ]
680 subprocess_cmd.extend(
681 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29682 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33683 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59684 if ignore_filename_regex:
685 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33686
Max Moroz7c5354f2018-05-06 00:03:48687 export_output = subprocess.check_output(subprocess_cmd)
688
689 # Write output on the disk to be used by code coverage bot.
Prakhar65d63832021-06-16 23:01:37690 with open(_GetSummaryFilePath(), 'wb') as f:
Max Moroz7c5354f2018-05-06 00:03:48691 f.write(export_output)
692
Max Moroz1de68d72018-08-21 13:38:18693 return export_output
Yuke Liaoea228d02018-01-05 19:10:33694
695
Yuke Liaob2926832018-03-02 17:34:29696def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
697 """Appends -arch arguments to the command list if it's ios platform.
698
699 iOS binaries are universal binaries, and require specifying the architecture
700 to use, and one architecture needs to be specified for each binary.
701 """
702 if _IsIOS():
703 cmd_list.extend(['-arch=x86_64'] * num_archs)
704
705
Yuke Liao506e8822017-12-04 16:52:54706def _GetBinaryPath(command):
707 """Returns a relative path to the binary to be run by the command.
708
Yuke Liao545db322018-02-15 17:12:01709 Currently, following types of commands are supported (e.g. url_unittests):
710 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
711 2. Use xvfb.
712 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
713 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37714 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
715 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10716 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37717 <iossim_arguments> -c <app_arguments>
718 out/Coverage-iphonesimulator/url_unittests.app"
719
Yuke Liao506e8822017-12-04 16:52:54720 Args:
721 command: A command used to run a target.
722
723 Returns:
724 A relative path to the binary.
725 """
Yuke Liao545db322018-02-15 17:12:01726 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
727
Sajjad Mirza0b96e002020-11-10 19:32:55728 command_parts = _SplitCommand(command)
Yuke Liao545db322018-02-15 17:12:01729 if os.path.basename(command_parts[0]) == 'python':
730 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40731 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01732 return command_parts[2]
733
734 if os.path.basename(command_parts[0]) == xvfb_script_name:
735 return command_parts[1]
736
Yuke Liaob2926832018-03-02 17:34:29737 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10738 # For a given application bundle, the binary resides in the bundle and has
739 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02740 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10741 app_name = os.path.splitext(os.path.basename(app_path))[0]
742 return os.path.join(app_path, app_name)
743
Sajjad Mirza07f52332020-11-11 01:50:47744 if coverage_utils.GetHostPlatform() == 'win' \
745 and not command_parts[0].endswith('.exe'):
746 return command_parts[0] + '.exe'
747
Yuke Liaob2926832018-03-02 17:34:29748 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54749
750
Yuke Liaob2926832018-03-02 17:34:29751def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10752 """Returns true if command is used to run tests on iOS platform."""
Sajjad Mirza0b96e002020-11-10 19:32:55753 return os.path.basename(_SplitCommand(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10754
755
Yuke Liao95d13d72017-12-07 18:18:50756def _VerifyTargetExecutablesAreInBuildDirectory(commands):
757 """Verifies that the target executables specified in the commands are inside
758 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54759 for command in commands:
760 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18761 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35762 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50763 'Target executable "%s" in command: "%s" is outside of '
764 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54765
766
767def _ValidateBuildingWithClangCoverage():
768 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20769 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54770
771 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
772 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59773 assert False, ('\'{} = true\' is required in args.gn.'
774 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54775
776
Yuke Liaoc60b2d02018-03-02 21:40:43777def _ValidateCurrentPlatformIsSupported():
778 """Asserts that this script suports running on the current platform"""
779 target_os = _GetTargetOS()
780 if target_os:
781 current_platform = target_os
782 else:
Max Moroz1de68d72018-08-21 13:38:18783 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43784
Ben Joyce88282362021-01-29 23:53:31785 supported_platforms = ['android', 'chromeos', 'ios', 'linux', 'mac', 'win']
786 assert current_platform in supported_platforms, ('Coverage is only'
787 'supported on %s' %
788 supported_platforms)
Yuke Liaoc60b2d02018-03-02 21:40:43789
790
Yuke Liao80afff32018-03-07 01:26:20791def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54792 """Parses args.gn file and returns results as a dictionary.
793
794 Returns:
795 A dictionary representing the build args.
796 """
Yuke Liao80afff32018-03-07 01:26:20797 global _BUILD_ARGS
798 if _BUILD_ARGS is not None:
799 return _BUILD_ARGS
800
801 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54802 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
803 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
804 'missing args.gn file.' % BUILD_DIR)
805 with open(build_args_path) as build_args_file:
806 build_args_lines = build_args_file.readlines()
807
Yuke Liao506e8822017-12-04 16:52:54808 for build_arg_line in build_args_lines:
809 build_arg_without_comments = build_arg_line.split('#')[0]
810 key_value_pair = build_arg_without_comments.split('=')
811 if len(key_value_pair) != 2:
812 continue
813
814 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43815
816 # Values are wrapped within a pair of double-quotes, so remove the leading
817 # and trailing double-quotes.
818 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20819 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54820
Yuke Liao80afff32018-03-07 01:26:20821 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54822
823
Abhishek Arya16f059a2017-12-07 17:47:32824def _VerifyPathsAndReturnAbsolutes(paths):
825 """Verifies that the paths specified in |paths| exist and returns absolute
826 versions.
Yuke Liao66da1732017-12-05 22:19:42827
828 Args:
829 paths: A list of files or directories.
830 """
Abhishek Arya16f059a2017-12-07 17:47:32831 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42832 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32833 absolute_path = os.path.join(SRC_ROOT_PATH, path)
834 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
835
836 absolute_paths.append(absolute_path)
837
838 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42839
840
Abhishek Arya64636af2018-05-04 14:42:13841def _GetBinaryPathsFromTargets(targets, build_dir):
842 """Return binary paths from target names."""
Ben Joyce88282362021-01-29 23:53:31843 # TODO(crbug.com/899974): Derive output binary from target build definitions
844 # rather than assuming that it is always the same name.
Abhishek Arya64636af2018-05-04 14:42:13845 binary_paths = []
846 for target in targets:
847 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18848 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13849 binary_path += '.exe'
850
851 if os.path.exists(binary_path):
852 binary_paths.append(binary_path)
853 else:
854 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40855 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13856 os.path.basename(binary_path))
857
858 return binary_paths
859
860
Abhishek Arya03911092018-05-21 16:42:35861def _GetCommandForWebTests(arguments):
862 """Return command to run for blink web tests."""
Dirk Pranke34c093a42021-03-25 19:19:05863 cpu_count = multiprocessing.cpu_count()
864 if sys.platform == 'win32':
865 # TODO(crbug.com/1190269) - we can't use more than 56
866 # cores on Windows or Python3 may hang.
867 cpu_count = min(cpu_count, 56)
868 cpu_count = max(1, cpu_count // 2)
869
Abhishek Arya03911092018-05-21 16:42:35870 command_list = [
Abhishek Arya03911092018-05-21 16:42:35871 'third_party/blink/tools/run_web_tests.py',
872 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24873 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Robert Ma339fc472018-12-14 21:22:42874 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Dirk Pranke34c093a42021-03-25 19:19:05875 '--child-processes=%d' % cpu_count, '--disable-breakpad',
876 '--no-show-results', '--skip-failing-tests',
Weizhong Xia91b53362022-01-05 17:13:35877 '--target=%s' % os.path.basename(BUILD_DIR), '--timeout-ms=30000'
Abhishek Arya03911092018-05-21 16:42:35878 ]
879 if arguments.strip():
880 command_list.append(arguments)
881 return ' '.join(command_list)
882
883
Ben Joyce88282362021-01-29 23:53:31884def _GetBinaryPathsForAndroid(targets):
885 """Return binary paths used when running android tests."""
886 # TODO(crbug.com/899974): Implement approach that doesn't assume .so file is
887 # based on the target's name.
888 android_binaries = set()
889 for target in targets:
890 so_library_path = os.path.join(BUILD_DIR, 'lib.unstripped',
891 'lib%s__library.so' % target)
892 if os.path.exists(so_library_path):
893 android_binaries.add(so_library_path)
894
895 return list(android_binaries)
896
897
Abhishek Arya03911092018-05-21 16:42:35898def _GetBinaryPathForWebTests():
899 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18900 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35901 if host_platform == 'win':
902 return os.path.join(BUILD_DIR, 'content_shell.exe')
903 elif host_platform == 'linux':
904 return os.path.join(BUILD_DIR, 'content_shell')
905 elif host_platform == 'mac':
906 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
907 'Content Shell')
908 else:
909 assert False, 'This platform is not supported for web tests.'
910
911
Abhishek Aryae5811afa2018-05-24 03:56:01912def _SetupOutputDir():
913 """Setup output directory."""
914 if os.path.exists(OUTPUT_DIR):
915 shutil.rmtree(OUTPUT_DIR)
916
917 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18918 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01919
920
Yuke Liaoabfbba42019-06-11 16:03:59921def _SetMacXcodePath():
922 """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
923 if sys.platform != 'darwin':
924 return
925
926 xcode_path = os.path.join(SRC_ROOT_PATH, 'build', 'mac_files', 'Xcode.app')
927 if os.path.exists(xcode_path):
928 os.environ['DEVELOPER_DIR'] = xcode_path
929
930
Yuke Liao506e8822017-12-04 16:52:54931def _ParseCommandArguments():
932 """Adds and parses relevant arguments for tool comands.
933
934 Returns:
935 A dictionary representing the arguments.
936 """
937 arg_parser = argparse.ArgumentParser()
938 arg_parser.usage = __doc__
939
Abhishek Arya1ec832c2017-12-05 18:06:59940 arg_parser.add_argument(
941 '-b',
942 '--build-dir',
943 type=str,
944 required=True,
945 help='The build directory, the path needs to be relative to the root of '
946 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54947
Abhishek Arya1ec832c2017-12-05 18:06:59948 arg_parser.add_argument(
949 '-o',
950 '--output-dir',
951 type=str,
952 required=True,
953 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54954
Abhishek Arya1ec832c2017-12-05 18:06:59955 arg_parser.add_argument(
956 '-c',
957 '--command',
958 action='append',
Abhishek Arya64636af2018-05-04 14:42:13959 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59960 help='Commands used to run test targets, one test target needs one and '
961 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13962 'current working directory is the root of the checkout. This option is '
963 'incompatible with -p/--profdata-file option.')
964
965 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35966 '-wt',
967 '--web-tests',
968 nargs='?',
969 type=str,
970 const=' ',
971 required=False,
972 help='Run blink web tests. Support passing arguments to run_web_tests.py')
973
974 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13975 '-p',
976 '--profdata-file',
977 type=str,
Julia Hansbrough57bd3fc2023-03-30 01:47:23978 action='append'
Abhishek Arya64636af2018-05-04 14:42:13979 required=False,
Julia Hansbrough57bd3fc2023-03-30 01:47:23980 help='Path(s) to profdata file(s) to use for generating code coverage reports. '
981 'This can be useful if you generated the profdata file seperately in '
982 'your own test harness. This option is ignored if run command(s) are '
983 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54984
Abhishek Arya1ec832c2017-12-05 18:06:59985 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42986 '-f',
987 '--filters',
988 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32989 required=False,
Yuke Liao66da1732017-12-05 22:19:42990 help='Directories or files to get code coverage for, and all files under '
991 'the directories are included recursively.')
992
993 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59994 '-i',
995 '--ignore-filename-regex',
996 type=str,
997 help='Skip source code files with file paths that match the given '
998 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
999 'to exclude files in third_party/ and out/ folders from the report.')
1000
1001 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:321002 '--no-file-view',
1003 action='store_true',
1004 help='Don\'t generate the file view in the coverage report. When there '
1005 'are large number of html files, the file view becomes heavy and may '
1006 'cause the browser to freeze, and this argument comes handy.')
1007
1008 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:181009 '--no-component-view',
1010 action='store_true',
1011 help='Don\'t generate the component view in the coverage report.')
1012
1013 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:401014 '--coverage-tools-dir',
1015 type=str,
1016 help='Path of the directory where LLVM coverage tools (llvm-cov, '
1017 'llvm-profdata) exist. This should be only needed if you are testing '
1018 'against a custom built clang revision. Otherwise, we pick coverage '
1019 'tools automatically from your current source checkout.')
1020
1021 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:591022 '-j',
1023 '--jobs',
1024 type=int,
1025 default=None,
1026 help='Run N jobs to build in parallel. If not specified, a default value '
Max Moroz06576292019-01-03 19:22:521027 'will be derived based on CPUs and goma availability. Please refer to '
1028 '\'autoninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:541029
Abhishek Arya1ec832c2017-12-05 18:06:591030 arg_parser.add_argument(
Sahel Sharify38cabdc2020-01-16 00:40:011031 '--format',
1032 type=str,
1033 default='html',
Akekawit Jitprasertf9cb6622021-08-24 17:48:021034 help='Output format of the "llvm-cov show/export" command. The '
1035 'supported formats are "text", "html" and "lcov".')
Sahel Sharify38cabdc2020-01-16 00:40:011036
1037 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:101038 '-v',
1039 '--verbose',
1040 action='store_true',
1041 help='Prints additional output for diagnostics.')
1042
1043 arg_parser.add_argument(
1044 '-l', '--log_file', type=str, help='Redirects logs to a file.')
1045
1046 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:021047 'targets',
1048 nargs='+',
1049 help='The names of the test targets to run. If multiple run commands are '
1050 'specified using the -c/--command option, then the order of targets and '
1051 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:541052
1053 args = arg_parser.parse_args()
1054 return args
1055
1056
1057def Main():
1058 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:401059
Abhishek Arya64636af2018-05-04 14:42:131060 # Change directory to source root to aid in relative paths calculations.
1061 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:111062
pasthanaa4844112020-05-21 18:03:551063 # Setup coverage binaries even when script is called with empty params. This
1064 # is used by coverage bot for initial setup.
1065 if len(sys.argv) == 1:
Choongwoo Hanbd1aa952021-06-09 22:25:381066 subprocess.check_call([
Akekawit Jitprasert928671e2021-09-20 18:40:581067 sys.executable, 'tools/clang/scripts/update.py', '--package',
1068 'coverage_tools'
Choongwoo Hanbd1aa952021-06-09 22:25:381069 ])
pasthanaa4844112020-05-21 18:03:551070 print(__doc__)
1071 return
1072
Yuke Liao506e8822017-12-04 16:52:541073 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:181074 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:401075 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:131076
Yuke Liao506e8822017-12-04 16:52:541077 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:181078 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:011079
Yuke Liao506e8822017-12-04 16:52:541080 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:181081 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:541082
Abhishek Arya03911092018-05-21 16:42:351083 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:131084 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:351085 'provide prof-data file as input using -p/--profdata-file option OR '
1086 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:431087
Abhishek Arya64636af2018-05-04 14:42:131088 assert not args.command or (len(args.targets) == len(args.command)), (
1089 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:431090
Abhishek Arya1ec832c2017-12-05 18:06:591091 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:401092 'Build directory: "%s" doesn\'t exist. '
1093 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:131094
Yuke Liaoc60b2d02018-03-02 21:40:431095 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:541096 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:321097
1098 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:421099 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:321100 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:421101
Abhishek Aryae5811afa2018-05-24 03:56:011102 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:541103
Abhishek Arya03911092018-05-21 16:42:351104 # Get .profdata file and list of binary paths.
1105 if args.web_tests:
1106 commands = [_GetCommandForWebTests(args.web_tests)]
1107 profdata_file_path = _CreateCoverageProfileDataForTargets(
1108 args.targets, commands, args.jobs)
1109 binary_paths = [_GetBinaryPathForWebTests()]
1110 elif args.command:
1111 for i in range(len(args.command)):
1112 assert not 'run_web_tests.py' in args.command[i], (
1113 'run_web_tests.py is not supported via --command argument. '
1114 'Please use --run-web-tests argument instead.')
1115
Abhishek Arya64636af2018-05-04 14:42:131116 # A list of commands are provided. Run them to generate profdata file, and
1117 # create a list of binary paths from parsing commands.
1118 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1119 profdata_file_path = _CreateCoverageProfileDataForTargets(
1120 args.targets, args.command, args.jobs)
1121 binary_paths = [_GetBinaryPath(command) for command in args.command]
1122 else:
Julia Hansbrough57bd3fc2023-03-30 01:47:231123 # An input prof-data file(s) is already provided.
1124 if len(args.profdata_file) == 1:
1125 # If it's just one input file, use as-is.
1126 profdata_file_path = args.profdata_file
1127 else:
1128 # Otherwise, there are multiple profdata files and we need to merge them.
1129 profdata_file_path = _CreateCoverageProfileDataFromTargetProfDataFiles(args.profdata_file)
1130 # Since input prof-data files were provided, we only need to calculate the
1131 # binary paths from here.
Abhishek Arya64636af2018-05-04 14:42:131132 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331133
Erik Chen283b92c72019-07-22 16:37:391134 # If the checkout uses the hermetic xcode binaries, then otool must be
1135 # directly invoked. The indirection via /usr/bin/otool won't work unless
1136 # there's an actual system install of Xcode.
1137 otool_path = None
1138 if sys.platform == 'darwin':
1139 hermetic_otool_path = os.path.join(
1140 SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents',
1141 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin',
1142 'otool')
1143 if os.path.exists(hermetic_otool_path):
1144 otool_path = hermetic_otool_path
Ben Joyce88282362021-01-29 23:53:311145
1146 if _IsAndroid():
1147 binary_paths = _GetBinaryPathsForAndroid(args.targets)
1148 elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
Brent McBrideb25b177a42020-05-11 18:13:061149 binary_paths.extend(
1150 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
Abhishek Arya78120bc2018-05-07 20:53:541151
Akekawit Jitprasertf9cb6622021-08-24 17:48:021152 assert args.format in ['html', 'lcov', 'text'], (
1153 '%s is not a valid output format for "llvm-cov show/export". Only '
1154 '"text", "html" and "lcov" formats are supported.' % (args.format))
Sahel Sharify38cabdc2020-01-16 00:40:011155 logging.info('Generating code coverage report in %s (this can take a while '
1156 'depending on size of target!).' % (args.format))
Max Moroz1de68d72018-08-21 13:38:181157 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591158 binary_paths, profdata_file_path, absolute_filter_paths,
1159 args.ignore_filename_regex)
Akekawit Jitprasertf9cb6622021-08-24 17:48:021160
1161 if args.format == 'lcov':
1162 _GeneratePerFileLineByLineCoverageInLcov(
1163 binary_paths, profdata_file_path, absolute_filter_paths,
1164 args.ignore_filename_regex)
1165 return
1166
Sahel Sharify38cabdc2020-01-16 00:40:011167 _GeneratePerFileLineByLineCoverageInFormat(
1168 binary_paths, profdata_file_path, absolute_filter_paths,
1169 args.ignore_filename_regex, args.format)
Max Moroz1de68d72018-08-21 13:38:181170 component_mappings = None
1171 if not args.no_component_view:
Choongwoo Hanbd1aa952021-06-09 22:25:381172 component_mappings = json.load(urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371173
Max Moroz1de68d72018-08-21 13:38:181174 # Call prepare here.
1175 processor = coverage_utils.CoverageReportPostProcessor(
1176 OUTPUT_DIR,
1177 SRC_ROOT_PATH,
1178 per_file_summary_data,
1179 no_component_view=args.no_component_view,
1180 no_file_view=args.no_file_view,
1181 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371182
Sahel Sharify38cabdc2020-01-16 00:40:011183 if args.format == 'html':
1184 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541185
Abhishek Arya1ec832c2017-12-05 18:06:591186
Yuke Liao506e8822017-12-04 16:52:541187if __name__ == '__main__':
1188 sys.exit(Main())