blob: a4b8f88c576804ab780ba2a0c601d3ec4f600329 [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:
Brent McBrideb25b177a42020-05-11 18:13:06160 update.UpdatePackage('coverage_tools', coverage_utils.GetHostPlatform())
161
162 if coverage_utils.GetHostPlatform() == 'win':
163 LLVM_COV_PATH += '.exe'
164 LLVM_PROFDATA_PATH += '.exe'
Yuke Liao082e99632018-05-18 15:40:40165
166 coverage_tools_exist = (
167 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
168 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
169 'both \'%s\' and \'%s\' exist.') % (
170 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
171
Abhishek Arya2f261182019-04-24 17:06:45172
Abhishek Arya1c97ea542018-05-10 03:53:19173def _GetPathWithLLVMSymbolizerDir():
174 """Add llvm-symbolizer directory to path for symbolized stacks."""
175 path = os.getenv('PATH')
176 dirs = path.split(os.pathsep)
177 if LLVM_BIN_DIR in dirs:
178 return path
179
180 return path + os.pathsep + LLVM_BIN_DIR
181
182
Yuke Liaoc60b2d02018-03-02 21:40:43183def _GetTargetOS():
184 """Returns the target os specified in args.gn file.
185
186 Returns an empty string is target_os is not specified.
187 """
Yuke Liao80afff32018-03-07 01:26:20188 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43189 return build_args['target_os'] if 'target_os' in build_args else ''
190
191
Yuke Liaob2926832018-03-02 17:34:29192def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10193 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43194 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10195
196
Sahel Sharify38cabdc2020-01-16 00:40:01197def _GeneratePerFileLineByLineCoverageInFormat(binary_paths, profdata_file_path,
198 filters, ignore_filename_regex,
199 output_format):
200 """Generates per file line-by-line coverage in html or text using
201 'llvm-cov show'.
Yuke Liao506e8822017-12-04 16:52:54202
Sahel Sharify38cabdc2020-01-16 00:40:01203 For a file with absolute path /a/b/x.cc, a html/txt report is generated as:
204 OUTPUT_DIR/coverage/a/b/x.cc.[html|txt]. For html format, an index html file
205 is also generated as: OUTPUT_DIR/index.html.
Yuke Liao506e8822017-12-04 16:52:54206
207 Args:
208 binary_paths: A list of paths to the instrumented binaries.
209 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42210 filters: A list of directories and files to get coverage for.
Sahel Sharify38cabdc2020-01-16 00:40:01211 ignore_filename_regex: A regular expression for skipping source code files
212 with certain file paths.
213 output_format: The output format of generated report files.
Yuke Liao506e8822017-12-04 16:52:54214 """
Yuke Liao506e8822017-12-04 16:52:54215 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
216 # [[-object BIN]] [SOURCES]
217 # NOTE: For object files, the first one is specified as a positional argument,
218 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10219 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40220 '"llvm-cov show" command.')
Sahel Sharify38cabdc2020-01-16 00:40:01221
Abhishek Arya1ec832c2017-12-05 18:06:59222 subprocess_cmd = [
Sahel Sharify38cabdc2020-01-16 00:40:01223 LLVM_COV_PATH, 'show', '-format={}'.format(output_format),
Abhishek Arya1ec832c2017-12-05 18:06:59224 '-output-dir={}'.format(OUTPUT_DIR),
225 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
226 ]
227 subprocess_cmd.extend(
228 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29229 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18230 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17231 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42232 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59233 if ignore_filename_regex:
234 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
235
Yuke Liao506e8822017-12-04 16:52:54236 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34237
Abhishek Aryafb70b532018-05-06 17:47:40238 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54239
240
Max Moroz7c5354f2018-05-06 00:03:48241def _GetLogsDirectoryPath():
242 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18243 return os.path.join(
244 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48245
246
247def _GetProfdataFilePath():
248 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18249 return os.path.join(
250 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
251 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48252
253
254def _GetSummaryFilePath():
255 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18256 return os.path.join(
257 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
258 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33259
260
Yuke Liao506e8822017-12-04 16:52:54261def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
262 """Builds and runs target to generate the coverage profile data.
263
264 Args:
265 targets: A list of targets to build with coverage instrumentation.
266 commands: A list of commands used to run the targets.
267 jobs_count: Number of jobs to run in parallel for building. If None, a
268 default value is derived based on CPUs availability.
269
270 Returns:
271 A relative path to the generated profdata file.
272 """
273 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02274 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59275 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02276 coverage_profdata_file_path = (
277 _CreateCoverageProfileDataFromTargetProfDataFiles(
278 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54279
Abhishek Aryac19bc5ef2018-05-04 22:10:02280 for target_profdata_file_path in target_profdata_file_paths:
281 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52282
Abhishek Aryac19bc5ef2018-05-04 22:10:02283 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54284
285
286def _BuildTargets(targets, jobs_count):
287 """Builds target with Clang coverage instrumentation.
288
289 This function requires current working directory to be the root of checkout.
290
291 Args:
292 targets: A list of targets to build with coverage instrumentation.
293 jobs_count: Number of jobs to run in parallel for compilation. If None, a
294 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54295 """
Abhishek Aryafb70b532018-05-06 17:47:40296 logging.info('Building %s.', str(targets))
Brent McBrideb25b177a42020-05-11 18:13:06297 autoninja = 'autoninja'
298 if coverage_utils.GetHostPlatform() == 'win':
299 autoninja += '.bat'
Yuke Liao506e8822017-12-04 16:52:54300
Brent McBrideb25b177a42020-05-11 18:13:06301 subprocess_cmd = [autoninja, '-C', BUILD_DIR]
Yuke Liao506e8822017-12-04 16:52:54302 if jobs_count is not None:
303 subprocess_cmd.append('-j' + str(jobs_count))
304
305 subprocess_cmd.extend(targets)
306 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40307 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54308
309
Abhishek Aryac19bc5ef2018-05-04 22:10:02310def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54311 """Runs commands and returns the relative paths to the profraw data files.
312
313 Args:
314 targets: A list of targets built with coverage instrumentation.
315 commands: A list of commands used to run the targets.
316
317 Returns:
318 A list of relative paths to the generated profraw data files.
319 """
Abhishek Aryafb70b532018-05-06 17:47:40320 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10321
Yuke Liao506e8822017-12-04 16:52:54322 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18323 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
324 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54325 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18326 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48327
328 # Ensure that logs directory exists.
329 if not os.path.exists(_GetLogsDirectoryPath()):
330 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54331
Abhishek Aryac19bc5ef2018-05-04 22:10:02332 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10333
Yuke Liaod4a9865202018-01-12 23:17:52334 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54335 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48336 output_file_name = os.extsep.join([target + '_output', 'log'])
337 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10338
Abhishek Aryac19bc5ef2018-05-04 22:10:02339 profdata_file_path = None
340 for _ in xrange(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40341 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02342 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10343
Abhishek Aryac19bc5ef2018-05-04 22:10:02344 if _IsIOSCommand(command):
345 # On iOS platform, due to lack of write permissions, profraw files are
346 # generated outside of the OUTPUT_DIR, and the exact paths are contained
347 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35348 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02349 else:
350 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35351 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02352
353 profraw_file_paths = []
354 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57355 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Abhishek Aryac19bc5ef2018-05-04 22:10:02356 else:
Max Moroz1de68d72018-08-21 13:38:18357 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02358 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48359 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18360 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02361
362 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40363 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04364 'please make sure the binary exists, is properly instrumented and '
365 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02366
Yuke Liao9c2c70b2018-05-23 15:37:57367 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18368 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
369 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57370
Abhishek Aryac19bc5ef2018-05-04 22:10:02371 try:
372 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
373 target, profraw_file_paths)
374 break
375 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04376 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02377 finally:
378 # Remove profraw files now so that they are not used in next iteration.
379 for profraw_file_path in profraw_file_paths:
380 os.remove(profraw_file_path)
381
382 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04383 'Failed to merge target "%s" profraw files after %d retries. %s' %
384 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02385 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54386
Abhishek Aryafb70b532018-05-06 17:47:40387 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10388
Abhishek Aryac19bc5ef2018-05-04 22:10:02389 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54390
391
Abhishek Arya03911092018-05-21 16:42:35392def _GetEnvironmentVars(profraw_file_path):
393 """Return environment vars for subprocess, given a profraw file path."""
394 env = os.environ.copy()
395 env.update({
396 'LLVM_PROFILE_FILE': profraw_file_path,
397 'PATH': _GetPathWithLLVMSymbolizerDir()
398 })
399 return env
400
401
402def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10403 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52404 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01405 #
Max Morozd73e45f2018-04-24 18:32:47406 # "%p" expands out to the process ID. It's not used by this scripts due to:
407 # 1) If a target program spawns too many processess, it may exhaust all disk
408 # space available. For example, unit_tests writes thousands of .profraw
409 # files each of size 1GB+.
410 # 2) If a target binary uses shared libraries, coverage profile data for them
411 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01412 #
Yuke Liaod4a9865202018-01-12 23:17:52413 # "%Nm" expands out to the instrumented binary's signature. When this pattern
414 # is specified, the runtime creates a pool of N raw profiles which are used
415 # for on-line profile merging. The runtime takes care of selecting a raw
416 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52417 # N must be between 1 and 9. The merge pool specifier can only occur once per
418 # filename pattern.
419 #
Max Morozd73e45f2018-04-24 18:32:47420 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01421 #
Max Morozd73e45f2018-04-24 18:32:47422 # For other cases, "%4m" is chosen as it creates some level of parallelism,
423 # but it's not too big to consume too much computing resource or disk space.
424 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59425 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01426 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18427 expected_profraw_file_path = os.path.join(
428 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
429 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24430 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
431 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54432
Yuke Liaoa0c8c2f2018-02-28 20:14:10433 try:
Max Moroz7c5354f2018-05-06 00:03:48434 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35435 with open(output_file_path, 'wb') as output_file_handle:
436 subprocess.check_call(
437 shlex.split(command),
438 stdout=output_file_handle,
439 stderr=subprocess.STDOUT,
440 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10441 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35442 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10443
Abhishek Arya03911092018-05-21 16:42:35444 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10445
446
Yuke Liao27349c92018-03-22 21:10:01447def _IsFuzzerTarget(target):
448 """Returns true if the target is a fuzzer target."""
449 build_args = _GetBuildArgs()
450 use_libfuzzer = ('use_libfuzzer' in build_args and
451 build_args['use_libfuzzer'] == 'true')
452 return use_libfuzzer and target.endswith('_fuzzer')
453
454
Abhishek Arya03911092018-05-21 16:42:35455def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10456 """Runs a single iOS command and generates a profraw data file.
457
458 iOS application doesn't have write access to folders outside of the app, so
459 it's impossible to instruct the app to flush the profraw data file to the
460 desired location. The profraw data file will be generated somewhere within the
461 application's Documents folder, and the full path can be obtained by parsing
462 the output.
463 """
Yuke Liaob2926832018-03-02 17:34:29464 assert _IsIOSCommand(command)
465
466 # After running tests, iossim generates a profraw data file, it won't be
467 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
468 # checkout.
469 iossim_profraw_file_path = os.path.join(
470 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24471 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
472 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10473
474 try:
Abhishek Arya03911092018-05-21 16:42:35475 with open(output_file_path, 'wb') as output_file_handle:
476 subprocess.check_call(
477 shlex.split(command),
478 stdout=output_file_handle,
479 stderr=subprocess.STDOUT,
480 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10481 except subprocess.CalledProcessError as e:
482 # iossim emits non-zero return code even if tests run successfully, so
483 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35484 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10485
Abhishek Arya03911092018-05-21 16:42:35486 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10487
488
489def _GetProfrawDataFileByParsingOutput(output):
490 """Returns the path to the profraw data file obtained by parsing the output.
491
492 The output of running the test target has no format, but it is guaranteed to
493 have a single line containing the path to the generated profraw data file.
494 NOTE: This should only be called when target os is iOS.
495 """
Yuke Liaob2926832018-03-02 17:34:29496 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10497
Yuke Liaob2926832018-03-02 17:34:29498 output_by_lines = ''.join(output).splitlines()
499 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10500
501 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29502 result = profraw_file_pattern.match(line)
503 if result:
504 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10505
506 assert False, ('No profraw data file was generated, did you call '
507 'coverage_util::ConfigureCoverageReportPath() in test setup? '
508 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54509
510
Abhishek Aryac19bc5ef2018-05-04 22:10:02511def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
512 """Returns a relative path to coverage profdata file by merging target
513 profdata files.
Yuke Liao506e8822017-12-04 16:52:54514
515 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02516 profdata_file_paths: A list of relative paths to the profdata data files
517 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54518
519 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02520 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54521
522 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02523 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54524 """
Abhishek Aryafb70b532018-05-06 17:47:40525 logging.info('Creating the coverage profile data file.')
526 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48527 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54528 try:
Abhishek Arya1ec832c2017-12-05 18:06:59529 subprocess_cmd = [
530 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
531 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02532 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01533
534 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18535 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02536 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04537 logging.error(
538 'Failed to merge target profdata files to create coverage profdata. %s',
539 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02540 raise error
541
Abhishek Aryafb70b532018-05-06 17:47:40542 logging.debug('Finished merging target profdata files.')
543 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02544 profdata_file_path)
545 return profdata_file_path
546
547
548def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
549 """Returns a relative path to target profdata file by merging target
550 profraw files.
551
552 Args:
553 profraw_file_paths: A list of relative paths to the profdata data files
554 that are to be merged.
555
556 Returns:
557 A relative path to the merged coverage profdata file.
558
559 Raises:
560 CalledProcessError: An error occurred merging profdata files.
561 """
Abhishek Aryafb70b532018-05-06 17:47:40562 logging.info('Creating target profile data file.')
563 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02564 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
565
566 try:
567 subprocess_cmd = [
568 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
569 ]
Yuke Liao506e8822017-12-04 16:52:54570 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01571
572 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18573 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54574 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04575 logging.error(
576 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54577 raise error
578
Abhishek Aryafb70b532018-05-06 17:47:40579 logging.debug('Finished merging target profraw files.')
580 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10581 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54582 return profdata_file_path
583
584
Yuke Liao0e4c8682018-04-18 21:06:59585def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
586 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33587 """Generates per file coverage summary using "llvm-cov export" command."""
588 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
589 # [[-object BIN]] [SOURCES].
590 # NOTE: For object files, the first one is specified as a positional argument,
591 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10592 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40593 'export -summary-only" command.')
Yuke Liaoea228d02018-01-05 19:10:33594 subprocess_cmd = [
595 LLVM_COV_PATH, 'export', '-summary-only',
596 '-instr-profile=' + profdata_file_path, binary_paths[0]
597 ]
598 subprocess_cmd.extend(
599 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29600 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33601 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59602 if ignore_filename_regex:
603 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33604
Max Moroz7c5354f2018-05-06 00:03:48605 export_output = subprocess.check_output(subprocess_cmd)
606
607 # Write output on the disk to be used by code coverage bot.
608 with open(_GetSummaryFilePath(), 'w') as f:
609 f.write(export_output)
610
Max Moroz1de68d72018-08-21 13:38:18611 return export_output
Yuke Liaoea228d02018-01-05 19:10:33612
613
Yuke Liaob2926832018-03-02 17:34:29614def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
615 """Appends -arch arguments to the command list if it's ios platform.
616
617 iOS binaries are universal binaries, and require specifying the architecture
618 to use, and one architecture needs to be specified for each binary.
619 """
620 if _IsIOS():
621 cmd_list.extend(['-arch=x86_64'] * num_archs)
622
623
Yuke Liao506e8822017-12-04 16:52:54624def _GetBinaryPath(command):
625 """Returns a relative path to the binary to be run by the command.
626
Yuke Liao545db322018-02-15 17:12:01627 Currently, following types of commands are supported (e.g. url_unittests):
628 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
629 2. Use xvfb.
630 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
631 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37632 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
633 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10634 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37635 <iossim_arguments> -c <app_arguments>
636 out/Coverage-iphonesimulator/url_unittests.app"
637
Yuke Liao506e8822017-12-04 16:52:54638 Args:
639 command: A command used to run a target.
640
641 Returns:
642 A relative path to the binary.
643 """
Yuke Liao545db322018-02-15 17:12:01644 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
645
Yuke Liaob2926832018-03-02 17:34:29646 command_parts = shlex.split(command)
Yuke Liao545db322018-02-15 17:12:01647 if os.path.basename(command_parts[0]) == 'python':
648 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40649 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01650 return command_parts[2]
651
652 if os.path.basename(command_parts[0]) == xvfb_script_name:
653 return command_parts[1]
654
Yuke Liaob2926832018-03-02 17:34:29655 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10656 # For a given application bundle, the binary resides in the bundle and has
657 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02658 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10659 app_name = os.path.splitext(os.path.basename(app_path))[0]
660 return os.path.join(app_path, app_name)
661
Yuke Liaob2926832018-03-02 17:34:29662 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54663
664
Yuke Liaob2926832018-03-02 17:34:29665def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10666 """Returns true if command is used to run tests on iOS platform."""
Yuke Liaob2926832018-03-02 17:34:29667 return os.path.basename(shlex.split(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10668
669
Yuke Liao95d13d72017-12-07 18:18:50670def _VerifyTargetExecutablesAreInBuildDirectory(commands):
671 """Verifies that the target executables specified in the commands are inside
672 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54673 for command in commands:
674 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18675 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35676 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50677 'Target executable "%s" in command: "%s" is outside of '
678 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54679
680
681def _ValidateBuildingWithClangCoverage():
682 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20683 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54684
685 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
686 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59687 assert False, ('\'{} = true\' is required in args.gn.'
688 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54689
690
Yuke Liaoc60b2d02018-03-02 21:40:43691def _ValidateCurrentPlatformIsSupported():
692 """Asserts that this script suports running on the current platform"""
693 target_os = _GetTargetOS()
694 if target_os:
695 current_platform = target_os
696 else:
Max Moroz1de68d72018-08-21 13:38:18697 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43698
699 assert current_platform in [
Brent McBrideb25b177a42020-05-11 18:13:06700 'linux', 'mac', 'chromeos', 'ios', 'win'
701 ], ('Coverage is only supported on linux, mac, chromeos, ios and win.')
Yuke Liaoc60b2d02018-03-02 21:40:43702
703
Yuke Liao80afff32018-03-07 01:26:20704def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54705 """Parses args.gn file and returns results as a dictionary.
706
707 Returns:
708 A dictionary representing the build args.
709 """
Yuke Liao80afff32018-03-07 01:26:20710 global _BUILD_ARGS
711 if _BUILD_ARGS is not None:
712 return _BUILD_ARGS
713
714 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54715 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
716 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
717 'missing args.gn file.' % BUILD_DIR)
718 with open(build_args_path) as build_args_file:
719 build_args_lines = build_args_file.readlines()
720
Yuke Liao506e8822017-12-04 16:52:54721 for build_arg_line in build_args_lines:
722 build_arg_without_comments = build_arg_line.split('#')[0]
723 key_value_pair = build_arg_without_comments.split('=')
724 if len(key_value_pair) != 2:
725 continue
726
727 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43728
729 # Values are wrapped within a pair of double-quotes, so remove the leading
730 # and trailing double-quotes.
731 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20732 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54733
Yuke Liao80afff32018-03-07 01:26:20734 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54735
736
Abhishek Arya16f059a2017-12-07 17:47:32737def _VerifyPathsAndReturnAbsolutes(paths):
738 """Verifies that the paths specified in |paths| exist and returns absolute
739 versions.
Yuke Liao66da1732017-12-05 22:19:42740
741 Args:
742 paths: A list of files or directories.
743 """
Abhishek Arya16f059a2017-12-07 17:47:32744 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42745 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32746 absolute_path = os.path.join(SRC_ROOT_PATH, path)
747 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
748
749 absolute_paths.append(absolute_path)
750
751 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42752
753
Abhishek Arya64636af2018-05-04 14:42:13754def _GetBinaryPathsFromTargets(targets, build_dir):
755 """Return binary paths from target names."""
756 # FIXME: Derive output binary from target build definitions rather than
757 # assuming that it is always the same name.
758 binary_paths = []
759 for target in targets:
760 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18761 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13762 binary_path += '.exe'
763
764 if os.path.exists(binary_path):
765 binary_paths.append(binary_path)
766 else:
767 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40768 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13769 os.path.basename(binary_path))
770
771 return binary_paths
772
773
Abhishek Arya03911092018-05-21 16:42:35774def _GetCommandForWebTests(arguments):
775 """Return command to run for blink web tests."""
776 command_list = [
777 'python', 'testing/xvfb.py', 'python',
778 'third_party/blink/tools/run_web_tests.py',
779 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24780 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Robert Ma339fc472018-12-14 21:22:42781 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Abhishek Arya03911092018-05-21 16:42:35782 '--child-processes=%d' % max(1, int(multiprocessing.cpu_count() / 2)),
Abhishek Aryabd0655d2018-05-21 19:55:24783 '--disable-breakpad', '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35784 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
785 ]
786 if arguments.strip():
787 command_list.append(arguments)
788 return ' '.join(command_list)
789
790
791def _GetBinaryPathForWebTests():
792 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18793 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35794 if host_platform == 'win':
795 return os.path.join(BUILD_DIR, 'content_shell.exe')
796 elif host_platform == 'linux':
797 return os.path.join(BUILD_DIR, 'content_shell')
798 elif host_platform == 'mac':
799 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
800 'Content Shell')
801 else:
802 assert False, 'This platform is not supported for web tests.'
803
804
Abhishek Aryae5811afa2018-05-24 03:56:01805def _SetupOutputDir():
806 """Setup output directory."""
807 if os.path.exists(OUTPUT_DIR):
808 shutil.rmtree(OUTPUT_DIR)
809
810 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18811 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01812
813
Yuke Liaoabfbba42019-06-11 16:03:59814def _SetMacXcodePath():
815 """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
816 if sys.platform != 'darwin':
817 return
818
819 xcode_path = os.path.join(SRC_ROOT_PATH, 'build', 'mac_files', 'Xcode.app')
820 if os.path.exists(xcode_path):
821 os.environ['DEVELOPER_DIR'] = xcode_path
822
823
Yuke Liao506e8822017-12-04 16:52:54824def _ParseCommandArguments():
825 """Adds and parses relevant arguments for tool comands.
826
827 Returns:
828 A dictionary representing the arguments.
829 """
830 arg_parser = argparse.ArgumentParser()
831 arg_parser.usage = __doc__
832
Abhishek Arya1ec832c2017-12-05 18:06:59833 arg_parser.add_argument(
834 '-b',
835 '--build-dir',
836 type=str,
837 required=True,
838 help='The build directory, the path needs to be relative to the root of '
839 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54840
Abhishek Arya1ec832c2017-12-05 18:06:59841 arg_parser.add_argument(
842 '-o',
843 '--output-dir',
844 type=str,
845 required=True,
846 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54847
Abhishek Arya1ec832c2017-12-05 18:06:59848 arg_parser.add_argument(
849 '-c',
850 '--command',
851 action='append',
Abhishek Arya64636af2018-05-04 14:42:13852 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59853 help='Commands used to run test targets, one test target needs one and '
854 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13855 'current working directory is the root of the checkout. This option is '
856 'incompatible with -p/--profdata-file option.')
857
858 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35859 '-wt',
860 '--web-tests',
861 nargs='?',
862 type=str,
863 const=' ',
864 required=False,
865 help='Run blink web tests. Support passing arguments to run_web_tests.py')
866
867 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13868 '-p',
869 '--profdata-file',
870 type=str,
871 required=False,
872 help='Path to profdata file to use for generating code coverage reports. '
873 'This can be useful if you generated the profdata file seperately in '
874 'your own test harness. This option is ignored if run command(s) are '
875 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54876
Abhishek Arya1ec832c2017-12-05 18:06:59877 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42878 '-f',
879 '--filters',
880 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32881 required=False,
Yuke Liao66da1732017-12-05 22:19:42882 help='Directories or files to get code coverage for, and all files under '
883 'the directories are included recursively.')
884
885 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59886 '-i',
887 '--ignore-filename-regex',
888 type=str,
889 help='Skip source code files with file paths that match the given '
890 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
891 'to exclude files in third_party/ and out/ folders from the report.')
892
893 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32894 '--no-file-view',
895 action='store_true',
896 help='Don\'t generate the file view in the coverage report. When there '
897 'are large number of html files, the file view becomes heavy and may '
898 'cause the browser to freeze, and this argument comes handy.')
899
900 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18901 '--no-component-view',
902 action='store_true',
903 help='Don\'t generate the component view in the coverage report.')
904
905 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:40906 '--coverage-tools-dir',
907 type=str,
908 help='Path of the directory where LLVM coverage tools (llvm-cov, '
909 'llvm-profdata) exist. This should be only needed if you are testing '
910 'against a custom built clang revision. Otherwise, we pick coverage '
911 'tools automatically from your current source checkout.')
912
913 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:59914 '-j',
915 '--jobs',
916 type=int,
917 default=None,
918 help='Run N jobs to build in parallel. If not specified, a default value '
Max Moroz06576292019-01-03 19:22:52919 'will be derived based on CPUs and goma availability. Please refer to '
920 '\'autoninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:54921
Abhishek Arya1ec832c2017-12-05 18:06:59922 arg_parser.add_argument(
Sahel Sharify38cabdc2020-01-16 00:40:01923 '--format',
924 type=str,
925 default='html',
926 help='Output format of the "llvm-cov show" command. The supported '
927 'formats are "text" and "html".')
928
929 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:10930 '-v',
931 '--verbose',
932 action='store_true',
933 help='Prints additional output for diagnostics.')
934
935 arg_parser.add_argument(
936 '-l', '--log_file', type=str, help='Redirects logs to a file.')
937
938 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:02939 'targets',
940 nargs='+',
941 help='The names of the test targets to run. If multiple run commands are '
942 'specified using the -c/--command option, then the order of targets and '
943 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:54944
945 args = arg_parser.parse_args()
946 return args
947
948
949def Main():
950 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:40951 # Setup coverage binaries even when script is called with empty params. This
952 # is used by coverage bot for initial setup.
953 if len(sys.argv) == 1:
Brent McBrideb25b177a42020-05-11 18:13:06954 update.UpdatePackage('coverage_tools', coverage_utils.GetHostPlatform())
Yuke Liao082e99632018-05-18 15:40:40955 print(__doc__)
956 return
957
Abhishek Arya64636af2018-05-04 14:42:13958 # Change directory to source root to aid in relative paths calculations.
Abhishek Arya03911092018-05-21 16:42:35959 global SRC_ROOT_PATH
Max Moroz1de68d72018-08-21 13:38:18960 SRC_ROOT_PATH = coverage_utils.GetFullPath(
Abhishek Arya03911092018-05-21 16:42:35961 os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
Abhishek Arya64636af2018-05-04 14:42:13962 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:11963
Yuke Liao506e8822017-12-04 16:52:54964 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:18965 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:40966 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:13967
Yuke Liao506e8822017-12-04 16:52:54968 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:18969 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:01970
Yuke Liao506e8822017-12-04 16:52:54971 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:18972 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:54973
Abhishek Arya03911092018-05-21 16:42:35974 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:13975 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:35976 'provide prof-data file as input using -p/--profdata-file option OR '
977 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:43978
Abhishek Arya64636af2018-05-04 14:42:13979 assert not args.command or (len(args.targets) == len(args.command)), (
980 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:43981
Abhishek Arya1ec832c2017-12-05 18:06:59982 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:40983 'Build directory: "%s" doesn\'t exist. '
984 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:13985
Yuke Liaoc60b2d02018-03-02 21:40:43986 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:54987 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:32988
989 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:42990 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:32991 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:42992
Abhishek Aryae5811afa2018-05-24 03:56:01993 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:54994
Abhishek Arya03911092018-05-21 16:42:35995 # Get .profdata file and list of binary paths.
996 if args.web_tests:
997 commands = [_GetCommandForWebTests(args.web_tests)]
998 profdata_file_path = _CreateCoverageProfileDataForTargets(
999 args.targets, commands, args.jobs)
1000 binary_paths = [_GetBinaryPathForWebTests()]
1001 elif args.command:
1002 for i in range(len(args.command)):
1003 assert not 'run_web_tests.py' in args.command[i], (
1004 'run_web_tests.py is not supported via --command argument. '
1005 'Please use --run-web-tests argument instead.')
1006
Abhishek Arya64636af2018-05-04 14:42:131007 # A list of commands are provided. Run them to generate profdata file, and
1008 # create a list of binary paths from parsing commands.
1009 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1010 profdata_file_path = _CreateCoverageProfileDataForTargets(
1011 args.targets, args.command, args.jobs)
1012 binary_paths = [_GetBinaryPath(command) for command in args.command]
1013 else:
1014 # An input prof-data file is already provided. Just calculate binary paths.
1015 profdata_file_path = args.profdata_file
1016 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331017
Erik Chen283b92c72019-07-22 16:37:391018 # If the checkout uses the hermetic xcode binaries, then otool must be
1019 # directly invoked. The indirection via /usr/bin/otool won't work unless
1020 # there's an actual system install of Xcode.
1021 otool_path = None
1022 if sys.platform == 'darwin':
1023 hermetic_otool_path = os.path.join(
1024 SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents',
1025 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin',
1026 'otool')
1027 if os.path.exists(hermetic_otool_path):
1028 otool_path = hermetic_otool_path
Brent McBrideb25b177a42020-05-11 18:13:061029 if sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
1030 binary_paths.extend(
1031 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
Abhishek Arya78120bc2018-05-07 20:53:541032
Sahel Sharify38cabdc2020-01-16 00:40:011033 assert args.format == 'html' or args.format == 'text', (
1034 '%s is not a valid output format for "llvm-cov show". Only "text" and '
1035 '"html" formats are supported.' % (args.format))
1036 logging.info('Generating code coverage report in %s (this can take a while '
1037 'depending on size of target!).' % (args.format))
Max Moroz1de68d72018-08-21 13:38:181038 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591039 binary_paths, profdata_file_path, absolute_filter_paths,
1040 args.ignore_filename_regex)
Sahel Sharify38cabdc2020-01-16 00:40:011041 _GeneratePerFileLineByLineCoverageInFormat(
1042 binary_paths, profdata_file_path, absolute_filter_paths,
1043 args.ignore_filename_regex, args.format)
Max Moroz1de68d72018-08-21 13:38:181044 component_mappings = None
1045 if not args.no_component_view:
1046 component_mappings = json.load(urllib2.urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371047
Max Moroz1de68d72018-08-21 13:38:181048 # Call prepare here.
1049 processor = coverage_utils.CoverageReportPostProcessor(
1050 OUTPUT_DIR,
1051 SRC_ROOT_PATH,
1052 per_file_summary_data,
1053 no_component_view=args.no_component_view,
1054 no_file_view=args.no_file_view,
1055 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371056
Sahel Sharify38cabdc2020-01-16 00:40:011057 if args.format == 'html':
1058 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541059
Abhishek Arya1ec832c2017-12-05 18:06:591060
Yuke Liao506e8822017-12-04 16:52:541061if __name__ == '__main__':
1062 sys.exit(Main())