blob: c190f33d4954845fb0727eb16382b0e721e99b21 [file] [log] [blame]
Yuke Liao506e8822017-12-04 16:52:541#!/usr/bin/python
2# Copyright 2017 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
Abhishek Arya1ec832c2017-12-05 18:06:595"""This script helps to generate code coverage report.
Yuke Liao506e8822017-12-04 16:52:546
Abhishek Arya1ec832c2017-12-05 18:06:597 It uses Clang Source-based Code Coverage -
8 https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
Yuke Liao506e8822017-12-04 16:52:549
Abhishek Arya16f059a2017-12-07 17:47:3210 In order to generate code coverage report, you need to first add
Yuke Liaoab9c44e2018-02-21 00:24:4011 "use_clang_coverage=true" and "is_component_build=false" GN flags to args.gn
12 file in your build output directory (e.g. out/coverage).
Yuke Liao506e8822017-12-04 16:52:5413
Abhishek Arya03911092018-05-21 16:42:3514 * Example usage:
Abhishek Arya1ec832c2017-12-05 18:06:5915
Max Moroza5a95272018-08-31 16:20:5516 gn gen out/coverage \\
Abhishek Arya2f261182019-04-24 17:06:4517 --args="use_clang_coverage=true is_component_build=false\\
18 is_debug=false dcheck_always_on=true"
Abhishek Arya16f059a2017-12-07 17:47:3219 gclient runhooks
Abhishek Arya1ec832c2017-12-05 18:06:5920 python tools/code_coverage/coverage.py crypto_unittests url_unittests \\
Abhishek Arya16f059a2017-12-07 17:47:3221 -b out/coverage -o out/report -c 'out/coverage/crypto_unittests' \\
22 -c 'out/coverage/url_unittests --gtest_filter=URLParser.PathURL' \\
23 -f url/ -f crypto/
Abhishek Arya1ec832c2017-12-05 18:06:5924
Abhishek Arya16f059a2017-12-07 17:47:3225 The command above builds crypto_unittests and url_unittests targets and then
26 runs them with specified command line arguments. For url_unittests, it only
27 runs the test URLParser.PathURL. The coverage report is filtered to include
28 only files and sub-directories under url/ and crypto/ directories.
Abhishek Arya1ec832c2017-12-05 18:06:5929
Yuke Liao545db322018-02-15 17:12:0130 If you want to run tests that try to draw to the screen but don't have a
31 display connected, you can run tests in headless mode with xvfb.
32
Abhishek Arya03911092018-05-21 16:42:3533 * Sample flow for running a test target with xvfb (e.g. unit_tests):
Yuke Liao545db322018-02-15 17:12:0134
35 python tools/code_coverage/coverage.py unit_tests -b out/coverage \\
36 -o out/report -c 'python testing/xvfb.py out/coverage/unit_tests'
37
Abhishek Arya1ec832c2017-12-05 18:06:5938 If you are building a fuzz target, you need to add "use_libfuzzer=true" GN
39 flag as well.
40
Abhishek Arya03911092018-05-21 16:42:3541 * Sample workflow for a fuzz target (e.g. pdfium_fuzzer):
Abhishek Arya1ec832c2017-12-05 18:06:5942
Abhishek Arya16f059a2017-12-07 17:47:3243 python tools/code_coverage/coverage.py pdfium_fuzzer \\
44 -b out/coverage -o out/report \\
Max Moroz13c23182018-11-17 00:23:2245 -c 'out/coverage/pdfium_fuzzer -runs=0 <corpus_dir>' \\
Abhishek Arya16f059a2017-12-07 17:47:3246 -f third_party/pdfium
Abhishek Arya1ec832c2017-12-05 18:06:5947
48 where:
49 <corpus_dir> - directory containing samples files for this format.
Max Moroz13c23182018-11-17 00:23:2250
51 To learn more about generating code coverage reports for fuzz targets, see
52 https://chromium.googlesource.com/chromium/src/+/master/testing/libfuzzer/efficient_fuzzer.md#Code-Coverage
Abhishek Arya1ec832c2017-12-05 18:06:5953
Abhishek Arya03911092018-05-21 16:42:3554 * Sample workflow for running Blink web tests:
55
56 python tools/code_coverage/coverage.py blink_tests \\
57 -wt -b out/coverage -o out/report -f third_party/blink
58
59 If you need to pass arguments to run_web_tests.py, use
60 -wt='arguments to run_web_tests.py e.g. test directories'
61
Abhishek Arya1ec832c2017-12-05 18:06:5962 For more options, please refer to tools/code_coverage/coverage.py -h.
Yuke Liao8e209fe82018-04-18 20:36:3863
64 For an overview of how code coverage works in Chromium, please refer to
Yuke Liao43bbbcd52019-06-21 19:34:5065 https://chromium.googlesource.com/chromium/src/+/master/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
Yuke Liaob2926832018-03-02 17:34:2977import re
78import shlex
Max Moroz025d8952018-05-03 16:33:3479import shutil
Yuke Liao506e8822017-12-04 16:52:5480import subprocess
Yuke Liao506e8822017-12-04 16:52:5481import urllib2
82
Abhishek Arya1ec832c2017-12-05 18:06:5983sys.path.append(
84 os.path.join(
85 os.path.dirname(__file__), os.path.pardir, os.path.pardir, 'tools',
86 'clang', 'scripts'))
Hans Wennborg8ee64a12019-11-05 17:31:3087import update
Yuke Liao506e8822017-12-04 16:52:5488
Yuke Liaoea228d02018-01-05 19:10:3389sys.path.append(
90 os.path.join(
91 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
Max Moroz1de68d72018-08-21 13:38:1896
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.
Hans Wennborg8ee64a12019-11-05 17:31:3099LLVM_BIN_DIR = os.path.join(update.LLVM_BUILD_DIR, 'bin')
Abhishek Arya1c97ea542018-05-10 03:53:19100LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
101LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:54102
Abhishek Arya03911092018-05-21 16:42:35103# Absolute path to the root of the checkout.
104SRC_ROOT_PATH = None
105
Yuke Liao506e8822017-12-04 16:52:54106# Build directory, the value is parsed from command line arguments.
107BUILD_DIR = None
108
109# Output directory for generated artifacts, the value is parsed from command
110# line arguemnts.
111OUTPUT_DIR = None
112
Yuke Liao506e8822017-12-04 16:52:54113# Name of the file extension for profraw data files.
114PROFRAW_FILE_EXTENSION = 'profraw'
115
116# Name of the final profdata file, and this file needs to be passed to
117# "llvm-cov" command in order to call "llvm-cov show" to inspect the
118# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48119PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
120
121# Name of the file with summary information generated by llvm-cov export.
122SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54123
124# Build arg required for generating code coverage data.
125CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
126
Max Moroz7c5354f2018-05-06 00:03:48127LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37128
129# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19130COMPONENT_MAPPING_URL = (
131 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37132
Yuke Liao80afff32018-03-07 01:26:20133# Caches the results returned by _GetBuildArgs, don't use this variable
134# directly, call _GetBuildArgs instead.
135_BUILD_ARGS = None
136
Abhishek Aryac19bc5ef2018-05-04 22:10:02137# Retry failed merges.
138MERGE_RETRIES = 3
139
Abhishek Aryad35de7e2018-05-10 22:23:04140# Message to guide user to file a bug when everything else fails.
141FILE_BUG_MESSAGE = (
142 'If it persists, please file a bug with the command you used, git revision '
143 'and args.gn config here: '
144 'https://bugs.chromium.org/p/chromium/issues/entry?'
Yuke Liao03c644072019-07-30 18:33:40145 'components=Infra%3ETest%3ECodeCoverage')
Abhishek Aryad35de7e2018-05-10 22:23:04146
Abhishek Aryabd0655d2018-05-21 19:55:24147# String to replace with actual llvm profile path.
148LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
149
Yuke Liaoea228d02018-01-05 19:10:33150
Yuke Liao082e99632018-05-18 15:40:40151def _ConfigureLLVMCoverageTools(args):
152 """Configures llvm coverage tools."""
153 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18154 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40155 global LLVM_COV_PATH
156 global LLVM_PROFDATA_PATH
157 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
158 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
159 else:
pasthanaa4844112020-05-21 18:03:55160 subprocess.check_call(
161 ['tools/clang/scripts/update.py', '--package', 'coverage_tools'])
Brent McBrideb25b177a42020-05-11 18:13:06162
163 if coverage_utils.GetHostPlatform() == 'win':
164 LLVM_COV_PATH += '.exe'
165 LLVM_PROFDATA_PATH += '.exe'
Yuke Liao082e99632018-05-18 15:40:40166
167 coverage_tools_exist = (
168 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
169 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
170 'both \'%s\' and \'%s\' exist.') % (
171 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
172
Abhishek Arya2f261182019-04-24 17:06:45173
Abhishek Arya1c97ea542018-05-10 03:53:19174def _GetPathWithLLVMSymbolizerDir():
175 """Add llvm-symbolizer directory to path for symbolized stacks."""
176 path = os.getenv('PATH')
177 dirs = path.split(os.pathsep)
178 if LLVM_BIN_DIR in dirs:
179 return path
180
181 return path + os.pathsep + LLVM_BIN_DIR
182
183
Yuke Liaoc60b2d02018-03-02 21:40:43184def _GetTargetOS():
185 """Returns the target os specified in args.gn file.
186
187 Returns an empty string is target_os is not specified.
188 """
Yuke Liao80afff32018-03-07 01:26:20189 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43190 return build_args['target_os'] if 'target_os' in build_args else ''
191
192
Yuke Liaob2926832018-03-02 17:34:29193def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10194 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43195 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10196
197
Sahel Sharify38cabdc2020-01-16 00:40:01198def _GeneratePerFileLineByLineCoverageInFormat(binary_paths, profdata_file_path,
199 filters, ignore_filename_regex,
200 output_format):
201 """Generates per file line-by-line coverage in html or text using
202 'llvm-cov show'.
Yuke Liao506e8822017-12-04 16:52:54203
Sahel Sharify38cabdc2020-01-16 00:40:01204 For a file with absolute path /a/b/x.cc, a html/txt report is generated as:
205 OUTPUT_DIR/coverage/a/b/x.cc.[html|txt]. For html format, an index html file
206 is also generated as: OUTPUT_DIR/index.html.
Yuke Liao506e8822017-12-04 16:52:54207
208 Args:
209 binary_paths: A list of paths to the instrumented binaries.
210 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42211 filters: A list of directories and files to get coverage for.
Sahel Sharify38cabdc2020-01-16 00:40:01212 ignore_filename_regex: A regular expression for skipping source code files
213 with certain file paths.
214 output_format: The output format of generated report files.
Yuke Liao506e8822017-12-04 16:52:54215 """
Yuke Liao506e8822017-12-04 16:52:54216 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
217 # [[-object BIN]] [SOURCES]
218 # NOTE: For object files, the first one is specified as a positional argument,
219 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10220 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40221 '"llvm-cov show" command.')
Sahel Sharify38cabdc2020-01-16 00:40:01222
Abhishek Arya1ec832c2017-12-05 18:06:59223 subprocess_cmd = [
Sahel Sharify38cabdc2020-01-16 00:40:01224 LLVM_COV_PATH, 'show', '-format={}'.format(output_format),
Abhishek Arya1ec832c2017-12-05 18:06:59225 '-output-dir={}'.format(OUTPUT_DIR),
226 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
227 ]
228 subprocess_cmd.extend(
229 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29230 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18231 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17232 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42233 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59234 if ignore_filename_regex:
235 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
236
Yuke Liao506e8822017-12-04 16:52:54237 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34238
Abhishek Aryafb70b532018-05-06 17:47:40239 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54240
241
Max Moroz7c5354f2018-05-06 00:03:48242def _GetLogsDirectoryPath():
243 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18244 return os.path.join(
245 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48246
247
248def _GetProfdataFilePath():
249 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18250 return os.path.join(
251 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
252 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48253
254
255def _GetSummaryFilePath():
256 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18257 return os.path.join(
258 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
259 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33260
261
Yuke Liao506e8822017-12-04 16:52:54262def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
263 """Builds and runs target to generate the coverage profile data.
264
265 Args:
266 targets: A list of targets to build with coverage instrumentation.
267 commands: A list of commands used to run the targets.
268 jobs_count: Number of jobs to run in parallel for building. If None, a
269 default value is derived based on CPUs availability.
270
271 Returns:
272 A relative path to the generated profdata file.
273 """
274 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02275 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59276 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02277 coverage_profdata_file_path = (
278 _CreateCoverageProfileDataFromTargetProfDataFiles(
279 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54280
Abhishek Aryac19bc5ef2018-05-04 22:10:02281 for target_profdata_file_path in target_profdata_file_paths:
282 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52283
Abhishek Aryac19bc5ef2018-05-04 22:10:02284 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54285
286
287def _BuildTargets(targets, jobs_count):
288 """Builds target with Clang coverage instrumentation.
289
290 This function requires current working directory to be the root of checkout.
291
292 Args:
293 targets: A list of targets to build with coverage instrumentation.
294 jobs_count: Number of jobs to run in parallel for compilation. If None, a
295 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54296 """
Abhishek Aryafb70b532018-05-06 17:47:40297 logging.info('Building %s.', str(targets))
Brent McBrideb25b177a42020-05-11 18:13:06298 autoninja = 'autoninja'
299 if coverage_utils.GetHostPlatform() == 'win':
300 autoninja += '.bat'
Yuke Liao506e8822017-12-04 16:52:54301
Brent McBrideb25b177a42020-05-11 18:13:06302 subprocess_cmd = [autoninja, '-C', BUILD_DIR]
Yuke Liao506e8822017-12-04 16:52:54303 if jobs_count is not None:
304 subprocess_cmd.append('-j' + str(jobs_count))
305
306 subprocess_cmd.extend(targets)
307 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40308 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54309
310
Abhishek Aryac19bc5ef2018-05-04 22:10:02311def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54312 """Runs commands and returns the relative paths to the profraw data files.
313
314 Args:
315 targets: A list of targets built with coverage instrumentation.
316 commands: A list of commands used to run the targets.
317
318 Returns:
319 A list of relative paths to the generated profraw data files.
320 """
Abhishek Aryafb70b532018-05-06 17:47:40321 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10322
Yuke Liao506e8822017-12-04 16:52:54323 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18324 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
325 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54326 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18327 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48328
329 # Ensure that logs directory exists.
330 if not os.path.exists(_GetLogsDirectoryPath()):
331 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54332
Abhishek Aryac19bc5ef2018-05-04 22:10:02333 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10334
Yuke Liaod4a9865202018-01-12 23:17:52335 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54336 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48337 output_file_name = os.extsep.join([target + '_output', 'log'])
338 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10339
Abhishek Aryac19bc5ef2018-05-04 22:10:02340 profdata_file_path = None
341 for _ in xrange(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40342 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02343 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10344
Abhishek Aryac19bc5ef2018-05-04 22:10:02345 if _IsIOSCommand(command):
346 # On iOS platform, due to lack of write permissions, profraw files are
347 # generated outside of the OUTPUT_DIR, and the exact paths are contained
348 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35349 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02350 else:
351 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35352 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02353
354 profraw_file_paths = []
355 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57356 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Abhishek Aryac19bc5ef2018-05-04 22:10:02357 else:
Max Moroz1de68d72018-08-21 13:38:18358 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02359 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48360 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18361 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02362
363 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40364 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04365 'please make sure the binary exists, is properly instrumented and '
366 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02367
Yuke Liao9c2c70b2018-05-23 15:37:57368 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18369 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
370 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57371
Abhishek Aryac19bc5ef2018-05-04 22:10:02372 try:
373 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
374 target, profraw_file_paths)
375 break
376 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04377 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02378 finally:
379 # Remove profraw files now so that they are not used in next iteration.
380 for profraw_file_path in profraw_file_paths:
381 os.remove(profraw_file_path)
382
383 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04384 'Failed to merge target "%s" profraw files after %d retries. %s' %
385 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02386 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54387
Abhishek Aryafb70b532018-05-06 17:47:40388 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10389
Abhishek Aryac19bc5ef2018-05-04 22:10:02390 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54391
392
Abhishek Arya03911092018-05-21 16:42:35393def _GetEnvironmentVars(profraw_file_path):
394 """Return environment vars for subprocess, given a profraw file path."""
395 env = os.environ.copy()
396 env.update({
397 'LLVM_PROFILE_FILE': profraw_file_path,
398 'PATH': _GetPathWithLLVMSymbolizerDir()
399 })
400 return env
401
402
403def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10404 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52405 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01406 #
Max Morozd73e45f2018-04-24 18:32:47407 # "%p" expands out to the process ID. It's not used by this scripts due to:
408 # 1) If a target program spawns too many processess, it may exhaust all disk
409 # space available. For example, unit_tests writes thousands of .profraw
410 # files each of size 1GB+.
411 # 2) If a target binary uses shared libraries, coverage profile data for them
412 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01413 #
Yuke Liaod4a9865202018-01-12 23:17:52414 # "%Nm" expands out to the instrumented binary's signature. When this pattern
415 # is specified, the runtime creates a pool of N raw profiles which are used
416 # for on-line profile merging. The runtime takes care of selecting a raw
417 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52418 # N must be between 1 and 9. The merge pool specifier can only occur once per
419 # filename pattern.
420 #
Max Morozd73e45f2018-04-24 18:32:47421 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01422 #
Max Morozd73e45f2018-04-24 18:32:47423 # For other cases, "%4m" is chosen as it creates some level of parallelism,
424 # but it's not too big to consume too much computing resource or disk space.
425 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59426 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01427 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18428 expected_profraw_file_path = os.path.join(
429 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
430 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24431 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
432 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54433
Yuke Liaoa0c8c2f2018-02-28 20:14:10434 try:
Max Moroz7c5354f2018-05-06 00:03:48435 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35436 with open(output_file_path, 'wb') as output_file_handle:
437 subprocess.check_call(
438 shlex.split(command),
439 stdout=output_file_handle,
440 stderr=subprocess.STDOUT,
441 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10442 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35443 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10444
Abhishek Arya03911092018-05-21 16:42:35445 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10446
447
Yuke Liao27349c92018-03-22 21:10:01448def _IsFuzzerTarget(target):
449 """Returns true if the target is a fuzzer target."""
450 build_args = _GetBuildArgs()
451 use_libfuzzer = ('use_libfuzzer' in build_args and
452 build_args['use_libfuzzer'] == 'true')
453 return use_libfuzzer and target.endswith('_fuzzer')
454
455
Abhishek Arya03911092018-05-21 16:42:35456def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10457 """Runs a single iOS command and generates a profraw data file.
458
459 iOS application doesn't have write access to folders outside of the app, so
460 it's impossible to instruct the app to flush the profraw data file to the
461 desired location. The profraw data file will be generated somewhere within the
462 application's Documents folder, and the full path can be obtained by parsing
463 the output.
464 """
Yuke Liaob2926832018-03-02 17:34:29465 assert _IsIOSCommand(command)
466
467 # After running tests, iossim generates a profraw data file, it won't be
468 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
469 # checkout.
470 iossim_profraw_file_path = os.path.join(
471 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24472 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
473 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10474
475 try:
Abhishek Arya03911092018-05-21 16:42:35476 with open(output_file_path, 'wb') as output_file_handle:
477 subprocess.check_call(
478 shlex.split(command),
479 stdout=output_file_handle,
480 stderr=subprocess.STDOUT,
481 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10482 except subprocess.CalledProcessError as e:
483 # iossim emits non-zero return code even if tests run successfully, so
484 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35485 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10486
Abhishek Arya03911092018-05-21 16:42:35487 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10488
489
490def _GetProfrawDataFileByParsingOutput(output):
491 """Returns the path to the profraw data file obtained by parsing the output.
492
493 The output of running the test target has no format, but it is guaranteed to
494 have a single line containing the path to the generated profraw data file.
495 NOTE: This should only be called when target os is iOS.
496 """
Yuke Liaob2926832018-03-02 17:34:29497 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10498
Yuke Liaob2926832018-03-02 17:34:29499 output_by_lines = ''.join(output).splitlines()
500 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10501
502 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29503 result = profraw_file_pattern.match(line)
504 if result:
505 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10506
507 assert False, ('No profraw data file was generated, did you call '
508 'coverage_util::ConfigureCoverageReportPath() in test setup? '
509 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54510
511
Abhishek Aryac19bc5ef2018-05-04 22:10:02512def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
513 """Returns a relative path to coverage profdata file by merging target
514 profdata files.
Yuke Liao506e8822017-12-04 16:52:54515
516 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02517 profdata_file_paths: A list of relative paths to the profdata data files
518 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54519
520 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02521 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54522
523 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02524 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54525 """
Abhishek Aryafb70b532018-05-06 17:47:40526 logging.info('Creating the coverage profile data file.')
527 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48528 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54529 try:
Abhishek Arya1ec832c2017-12-05 18:06:59530 subprocess_cmd = [
531 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
532 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02533 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01534
535 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18536 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02537 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04538 logging.error(
539 'Failed to merge target profdata files to create coverage profdata. %s',
540 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02541 raise error
542
Abhishek Aryafb70b532018-05-06 17:47:40543 logging.debug('Finished merging target profdata files.')
544 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02545 profdata_file_path)
546 return profdata_file_path
547
548
549def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
550 """Returns a relative path to target profdata file by merging target
551 profraw files.
552
553 Args:
554 profraw_file_paths: A list of relative paths to the profdata data files
555 that are to be merged.
556
557 Returns:
558 A relative path to the merged coverage profdata file.
559
560 Raises:
561 CalledProcessError: An error occurred merging profdata files.
562 """
Abhishek Aryafb70b532018-05-06 17:47:40563 logging.info('Creating target profile data file.')
564 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02565 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
566
567 try:
568 subprocess_cmd = [
569 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
570 ]
Yuke Liao506e8822017-12-04 16:52:54571 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01572
573 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18574 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54575 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04576 logging.error(
577 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54578 raise error
579
Abhishek Aryafb70b532018-05-06 17:47:40580 logging.debug('Finished merging target profraw files.')
581 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10582 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54583 return profdata_file_path
584
585
Yuke Liao0e4c8682018-04-18 21:06:59586def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
587 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33588 """Generates per file coverage summary using "llvm-cov export" command."""
589 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
590 # [[-object BIN]] [SOURCES].
591 # NOTE: For object files, the first one is specified as a positional argument,
592 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10593 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40594 'export -summary-only" command.')
Yuke Liaoea228d02018-01-05 19:10:33595 subprocess_cmd = [
596 LLVM_COV_PATH, 'export', '-summary-only',
597 '-instr-profile=' + profdata_file_path, binary_paths[0]
598 ]
599 subprocess_cmd.extend(
600 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29601 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33602 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59603 if ignore_filename_regex:
604 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33605
Max Moroz7c5354f2018-05-06 00:03:48606 export_output = subprocess.check_output(subprocess_cmd)
607
608 # Write output on the disk to be used by code coverage bot.
609 with open(_GetSummaryFilePath(), 'w') as f:
610 f.write(export_output)
611
Max Moroz1de68d72018-08-21 13:38:18612 return export_output
Yuke Liaoea228d02018-01-05 19:10:33613
614
Yuke Liaob2926832018-03-02 17:34:29615def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
616 """Appends -arch arguments to the command list if it's ios platform.
617
618 iOS binaries are universal binaries, and require specifying the architecture
619 to use, and one architecture needs to be specified for each binary.
620 """
621 if _IsIOS():
622 cmd_list.extend(['-arch=x86_64'] * num_archs)
623
624
Yuke Liao506e8822017-12-04 16:52:54625def _GetBinaryPath(command):
626 """Returns a relative path to the binary to be run by the command.
627
Yuke Liao545db322018-02-15 17:12:01628 Currently, following types of commands are supported (e.g. url_unittests):
629 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
630 2. Use xvfb.
631 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
632 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37633 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
634 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10635 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37636 <iossim_arguments> -c <app_arguments>
637 out/Coverage-iphonesimulator/url_unittests.app"
638
Yuke Liao506e8822017-12-04 16:52:54639 Args:
640 command: A command used to run a target.
641
642 Returns:
643 A relative path to the binary.
644 """
Yuke Liao545db322018-02-15 17:12:01645 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
646
Yuke Liaob2926832018-03-02 17:34:29647 command_parts = shlex.split(command)
Yuke Liao545db322018-02-15 17:12:01648 if os.path.basename(command_parts[0]) == 'python':
649 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40650 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01651 return command_parts[2]
652
653 if os.path.basename(command_parts[0]) == xvfb_script_name:
654 return command_parts[1]
655
Yuke Liaob2926832018-03-02 17:34:29656 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10657 # For a given application bundle, the binary resides in the bundle and has
658 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02659 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10660 app_name = os.path.splitext(os.path.basename(app_path))[0]
661 return os.path.join(app_path, app_name)
662
Yuke Liaob2926832018-03-02 17:34:29663 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54664
665
Yuke Liaob2926832018-03-02 17:34:29666def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10667 """Returns true if command is used to run tests on iOS platform."""
Yuke Liaob2926832018-03-02 17:34:29668 return os.path.basename(shlex.split(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10669
670
Yuke Liao95d13d72017-12-07 18:18:50671def _VerifyTargetExecutablesAreInBuildDirectory(commands):
672 """Verifies that the target executables specified in the commands are inside
673 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54674 for command in commands:
675 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18676 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35677 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50678 'Target executable "%s" in command: "%s" is outside of '
679 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54680
681
682def _ValidateBuildingWithClangCoverage():
683 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20684 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54685
686 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
687 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59688 assert False, ('\'{} = true\' is required in args.gn.'
689 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54690
691
Yuke Liaoc60b2d02018-03-02 21:40:43692def _ValidateCurrentPlatformIsSupported():
693 """Asserts that this script suports running on the current platform"""
694 target_os = _GetTargetOS()
695 if target_os:
696 current_platform = target_os
697 else:
Max Moroz1de68d72018-08-21 13:38:18698 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43699
700 assert current_platform in [
Brent McBrideb25b177a42020-05-11 18:13:06701 'linux', 'mac', 'chromeos', 'ios', 'win'
702 ], ('Coverage is only supported on linux, mac, chromeos, ios and win.')
Yuke Liaoc60b2d02018-03-02 21:40:43703
704
Yuke Liao80afff32018-03-07 01:26:20705def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54706 """Parses args.gn file and returns results as a dictionary.
707
708 Returns:
709 A dictionary representing the build args.
710 """
Yuke Liao80afff32018-03-07 01:26:20711 global _BUILD_ARGS
712 if _BUILD_ARGS is not None:
713 return _BUILD_ARGS
714
715 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54716 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
717 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
718 'missing args.gn file.' % BUILD_DIR)
719 with open(build_args_path) as build_args_file:
720 build_args_lines = build_args_file.readlines()
721
Yuke Liao506e8822017-12-04 16:52:54722 for build_arg_line in build_args_lines:
723 build_arg_without_comments = build_arg_line.split('#')[0]
724 key_value_pair = build_arg_without_comments.split('=')
725 if len(key_value_pair) != 2:
726 continue
727
728 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43729
730 # Values are wrapped within a pair of double-quotes, so remove the leading
731 # and trailing double-quotes.
732 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20733 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54734
Yuke Liao80afff32018-03-07 01:26:20735 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54736
737
Abhishek Arya16f059a2017-12-07 17:47:32738def _VerifyPathsAndReturnAbsolutes(paths):
739 """Verifies that the paths specified in |paths| exist and returns absolute
740 versions.
Yuke Liao66da1732017-12-05 22:19:42741
742 Args:
743 paths: A list of files or directories.
744 """
Abhishek Arya16f059a2017-12-07 17:47:32745 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42746 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32747 absolute_path = os.path.join(SRC_ROOT_PATH, path)
748 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
749
750 absolute_paths.append(absolute_path)
751
752 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42753
754
Abhishek Arya64636af2018-05-04 14:42:13755def _GetBinaryPathsFromTargets(targets, build_dir):
756 """Return binary paths from target names."""
757 # FIXME: Derive output binary from target build definitions rather than
758 # assuming that it is always the same name.
759 binary_paths = []
760 for target in targets:
761 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18762 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13763 binary_path += '.exe'
764
765 if os.path.exists(binary_path):
766 binary_paths.append(binary_path)
767 else:
768 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40769 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13770 os.path.basename(binary_path))
771
772 return binary_paths
773
774
Abhishek Arya03911092018-05-21 16:42:35775def _GetCommandForWebTests(arguments):
776 """Return command to run for blink web tests."""
777 command_list = [
778 'python', 'testing/xvfb.py', 'python',
779 'third_party/blink/tools/run_web_tests.py',
780 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24781 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Robert Ma339fc472018-12-14 21:22:42782 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Abhishek Arya03911092018-05-21 16:42:35783 '--child-processes=%d' % max(1, int(multiprocessing.cpu_count() / 2)),
Abhishek Aryabd0655d2018-05-21 19:55:24784 '--disable-breakpad', '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35785 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
786 ]
787 if arguments.strip():
788 command_list.append(arguments)
789 return ' '.join(command_list)
790
791
792def _GetBinaryPathForWebTests():
793 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18794 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35795 if host_platform == 'win':
796 return os.path.join(BUILD_DIR, 'content_shell.exe')
797 elif host_platform == 'linux':
798 return os.path.join(BUILD_DIR, 'content_shell')
799 elif host_platform == 'mac':
800 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
801 'Content Shell')
802 else:
803 assert False, 'This platform is not supported for web tests.'
804
805
Abhishek Aryae5811afa2018-05-24 03:56:01806def _SetupOutputDir():
807 """Setup output directory."""
808 if os.path.exists(OUTPUT_DIR):
809 shutil.rmtree(OUTPUT_DIR)
810
811 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18812 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01813
814
Yuke Liaoabfbba42019-06-11 16:03:59815def _SetMacXcodePath():
816 """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
817 if sys.platform != 'darwin':
818 return
819
820 xcode_path = os.path.join(SRC_ROOT_PATH, 'build', 'mac_files', 'Xcode.app')
821 if os.path.exists(xcode_path):
822 os.environ['DEVELOPER_DIR'] = xcode_path
823
824
Yuke Liao506e8822017-12-04 16:52:54825def _ParseCommandArguments():
826 """Adds and parses relevant arguments for tool comands.
827
828 Returns:
829 A dictionary representing the arguments.
830 """
831 arg_parser = argparse.ArgumentParser()
832 arg_parser.usage = __doc__
833
Abhishek Arya1ec832c2017-12-05 18:06:59834 arg_parser.add_argument(
835 '-b',
836 '--build-dir',
837 type=str,
838 required=True,
839 help='The build directory, the path needs to be relative to the root of '
840 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54841
Abhishek Arya1ec832c2017-12-05 18:06:59842 arg_parser.add_argument(
843 '-o',
844 '--output-dir',
845 type=str,
846 required=True,
847 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54848
Abhishek Arya1ec832c2017-12-05 18:06:59849 arg_parser.add_argument(
850 '-c',
851 '--command',
852 action='append',
Abhishek Arya64636af2018-05-04 14:42:13853 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59854 help='Commands used to run test targets, one test target needs one and '
855 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13856 'current working directory is the root of the checkout. This option is '
857 'incompatible with -p/--profdata-file option.')
858
859 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35860 '-wt',
861 '--web-tests',
862 nargs='?',
863 type=str,
864 const=' ',
865 required=False,
866 help='Run blink web tests. Support passing arguments to run_web_tests.py')
867
868 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13869 '-p',
870 '--profdata-file',
871 type=str,
872 required=False,
873 help='Path to profdata file to use for generating code coverage reports. '
874 'This can be useful if you generated the profdata file seperately in '
875 'your own test harness. This option is ignored if run command(s) are '
876 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54877
Abhishek Arya1ec832c2017-12-05 18:06:59878 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42879 '-f',
880 '--filters',
881 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32882 required=False,
Yuke Liao66da1732017-12-05 22:19:42883 help='Directories or files to get code coverage for, and all files under '
884 'the directories are included recursively.')
885
886 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59887 '-i',
888 '--ignore-filename-regex',
889 type=str,
890 help='Skip source code files with file paths that match the given '
891 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
892 'to exclude files in third_party/ and out/ folders from the report.')
893
894 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32895 '--no-file-view',
896 action='store_true',
897 help='Don\'t generate the file view in the coverage report. When there '
898 'are large number of html files, the file view becomes heavy and may '
899 'cause the browser to freeze, and this argument comes handy.')
900
901 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18902 '--no-component-view',
903 action='store_true',
904 help='Don\'t generate the component view in the coverage report.')
905
906 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:40907 '--coverage-tools-dir',
908 type=str,
909 help='Path of the directory where LLVM coverage tools (llvm-cov, '
910 'llvm-profdata) exist. This should be only needed if you are testing '
911 'against a custom built clang revision. Otherwise, we pick coverage '
912 'tools automatically from your current source checkout.')
913
914 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:59915 '-j',
916 '--jobs',
917 type=int,
918 default=None,
919 help='Run N jobs to build in parallel. If not specified, a default value '
Max Moroz06576292019-01-03 19:22:52920 'will be derived based on CPUs and goma availability. Please refer to '
921 '\'autoninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:54922
Abhishek Arya1ec832c2017-12-05 18:06:59923 arg_parser.add_argument(
Sahel Sharify38cabdc2020-01-16 00:40:01924 '--format',
925 type=str,
926 default='html',
927 help='Output format of the "llvm-cov show" command. The supported '
928 'formats are "text" and "html".')
929
930 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:10931 '-v',
932 '--verbose',
933 action='store_true',
934 help='Prints additional output for diagnostics.')
935
936 arg_parser.add_argument(
937 '-l', '--log_file', type=str, help='Redirects logs to a file.')
938
939 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:02940 'targets',
941 nargs='+',
942 help='The names of the test targets to run. If multiple run commands are '
943 'specified using the -c/--command option, then the order of targets and '
944 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:54945
946 args = arg_parser.parse_args()
947 return args
948
949
950def Main():
951 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:40952
Abhishek Arya64636af2018-05-04 14:42:13953 # Change directory to source root to aid in relative paths calculations.
Abhishek Arya03911092018-05-21 16:42:35954 global SRC_ROOT_PATH
Max Moroz1de68d72018-08-21 13:38:18955 SRC_ROOT_PATH = coverage_utils.GetFullPath(
Abhishek Arya03911092018-05-21 16:42:35956 os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
Abhishek Arya64636af2018-05-04 14:42:13957 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:11958
pasthanaa4844112020-05-21 18:03:55959 # Setup coverage binaries even when script is called with empty params. This
960 # is used by coverage bot for initial setup.
961 if len(sys.argv) == 1:
962 subprocess.check_call(
963 ['tools/clang/scripts/update.py', '--package', 'coverage_tools'])
964 print(__doc__)
965 return
966
Yuke Liao506e8822017-12-04 16:52:54967 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:18968 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:40969 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:13970
Yuke Liao506e8822017-12-04 16:52:54971 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:18972 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:01973
Yuke Liao506e8822017-12-04 16:52:54974 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:18975 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:54976
Abhishek Arya03911092018-05-21 16:42:35977 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:13978 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:35979 'provide prof-data file as input using -p/--profdata-file option OR '
980 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:43981
Abhishek Arya64636af2018-05-04 14:42:13982 assert not args.command or (len(args.targets) == len(args.command)), (
983 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:43984
Abhishek Arya1ec832c2017-12-05 18:06:59985 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:40986 'Build directory: "%s" doesn\'t exist. '
987 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:13988
Yuke Liaoc60b2d02018-03-02 21:40:43989 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:54990 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:32991
992 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:42993 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:32994 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:42995
Abhishek Aryae5811afa2018-05-24 03:56:01996 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:54997
Abhishek Arya03911092018-05-21 16:42:35998 # Get .profdata file and list of binary paths.
999 if args.web_tests:
1000 commands = [_GetCommandForWebTests(args.web_tests)]
1001 profdata_file_path = _CreateCoverageProfileDataForTargets(
1002 args.targets, commands, args.jobs)
1003 binary_paths = [_GetBinaryPathForWebTests()]
1004 elif args.command:
1005 for i in range(len(args.command)):
1006 assert not 'run_web_tests.py' in args.command[i], (
1007 'run_web_tests.py is not supported via --command argument. '
1008 'Please use --run-web-tests argument instead.')
1009
Abhishek Arya64636af2018-05-04 14:42:131010 # A list of commands are provided. Run them to generate profdata file, and
1011 # create a list of binary paths from parsing commands.
1012 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1013 profdata_file_path = _CreateCoverageProfileDataForTargets(
1014 args.targets, args.command, args.jobs)
1015 binary_paths = [_GetBinaryPath(command) for command in args.command]
1016 else:
1017 # An input prof-data file is already provided. Just calculate binary paths.
1018 profdata_file_path = args.profdata_file
1019 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331020
Erik Chen283b92c72019-07-22 16:37:391021 # If the checkout uses the hermetic xcode binaries, then otool must be
1022 # directly invoked. The indirection via /usr/bin/otool won't work unless
1023 # there's an actual system install of Xcode.
1024 otool_path = None
1025 if sys.platform == 'darwin':
1026 hermetic_otool_path = os.path.join(
1027 SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents',
1028 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin',
1029 'otool')
1030 if os.path.exists(hermetic_otool_path):
1031 otool_path = hermetic_otool_path
Brent McBrideb25b177a42020-05-11 18:13:061032 if sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
1033 binary_paths.extend(
1034 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
Abhishek Arya78120bc2018-05-07 20:53:541035
Sahel Sharify38cabdc2020-01-16 00:40:011036 assert args.format == 'html' or args.format == 'text', (
1037 '%s is not a valid output format for "llvm-cov show". Only "text" and '
1038 '"html" formats are supported.' % (args.format))
1039 logging.info('Generating code coverage report in %s (this can take a while '
1040 'depending on size of target!).' % (args.format))
Max Moroz1de68d72018-08-21 13:38:181041 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591042 binary_paths, profdata_file_path, absolute_filter_paths,
1043 args.ignore_filename_regex)
Sahel Sharify38cabdc2020-01-16 00:40:011044 _GeneratePerFileLineByLineCoverageInFormat(
1045 binary_paths, profdata_file_path, absolute_filter_paths,
1046 args.ignore_filename_regex, args.format)
Max Moroz1de68d72018-08-21 13:38:181047 component_mappings = None
1048 if not args.no_component_view:
1049 component_mappings = json.load(urllib2.urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371050
Max Moroz1de68d72018-08-21 13:38:181051 # Call prepare here.
1052 processor = coverage_utils.CoverageReportPostProcessor(
1053 OUTPUT_DIR,
1054 SRC_ROOT_PATH,
1055 per_file_summary_data,
1056 no_component_view=args.no_component_view,
1057 no_file_view=args.no_file_view,
1058 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371059
Sahel Sharify38cabdc2020-01-16 00:40:011060 if args.format == 'html':
1061 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541062
Abhishek Arya1ec832c2017-12-05 18:06:591063
Yuke Liao506e8822017-12-04 16:52:541064if __name__ == '__main__':
1065 sys.exit(Main())