blob: 8f57960b7d0ad74c052e5ee05e91920ce0d6adf8 [file] [log] [blame]
Prakhar65d63832021-06-16 23:01:371#!/usr/bin/env vpython
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
Prakhar507de0b2021-07-02 18:02:4720 vpython 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
Prakhar507de0b2021-07-02 18:02:4735 vpython 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
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
Prakhar507de0b2021-07-02 18:02:4743 vpython tools/code_coverage/coverage.py pdfium_fuzzer \\
Abhishek Arya16f059a2017-12-07 17:47:3244 -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
Prakhar507de0b2021-07-02 18:02:4756 vpython tools/code_coverage/coverage.py blink_tests \\
Abhishek Arya03911092018-05-21 16:42:3557 -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
Choongwoo Hanbd1aa952021-06-09 22:25:3882import six
83
84if six.PY2:
85 from urllib2 import urlopen
86else:
87 from urllib.request import urlopen
88
Abhishek Arya1ec832c2017-12-05 18:06:5989sys.path.append(
90 os.path.join(
Yuke Liaoea228d02018-01-05 19:10:3391 os.path.dirname(__file__), os.path.pardir, os.path.pardir,
92 'third_party'))
Yuke Liaoea228d02018-01-05 19:10:3393from collections import defaultdict
94
Max Moroz1de68d72018-08-21 13:38:1895import coverage_utils
96
Yuke Liao082e99632018-05-18 15:40:4097# Absolute path to the code coverage tools binary. These paths can be
98# overwritten by user specified coverage tool paths.
pasthanab37d5bfd2020-05-28 12:18:3199# Absolute path to the root of the checkout.
100SRC_ROOT_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)),
101 os.path.pardir, os.path.pardir)
102LLVM_BIN_DIR = os.path.join(
103 os.path.join(SRC_ROOT_PATH, 'third_party', 'llvm-build', 'Release+Asserts'),
104 'bin')
Abhishek Arya1c97ea542018-05-10 03:53:19105LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
106LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:54107
Abhishek Arya03911092018-05-21 16:42:35108
Yuke Liao506e8822017-12-04 16:52:54109# Build directory, the value is parsed from command line arguments.
110BUILD_DIR = None
111
112# Output directory for generated artifacts, the value is parsed from command
113# line arguemnts.
114OUTPUT_DIR = None
115
Yuke Liao506e8822017-12-04 16:52:54116# Name of the file extension for profraw data files.
117PROFRAW_FILE_EXTENSION = 'profraw'
118
119# Name of the final profdata file, and this file needs to be passed to
120# "llvm-cov" command in order to call "llvm-cov show" to inspect the
121# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48122PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
123
124# Name of the file with summary information generated by llvm-cov export.
125SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54126
Akekawit Jitprasertf9cb6622021-08-24 17:48:02127# Name of the coverage file in lcov format generated by llvm-cov export.
128LCOV_FILE_NAME = os.extsep.join(['coverage', 'lcov'])
129
Yuke Liao506e8822017-12-04 16:52:54130# Build arg required for generating code coverage data.
131CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
132
Max Moroz7c5354f2018-05-06 00:03:48133LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37134
135# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19136COMPONENT_MAPPING_URL = (
137 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37138
Yuke Liao80afff32018-03-07 01:26:20139# Caches the results returned by _GetBuildArgs, don't use this variable
140# directly, call _GetBuildArgs instead.
141_BUILD_ARGS = None
142
Abhishek Aryac19bc5ef2018-05-04 22:10:02143# Retry failed merges.
144MERGE_RETRIES = 3
145
Abhishek Aryad35de7e2018-05-10 22:23:04146# Message to guide user to file a bug when everything else fails.
147FILE_BUG_MESSAGE = (
148 'If it persists, please file a bug with the command you used, git revision '
149 'and args.gn config here: '
150 'https://bugs.chromium.org/p/chromium/issues/entry?'
Yuke Liao03c644072019-07-30 18:33:40151 'components=Infra%3ETest%3ECodeCoverage')
Abhishek Aryad35de7e2018-05-10 22:23:04152
Abhishek Aryabd0655d2018-05-21 19:55:24153# String to replace with actual llvm profile path.
154LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
155
Yuke Liao082e99632018-05-18 15:40:40156def _ConfigureLLVMCoverageTools(args):
157 """Configures llvm coverage tools."""
158 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18159 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40160 global LLVM_COV_PATH
161 global LLVM_PROFDATA_PATH
162 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
163 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
164 else:
Choongwoo Hanbd1aa952021-06-09 22:25:38165 subprocess.check_call([
Akekawit Jitprasert928671e2021-09-20 18:40:58166 sys.executable, 'tools/clang/scripts/update.py', '--package',
167 'coverage_tools'
Choongwoo Hanbd1aa952021-06-09 22:25:38168 ])
Brent McBrideb25b177a42020-05-11 18:13:06169
170 if coverage_utils.GetHostPlatform() == 'win':
171 LLVM_COV_PATH += '.exe'
172 LLVM_PROFDATA_PATH += '.exe'
Yuke Liao082e99632018-05-18 15:40:40173
174 coverage_tools_exist = (
175 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
176 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
177 'both \'%s\' and \'%s\' exist.') % (
178 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
179
Abhishek Arya2f261182019-04-24 17:06:45180
Abhishek Arya1c97ea542018-05-10 03:53:19181def _GetPathWithLLVMSymbolizerDir():
182 """Add llvm-symbolizer directory to path for symbolized stacks."""
183 path = os.getenv('PATH')
184 dirs = path.split(os.pathsep)
185 if LLVM_BIN_DIR in dirs:
186 return path
187
188 return path + os.pathsep + LLVM_BIN_DIR
189
190
Yuke Liaoc60b2d02018-03-02 21:40:43191def _GetTargetOS():
192 """Returns the target os specified in args.gn file.
193
194 Returns an empty string is target_os is not specified.
195 """
Yuke Liao80afff32018-03-07 01:26:20196 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43197 return build_args['target_os'] if 'target_os' in build_args else ''
198
199
Ben Joyce88282362021-01-29 23:53:31200def _IsAndroid():
201 """Returns true if the target_os specified in args.gn file is android"""
202 return _GetTargetOS() == 'android'
203
204
Yuke Liaob2926832018-03-02 17:34:29205def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10206 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43207 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10208
209
Sahel Sharify38cabdc2020-01-16 00:40:01210def _GeneratePerFileLineByLineCoverageInFormat(binary_paths, profdata_file_path,
211 filters, ignore_filename_regex,
212 output_format):
213 """Generates per file line-by-line coverage in html or text using
214 'llvm-cov show'.
Yuke Liao506e8822017-12-04 16:52:54215
Sahel Sharify38cabdc2020-01-16 00:40:01216 For a file with absolute path /a/b/x.cc, a html/txt report is generated as:
217 OUTPUT_DIR/coverage/a/b/x.cc.[html|txt]. For html format, an index html file
218 is also generated as: OUTPUT_DIR/index.html.
Yuke Liao506e8822017-12-04 16:52:54219
220 Args:
221 binary_paths: A list of paths to the instrumented binaries.
222 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42223 filters: A list of directories and files to get coverage for.
Sahel Sharify38cabdc2020-01-16 00:40:01224 ignore_filename_regex: A regular expression for skipping source code files
225 with certain file paths.
226 output_format: The output format of generated report files.
Yuke Liao506e8822017-12-04 16:52:54227 """
Yuke Liao506e8822017-12-04 16:52:54228 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
229 # [[-object BIN]] [SOURCES]
230 # NOTE: For object files, the first one is specified as a positional argument,
231 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10232 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40233 '"llvm-cov show" command.')
Sahel Sharify38cabdc2020-01-16 00:40:01234
Abhishek Arya1ec832c2017-12-05 18:06:59235 subprocess_cmd = [
Sahel Sharify38cabdc2020-01-16 00:40:01236 LLVM_COV_PATH, 'show', '-format={}'.format(output_format),
Choongwoo Han56752522021-06-10 17:38:34237 '-compilation-dir={}'.format(BUILD_DIR),
Abhishek Arya1ec832c2017-12-05 18:06:59238 '-output-dir={}'.format(OUTPUT_DIR),
239 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
240 ]
241 subprocess_cmd.extend(
242 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29243 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18244 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17245 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42246 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59247 if ignore_filename_regex:
248 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
249
Yuke Liao506e8822017-12-04 16:52:54250 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34251
Abhishek Aryafb70b532018-05-06 17:47:40252 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54253
254
Akekawit Jitprasertf9cb6622021-08-24 17:48:02255def _GeneratePerFileLineByLineCoverageInLcov(binary_paths, profdata_file_path, filters,
256 ignore_filename_regex):
257 """Generates per file line-by-line coverage using "llvm-cov export".
258
259 Args:
260 binary_paths: A list of paths to the instrumented binaries.
261 profdata_file_path: A path to the profdata file.
262 filters: A list of directories and files to get coverage for.
263 ignore_filename_regex: A regular expression for skipping source code files
264 with certain file paths.
265 """
266 logging.debug('Generating per file line by line coverage reports using '
267 '"llvm-cov export" command.')
268 for path in binary_paths:
269 if not os.path.exists(path):
270 logging.error("Binary %s does not exist", path)
271 subprocess_cmd = [
272 LLVM_COV_PATH, 'export', '-format=lcov',
273 '-instr-profile=' + profdata_file_path, binary_paths[0]
274 ]
275 subprocess_cmd.extend(
276 ['-object=' + binary_path for binary_path in binary_paths[1:]])
277 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
278 subprocess_cmd.extend(filters)
279 if ignore_filename_regex:
280 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
281
282 # Write output on the disk to be used by code coverage bot.
283 with open(_GetLcovFilePath(), 'w') as f:
284 subprocess.check_call(subprocess_cmd, stdout=f)
285
286 logging.debug('Finished running "llvm-cov export" command.')
287
288
Max Moroz7c5354f2018-05-06 00:03:48289def _GetLogsDirectoryPath():
290 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18291 return os.path.join(
292 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48293
294
295def _GetProfdataFilePath():
296 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18297 return os.path.join(
298 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
299 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48300
301
302def _GetSummaryFilePath():
303 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18304 return os.path.join(
305 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
306 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33307
308
Akekawit Jitprasertf9cb6622021-08-24 17:48:02309def _GetLcovFilePath():
310 """The LCOV file that contains coverage data written by llvm-cov export."""
311 return os.path.join(
312 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
313 LCOV_FILE_NAME)
314
315
Yuke Liao506e8822017-12-04 16:52:54316def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
317 """Builds and runs target to generate the coverage profile data.
318
319 Args:
320 targets: A list of targets to build with coverage instrumentation.
321 commands: A list of commands used to run the targets.
322 jobs_count: Number of jobs to run in parallel for building. If None, a
323 default value is derived based on CPUs availability.
324
325 Returns:
326 A relative path to the generated profdata file.
327 """
328 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02329 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59330 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02331 coverage_profdata_file_path = (
332 _CreateCoverageProfileDataFromTargetProfDataFiles(
333 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54334
Abhishek Aryac19bc5ef2018-05-04 22:10:02335 for target_profdata_file_path in target_profdata_file_paths:
336 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52337
Abhishek Aryac19bc5ef2018-05-04 22:10:02338 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54339
340
341def _BuildTargets(targets, jobs_count):
342 """Builds target with Clang coverage instrumentation.
343
344 This function requires current working directory to be the root of checkout.
345
346 Args:
347 targets: A list of targets to build with coverage instrumentation.
348 jobs_count: Number of jobs to run in parallel for compilation. If None, a
349 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54350 """
Abhishek Aryafb70b532018-05-06 17:47:40351 logging.info('Building %s.', str(targets))
Brent McBrideb25b177a42020-05-11 18:13:06352 autoninja = 'autoninja'
353 if coverage_utils.GetHostPlatform() == 'win':
354 autoninja += '.bat'
Yuke Liao506e8822017-12-04 16:52:54355
Brent McBrideb25b177a42020-05-11 18:13:06356 subprocess_cmd = [autoninja, '-C', BUILD_DIR]
Yuke Liao506e8822017-12-04 16:52:54357 if jobs_count is not None:
358 subprocess_cmd.append('-j' + str(jobs_count))
359
360 subprocess_cmd.extend(targets)
361 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40362 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54363
364
Abhishek Aryac19bc5ef2018-05-04 22:10:02365def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54366 """Runs commands and returns the relative paths to the profraw data files.
367
368 Args:
369 targets: A list of targets built with coverage instrumentation.
370 commands: A list of commands used to run the targets.
371
372 Returns:
373 A list of relative paths to the generated profraw data files.
374 """
Abhishek Aryafb70b532018-05-06 17:47:40375 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10376
Yuke Liao506e8822017-12-04 16:52:54377 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18378 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
379 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54380 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18381 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48382
383 # Ensure that logs directory exists.
384 if not os.path.exists(_GetLogsDirectoryPath()):
385 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54386
Abhishek Aryac19bc5ef2018-05-04 22:10:02387 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10388
Yuke Liaod4a9865202018-01-12 23:17:52389 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54390 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48391 output_file_name = os.extsep.join([target + '_output', 'log'])
392 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10393
Abhishek Aryac19bc5ef2018-05-04 22:10:02394 profdata_file_path = None
Prakhar65d63832021-06-16 23:01:37395 for _ in range(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40396 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02397 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10398
Abhishek Aryac19bc5ef2018-05-04 22:10:02399 if _IsIOSCommand(command):
400 # On iOS platform, due to lack of write permissions, profraw files are
401 # generated outside of the OUTPUT_DIR, and the exact paths are contained
402 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35403 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02404 else:
405 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35406 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02407
408 profraw_file_paths = []
409 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57410 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Ben Joyce88282362021-01-29 23:53:31411 elif _IsAndroid():
412 android_coverage_dir = os.path.join(BUILD_DIR, 'coverage')
413 for r, _, files in os.walk(android_coverage_dir):
414 for f in files:
415 if f.endswith(PROFRAW_FILE_EXTENSION):
416 profraw_file_paths.append(os.path.join(r, f))
Abhishek Aryac19bc5ef2018-05-04 22:10:02417 else:
Max Moroz1de68d72018-08-21 13:38:18418 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02419 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48420 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18421 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02422
423 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40424 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04425 'please make sure the binary exists, is properly instrumented and '
426 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02427
Yuke Liao9c2c70b2018-05-23 15:37:57428 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18429 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
430 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57431
Abhishek Aryac19bc5ef2018-05-04 22:10:02432 try:
433 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
434 target, profraw_file_paths)
435 break
436 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04437 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02438 finally:
439 # Remove profraw files now so that they are not used in next iteration.
440 for profraw_file_path in profraw_file_paths:
441 os.remove(profraw_file_path)
442
443 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04444 'Failed to merge target "%s" profraw files after %d retries. %s' %
445 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02446 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54447
Abhishek Aryafb70b532018-05-06 17:47:40448 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10449
Abhishek Aryac19bc5ef2018-05-04 22:10:02450 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54451
452
Abhishek Arya03911092018-05-21 16:42:35453def _GetEnvironmentVars(profraw_file_path):
454 """Return environment vars for subprocess, given a profraw file path."""
455 env = os.environ.copy()
456 env.update({
457 'LLVM_PROFILE_FILE': profraw_file_path,
458 'PATH': _GetPathWithLLVMSymbolizerDir()
459 })
460 return env
461
462
Sajjad Mirza0b96e002020-11-10 19:32:55463def _SplitCommand(command):
464 """Split a command string into parts in a platform-specific way."""
465 if coverage_utils.GetHostPlatform() == 'win':
466 return command.split()
467 return shlex.split(command)
468
469
Abhishek Arya03911092018-05-21 16:42:35470def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10471 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52472 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01473 #
Max Morozd73e45f2018-04-24 18:32:47474 # "%p" expands out to the process ID. It's not used by this scripts due to:
475 # 1) If a target program spawns too many processess, it may exhaust all disk
476 # space available. For example, unit_tests writes thousands of .profraw
477 # files each of size 1GB+.
478 # 2) If a target binary uses shared libraries, coverage profile data for them
479 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01480 #
Yuke Liaod4a9865202018-01-12 23:17:52481 # "%Nm" expands out to the instrumented binary's signature. When this pattern
482 # is specified, the runtime creates a pool of N raw profiles which are used
483 # for on-line profile merging. The runtime takes care of selecting a raw
484 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52485 # N must be between 1 and 9. The merge pool specifier can only occur once per
486 # filename pattern.
487 #
Max Morozd73e45f2018-04-24 18:32:47488 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01489 #
Max Morozd73e45f2018-04-24 18:32:47490 # For other cases, "%4m" is chosen as it creates some level of parallelism,
491 # but it's not too big to consume too much computing resource or disk space.
492 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59493 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01494 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18495 expected_profraw_file_path = os.path.join(
496 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
497 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24498 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
499 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54500
Yuke Liaoa0c8c2f2018-02-28 20:14:10501 try:
Max Moroz7c5354f2018-05-06 00:03:48502 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35503 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55504 subprocess.check_call(_SplitCommand(command),
505 stdout=output_file_handle,
506 stderr=subprocess.STDOUT,
507 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10508 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35509 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10510
Abhishek Arya03911092018-05-21 16:42:35511 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10512
513
Yuke Liao27349c92018-03-22 21:10:01514def _IsFuzzerTarget(target):
515 """Returns true if the target is a fuzzer target."""
516 build_args = _GetBuildArgs()
517 use_libfuzzer = ('use_libfuzzer' in build_args and
518 build_args['use_libfuzzer'] == 'true')
519 return use_libfuzzer and target.endswith('_fuzzer')
520
521
Abhishek Arya03911092018-05-21 16:42:35522def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10523 """Runs a single iOS command and generates a profraw data file.
524
525 iOS application doesn't have write access to folders outside of the app, so
526 it's impossible to instruct the app to flush the profraw data file to the
527 desired location. The profraw data file will be generated somewhere within the
528 application's Documents folder, and the full path can be obtained by parsing
529 the output.
530 """
Yuke Liaob2926832018-03-02 17:34:29531 assert _IsIOSCommand(command)
532
533 # After running tests, iossim generates a profraw data file, it won't be
534 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
535 # checkout.
536 iossim_profraw_file_path = os.path.join(
537 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24538 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
539 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10540
541 try:
Abhishek Arya03911092018-05-21 16:42:35542 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55543 subprocess.check_call(_SplitCommand(command),
544 stdout=output_file_handle,
545 stderr=subprocess.STDOUT,
546 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10547 except subprocess.CalledProcessError as e:
548 # iossim emits non-zero return code even if tests run successfully, so
549 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35550 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10551
Abhishek Arya03911092018-05-21 16:42:35552 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10553
554
555def _GetProfrawDataFileByParsingOutput(output):
556 """Returns the path to the profraw data file obtained by parsing the output.
557
558 The output of running the test target has no format, but it is guaranteed to
559 have a single line containing the path to the generated profraw data file.
560 NOTE: This should only be called when target os is iOS.
561 """
Yuke Liaob2926832018-03-02 17:34:29562 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10563
Yuke Liaob2926832018-03-02 17:34:29564 output_by_lines = ''.join(output).splitlines()
565 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10566
567 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29568 result = profraw_file_pattern.match(line)
569 if result:
570 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10571
572 assert False, ('No profraw data file was generated, did you call '
573 'coverage_util::ConfigureCoverageReportPath() in test setup? '
574 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54575
576
Abhishek Aryac19bc5ef2018-05-04 22:10:02577def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
578 """Returns a relative path to coverage profdata file by merging target
579 profdata files.
Yuke Liao506e8822017-12-04 16:52:54580
581 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02582 profdata_file_paths: A list of relative paths to the profdata data files
583 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54584
585 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02586 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54587
588 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02589 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54590 """
Abhishek Aryafb70b532018-05-06 17:47:40591 logging.info('Creating the coverage profile data file.')
592 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48593 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54594 try:
Abhishek Arya1ec832c2017-12-05 18:06:59595 subprocess_cmd = [
596 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
597 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02598 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01599
600 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18601 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02602 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04603 logging.error(
604 'Failed to merge target profdata files to create coverage profdata. %s',
605 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02606 raise error
607
Abhishek Aryafb70b532018-05-06 17:47:40608 logging.debug('Finished merging target profdata files.')
609 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02610 profdata_file_path)
611 return profdata_file_path
612
613
614def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
615 """Returns a relative path to target profdata file by merging target
616 profraw files.
617
618 Args:
619 profraw_file_paths: A list of relative paths to the profdata data files
620 that are to be merged.
621
622 Returns:
623 A relative path to the merged coverage profdata file.
624
625 Raises:
626 CalledProcessError: An error occurred merging profdata files.
627 """
Abhishek Aryafb70b532018-05-06 17:47:40628 logging.info('Creating target profile data file.')
629 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02630 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
631
632 try:
633 subprocess_cmd = [
634 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
635 ]
Yuke Liao506e8822017-12-04 16:52:54636 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01637 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18638 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54639 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04640 logging.error(
641 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54642 raise error
643
Abhishek Aryafb70b532018-05-06 17:47:40644 logging.debug('Finished merging target profraw files.')
645 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10646 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54647 return profdata_file_path
648
649
Yuke Liao0e4c8682018-04-18 21:06:59650def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
651 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33652 """Generates per file coverage summary using "llvm-cov export" command."""
653 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
654 # [[-object BIN]] [SOURCES].
655 # NOTE: For object files, the first one is specified as a positional argument,
656 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10657 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40658 'export -summary-only" command.')
Sajjad Mirza07f52332020-11-11 01:50:47659 for path in binary_paths:
660 if not os.path.exists(path):
661 logging.error("Binary %s does not exist", path)
Yuke Liaoea228d02018-01-05 19:10:33662 subprocess_cmd = [
663 LLVM_COV_PATH, 'export', '-summary-only',
Choongwoo Han56752522021-06-10 17:38:34664 '-compilation-dir={}'.format(BUILD_DIR),
Yuke Liaoea228d02018-01-05 19:10:33665 '-instr-profile=' + profdata_file_path, binary_paths[0]
666 ]
667 subprocess_cmd.extend(
668 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29669 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33670 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59671 if ignore_filename_regex:
672 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33673
Max Moroz7c5354f2018-05-06 00:03:48674 export_output = subprocess.check_output(subprocess_cmd)
675
676 # Write output on the disk to be used by code coverage bot.
Prakhar65d63832021-06-16 23:01:37677 with open(_GetSummaryFilePath(), 'wb') as f:
Max Moroz7c5354f2018-05-06 00:03:48678 f.write(export_output)
679
Max Moroz1de68d72018-08-21 13:38:18680 return export_output
Yuke Liaoea228d02018-01-05 19:10:33681
682
Yuke Liaob2926832018-03-02 17:34:29683def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
684 """Appends -arch arguments to the command list if it's ios platform.
685
686 iOS binaries are universal binaries, and require specifying the architecture
687 to use, and one architecture needs to be specified for each binary.
688 """
689 if _IsIOS():
690 cmd_list.extend(['-arch=x86_64'] * num_archs)
691
692
Yuke Liao506e8822017-12-04 16:52:54693def _GetBinaryPath(command):
694 """Returns a relative path to the binary to be run by the command.
695
Yuke Liao545db322018-02-15 17:12:01696 Currently, following types of commands are supported (e.g. url_unittests):
697 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
698 2. Use xvfb.
699 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
700 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37701 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
702 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10703 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37704 <iossim_arguments> -c <app_arguments>
705 out/Coverage-iphonesimulator/url_unittests.app"
706
Yuke Liao506e8822017-12-04 16:52:54707 Args:
708 command: A command used to run a target.
709
710 Returns:
711 A relative path to the binary.
712 """
Yuke Liao545db322018-02-15 17:12:01713 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
714
Sajjad Mirza0b96e002020-11-10 19:32:55715 command_parts = _SplitCommand(command)
Yuke Liao545db322018-02-15 17:12:01716 if os.path.basename(command_parts[0]) == 'python':
717 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40718 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01719 return command_parts[2]
720
721 if os.path.basename(command_parts[0]) == xvfb_script_name:
722 return command_parts[1]
723
Yuke Liaob2926832018-03-02 17:34:29724 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10725 # For a given application bundle, the binary resides in the bundle and has
726 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02727 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10728 app_name = os.path.splitext(os.path.basename(app_path))[0]
729 return os.path.join(app_path, app_name)
730
Sajjad Mirza07f52332020-11-11 01:50:47731 if coverage_utils.GetHostPlatform() == 'win' \
732 and not command_parts[0].endswith('.exe'):
733 return command_parts[0] + '.exe'
734
Yuke Liaob2926832018-03-02 17:34:29735 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54736
737
Yuke Liaob2926832018-03-02 17:34:29738def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10739 """Returns true if command is used to run tests on iOS platform."""
Sajjad Mirza0b96e002020-11-10 19:32:55740 return os.path.basename(_SplitCommand(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10741
742
Yuke Liao95d13d72017-12-07 18:18:50743def _VerifyTargetExecutablesAreInBuildDirectory(commands):
744 """Verifies that the target executables specified in the commands are inside
745 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54746 for command in commands:
747 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18748 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35749 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50750 'Target executable "%s" in command: "%s" is outside of '
751 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54752
753
754def _ValidateBuildingWithClangCoverage():
755 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20756 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54757
758 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
759 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59760 assert False, ('\'{} = true\' is required in args.gn.'
761 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54762
763
Yuke Liaoc60b2d02018-03-02 21:40:43764def _ValidateCurrentPlatformIsSupported():
765 """Asserts that this script suports running on the current platform"""
766 target_os = _GetTargetOS()
767 if target_os:
768 current_platform = target_os
769 else:
Max Moroz1de68d72018-08-21 13:38:18770 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43771
Ben Joyce88282362021-01-29 23:53:31772 supported_platforms = ['android', 'chromeos', 'ios', 'linux', 'mac', 'win']
773 assert current_platform in supported_platforms, ('Coverage is only'
774 'supported on %s' %
775 supported_platforms)
Yuke Liaoc60b2d02018-03-02 21:40:43776
777
Yuke Liao80afff32018-03-07 01:26:20778def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54779 """Parses args.gn file and returns results as a dictionary.
780
781 Returns:
782 A dictionary representing the build args.
783 """
Yuke Liao80afff32018-03-07 01:26:20784 global _BUILD_ARGS
785 if _BUILD_ARGS is not None:
786 return _BUILD_ARGS
787
788 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54789 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
790 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
791 'missing args.gn file.' % BUILD_DIR)
792 with open(build_args_path) as build_args_file:
793 build_args_lines = build_args_file.readlines()
794
Yuke Liao506e8822017-12-04 16:52:54795 for build_arg_line in build_args_lines:
796 build_arg_without_comments = build_arg_line.split('#')[0]
797 key_value_pair = build_arg_without_comments.split('=')
798 if len(key_value_pair) != 2:
799 continue
800
801 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43802
803 # Values are wrapped within a pair of double-quotes, so remove the leading
804 # and trailing double-quotes.
805 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20806 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54807
Yuke Liao80afff32018-03-07 01:26:20808 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54809
810
Abhishek Arya16f059a2017-12-07 17:47:32811def _VerifyPathsAndReturnAbsolutes(paths):
812 """Verifies that the paths specified in |paths| exist and returns absolute
813 versions.
Yuke Liao66da1732017-12-05 22:19:42814
815 Args:
816 paths: A list of files or directories.
817 """
Abhishek Arya16f059a2017-12-07 17:47:32818 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42819 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32820 absolute_path = os.path.join(SRC_ROOT_PATH, path)
821 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
822
823 absolute_paths.append(absolute_path)
824
825 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42826
827
Abhishek Arya64636af2018-05-04 14:42:13828def _GetBinaryPathsFromTargets(targets, build_dir):
829 """Return binary paths from target names."""
Ben Joyce88282362021-01-29 23:53:31830 # TODO(crbug.com/899974): Derive output binary from target build definitions
831 # rather than assuming that it is always the same name.
Abhishek Arya64636af2018-05-04 14:42:13832 binary_paths = []
833 for target in targets:
834 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18835 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13836 binary_path += '.exe'
837
838 if os.path.exists(binary_path):
839 binary_paths.append(binary_path)
840 else:
841 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40842 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13843 os.path.basename(binary_path))
844
845 return binary_paths
846
847
Abhishek Arya03911092018-05-21 16:42:35848def _GetCommandForWebTests(arguments):
849 """Return command to run for blink web tests."""
Dirk Pranke34c093a42021-03-25 19:19:05850 cpu_count = multiprocessing.cpu_count()
851 if sys.platform == 'win32':
852 # TODO(crbug.com/1190269) - we can't use more than 56
853 # cores on Windows or Python3 may hang.
854 cpu_count = min(cpu_count, 56)
855 cpu_count = max(1, cpu_count // 2)
856
Abhishek Arya03911092018-05-21 16:42:35857 command_list = [
858 'python', 'testing/xvfb.py', 'python',
859 'third_party/blink/tools/run_web_tests.py',
860 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24861 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Robert Ma339fc472018-12-14 21:22:42862 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Dirk Pranke34c093a42021-03-25 19:19:05863 '--child-processes=%d' % cpu_count, '--disable-breakpad',
864 '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35865 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
866 ]
867 if arguments.strip():
868 command_list.append(arguments)
869 return ' '.join(command_list)
870
871
Ben Joyce88282362021-01-29 23:53:31872def _GetBinaryPathsForAndroid(targets):
873 """Return binary paths used when running android tests."""
874 # TODO(crbug.com/899974): Implement approach that doesn't assume .so file is
875 # based on the target's name.
876 android_binaries = set()
877 for target in targets:
878 so_library_path = os.path.join(BUILD_DIR, 'lib.unstripped',
879 'lib%s__library.so' % target)
880 if os.path.exists(so_library_path):
881 android_binaries.add(so_library_path)
882
883 return list(android_binaries)
884
885
Abhishek Arya03911092018-05-21 16:42:35886def _GetBinaryPathForWebTests():
887 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18888 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35889 if host_platform == 'win':
890 return os.path.join(BUILD_DIR, 'content_shell.exe')
891 elif host_platform == 'linux':
892 return os.path.join(BUILD_DIR, 'content_shell')
893 elif host_platform == 'mac':
894 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
895 'Content Shell')
896 else:
897 assert False, 'This platform is not supported for web tests.'
898
899
Abhishek Aryae5811afa2018-05-24 03:56:01900def _SetupOutputDir():
901 """Setup output directory."""
902 if os.path.exists(OUTPUT_DIR):
903 shutil.rmtree(OUTPUT_DIR)
904
905 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18906 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01907
908
Yuke Liaoabfbba42019-06-11 16:03:59909def _SetMacXcodePath():
910 """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
911 if sys.platform != 'darwin':
912 return
913
914 xcode_path = os.path.join(SRC_ROOT_PATH, 'build', 'mac_files', 'Xcode.app')
915 if os.path.exists(xcode_path):
916 os.environ['DEVELOPER_DIR'] = xcode_path
917
918
Yuke Liao506e8822017-12-04 16:52:54919def _ParseCommandArguments():
920 """Adds and parses relevant arguments for tool comands.
921
922 Returns:
923 A dictionary representing the arguments.
924 """
925 arg_parser = argparse.ArgumentParser()
926 arg_parser.usage = __doc__
927
Abhishek Arya1ec832c2017-12-05 18:06:59928 arg_parser.add_argument(
929 '-b',
930 '--build-dir',
931 type=str,
932 required=True,
933 help='The build directory, the path needs to be relative to the root of '
934 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54935
Abhishek Arya1ec832c2017-12-05 18:06:59936 arg_parser.add_argument(
937 '-o',
938 '--output-dir',
939 type=str,
940 required=True,
941 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54942
Abhishek Arya1ec832c2017-12-05 18:06:59943 arg_parser.add_argument(
944 '-c',
945 '--command',
946 action='append',
Abhishek Arya64636af2018-05-04 14:42:13947 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59948 help='Commands used to run test targets, one test target needs one and '
949 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13950 'current working directory is the root of the checkout. This option is '
951 'incompatible with -p/--profdata-file option.')
952
953 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35954 '-wt',
955 '--web-tests',
956 nargs='?',
957 type=str,
958 const=' ',
959 required=False,
960 help='Run blink web tests. Support passing arguments to run_web_tests.py')
961
962 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13963 '-p',
964 '--profdata-file',
965 type=str,
966 required=False,
967 help='Path to profdata file to use for generating code coverage reports. '
968 'This can be useful if you generated the profdata file seperately in '
969 'your own test harness. This option is ignored if run command(s) are '
970 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54971
Abhishek Arya1ec832c2017-12-05 18:06:59972 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42973 '-f',
974 '--filters',
975 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32976 required=False,
Yuke Liao66da1732017-12-05 22:19:42977 help='Directories or files to get code coverage for, and all files under '
978 'the directories are included recursively.')
979
980 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59981 '-i',
982 '--ignore-filename-regex',
983 type=str,
984 help='Skip source code files with file paths that match the given '
985 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
986 'to exclude files in third_party/ and out/ folders from the report.')
987
988 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32989 '--no-file-view',
990 action='store_true',
991 help='Don\'t generate the file view in the coverage report. When there '
992 'are large number of html files, the file view becomes heavy and may '
993 'cause the browser to freeze, and this argument comes handy.')
994
995 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18996 '--no-component-view',
997 action='store_true',
998 help='Don\'t generate the component view in the coverage report.')
999
1000 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:401001 '--coverage-tools-dir',
1002 type=str,
1003 help='Path of the directory where LLVM coverage tools (llvm-cov, '
1004 'llvm-profdata) exist. This should be only needed if you are testing '
1005 'against a custom built clang revision. Otherwise, we pick coverage '
1006 'tools automatically from your current source checkout.')
1007
1008 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:591009 '-j',
1010 '--jobs',
1011 type=int,
1012 default=None,
1013 help='Run N jobs to build in parallel. If not specified, a default value '
Max Moroz06576292019-01-03 19:22:521014 'will be derived based on CPUs and goma availability. Please refer to '
1015 '\'autoninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:541016
Abhishek Arya1ec832c2017-12-05 18:06:591017 arg_parser.add_argument(
Sahel Sharify38cabdc2020-01-16 00:40:011018 '--format',
1019 type=str,
1020 default='html',
Akekawit Jitprasertf9cb6622021-08-24 17:48:021021 help='Output format of the "llvm-cov show/export" command. The '
1022 'supported formats are "text", "html" and "lcov".')
Sahel Sharify38cabdc2020-01-16 00:40:011023
1024 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:101025 '-v',
1026 '--verbose',
1027 action='store_true',
1028 help='Prints additional output for diagnostics.')
1029
1030 arg_parser.add_argument(
1031 '-l', '--log_file', type=str, help='Redirects logs to a file.')
1032
1033 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:021034 'targets',
1035 nargs='+',
1036 help='The names of the test targets to run. If multiple run commands are '
1037 'specified using the -c/--command option, then the order of targets and '
1038 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:541039
1040 args = arg_parser.parse_args()
1041 return args
1042
1043
1044def Main():
1045 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:401046
Abhishek Arya64636af2018-05-04 14:42:131047 # Change directory to source root to aid in relative paths calculations.
1048 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:111049
pasthanaa4844112020-05-21 18:03:551050 # Setup coverage binaries even when script is called with empty params. This
1051 # is used by coverage bot for initial setup.
1052 if len(sys.argv) == 1:
Choongwoo Hanbd1aa952021-06-09 22:25:381053 subprocess.check_call([
Akekawit Jitprasert928671e2021-09-20 18:40:581054 sys.executable, 'tools/clang/scripts/update.py', '--package',
1055 'coverage_tools'
Choongwoo Hanbd1aa952021-06-09 22:25:381056 ])
pasthanaa4844112020-05-21 18:03:551057 print(__doc__)
1058 return
1059
Yuke Liao506e8822017-12-04 16:52:541060 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:181061 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:401062 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:131063
Yuke Liao506e8822017-12-04 16:52:541064 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:181065 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:011066
Yuke Liao506e8822017-12-04 16:52:541067 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:181068 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:541069
Abhishek Arya03911092018-05-21 16:42:351070 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:131071 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:351072 'provide prof-data file as input using -p/--profdata-file option OR '
1073 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:431074
Abhishek Arya64636af2018-05-04 14:42:131075 assert not args.command or (len(args.targets) == len(args.command)), (
1076 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:431077
Abhishek Arya1ec832c2017-12-05 18:06:591078 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:401079 'Build directory: "%s" doesn\'t exist. '
1080 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:131081
Yuke Liaoc60b2d02018-03-02 21:40:431082 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:541083 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:321084
1085 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:421086 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:321087 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:421088
Abhishek Aryae5811afa2018-05-24 03:56:011089 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:541090
Abhishek Arya03911092018-05-21 16:42:351091 # Get .profdata file and list of binary paths.
1092 if args.web_tests:
1093 commands = [_GetCommandForWebTests(args.web_tests)]
1094 profdata_file_path = _CreateCoverageProfileDataForTargets(
1095 args.targets, commands, args.jobs)
1096 binary_paths = [_GetBinaryPathForWebTests()]
1097 elif args.command:
1098 for i in range(len(args.command)):
1099 assert not 'run_web_tests.py' in args.command[i], (
1100 'run_web_tests.py is not supported via --command argument. '
1101 'Please use --run-web-tests argument instead.')
1102
Abhishek Arya64636af2018-05-04 14:42:131103 # A list of commands are provided. Run them to generate profdata file, and
1104 # create a list of binary paths from parsing commands.
1105 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1106 profdata_file_path = _CreateCoverageProfileDataForTargets(
1107 args.targets, args.command, args.jobs)
1108 binary_paths = [_GetBinaryPath(command) for command in args.command]
1109 else:
1110 # An input prof-data file is already provided. Just calculate binary paths.
1111 profdata_file_path = args.profdata_file
1112 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331113
Erik Chen283b92c72019-07-22 16:37:391114 # If the checkout uses the hermetic xcode binaries, then otool must be
1115 # directly invoked. The indirection via /usr/bin/otool won't work unless
1116 # there's an actual system install of Xcode.
1117 otool_path = None
1118 if sys.platform == 'darwin':
1119 hermetic_otool_path = os.path.join(
1120 SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents',
1121 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin',
1122 'otool')
1123 if os.path.exists(hermetic_otool_path):
1124 otool_path = hermetic_otool_path
Ben Joyce88282362021-01-29 23:53:311125
1126 if _IsAndroid():
1127 binary_paths = _GetBinaryPathsForAndroid(args.targets)
1128 elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
Brent McBrideb25b177a42020-05-11 18:13:061129 binary_paths.extend(
1130 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
Abhishek Arya78120bc2018-05-07 20:53:541131
Akekawit Jitprasertf9cb6622021-08-24 17:48:021132 assert args.format in ['html', 'lcov', 'text'], (
1133 '%s is not a valid output format for "llvm-cov show/export". Only '
1134 '"text", "html" and "lcov" formats are supported.' % (args.format))
Sahel Sharify38cabdc2020-01-16 00:40:011135 logging.info('Generating code coverage report in %s (this can take a while '
1136 'depending on size of target!).' % (args.format))
Max Moroz1de68d72018-08-21 13:38:181137 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591138 binary_paths, profdata_file_path, absolute_filter_paths,
1139 args.ignore_filename_regex)
Akekawit Jitprasertf9cb6622021-08-24 17:48:021140
1141 if args.format == 'lcov':
1142 _GeneratePerFileLineByLineCoverageInLcov(
1143 binary_paths, profdata_file_path, absolute_filter_paths,
1144 args.ignore_filename_regex)
1145 return
1146
Sahel Sharify38cabdc2020-01-16 00:40:011147 _GeneratePerFileLineByLineCoverageInFormat(
1148 binary_paths, profdata_file_path, absolute_filter_paths,
1149 args.ignore_filename_regex, args.format)
Max Moroz1de68d72018-08-21 13:38:181150 component_mappings = None
1151 if not args.no_component_view:
Choongwoo Hanbd1aa952021-06-09 22:25:381152 component_mappings = json.load(urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371153
Max Moroz1de68d72018-08-21 13:38:181154 # Call prepare here.
1155 processor = coverage_utils.CoverageReportPostProcessor(
1156 OUTPUT_DIR,
1157 SRC_ROOT_PATH,
1158 per_file_summary_data,
1159 no_component_view=args.no_component_view,
1160 no_file_view=args.no_file_view,
1161 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371162
Sahel Sharify38cabdc2020-01-16 00:40:011163 if args.format == 'html':
1164 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541165
Abhishek Arya1ec832c2017-12-05 18:06:591166
Yuke Liao506e8822017-12-04 16:52:541167if __name__ == '__main__':
1168 sys.exit(Main())