blob: 9c519a92473f51e333f099482fc3c9640993f19a [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([
166 'python', 'tools/clang/scripts/update.py', '--package', 'coverage_tools'
167 ])
Brent McBrideb25b177a42020-05-11 18:13:06168
169 if coverage_utils.GetHostPlatform() == 'win':
170 LLVM_COV_PATH += '.exe'
171 LLVM_PROFDATA_PATH += '.exe'
Yuke Liao082e99632018-05-18 15:40:40172
173 coverage_tools_exist = (
174 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
175 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
176 'both \'%s\' and \'%s\' exist.') % (
177 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
178
Abhishek Arya2f261182019-04-24 17:06:45179
Abhishek Arya1c97ea542018-05-10 03:53:19180def _GetPathWithLLVMSymbolizerDir():
181 """Add llvm-symbolizer directory to path for symbolized stacks."""
182 path = os.getenv('PATH')
183 dirs = path.split(os.pathsep)
184 if LLVM_BIN_DIR in dirs:
185 return path
186
187 return path + os.pathsep + LLVM_BIN_DIR
188
189
Yuke Liaoc60b2d02018-03-02 21:40:43190def _GetTargetOS():
191 """Returns the target os specified in args.gn file.
192
193 Returns an empty string is target_os is not specified.
194 """
Yuke Liao80afff32018-03-07 01:26:20195 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43196 return build_args['target_os'] if 'target_os' in build_args else ''
197
198
Ben Joyce88282362021-01-29 23:53:31199def _IsAndroid():
200 """Returns true if the target_os specified in args.gn file is android"""
201 return _GetTargetOS() == 'android'
202
203
Yuke Liaob2926832018-03-02 17:34:29204def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10205 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43206 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10207
208
Sahel Sharify38cabdc2020-01-16 00:40:01209def _GeneratePerFileLineByLineCoverageInFormat(binary_paths, profdata_file_path,
210 filters, ignore_filename_regex,
211 output_format):
212 """Generates per file line-by-line coverage in html or text using
213 'llvm-cov show'.
Yuke Liao506e8822017-12-04 16:52:54214
Sahel Sharify38cabdc2020-01-16 00:40:01215 For a file with absolute path /a/b/x.cc, a html/txt report is generated as:
216 OUTPUT_DIR/coverage/a/b/x.cc.[html|txt]. For html format, an index html file
217 is also generated as: OUTPUT_DIR/index.html.
Yuke Liao506e8822017-12-04 16:52:54218
219 Args:
220 binary_paths: A list of paths to the instrumented binaries.
221 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42222 filters: A list of directories and files to get coverage for.
Sahel Sharify38cabdc2020-01-16 00:40:01223 ignore_filename_regex: A regular expression for skipping source code files
224 with certain file paths.
225 output_format: The output format of generated report files.
Yuke Liao506e8822017-12-04 16:52:54226 """
Yuke Liao506e8822017-12-04 16:52:54227 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
228 # [[-object BIN]] [SOURCES]
229 # NOTE: For object files, the first one is specified as a positional argument,
230 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10231 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40232 '"llvm-cov show" command.')
Sahel Sharify38cabdc2020-01-16 00:40:01233
Abhishek Arya1ec832c2017-12-05 18:06:59234 subprocess_cmd = [
Sahel Sharify38cabdc2020-01-16 00:40:01235 LLVM_COV_PATH, 'show', '-format={}'.format(output_format),
Choongwoo Han56752522021-06-10 17:38:34236 '-compilation-dir={}'.format(BUILD_DIR),
Abhishek Arya1ec832c2017-12-05 18:06:59237 '-output-dir={}'.format(OUTPUT_DIR),
238 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
239 ]
240 subprocess_cmd.extend(
241 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29242 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18243 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17244 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42245 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59246 if ignore_filename_regex:
247 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
248
Yuke Liao506e8822017-12-04 16:52:54249 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34250
Abhishek Aryafb70b532018-05-06 17:47:40251 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54252
253
Akekawit Jitprasertf9cb6622021-08-24 17:48:02254def _GeneratePerFileLineByLineCoverageInLcov(binary_paths, profdata_file_path, filters,
255 ignore_filename_regex):
256 """Generates per file line-by-line coverage using "llvm-cov export".
257
258 Args:
259 binary_paths: A list of paths to the instrumented binaries.
260 profdata_file_path: A path to the profdata file.
261 filters: A list of directories and files to get coverage for.
262 ignore_filename_regex: A regular expression for skipping source code files
263 with certain file paths.
264 """
265 logging.debug('Generating per file line by line coverage reports using '
266 '"llvm-cov export" command.')
267 for path in binary_paths:
268 if not os.path.exists(path):
269 logging.error("Binary %s does not exist", path)
270 subprocess_cmd = [
271 LLVM_COV_PATH, 'export', '-format=lcov',
272 '-instr-profile=' + profdata_file_path, binary_paths[0]
273 ]
274 subprocess_cmd.extend(
275 ['-object=' + binary_path for binary_path in binary_paths[1:]])
276 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
277 subprocess_cmd.extend(filters)
278 if ignore_filename_regex:
279 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
280
281 # Write output on the disk to be used by code coverage bot.
282 with open(_GetLcovFilePath(), 'w') as f:
283 subprocess.check_call(subprocess_cmd, stdout=f)
284
285 logging.debug('Finished running "llvm-cov export" command.')
286
287
Max Moroz7c5354f2018-05-06 00:03:48288def _GetLogsDirectoryPath():
289 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18290 return os.path.join(
291 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48292
293
294def _GetProfdataFilePath():
295 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18296 return os.path.join(
297 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
298 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48299
300
301def _GetSummaryFilePath():
302 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18303 return os.path.join(
304 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
305 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33306
307
Akekawit Jitprasertf9cb6622021-08-24 17:48:02308def _GetLcovFilePath():
309 """The LCOV file that contains coverage data written by llvm-cov export."""
310 return os.path.join(
311 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
312 LCOV_FILE_NAME)
313
314
Yuke Liao506e8822017-12-04 16:52:54315def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
316 """Builds and runs target to generate the coverage profile data.
317
318 Args:
319 targets: A list of targets to build with coverage instrumentation.
320 commands: A list of commands used to run the targets.
321 jobs_count: Number of jobs to run in parallel for building. If None, a
322 default value is derived based on CPUs availability.
323
324 Returns:
325 A relative path to the generated profdata file.
326 """
327 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02328 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59329 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02330 coverage_profdata_file_path = (
331 _CreateCoverageProfileDataFromTargetProfDataFiles(
332 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54333
Abhishek Aryac19bc5ef2018-05-04 22:10:02334 for target_profdata_file_path in target_profdata_file_paths:
335 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52336
Abhishek Aryac19bc5ef2018-05-04 22:10:02337 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54338
339
340def _BuildTargets(targets, jobs_count):
341 """Builds target with Clang coverage instrumentation.
342
343 This function requires current working directory to be the root of checkout.
344
345 Args:
346 targets: A list of targets to build with coverage instrumentation.
347 jobs_count: Number of jobs to run in parallel for compilation. If None, a
348 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54349 """
Abhishek Aryafb70b532018-05-06 17:47:40350 logging.info('Building %s.', str(targets))
Brent McBrideb25b177a42020-05-11 18:13:06351 autoninja = 'autoninja'
352 if coverage_utils.GetHostPlatform() == 'win':
353 autoninja += '.bat'
Yuke Liao506e8822017-12-04 16:52:54354
Brent McBrideb25b177a42020-05-11 18:13:06355 subprocess_cmd = [autoninja, '-C', BUILD_DIR]
Yuke Liao506e8822017-12-04 16:52:54356 if jobs_count is not None:
357 subprocess_cmd.append('-j' + str(jobs_count))
358
359 subprocess_cmd.extend(targets)
360 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40361 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54362
363
Abhishek Aryac19bc5ef2018-05-04 22:10:02364def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54365 """Runs commands and returns the relative paths to the profraw data files.
366
367 Args:
368 targets: A list of targets built with coverage instrumentation.
369 commands: A list of commands used to run the targets.
370
371 Returns:
372 A list of relative paths to the generated profraw data files.
373 """
Abhishek Aryafb70b532018-05-06 17:47:40374 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10375
Yuke Liao506e8822017-12-04 16:52:54376 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18377 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
378 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54379 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18380 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48381
382 # Ensure that logs directory exists.
383 if not os.path.exists(_GetLogsDirectoryPath()):
384 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54385
Abhishek Aryac19bc5ef2018-05-04 22:10:02386 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10387
Yuke Liaod4a9865202018-01-12 23:17:52388 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54389 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48390 output_file_name = os.extsep.join([target + '_output', 'log'])
391 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10392
Abhishek Aryac19bc5ef2018-05-04 22:10:02393 profdata_file_path = None
Prakhar65d63832021-06-16 23:01:37394 for _ in range(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40395 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02396 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10397
Abhishek Aryac19bc5ef2018-05-04 22:10:02398 if _IsIOSCommand(command):
399 # On iOS platform, due to lack of write permissions, profraw files are
400 # generated outside of the OUTPUT_DIR, and the exact paths are contained
401 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35402 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02403 else:
404 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35405 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02406
407 profraw_file_paths = []
408 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57409 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Ben Joyce88282362021-01-29 23:53:31410 elif _IsAndroid():
411 android_coverage_dir = os.path.join(BUILD_DIR, 'coverage')
412 for r, _, files in os.walk(android_coverage_dir):
413 for f in files:
414 if f.endswith(PROFRAW_FILE_EXTENSION):
415 profraw_file_paths.append(os.path.join(r, f))
Abhishek Aryac19bc5ef2018-05-04 22:10:02416 else:
Max Moroz1de68d72018-08-21 13:38:18417 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02418 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48419 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18420 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02421
422 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40423 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04424 'please make sure the binary exists, is properly instrumented and '
425 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02426
Yuke Liao9c2c70b2018-05-23 15:37:57427 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18428 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
429 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57430
Abhishek Aryac19bc5ef2018-05-04 22:10:02431 try:
432 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
433 target, profraw_file_paths)
434 break
435 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04436 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02437 finally:
438 # Remove profraw files now so that they are not used in next iteration.
439 for profraw_file_path in profraw_file_paths:
440 os.remove(profraw_file_path)
441
442 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04443 'Failed to merge target "%s" profraw files after %d retries. %s' %
444 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02445 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54446
Abhishek Aryafb70b532018-05-06 17:47:40447 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10448
Abhishek Aryac19bc5ef2018-05-04 22:10:02449 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54450
451
Abhishek Arya03911092018-05-21 16:42:35452def _GetEnvironmentVars(profraw_file_path):
453 """Return environment vars for subprocess, given a profraw file path."""
454 env = os.environ.copy()
455 env.update({
456 'LLVM_PROFILE_FILE': profraw_file_path,
457 'PATH': _GetPathWithLLVMSymbolizerDir()
458 })
459 return env
460
461
Sajjad Mirza0b96e002020-11-10 19:32:55462def _SplitCommand(command):
463 """Split a command string into parts in a platform-specific way."""
464 if coverage_utils.GetHostPlatform() == 'win':
465 return command.split()
466 return shlex.split(command)
467
468
Abhishek Arya03911092018-05-21 16:42:35469def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10470 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52471 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01472 #
Max Morozd73e45f2018-04-24 18:32:47473 # "%p" expands out to the process ID. It's not used by this scripts due to:
474 # 1) If a target program spawns too many processess, it may exhaust all disk
475 # space available. For example, unit_tests writes thousands of .profraw
476 # files each of size 1GB+.
477 # 2) If a target binary uses shared libraries, coverage profile data for them
478 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01479 #
Yuke Liaod4a9865202018-01-12 23:17:52480 # "%Nm" expands out to the instrumented binary's signature. When this pattern
481 # is specified, the runtime creates a pool of N raw profiles which are used
482 # for on-line profile merging. The runtime takes care of selecting a raw
483 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52484 # N must be between 1 and 9. The merge pool specifier can only occur once per
485 # filename pattern.
486 #
Max Morozd73e45f2018-04-24 18:32:47487 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01488 #
Max Morozd73e45f2018-04-24 18:32:47489 # For other cases, "%4m" is chosen as it creates some level of parallelism,
490 # but it's not too big to consume too much computing resource or disk space.
491 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59492 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01493 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18494 expected_profraw_file_path = os.path.join(
495 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
496 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24497 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
498 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54499
Yuke Liaoa0c8c2f2018-02-28 20:14:10500 try:
Max Moroz7c5354f2018-05-06 00:03:48501 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35502 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55503 subprocess.check_call(_SplitCommand(command),
504 stdout=output_file_handle,
505 stderr=subprocess.STDOUT,
506 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10507 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35508 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10509
Abhishek Arya03911092018-05-21 16:42:35510 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10511
512
Yuke Liao27349c92018-03-22 21:10:01513def _IsFuzzerTarget(target):
514 """Returns true if the target is a fuzzer target."""
515 build_args = _GetBuildArgs()
516 use_libfuzzer = ('use_libfuzzer' in build_args and
517 build_args['use_libfuzzer'] == 'true')
518 return use_libfuzzer and target.endswith('_fuzzer')
519
520
Abhishek Arya03911092018-05-21 16:42:35521def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10522 """Runs a single iOS command and generates a profraw data file.
523
524 iOS application doesn't have write access to folders outside of the app, so
525 it's impossible to instruct the app to flush the profraw data file to the
526 desired location. The profraw data file will be generated somewhere within the
527 application's Documents folder, and the full path can be obtained by parsing
528 the output.
529 """
Yuke Liaob2926832018-03-02 17:34:29530 assert _IsIOSCommand(command)
531
532 # After running tests, iossim generates a profraw data file, it won't be
533 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
534 # checkout.
535 iossim_profraw_file_path = os.path.join(
536 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24537 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
538 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10539
540 try:
Abhishek Arya03911092018-05-21 16:42:35541 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55542 subprocess.check_call(_SplitCommand(command),
543 stdout=output_file_handle,
544 stderr=subprocess.STDOUT,
545 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10546 except subprocess.CalledProcessError as e:
547 # iossim emits non-zero return code even if tests run successfully, so
548 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35549 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10550
Abhishek Arya03911092018-05-21 16:42:35551 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10552
553
554def _GetProfrawDataFileByParsingOutput(output):
555 """Returns the path to the profraw data file obtained by parsing the output.
556
557 The output of running the test target has no format, but it is guaranteed to
558 have a single line containing the path to the generated profraw data file.
559 NOTE: This should only be called when target os is iOS.
560 """
Yuke Liaob2926832018-03-02 17:34:29561 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10562
Yuke Liaob2926832018-03-02 17:34:29563 output_by_lines = ''.join(output).splitlines()
564 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10565
566 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29567 result = profraw_file_pattern.match(line)
568 if result:
569 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10570
571 assert False, ('No profraw data file was generated, did you call '
572 'coverage_util::ConfigureCoverageReportPath() in test setup? '
573 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54574
575
Abhishek Aryac19bc5ef2018-05-04 22:10:02576def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
577 """Returns a relative path to coverage profdata file by merging target
578 profdata files.
Yuke Liao506e8822017-12-04 16:52:54579
580 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02581 profdata_file_paths: A list of relative paths to the profdata data files
582 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54583
584 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02585 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54586
587 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02588 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54589 """
Abhishek Aryafb70b532018-05-06 17:47:40590 logging.info('Creating the coverage profile data file.')
591 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48592 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54593 try:
Abhishek Arya1ec832c2017-12-05 18:06:59594 subprocess_cmd = [
595 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
596 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02597 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01598
599 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18600 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02601 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04602 logging.error(
603 'Failed to merge target profdata files to create coverage profdata. %s',
604 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02605 raise error
606
Abhishek Aryafb70b532018-05-06 17:47:40607 logging.debug('Finished merging target profdata files.')
608 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02609 profdata_file_path)
610 return profdata_file_path
611
612
613def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
614 """Returns a relative path to target profdata file by merging target
615 profraw files.
616
617 Args:
618 profraw_file_paths: A list of relative paths to the profdata data files
619 that are to be merged.
620
621 Returns:
622 A relative path to the merged coverage profdata file.
623
624 Raises:
625 CalledProcessError: An error occurred merging profdata files.
626 """
Abhishek Aryafb70b532018-05-06 17:47:40627 logging.info('Creating target profile data file.')
628 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02629 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
630
631 try:
632 subprocess_cmd = [
633 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
634 ]
Yuke Liao506e8822017-12-04 16:52:54635 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01636 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18637 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54638 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04639 logging.error(
640 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54641 raise error
642
Abhishek Aryafb70b532018-05-06 17:47:40643 logging.debug('Finished merging target profraw files.')
644 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10645 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54646 return profdata_file_path
647
648
Yuke Liao0e4c8682018-04-18 21:06:59649def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
650 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33651 """Generates per file coverage summary using "llvm-cov export" command."""
652 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
653 # [[-object BIN]] [SOURCES].
654 # NOTE: For object files, the first one is specified as a positional argument,
655 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10656 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40657 'export -summary-only" command.')
Sajjad Mirza07f52332020-11-11 01:50:47658 for path in binary_paths:
659 if not os.path.exists(path):
660 logging.error("Binary %s does not exist", path)
Yuke Liaoea228d02018-01-05 19:10:33661 subprocess_cmd = [
662 LLVM_COV_PATH, 'export', '-summary-only',
Choongwoo Han56752522021-06-10 17:38:34663 '-compilation-dir={}'.format(BUILD_DIR),
Yuke Liaoea228d02018-01-05 19:10:33664 '-instr-profile=' + profdata_file_path, binary_paths[0]
665 ]
666 subprocess_cmd.extend(
667 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29668 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33669 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59670 if ignore_filename_regex:
671 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33672
Max Moroz7c5354f2018-05-06 00:03:48673 export_output = subprocess.check_output(subprocess_cmd)
674
675 # Write output on the disk to be used by code coverage bot.
Prakhar65d63832021-06-16 23:01:37676 with open(_GetSummaryFilePath(), 'wb') as f:
Max Moroz7c5354f2018-05-06 00:03:48677 f.write(export_output)
678
Max Moroz1de68d72018-08-21 13:38:18679 return export_output
Yuke Liaoea228d02018-01-05 19:10:33680
681
Yuke Liaob2926832018-03-02 17:34:29682def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
683 """Appends -arch arguments to the command list if it's ios platform.
684
685 iOS binaries are universal binaries, and require specifying the architecture
686 to use, and one architecture needs to be specified for each binary.
687 """
688 if _IsIOS():
689 cmd_list.extend(['-arch=x86_64'] * num_archs)
690
691
Yuke Liao506e8822017-12-04 16:52:54692def _GetBinaryPath(command):
693 """Returns a relative path to the binary to be run by the command.
694
Yuke Liao545db322018-02-15 17:12:01695 Currently, following types of commands are supported (e.g. url_unittests):
696 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
697 2. Use xvfb.
698 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
699 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37700 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
701 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10702 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37703 <iossim_arguments> -c <app_arguments>
704 out/Coverage-iphonesimulator/url_unittests.app"
705
Yuke Liao506e8822017-12-04 16:52:54706 Args:
707 command: A command used to run a target.
708
709 Returns:
710 A relative path to the binary.
711 """
Yuke Liao545db322018-02-15 17:12:01712 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
713
Sajjad Mirza0b96e002020-11-10 19:32:55714 command_parts = _SplitCommand(command)
Yuke Liao545db322018-02-15 17:12:01715 if os.path.basename(command_parts[0]) == 'python':
716 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40717 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01718 return command_parts[2]
719
720 if os.path.basename(command_parts[0]) == xvfb_script_name:
721 return command_parts[1]
722
Yuke Liaob2926832018-03-02 17:34:29723 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10724 # For a given application bundle, the binary resides in the bundle and has
725 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02726 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10727 app_name = os.path.splitext(os.path.basename(app_path))[0]
728 return os.path.join(app_path, app_name)
729
Sajjad Mirza07f52332020-11-11 01:50:47730 if coverage_utils.GetHostPlatform() == 'win' \
731 and not command_parts[0].endswith('.exe'):
732 return command_parts[0] + '.exe'
733
Yuke Liaob2926832018-03-02 17:34:29734 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54735
736
Yuke Liaob2926832018-03-02 17:34:29737def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10738 """Returns true if command is used to run tests on iOS platform."""
Sajjad Mirza0b96e002020-11-10 19:32:55739 return os.path.basename(_SplitCommand(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10740
741
Yuke Liao95d13d72017-12-07 18:18:50742def _VerifyTargetExecutablesAreInBuildDirectory(commands):
743 """Verifies that the target executables specified in the commands are inside
744 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54745 for command in commands:
746 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18747 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35748 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50749 'Target executable "%s" in command: "%s" is outside of '
750 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54751
752
753def _ValidateBuildingWithClangCoverage():
754 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20755 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54756
757 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
758 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59759 assert False, ('\'{} = true\' is required in args.gn.'
760 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54761
762
Yuke Liaoc60b2d02018-03-02 21:40:43763def _ValidateCurrentPlatformIsSupported():
764 """Asserts that this script suports running on the current platform"""
765 target_os = _GetTargetOS()
766 if target_os:
767 current_platform = target_os
768 else:
Max Moroz1de68d72018-08-21 13:38:18769 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43770
Ben Joyce88282362021-01-29 23:53:31771 supported_platforms = ['android', 'chromeos', 'ios', 'linux', 'mac', 'win']
772 assert current_platform in supported_platforms, ('Coverage is only'
773 'supported on %s' %
774 supported_platforms)
Yuke Liaoc60b2d02018-03-02 21:40:43775
776
Yuke Liao80afff32018-03-07 01:26:20777def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54778 """Parses args.gn file and returns results as a dictionary.
779
780 Returns:
781 A dictionary representing the build args.
782 """
Yuke Liao80afff32018-03-07 01:26:20783 global _BUILD_ARGS
784 if _BUILD_ARGS is not None:
785 return _BUILD_ARGS
786
787 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54788 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
789 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
790 'missing args.gn file.' % BUILD_DIR)
791 with open(build_args_path) as build_args_file:
792 build_args_lines = build_args_file.readlines()
793
Yuke Liao506e8822017-12-04 16:52:54794 for build_arg_line in build_args_lines:
795 build_arg_without_comments = build_arg_line.split('#')[0]
796 key_value_pair = build_arg_without_comments.split('=')
797 if len(key_value_pair) != 2:
798 continue
799
800 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43801
802 # Values are wrapped within a pair of double-quotes, so remove the leading
803 # and trailing double-quotes.
804 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20805 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54806
Yuke Liao80afff32018-03-07 01:26:20807 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54808
809
Abhishek Arya16f059a2017-12-07 17:47:32810def _VerifyPathsAndReturnAbsolutes(paths):
811 """Verifies that the paths specified in |paths| exist and returns absolute
812 versions.
Yuke Liao66da1732017-12-05 22:19:42813
814 Args:
815 paths: A list of files or directories.
816 """
Abhishek Arya16f059a2017-12-07 17:47:32817 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42818 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32819 absolute_path = os.path.join(SRC_ROOT_PATH, path)
820 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
821
822 absolute_paths.append(absolute_path)
823
824 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42825
826
Abhishek Arya64636af2018-05-04 14:42:13827def _GetBinaryPathsFromTargets(targets, build_dir):
828 """Return binary paths from target names."""
Ben Joyce88282362021-01-29 23:53:31829 # TODO(crbug.com/899974): Derive output binary from target build definitions
830 # rather than assuming that it is always the same name.
Abhishek Arya64636af2018-05-04 14:42:13831 binary_paths = []
832 for target in targets:
833 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18834 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13835 binary_path += '.exe'
836
837 if os.path.exists(binary_path):
838 binary_paths.append(binary_path)
839 else:
840 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40841 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13842 os.path.basename(binary_path))
843
844 return binary_paths
845
846
Abhishek Arya03911092018-05-21 16:42:35847def _GetCommandForWebTests(arguments):
848 """Return command to run for blink web tests."""
Dirk Pranke34c093a42021-03-25 19:19:05849 cpu_count = multiprocessing.cpu_count()
850 if sys.platform == 'win32':
851 # TODO(crbug.com/1190269) - we can't use more than 56
852 # cores on Windows or Python3 may hang.
853 cpu_count = min(cpu_count, 56)
854 cpu_count = max(1, cpu_count // 2)
855
Abhishek Arya03911092018-05-21 16:42:35856 command_list = [
857 'python', 'testing/xvfb.py', 'python',
858 'third_party/blink/tools/run_web_tests.py',
859 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24860 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Robert Ma339fc472018-12-14 21:22:42861 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Dirk Pranke34c093a42021-03-25 19:19:05862 '--child-processes=%d' % cpu_count, '--disable-breakpad',
863 '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35864 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
865 ]
866 if arguments.strip():
867 command_list.append(arguments)
868 return ' '.join(command_list)
869
870
Ben Joyce88282362021-01-29 23:53:31871def _GetBinaryPathsForAndroid(targets):
872 """Return binary paths used when running android tests."""
873 # TODO(crbug.com/899974): Implement approach that doesn't assume .so file is
874 # based on the target's name.
875 android_binaries = set()
876 for target in targets:
877 so_library_path = os.path.join(BUILD_DIR, 'lib.unstripped',
878 'lib%s__library.so' % target)
879 if os.path.exists(so_library_path):
880 android_binaries.add(so_library_path)
881
882 return list(android_binaries)
883
884
Abhishek Arya03911092018-05-21 16:42:35885def _GetBinaryPathForWebTests():
886 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18887 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35888 if host_platform == 'win':
889 return os.path.join(BUILD_DIR, 'content_shell.exe')
890 elif host_platform == 'linux':
891 return os.path.join(BUILD_DIR, 'content_shell')
892 elif host_platform == 'mac':
893 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
894 'Content Shell')
895 else:
896 assert False, 'This platform is not supported for web tests.'
897
898
Abhishek Aryae5811afa2018-05-24 03:56:01899def _SetupOutputDir():
900 """Setup output directory."""
901 if os.path.exists(OUTPUT_DIR):
902 shutil.rmtree(OUTPUT_DIR)
903
904 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18905 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01906
907
Yuke Liaoabfbba42019-06-11 16:03:59908def _SetMacXcodePath():
909 """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
910 if sys.platform != 'darwin':
911 return
912
913 xcode_path = os.path.join(SRC_ROOT_PATH, 'build', 'mac_files', 'Xcode.app')
914 if os.path.exists(xcode_path):
915 os.environ['DEVELOPER_DIR'] = xcode_path
916
917
Yuke Liao506e8822017-12-04 16:52:54918def _ParseCommandArguments():
919 """Adds and parses relevant arguments for tool comands.
920
921 Returns:
922 A dictionary representing the arguments.
923 """
924 arg_parser = argparse.ArgumentParser()
925 arg_parser.usage = __doc__
926
Abhishek Arya1ec832c2017-12-05 18:06:59927 arg_parser.add_argument(
928 '-b',
929 '--build-dir',
930 type=str,
931 required=True,
932 help='The build directory, the path needs to be relative to the root of '
933 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54934
Abhishek Arya1ec832c2017-12-05 18:06:59935 arg_parser.add_argument(
936 '-o',
937 '--output-dir',
938 type=str,
939 required=True,
940 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54941
Abhishek Arya1ec832c2017-12-05 18:06:59942 arg_parser.add_argument(
943 '-c',
944 '--command',
945 action='append',
Abhishek Arya64636af2018-05-04 14:42:13946 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59947 help='Commands used to run test targets, one test target needs one and '
948 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13949 'current working directory is the root of the checkout. This option is '
950 'incompatible with -p/--profdata-file option.')
951
952 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35953 '-wt',
954 '--web-tests',
955 nargs='?',
956 type=str,
957 const=' ',
958 required=False,
959 help='Run blink web tests. Support passing arguments to run_web_tests.py')
960
961 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13962 '-p',
963 '--profdata-file',
964 type=str,
965 required=False,
966 help='Path to profdata file to use for generating code coverage reports. '
967 'This can be useful if you generated the profdata file seperately in '
968 'your own test harness. This option is ignored if run command(s) are '
969 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54970
Abhishek Arya1ec832c2017-12-05 18:06:59971 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42972 '-f',
973 '--filters',
974 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32975 required=False,
Yuke Liao66da1732017-12-05 22:19:42976 help='Directories or files to get code coverage for, and all files under '
977 'the directories are included recursively.')
978
979 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59980 '-i',
981 '--ignore-filename-regex',
982 type=str,
983 help='Skip source code files with file paths that match the given '
984 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
985 'to exclude files in third_party/ and out/ folders from the report.')
986
987 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32988 '--no-file-view',
989 action='store_true',
990 help='Don\'t generate the file view in the coverage report. When there '
991 'are large number of html files, the file view becomes heavy and may '
992 'cause the browser to freeze, and this argument comes handy.')
993
994 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18995 '--no-component-view',
996 action='store_true',
997 help='Don\'t generate the component view in the coverage report.')
998
999 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:401000 '--coverage-tools-dir',
1001 type=str,
1002 help='Path of the directory where LLVM coverage tools (llvm-cov, '
1003 'llvm-profdata) exist. This should be only needed if you are testing '
1004 'against a custom built clang revision. Otherwise, we pick coverage '
1005 'tools automatically from your current source checkout.')
1006
1007 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:591008 '-j',
1009 '--jobs',
1010 type=int,
1011 default=None,
1012 help='Run N jobs to build in parallel. If not specified, a default value '
Max Moroz06576292019-01-03 19:22:521013 'will be derived based on CPUs and goma availability. Please refer to '
1014 '\'autoninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:541015
Abhishek Arya1ec832c2017-12-05 18:06:591016 arg_parser.add_argument(
Sahel Sharify38cabdc2020-01-16 00:40:011017 '--format',
1018 type=str,
1019 default='html',
Akekawit Jitprasertf9cb6622021-08-24 17:48:021020 help='Output format of the "llvm-cov show/export" command. The '
1021 'supported formats are "text", "html" and "lcov".')
Sahel Sharify38cabdc2020-01-16 00:40:011022
1023 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:101024 '-v',
1025 '--verbose',
1026 action='store_true',
1027 help='Prints additional output for diagnostics.')
1028
1029 arg_parser.add_argument(
1030 '-l', '--log_file', type=str, help='Redirects logs to a file.')
1031
1032 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:021033 'targets',
1034 nargs='+',
1035 help='The names of the test targets to run. If multiple run commands are '
1036 'specified using the -c/--command option, then the order of targets and '
1037 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:541038
1039 args = arg_parser.parse_args()
1040 return args
1041
1042
1043def Main():
1044 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:401045
Abhishek Arya64636af2018-05-04 14:42:131046 # Change directory to source root to aid in relative paths calculations.
1047 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:111048
pasthanaa4844112020-05-21 18:03:551049 # Setup coverage binaries even when script is called with empty params. This
1050 # is used by coverage bot for initial setup.
1051 if len(sys.argv) == 1:
Choongwoo Hanbd1aa952021-06-09 22:25:381052 subprocess.check_call([
1053 'python', 'tools/clang/scripts/update.py', '--package', 'coverage_tools'
1054 ])
pasthanaa4844112020-05-21 18:03:551055 print(__doc__)
1056 return
1057
Yuke Liao506e8822017-12-04 16:52:541058 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:181059 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:401060 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:131061
Yuke Liao506e8822017-12-04 16:52:541062 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:181063 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:011064
Yuke Liao506e8822017-12-04 16:52:541065 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:181066 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:541067
Abhishek Arya03911092018-05-21 16:42:351068 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:131069 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:351070 'provide prof-data file as input using -p/--profdata-file option OR '
1071 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:431072
Abhishek Arya64636af2018-05-04 14:42:131073 assert not args.command or (len(args.targets) == len(args.command)), (
1074 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:431075
Abhishek Arya1ec832c2017-12-05 18:06:591076 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:401077 'Build directory: "%s" doesn\'t exist. '
1078 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:131079
Yuke Liaoc60b2d02018-03-02 21:40:431080 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:541081 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:321082
1083 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:421084 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:321085 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:421086
Abhishek Aryae5811afa2018-05-24 03:56:011087 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:541088
Abhishek Arya03911092018-05-21 16:42:351089 # Get .profdata file and list of binary paths.
1090 if args.web_tests:
1091 commands = [_GetCommandForWebTests(args.web_tests)]
1092 profdata_file_path = _CreateCoverageProfileDataForTargets(
1093 args.targets, commands, args.jobs)
1094 binary_paths = [_GetBinaryPathForWebTests()]
1095 elif args.command:
1096 for i in range(len(args.command)):
1097 assert not 'run_web_tests.py' in args.command[i], (
1098 'run_web_tests.py is not supported via --command argument. '
1099 'Please use --run-web-tests argument instead.')
1100
Abhishek Arya64636af2018-05-04 14:42:131101 # A list of commands are provided. Run them to generate profdata file, and
1102 # create a list of binary paths from parsing commands.
1103 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1104 profdata_file_path = _CreateCoverageProfileDataForTargets(
1105 args.targets, args.command, args.jobs)
1106 binary_paths = [_GetBinaryPath(command) for command in args.command]
1107 else:
1108 # An input prof-data file is already provided. Just calculate binary paths.
1109 profdata_file_path = args.profdata_file
1110 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331111
Erik Chen283b92c72019-07-22 16:37:391112 # If the checkout uses the hermetic xcode binaries, then otool must be
1113 # directly invoked. The indirection via /usr/bin/otool won't work unless
1114 # there's an actual system install of Xcode.
1115 otool_path = None
1116 if sys.platform == 'darwin':
1117 hermetic_otool_path = os.path.join(
1118 SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents',
1119 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin',
1120 'otool')
1121 if os.path.exists(hermetic_otool_path):
1122 otool_path = hermetic_otool_path
Ben Joyce88282362021-01-29 23:53:311123
1124 if _IsAndroid():
1125 binary_paths = _GetBinaryPathsForAndroid(args.targets)
1126 elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
Brent McBrideb25b177a42020-05-11 18:13:061127 binary_paths.extend(
1128 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
Abhishek Arya78120bc2018-05-07 20:53:541129
Akekawit Jitprasertf9cb6622021-08-24 17:48:021130 assert args.format in ['html', 'lcov', 'text'], (
1131 '%s is not a valid output format for "llvm-cov show/export". Only '
1132 '"text", "html" and "lcov" formats are supported.' % (args.format))
Sahel Sharify38cabdc2020-01-16 00:40:011133 logging.info('Generating code coverage report in %s (this can take a while '
1134 'depending on size of target!).' % (args.format))
Max Moroz1de68d72018-08-21 13:38:181135 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591136 binary_paths, profdata_file_path, absolute_filter_paths,
1137 args.ignore_filename_regex)
Akekawit Jitprasertf9cb6622021-08-24 17:48:021138
1139 if args.format == 'lcov':
1140 _GeneratePerFileLineByLineCoverageInLcov(
1141 binary_paths, profdata_file_path, absolute_filter_paths,
1142 args.ignore_filename_regex)
1143 return
1144
Sahel Sharify38cabdc2020-01-16 00:40:011145 _GeneratePerFileLineByLineCoverageInFormat(
1146 binary_paths, profdata_file_path, absolute_filter_paths,
1147 args.ignore_filename_regex, args.format)
Max Moroz1de68d72018-08-21 13:38:181148 component_mappings = None
1149 if not args.no_component_view:
Choongwoo Hanbd1aa952021-06-09 22:25:381150 component_mappings = json.load(urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371151
Max Moroz1de68d72018-08-21 13:38:181152 # Call prepare here.
1153 processor = coverage_utils.CoverageReportPostProcessor(
1154 OUTPUT_DIR,
1155 SRC_ROOT_PATH,
1156 per_file_summary_data,
1157 no_component_view=args.no_component_view,
1158 no_file_view=args.no_file_view,
1159 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371160
Sahel Sharify38cabdc2020-01-16 00:40:011161 if args.format == 'html':
1162 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541163
Abhishek Arya1ec832c2017-12-05 18:06:591164
Yuke Liao506e8822017-12-04 16:52:541165if __name__ == '__main__':
1166 sys.exit(Main())