blob: 6149485e0187f98fd654a1716509c132f906aacf [file] [log] [blame]
Dirk Pranke4d164bb2021-03-24 06:52:401#!/usr/bin/env python
Yuke Liao506e8822017-12-04 16:52:542# Copyright 2017 The Chromium Authors. All rights reserved.
3# 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
Abhishek Arya1ec832c2017-12-05 18:06:5920 python 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
35 python tools/code_coverage/coverage.py unit_tests -b out/coverage \\
36 -o out/report -c 'python testing/xvfb.py out/coverage/unit_tests'
37
Abhishek Arya1ec832c2017-12-05 18:06:5938 If you are building a fuzz target, you need to add "use_libfuzzer=true" GN
39 flag as well.
40
Abhishek Arya03911092018-05-21 16:42:3541 * Sample workflow for a fuzz target (e.g. pdfium_fuzzer):
Abhishek Arya1ec832c2017-12-05 18:06:5942
Abhishek Arya16f059a2017-12-07 17:47:3243 python tools/code_coverage/coverage.py pdfium_fuzzer \\
44 -b out/coverage -o out/report \\
Max Moroz13c23182018-11-17 00:23:2245 -c 'out/coverage/pdfium_fuzzer -runs=0 <corpus_dir>' \\
Abhishek Arya16f059a2017-12-07 17:47:3246 -f third_party/pdfium
Abhishek Arya1ec832c2017-12-05 18:06:5947
48 where:
49 <corpus_dir> - directory containing samples files for this format.
Max Moroz13c23182018-11-17 00:23:2250
51 To learn more about generating code coverage reports for fuzz targets, see
John Palmerab8812a2021-05-21 17:03:4352 https://chromium.googlesource.com/chromium/src/+/main/testing/libfuzzer/efficient_fuzzer.md#Code-Coverage
Abhishek Arya1ec832c2017-12-05 18:06:5953
Abhishek Arya03911092018-05-21 16:42:3554 * Sample workflow for running Blink web tests:
55
56 python tools/code_coverage/coverage.py blink_tests \\
57 -wt -b out/coverage -o out/report -f third_party/blink
58
59 If you need to pass arguments to run_web_tests.py, use
60 -wt='arguments to run_web_tests.py e.g. test directories'
61
Abhishek Arya1ec832c2017-12-05 18:06:5962 For more options, please refer to tools/code_coverage/coverage.py -h.
Yuke Liao8e209fe82018-04-18 20:36:3863
64 For an overview of how code coverage works in Chromium, please refer to
John Palmerab8812a2021-05-21 17:03:4365 https://chromium.googlesource.com/chromium/src/+/main/docs/testing/code_coverage.md
Yuke Liao506e8822017-12-04 16:52:5466"""
67
68from __future__ import print_function
69
70import sys
71
72import argparse
Yuke Liaoea228d02018-01-05 19:10:3373import json
Yuke Liao481d3482018-01-29 19:17:1074import logging
Abhishek Arya03911092018-05-21 16:42:3575import multiprocessing
Yuke Liao506e8822017-12-04 16:52:5476import os
Sajjad Mirza0b96e002020-11-10 19:32:5577import platform
Yuke Liaob2926832018-03-02 17:34:2978import re
79import shlex
Max Moroz025d8952018-05-03 16:33:3480import shutil
Yuke Liao506e8822017-12-04 16:52:5481import subprocess
Yuke Liao506e8822017-12-04 16:52:5482import urllib2
83
Abhishek Arya1ec832c2017-12-05 18:06:5984sys.path.append(
85 os.path.join(
Yuke Liaoea228d02018-01-05 19:10:3386 os.path.dirname(__file__), os.path.pardir, os.path.pardir,
87 'third_party'))
Yuke Liaoea228d02018-01-05 19:10:3388from collections import defaultdict
89
Max Moroz1de68d72018-08-21 13:38:1890import coverage_utils
91
Yuke Liao082e99632018-05-18 15:40:4092# Absolute path to the code coverage tools binary. These paths can be
93# overwritten by user specified coverage tool paths.
pasthanab37d5bfd2020-05-28 12:18:3194# Absolute path to the root of the checkout.
95SRC_ROOT_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)),
96 os.path.pardir, os.path.pardir)
97LLVM_BIN_DIR = os.path.join(
98 os.path.join(SRC_ROOT_PATH, 'third_party', 'llvm-build', 'Release+Asserts'),
99 'bin')
Abhishek Arya1c97ea542018-05-10 03:53:19100LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
101LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:54102
Abhishek Arya03911092018-05-21 16:42:35103
Yuke Liao506e8822017-12-04 16:52:54104# Build directory, the value is parsed from command line arguments.
105BUILD_DIR = None
106
107# Output directory for generated artifacts, the value is parsed from command
108# line arguemnts.
109OUTPUT_DIR = None
110
Yuke Liao506e8822017-12-04 16:52:54111# Name of the file extension for profraw data files.
112PROFRAW_FILE_EXTENSION = 'profraw'
113
114# Name of the final profdata file, and this file needs to be passed to
115# "llvm-cov" command in order to call "llvm-cov show" to inspect the
116# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48117PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
118
119# Name of the file with summary information generated by llvm-cov export.
120SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54121
122# Build arg required for generating code coverage data.
123CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
124
Max Moroz7c5354f2018-05-06 00:03:48125LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37126
127# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19128COMPONENT_MAPPING_URL = (
129 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37130
Yuke Liao80afff32018-03-07 01:26:20131# Caches the results returned by _GetBuildArgs, don't use this variable
132# directly, call _GetBuildArgs instead.
133_BUILD_ARGS = None
134
Abhishek Aryac19bc5ef2018-05-04 22:10:02135# Retry failed merges.
136MERGE_RETRIES = 3
137
Abhishek Aryad35de7e2018-05-10 22:23:04138# Message to guide user to file a bug when everything else fails.
139FILE_BUG_MESSAGE = (
140 'If it persists, please file a bug with the command you used, git revision '
141 'and args.gn config here: '
142 'https://bugs.chromium.org/p/chromium/issues/entry?'
Yuke Liao03c644072019-07-30 18:33:40143 'components=Infra%3ETest%3ECodeCoverage')
Abhishek Aryad35de7e2018-05-10 22:23:04144
Abhishek Aryabd0655d2018-05-21 19:55:24145# String to replace with actual llvm profile path.
146LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
147
Yuke Liao082e99632018-05-18 15:40:40148def _ConfigureLLVMCoverageTools(args):
149 """Configures llvm coverage tools."""
150 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18151 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40152 global LLVM_COV_PATH
153 global LLVM_PROFDATA_PATH
154 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
155 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
156 else:
pasthanaa4844112020-05-21 18:03:55157 subprocess.check_call(
158 ['tools/clang/scripts/update.py', '--package', 'coverage_tools'])
Brent McBrideb25b177a42020-05-11 18:13:06159
160 if coverage_utils.GetHostPlatform() == 'win':
161 LLVM_COV_PATH += '.exe'
162 LLVM_PROFDATA_PATH += '.exe'
Yuke Liao082e99632018-05-18 15:40:40163
164 coverage_tools_exist = (
165 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
166 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
167 'both \'%s\' and \'%s\' exist.') % (
168 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
169
Abhishek Arya2f261182019-04-24 17:06:45170
Abhishek Arya1c97ea542018-05-10 03:53:19171def _GetPathWithLLVMSymbolizerDir():
172 """Add llvm-symbolizer directory to path for symbolized stacks."""
173 path = os.getenv('PATH')
174 dirs = path.split(os.pathsep)
175 if LLVM_BIN_DIR in dirs:
176 return path
177
178 return path + os.pathsep + LLVM_BIN_DIR
179
180
Yuke Liaoc60b2d02018-03-02 21:40:43181def _GetTargetOS():
182 """Returns the target os specified in args.gn file.
183
184 Returns an empty string is target_os is not specified.
185 """
Yuke Liao80afff32018-03-07 01:26:20186 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43187 return build_args['target_os'] if 'target_os' in build_args else ''
188
189
Ben Joyce88282362021-01-29 23:53:31190def _IsAndroid():
191 """Returns true if the target_os specified in args.gn file is android"""
192 return _GetTargetOS() == 'android'
193
194
Yuke Liaob2926832018-03-02 17:34:29195def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10196 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43197 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10198
199
Sahel Sharify38cabdc2020-01-16 00:40:01200def _GeneratePerFileLineByLineCoverageInFormat(binary_paths, profdata_file_path,
201 filters, ignore_filename_regex,
202 output_format):
203 """Generates per file line-by-line coverage in html or text using
204 'llvm-cov show'.
Yuke Liao506e8822017-12-04 16:52:54205
Sahel Sharify38cabdc2020-01-16 00:40:01206 For a file with absolute path /a/b/x.cc, a html/txt report is generated as:
207 OUTPUT_DIR/coverage/a/b/x.cc.[html|txt]. For html format, an index html file
208 is also generated as: OUTPUT_DIR/index.html.
Yuke Liao506e8822017-12-04 16:52:54209
210 Args:
211 binary_paths: A list of paths to the instrumented binaries.
212 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42213 filters: A list of directories and files to get coverage for.
Sahel Sharify38cabdc2020-01-16 00:40:01214 ignore_filename_regex: A regular expression for skipping source code files
215 with certain file paths.
216 output_format: The output format of generated report files.
Yuke Liao506e8822017-12-04 16:52:54217 """
Yuke Liao506e8822017-12-04 16:52:54218 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
219 # [[-object BIN]] [SOURCES]
220 # NOTE: For object files, the first one is specified as a positional argument,
221 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10222 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40223 '"llvm-cov show" command.')
Sahel Sharify38cabdc2020-01-16 00:40:01224
Abhishek Arya1ec832c2017-12-05 18:06:59225 subprocess_cmd = [
Sahel Sharify38cabdc2020-01-16 00:40:01226 LLVM_COV_PATH, 'show', '-format={}'.format(output_format),
Abhishek Arya1ec832c2017-12-05 18:06:59227 '-output-dir={}'.format(OUTPUT_DIR),
228 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
229 ]
230 subprocess_cmd.extend(
231 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29232 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18233 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17234 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42235 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59236 if ignore_filename_regex:
237 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
238
Yuke Liao506e8822017-12-04 16:52:54239 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34240
Abhishek Aryafb70b532018-05-06 17:47:40241 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54242
243
Max Moroz7c5354f2018-05-06 00:03:48244def _GetLogsDirectoryPath():
245 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18246 return os.path.join(
247 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48248
249
250def _GetProfdataFilePath():
251 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18252 return os.path.join(
253 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
254 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48255
256
257def _GetSummaryFilePath():
258 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18259 return os.path.join(
260 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
261 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33262
263
Yuke Liao506e8822017-12-04 16:52:54264def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
265 """Builds and runs target to generate the coverage profile data.
266
267 Args:
268 targets: A list of targets to build with coverage instrumentation.
269 commands: A list of commands used to run the targets.
270 jobs_count: Number of jobs to run in parallel for building. If None, a
271 default value is derived based on CPUs availability.
272
273 Returns:
274 A relative path to the generated profdata file.
275 """
276 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02277 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59278 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02279 coverage_profdata_file_path = (
280 _CreateCoverageProfileDataFromTargetProfDataFiles(
281 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54282
Abhishek Aryac19bc5ef2018-05-04 22:10:02283 for target_profdata_file_path in target_profdata_file_paths:
284 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52285
Abhishek Aryac19bc5ef2018-05-04 22:10:02286 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54287
288
289def _BuildTargets(targets, jobs_count):
290 """Builds target with Clang coverage instrumentation.
291
292 This function requires current working directory to be the root of checkout.
293
294 Args:
295 targets: A list of targets to build with coverage instrumentation.
296 jobs_count: Number of jobs to run in parallel for compilation. If None, a
297 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54298 """
Abhishek Aryafb70b532018-05-06 17:47:40299 logging.info('Building %s.', str(targets))
Brent McBrideb25b177a42020-05-11 18:13:06300 autoninja = 'autoninja'
301 if coverage_utils.GetHostPlatform() == 'win':
302 autoninja += '.bat'
Yuke Liao506e8822017-12-04 16:52:54303
Brent McBrideb25b177a42020-05-11 18:13:06304 subprocess_cmd = [autoninja, '-C', BUILD_DIR]
Yuke Liao506e8822017-12-04 16:52:54305 if jobs_count is not None:
306 subprocess_cmd.append('-j' + str(jobs_count))
307
308 subprocess_cmd.extend(targets)
309 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40310 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54311
312
Abhishek Aryac19bc5ef2018-05-04 22:10:02313def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54314 """Runs commands and returns the relative paths to the profraw data files.
315
316 Args:
317 targets: A list of targets built with coverage instrumentation.
318 commands: A list of commands used to run the targets.
319
320 Returns:
321 A list of relative paths to the generated profraw data files.
322 """
Abhishek Aryafb70b532018-05-06 17:47:40323 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10324
Yuke Liao506e8822017-12-04 16:52:54325 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18326 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
327 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54328 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18329 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48330
331 # Ensure that logs directory exists.
332 if not os.path.exists(_GetLogsDirectoryPath()):
333 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54334
Abhishek Aryac19bc5ef2018-05-04 22:10:02335 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10336
Yuke Liaod4a9865202018-01-12 23:17:52337 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54338 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48339 output_file_name = os.extsep.join([target + '_output', 'log'])
340 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10341
Abhishek Aryac19bc5ef2018-05-04 22:10:02342 profdata_file_path = None
343 for _ in xrange(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40344 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02345 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10346
Abhishek Aryac19bc5ef2018-05-04 22:10:02347 if _IsIOSCommand(command):
348 # On iOS platform, due to lack of write permissions, profraw files are
349 # generated outside of the OUTPUT_DIR, and the exact paths are contained
350 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35351 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02352 else:
353 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35354 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02355
356 profraw_file_paths = []
357 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57358 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Ben Joyce88282362021-01-29 23:53:31359 elif _IsAndroid():
360 android_coverage_dir = os.path.join(BUILD_DIR, 'coverage')
361 for r, _, files in os.walk(android_coverage_dir):
362 for f in files:
363 if f.endswith(PROFRAW_FILE_EXTENSION):
364 profraw_file_paths.append(os.path.join(r, f))
Abhishek Aryac19bc5ef2018-05-04 22:10:02365 else:
Max Moroz1de68d72018-08-21 13:38:18366 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02367 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48368 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18369 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02370
371 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40372 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04373 'please make sure the binary exists, is properly instrumented and '
374 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02375
Yuke Liao9c2c70b2018-05-23 15:37:57376 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18377 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
378 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57379
Abhishek Aryac19bc5ef2018-05-04 22:10:02380 try:
381 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
382 target, profraw_file_paths)
383 break
384 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04385 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02386 finally:
387 # Remove profraw files now so that they are not used in next iteration.
388 for profraw_file_path in profraw_file_paths:
389 os.remove(profraw_file_path)
390
391 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04392 'Failed to merge target "%s" profraw files after %d retries. %s' %
393 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02394 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54395
Abhishek Aryafb70b532018-05-06 17:47:40396 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10397
Abhishek Aryac19bc5ef2018-05-04 22:10:02398 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54399
400
Abhishek Arya03911092018-05-21 16:42:35401def _GetEnvironmentVars(profraw_file_path):
402 """Return environment vars for subprocess, given a profraw file path."""
403 env = os.environ.copy()
404 env.update({
405 'LLVM_PROFILE_FILE': profraw_file_path,
406 'PATH': _GetPathWithLLVMSymbolizerDir()
407 })
408 return env
409
410
Sajjad Mirza0b96e002020-11-10 19:32:55411def _SplitCommand(command):
412 """Split a command string into parts in a platform-specific way."""
413 if coverage_utils.GetHostPlatform() == 'win':
414 return command.split()
415 return shlex.split(command)
416
417
Abhishek Arya03911092018-05-21 16:42:35418def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10419 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52420 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01421 #
Max Morozd73e45f2018-04-24 18:32:47422 # "%p" expands out to the process ID. It's not used by this scripts due to:
423 # 1) If a target program spawns too many processess, it may exhaust all disk
424 # space available. For example, unit_tests writes thousands of .profraw
425 # files each of size 1GB+.
426 # 2) If a target binary uses shared libraries, coverage profile data for them
427 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01428 #
Yuke Liaod4a9865202018-01-12 23:17:52429 # "%Nm" expands out to the instrumented binary's signature. When this pattern
430 # is specified, the runtime creates a pool of N raw profiles which are used
431 # for on-line profile merging. The runtime takes care of selecting a raw
432 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52433 # N must be between 1 and 9. The merge pool specifier can only occur once per
434 # filename pattern.
435 #
Max Morozd73e45f2018-04-24 18:32:47436 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01437 #
Max Morozd73e45f2018-04-24 18:32:47438 # For other cases, "%4m" is chosen as it creates some level of parallelism,
439 # but it's not too big to consume too much computing resource or disk space.
440 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59441 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01442 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18443 expected_profraw_file_path = os.path.join(
444 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
445 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24446 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
447 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54448
Yuke Liaoa0c8c2f2018-02-28 20:14:10449 try:
Max Moroz7c5354f2018-05-06 00:03:48450 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35451 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55452 subprocess.check_call(_SplitCommand(command),
453 stdout=output_file_handle,
454 stderr=subprocess.STDOUT,
455 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10456 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35457 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10458
Abhishek Arya03911092018-05-21 16:42:35459 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10460
461
Yuke Liao27349c92018-03-22 21:10:01462def _IsFuzzerTarget(target):
463 """Returns true if the target is a fuzzer target."""
464 build_args = _GetBuildArgs()
465 use_libfuzzer = ('use_libfuzzer' in build_args and
466 build_args['use_libfuzzer'] == 'true')
467 return use_libfuzzer and target.endswith('_fuzzer')
468
469
Abhishek Arya03911092018-05-21 16:42:35470def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10471 """Runs a single iOS command and generates a profraw data file.
472
473 iOS application doesn't have write access to folders outside of the app, so
474 it's impossible to instruct the app to flush the profraw data file to the
475 desired location. The profraw data file will be generated somewhere within the
476 application's Documents folder, and the full path can be obtained by parsing
477 the output.
478 """
Yuke Liaob2926832018-03-02 17:34:29479 assert _IsIOSCommand(command)
480
481 # After running tests, iossim generates a profraw data file, it won't be
482 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
483 # checkout.
484 iossim_profraw_file_path = os.path.join(
485 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24486 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
487 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10488
489 try:
Abhishek Arya03911092018-05-21 16:42:35490 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55491 subprocess.check_call(_SplitCommand(command),
492 stdout=output_file_handle,
493 stderr=subprocess.STDOUT,
494 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10495 except subprocess.CalledProcessError as e:
496 # iossim emits non-zero return code even if tests run successfully, so
497 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35498 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10499
Abhishek Arya03911092018-05-21 16:42:35500 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10501
502
503def _GetProfrawDataFileByParsingOutput(output):
504 """Returns the path to the profraw data file obtained by parsing the output.
505
506 The output of running the test target has no format, but it is guaranteed to
507 have a single line containing the path to the generated profraw data file.
508 NOTE: This should only be called when target os is iOS.
509 """
Yuke Liaob2926832018-03-02 17:34:29510 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10511
Yuke Liaob2926832018-03-02 17:34:29512 output_by_lines = ''.join(output).splitlines()
513 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10514
515 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29516 result = profraw_file_pattern.match(line)
517 if result:
518 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10519
520 assert False, ('No profraw data file was generated, did you call '
521 'coverage_util::ConfigureCoverageReportPath() in test setup? '
522 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54523
524
Abhishek Aryac19bc5ef2018-05-04 22:10:02525def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
526 """Returns a relative path to coverage profdata file by merging target
527 profdata files.
Yuke Liao506e8822017-12-04 16:52:54528
529 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02530 profdata_file_paths: A list of relative paths to the profdata data files
531 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54532
533 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02534 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54535
536 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02537 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54538 """
Abhishek Aryafb70b532018-05-06 17:47:40539 logging.info('Creating the coverage profile data file.')
540 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48541 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54542 try:
Abhishek Arya1ec832c2017-12-05 18:06:59543 subprocess_cmd = [
544 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
545 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02546 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01547
548 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18549 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02550 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04551 logging.error(
552 'Failed to merge target profdata files to create coverage profdata. %s',
553 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02554 raise error
555
Abhishek Aryafb70b532018-05-06 17:47:40556 logging.debug('Finished merging target profdata files.')
557 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02558 profdata_file_path)
559 return profdata_file_path
560
561
562def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
563 """Returns a relative path to target profdata file by merging target
564 profraw files.
565
566 Args:
567 profraw_file_paths: A list of relative paths to the profdata data files
568 that are to be merged.
569
570 Returns:
571 A relative path to the merged coverage profdata file.
572
573 Raises:
574 CalledProcessError: An error occurred merging profdata files.
575 """
Abhishek Aryafb70b532018-05-06 17:47:40576 logging.info('Creating target profile data file.')
577 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02578 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
579
580 try:
581 subprocess_cmd = [
582 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
583 ]
Yuke Liao506e8822017-12-04 16:52:54584 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01585 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18586 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54587 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04588 logging.error(
589 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54590 raise error
591
Abhishek Aryafb70b532018-05-06 17:47:40592 logging.debug('Finished merging target profraw files.')
593 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10594 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54595 return profdata_file_path
596
597
Yuke Liao0e4c8682018-04-18 21:06:59598def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
599 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33600 """Generates per file coverage summary using "llvm-cov export" command."""
601 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
602 # [[-object BIN]] [SOURCES].
603 # NOTE: For object files, the first one is specified as a positional argument,
604 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10605 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40606 'export -summary-only" command.')
Sajjad Mirza07f52332020-11-11 01:50:47607 for path in binary_paths:
608 if not os.path.exists(path):
609 logging.error("Binary %s does not exist", path)
Yuke Liaoea228d02018-01-05 19:10:33610 subprocess_cmd = [
611 LLVM_COV_PATH, 'export', '-summary-only',
612 '-instr-profile=' + profdata_file_path, binary_paths[0]
613 ]
614 subprocess_cmd.extend(
615 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29616 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33617 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59618 if ignore_filename_regex:
619 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33620
Max Moroz7c5354f2018-05-06 00:03:48621 export_output = subprocess.check_output(subprocess_cmd)
622
623 # Write output on the disk to be used by code coverage bot.
624 with open(_GetSummaryFilePath(), 'w') as f:
625 f.write(export_output)
626
Max Moroz1de68d72018-08-21 13:38:18627 return export_output
Yuke Liaoea228d02018-01-05 19:10:33628
629
Yuke Liaob2926832018-03-02 17:34:29630def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
631 """Appends -arch arguments to the command list if it's ios platform.
632
633 iOS binaries are universal binaries, and require specifying the architecture
634 to use, and one architecture needs to be specified for each binary.
635 """
636 if _IsIOS():
637 cmd_list.extend(['-arch=x86_64'] * num_archs)
638
639
Yuke Liao506e8822017-12-04 16:52:54640def _GetBinaryPath(command):
641 """Returns a relative path to the binary to be run by the command.
642
Yuke Liao545db322018-02-15 17:12:01643 Currently, following types of commands are supported (e.g. url_unittests):
644 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
645 2. Use xvfb.
646 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
647 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37648 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
649 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10650 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37651 <iossim_arguments> -c <app_arguments>
652 out/Coverage-iphonesimulator/url_unittests.app"
653
Yuke Liao506e8822017-12-04 16:52:54654 Args:
655 command: A command used to run a target.
656
657 Returns:
658 A relative path to the binary.
659 """
Yuke Liao545db322018-02-15 17:12:01660 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
661
Sajjad Mirza0b96e002020-11-10 19:32:55662 command_parts = _SplitCommand(command)
Yuke Liao545db322018-02-15 17:12:01663 if os.path.basename(command_parts[0]) == 'python':
664 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40665 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01666 return command_parts[2]
667
668 if os.path.basename(command_parts[0]) == xvfb_script_name:
669 return command_parts[1]
670
Yuke Liaob2926832018-03-02 17:34:29671 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10672 # For a given application bundle, the binary resides in the bundle and has
673 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02674 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10675 app_name = os.path.splitext(os.path.basename(app_path))[0]
676 return os.path.join(app_path, app_name)
677
Sajjad Mirza07f52332020-11-11 01:50:47678 if coverage_utils.GetHostPlatform() == 'win' \
679 and not command_parts[0].endswith('.exe'):
680 return command_parts[0] + '.exe'
681
Yuke Liaob2926832018-03-02 17:34:29682 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54683
684
Yuke Liaob2926832018-03-02 17:34:29685def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10686 """Returns true if command is used to run tests on iOS platform."""
Sajjad Mirza0b96e002020-11-10 19:32:55687 return os.path.basename(_SplitCommand(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10688
689
Yuke Liao95d13d72017-12-07 18:18:50690def _VerifyTargetExecutablesAreInBuildDirectory(commands):
691 """Verifies that the target executables specified in the commands are inside
692 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54693 for command in commands:
694 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18695 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35696 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50697 'Target executable "%s" in command: "%s" is outside of '
698 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54699
700
701def _ValidateBuildingWithClangCoverage():
702 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20703 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54704
705 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
706 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59707 assert False, ('\'{} = true\' is required in args.gn.'
708 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54709
710
Yuke Liaoc60b2d02018-03-02 21:40:43711def _ValidateCurrentPlatformIsSupported():
712 """Asserts that this script suports running on the current platform"""
713 target_os = _GetTargetOS()
714 if target_os:
715 current_platform = target_os
716 else:
Max Moroz1de68d72018-08-21 13:38:18717 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43718
Ben Joyce88282362021-01-29 23:53:31719 supported_platforms = ['android', 'chromeos', 'ios', 'linux', 'mac', 'win']
720 assert current_platform in supported_platforms, ('Coverage is only'
721 'supported on %s' %
722 supported_platforms)
Yuke Liaoc60b2d02018-03-02 21:40:43723
724
Yuke Liao80afff32018-03-07 01:26:20725def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54726 """Parses args.gn file and returns results as a dictionary.
727
728 Returns:
729 A dictionary representing the build args.
730 """
Yuke Liao80afff32018-03-07 01:26:20731 global _BUILD_ARGS
732 if _BUILD_ARGS is not None:
733 return _BUILD_ARGS
734
735 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54736 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
737 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
738 'missing args.gn file.' % BUILD_DIR)
739 with open(build_args_path) as build_args_file:
740 build_args_lines = build_args_file.readlines()
741
Yuke Liao506e8822017-12-04 16:52:54742 for build_arg_line in build_args_lines:
743 build_arg_without_comments = build_arg_line.split('#')[0]
744 key_value_pair = build_arg_without_comments.split('=')
745 if len(key_value_pair) != 2:
746 continue
747
748 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43749
750 # Values are wrapped within a pair of double-quotes, so remove the leading
751 # and trailing double-quotes.
752 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20753 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54754
Yuke Liao80afff32018-03-07 01:26:20755 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54756
757
Abhishek Arya16f059a2017-12-07 17:47:32758def _VerifyPathsAndReturnAbsolutes(paths):
759 """Verifies that the paths specified in |paths| exist and returns absolute
760 versions.
Yuke Liao66da1732017-12-05 22:19:42761
762 Args:
763 paths: A list of files or directories.
764 """
Abhishek Arya16f059a2017-12-07 17:47:32765 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42766 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32767 absolute_path = os.path.join(SRC_ROOT_PATH, path)
768 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
769
770 absolute_paths.append(absolute_path)
771
772 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42773
774
Abhishek Arya64636af2018-05-04 14:42:13775def _GetBinaryPathsFromTargets(targets, build_dir):
776 """Return binary paths from target names."""
Ben Joyce88282362021-01-29 23:53:31777 # TODO(crbug.com/899974): Derive output binary from target build definitions
778 # rather than assuming that it is always the same name.
Abhishek Arya64636af2018-05-04 14:42:13779 binary_paths = []
780 for target in targets:
781 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18782 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13783 binary_path += '.exe'
784
785 if os.path.exists(binary_path):
786 binary_paths.append(binary_path)
787 else:
788 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40789 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13790 os.path.basename(binary_path))
791
792 return binary_paths
793
794
Abhishek Arya03911092018-05-21 16:42:35795def _GetCommandForWebTests(arguments):
796 """Return command to run for blink web tests."""
Dirk Pranke34c093a42021-03-25 19:19:05797 cpu_count = multiprocessing.cpu_count()
798 if sys.platform == 'win32':
799 # TODO(crbug.com/1190269) - we can't use more than 56
800 # cores on Windows or Python3 may hang.
801 cpu_count = min(cpu_count, 56)
802 cpu_count = max(1, cpu_count // 2)
803
Abhishek Arya03911092018-05-21 16:42:35804 command_list = [
805 'python', 'testing/xvfb.py', 'python',
806 'third_party/blink/tools/run_web_tests.py',
807 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24808 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Robert Ma339fc472018-12-14 21:22:42809 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Dirk Pranke34c093a42021-03-25 19:19:05810 '--child-processes=%d' % cpu_count, '--disable-breakpad',
811 '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35812 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
813 ]
814 if arguments.strip():
815 command_list.append(arguments)
816 return ' '.join(command_list)
817
818
Ben Joyce88282362021-01-29 23:53:31819def _GetBinaryPathsForAndroid(targets):
820 """Return binary paths used when running android tests."""
821 # TODO(crbug.com/899974): Implement approach that doesn't assume .so file is
822 # based on the target's name.
823 android_binaries = set()
824 for target in targets:
825 so_library_path = os.path.join(BUILD_DIR, 'lib.unstripped',
826 'lib%s__library.so' % target)
827 if os.path.exists(so_library_path):
828 android_binaries.add(so_library_path)
829
830 return list(android_binaries)
831
832
Abhishek Arya03911092018-05-21 16:42:35833def _GetBinaryPathForWebTests():
834 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18835 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35836 if host_platform == 'win':
837 return os.path.join(BUILD_DIR, 'content_shell.exe')
838 elif host_platform == 'linux':
839 return os.path.join(BUILD_DIR, 'content_shell')
840 elif host_platform == 'mac':
841 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
842 'Content Shell')
843 else:
844 assert False, 'This platform is not supported for web tests.'
845
846
Abhishek Aryae5811afa2018-05-24 03:56:01847def _SetupOutputDir():
848 """Setup output directory."""
849 if os.path.exists(OUTPUT_DIR):
850 shutil.rmtree(OUTPUT_DIR)
851
852 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18853 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01854
855
Yuke Liaoabfbba42019-06-11 16:03:59856def _SetMacXcodePath():
857 """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
858 if sys.platform != 'darwin':
859 return
860
861 xcode_path = os.path.join(SRC_ROOT_PATH, 'build', 'mac_files', 'Xcode.app')
862 if os.path.exists(xcode_path):
863 os.environ['DEVELOPER_DIR'] = xcode_path
864
865
Yuke Liao506e8822017-12-04 16:52:54866def _ParseCommandArguments():
867 """Adds and parses relevant arguments for tool comands.
868
869 Returns:
870 A dictionary representing the arguments.
871 """
872 arg_parser = argparse.ArgumentParser()
873 arg_parser.usage = __doc__
874
Abhishek Arya1ec832c2017-12-05 18:06:59875 arg_parser.add_argument(
876 '-b',
877 '--build-dir',
878 type=str,
879 required=True,
880 help='The build directory, the path needs to be relative to the root of '
881 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54882
Abhishek Arya1ec832c2017-12-05 18:06:59883 arg_parser.add_argument(
884 '-o',
885 '--output-dir',
886 type=str,
887 required=True,
888 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54889
Abhishek Arya1ec832c2017-12-05 18:06:59890 arg_parser.add_argument(
891 '-c',
892 '--command',
893 action='append',
Abhishek Arya64636af2018-05-04 14:42:13894 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59895 help='Commands used to run test targets, one test target needs one and '
896 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13897 'current working directory is the root of the checkout. This option is '
898 'incompatible with -p/--profdata-file option.')
899
900 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35901 '-wt',
902 '--web-tests',
903 nargs='?',
904 type=str,
905 const=' ',
906 required=False,
907 help='Run blink web tests. Support passing arguments to run_web_tests.py')
908
909 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13910 '-p',
911 '--profdata-file',
912 type=str,
913 required=False,
914 help='Path to profdata file to use for generating code coverage reports. '
915 'This can be useful if you generated the profdata file seperately in '
916 'your own test harness. This option is ignored if run command(s) are '
917 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54918
Abhishek Arya1ec832c2017-12-05 18:06:59919 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42920 '-f',
921 '--filters',
922 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32923 required=False,
Yuke Liao66da1732017-12-05 22:19:42924 help='Directories or files to get code coverage for, and all files under '
925 'the directories are included recursively.')
926
927 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59928 '-i',
929 '--ignore-filename-regex',
930 type=str,
931 help='Skip source code files with file paths that match the given '
932 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
933 'to exclude files in third_party/ and out/ folders from the report.')
934
935 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32936 '--no-file-view',
937 action='store_true',
938 help='Don\'t generate the file view in the coverage report. When there '
939 'are large number of html files, the file view becomes heavy and may '
940 'cause the browser to freeze, and this argument comes handy.')
941
942 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18943 '--no-component-view',
944 action='store_true',
945 help='Don\'t generate the component view in the coverage report.')
946
947 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:40948 '--coverage-tools-dir',
949 type=str,
950 help='Path of the directory where LLVM coverage tools (llvm-cov, '
951 'llvm-profdata) exist. This should be only needed if you are testing '
952 'against a custom built clang revision. Otherwise, we pick coverage '
953 'tools automatically from your current source checkout.')
954
955 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:59956 '-j',
957 '--jobs',
958 type=int,
959 default=None,
960 help='Run N jobs to build in parallel. If not specified, a default value '
Max Moroz06576292019-01-03 19:22:52961 'will be derived based on CPUs and goma availability. Please refer to '
962 '\'autoninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:54963
Abhishek Arya1ec832c2017-12-05 18:06:59964 arg_parser.add_argument(
Sahel Sharify38cabdc2020-01-16 00:40:01965 '--format',
966 type=str,
967 default='html',
968 help='Output format of the "llvm-cov show" command. The supported '
969 'formats are "text" and "html".')
970
971 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:10972 '-v',
973 '--verbose',
974 action='store_true',
975 help='Prints additional output for diagnostics.')
976
977 arg_parser.add_argument(
978 '-l', '--log_file', type=str, help='Redirects logs to a file.')
979
980 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:02981 'targets',
982 nargs='+',
983 help='The names of the test targets to run. If multiple run commands are '
984 'specified using the -c/--command option, then the order of targets and '
985 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:54986
987 args = arg_parser.parse_args()
988 return args
989
990
991def Main():
992 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:40993
Abhishek Arya64636af2018-05-04 14:42:13994 # Change directory to source root to aid in relative paths calculations.
995 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:11996
pasthanaa4844112020-05-21 18:03:55997 # Setup coverage binaries even when script is called with empty params. This
998 # is used by coverage bot for initial setup.
999 if len(sys.argv) == 1:
1000 subprocess.check_call(
1001 ['tools/clang/scripts/update.py', '--package', 'coverage_tools'])
1002 print(__doc__)
1003 return
1004
Yuke Liao506e8822017-12-04 16:52:541005 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:181006 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:401007 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:131008
Yuke Liao506e8822017-12-04 16:52:541009 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:181010 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:011011
Yuke Liao506e8822017-12-04 16:52:541012 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:181013 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:541014
Abhishek Arya03911092018-05-21 16:42:351015 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:131016 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:351017 'provide prof-data file as input using -p/--profdata-file option OR '
1018 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:431019
Abhishek Arya64636af2018-05-04 14:42:131020 assert not args.command or (len(args.targets) == len(args.command)), (
1021 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:431022
Abhishek Arya1ec832c2017-12-05 18:06:591023 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:401024 'Build directory: "%s" doesn\'t exist. '
1025 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:131026
Yuke Liaoc60b2d02018-03-02 21:40:431027 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:541028 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:321029
1030 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:421031 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:321032 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:421033
Abhishek Aryae5811afa2018-05-24 03:56:011034 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:541035
Abhishek Arya03911092018-05-21 16:42:351036 # Get .profdata file and list of binary paths.
1037 if args.web_tests:
1038 commands = [_GetCommandForWebTests(args.web_tests)]
1039 profdata_file_path = _CreateCoverageProfileDataForTargets(
1040 args.targets, commands, args.jobs)
1041 binary_paths = [_GetBinaryPathForWebTests()]
1042 elif args.command:
1043 for i in range(len(args.command)):
1044 assert not 'run_web_tests.py' in args.command[i], (
1045 'run_web_tests.py is not supported via --command argument. '
1046 'Please use --run-web-tests argument instead.')
1047
Abhishek Arya64636af2018-05-04 14:42:131048 # A list of commands are provided. Run them to generate profdata file, and
1049 # create a list of binary paths from parsing commands.
1050 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1051 profdata_file_path = _CreateCoverageProfileDataForTargets(
1052 args.targets, args.command, args.jobs)
1053 binary_paths = [_GetBinaryPath(command) for command in args.command]
1054 else:
1055 # An input prof-data file is already provided. Just calculate binary paths.
1056 profdata_file_path = args.profdata_file
1057 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331058
Erik Chen283b92c72019-07-22 16:37:391059 # If the checkout uses the hermetic xcode binaries, then otool must be
1060 # directly invoked. The indirection via /usr/bin/otool won't work unless
1061 # there's an actual system install of Xcode.
1062 otool_path = None
1063 if sys.platform == 'darwin':
1064 hermetic_otool_path = os.path.join(
1065 SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents',
1066 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin',
1067 'otool')
1068 if os.path.exists(hermetic_otool_path):
1069 otool_path = hermetic_otool_path
Ben Joyce88282362021-01-29 23:53:311070
1071 if _IsAndroid():
1072 binary_paths = _GetBinaryPathsForAndroid(args.targets)
1073 elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
Brent McBrideb25b177a42020-05-11 18:13:061074 binary_paths.extend(
1075 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
Abhishek Arya78120bc2018-05-07 20:53:541076
Sahel Sharify38cabdc2020-01-16 00:40:011077 assert args.format == 'html' or args.format == 'text', (
1078 '%s is not a valid output format for "llvm-cov show". Only "text" and '
1079 '"html" formats are supported.' % (args.format))
1080 logging.info('Generating code coverage report in %s (this can take a while '
1081 'depending on size of target!).' % (args.format))
Max Moroz1de68d72018-08-21 13:38:181082 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591083 binary_paths, profdata_file_path, absolute_filter_paths,
1084 args.ignore_filename_regex)
Sahel Sharify38cabdc2020-01-16 00:40:011085 _GeneratePerFileLineByLineCoverageInFormat(
1086 binary_paths, profdata_file_path, absolute_filter_paths,
1087 args.ignore_filename_regex, args.format)
Max Moroz1de68d72018-08-21 13:38:181088 component_mappings = None
1089 if not args.no_component_view:
1090 component_mappings = json.load(urllib2.urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371091
Max Moroz1de68d72018-08-21 13:38:181092 # Call prepare here.
1093 processor = coverage_utils.CoverageReportPostProcessor(
1094 OUTPUT_DIR,
1095 SRC_ROOT_PATH,
1096 per_file_summary_data,
1097 no_component_view=args.no_component_view,
1098 no_file_view=args.no_file_view,
1099 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371100
Sahel Sharify38cabdc2020-01-16 00:40:011101 if args.format == 'html':
1102 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541103
Abhishek Arya1ec832c2017-12-05 18:06:591104
Yuke Liao506e8822017-12-04 16:52:541105if __name__ == '__main__':
1106 sys.exit(Main())