blob: f8404565d001855e30d48dfc56da0308be2287b7 [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
Sajjad Mirza0b96e002020-11-10 19:32:5577import platform
Yuke Liaob2926832018-03-02 17:34:2978import re
79import shlex
Max Moroz025d8952018-05-03 16:33:3480import shutil
Yuke Liao506e8822017-12-04 16:52:5481import subprocess
Yuke Liao506e8822017-12-04 16:52:5482import urllib2
83
Abhishek Arya1ec832c2017-12-05 18:06:5984sys.path.append(
85 os.path.join(
Yuke Liaoea228d02018-01-05 19:10:3386 os.path.dirname(__file__), os.path.pardir, os.path.pardir,
87 'third_party'))
Yuke Liaoea228d02018-01-05 19:10:3388from collections import defaultdict
89
Max Moroz1de68d72018-08-21 13:38:1890import coverage_utils
91
Yuke Liao082e99632018-05-18 15:40:4092# Absolute path to the code coverage tools binary. These paths can be
93# overwritten by user specified coverage tool paths.
pasthanab37d5bfd2020-05-28 12:18:3194# Absolute path to the root of the checkout.
95SRC_ROOT_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)),
96 os.path.pardir, os.path.pardir)
97LLVM_BIN_DIR = os.path.join(
98 os.path.join(SRC_ROOT_PATH, 'third_party', 'llvm-build', 'Release+Asserts'),
99 'bin')
Abhishek Arya1c97ea542018-05-10 03:53:19100LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
101LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:54102
Abhishek Arya03911092018-05-21 16:42:35103
Yuke Liao506e8822017-12-04 16:52:54104# Build directory, the value is parsed from command line arguments.
105BUILD_DIR = None
106
107# Output directory for generated artifacts, the value is parsed from command
108# line arguemnts.
109OUTPUT_DIR = None
110
Yuke Liao506e8822017-12-04 16:52:54111# Name of the file extension for profraw data files.
112PROFRAW_FILE_EXTENSION = 'profraw'
113
114# Name of the final profdata file, and this file needs to be passed to
115# "llvm-cov" command in order to call "llvm-cov show" to inspect the
116# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48117PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
118
119# Name of the file with summary information generated by llvm-cov export.
120SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54121
122# Build arg required for generating code coverage data.
123CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
124
Max Moroz7c5354f2018-05-06 00:03:48125LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37126
127# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19128COMPONENT_MAPPING_URL = (
129 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37130
Yuke Liao80afff32018-03-07 01:26:20131# Caches the results returned by _GetBuildArgs, don't use this variable
132# directly, call _GetBuildArgs instead.
133_BUILD_ARGS = None
134
Abhishek Aryac19bc5ef2018-05-04 22:10:02135# Retry failed merges.
136MERGE_RETRIES = 3
137
Abhishek Aryad35de7e2018-05-10 22:23:04138# Message to guide user to file a bug when everything else fails.
139FILE_BUG_MESSAGE = (
140 'If it persists, please file a bug with the command you used, git revision '
141 'and args.gn config here: '
142 'https://bugs.chromium.org/p/chromium/issues/entry?'
Yuke Liao03c644072019-07-30 18:33:40143 'components=Infra%3ETest%3ECodeCoverage')
Abhishek Aryad35de7e2018-05-10 22:23:04144
Abhishek Aryabd0655d2018-05-21 19:55:24145# String to replace with actual llvm profile path.
146LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
147
Yuke Liao082e99632018-05-18 15:40:40148def _ConfigureLLVMCoverageTools(args):
149 """Configures llvm coverage tools."""
150 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18151 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40152 global LLVM_COV_PATH
153 global LLVM_PROFDATA_PATH
154 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
155 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
156 else:
pasthanaa4844112020-05-21 18:03:55157 subprocess.check_call(
158 ['tools/clang/scripts/update.py', '--package', 'coverage_tools'])
Brent McBrideb25b177a42020-05-11 18:13:06159
160 if coverage_utils.GetHostPlatform() == 'win':
161 LLVM_COV_PATH += '.exe'
162 LLVM_PROFDATA_PATH += '.exe'
Yuke Liao082e99632018-05-18 15:40:40163
164 coverage_tools_exist = (
165 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
166 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
167 'both \'%s\' and \'%s\' exist.') % (
168 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
169
Abhishek Arya2f261182019-04-24 17:06:45170
Abhishek Arya1c97ea542018-05-10 03:53:19171def _GetPathWithLLVMSymbolizerDir():
172 """Add llvm-symbolizer directory to path for symbolized stacks."""
173 path = os.getenv('PATH')
174 dirs = path.split(os.pathsep)
175 if LLVM_BIN_DIR in dirs:
176 return path
177
178 return path + os.pathsep + LLVM_BIN_DIR
179
180
Yuke Liaoc60b2d02018-03-02 21:40:43181def _GetTargetOS():
182 """Returns the target os specified in args.gn file.
183
184 Returns an empty string is target_os is not specified.
185 """
Yuke Liao80afff32018-03-07 01:26:20186 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43187 return build_args['target_os'] if 'target_os' in build_args else ''
188
189
Yuke Liaob2926832018-03-02 17:34:29190def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10191 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43192 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10193
194
Sahel Sharify38cabdc2020-01-16 00:40:01195def _GeneratePerFileLineByLineCoverageInFormat(binary_paths, profdata_file_path,
196 filters, ignore_filename_regex,
197 output_format):
198 """Generates per file line-by-line coverage in html or text using
199 'llvm-cov show'.
Yuke Liao506e8822017-12-04 16:52:54200
Sahel Sharify38cabdc2020-01-16 00:40:01201 For a file with absolute path /a/b/x.cc, a html/txt report is generated as:
202 OUTPUT_DIR/coverage/a/b/x.cc.[html|txt]. For html format, an index html file
203 is also generated as: OUTPUT_DIR/index.html.
Yuke Liao506e8822017-12-04 16:52:54204
205 Args:
206 binary_paths: A list of paths to the instrumented binaries.
207 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42208 filters: A list of directories and files to get coverage for.
Sahel Sharify38cabdc2020-01-16 00:40:01209 ignore_filename_regex: A regular expression for skipping source code files
210 with certain file paths.
211 output_format: The output format of generated report files.
Yuke Liao506e8822017-12-04 16:52:54212 """
Yuke Liao506e8822017-12-04 16:52:54213 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
214 # [[-object BIN]] [SOURCES]
215 # NOTE: For object files, the first one is specified as a positional argument,
216 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10217 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40218 '"llvm-cov show" command.')
Sahel Sharify38cabdc2020-01-16 00:40:01219
Abhishek Arya1ec832c2017-12-05 18:06:59220 subprocess_cmd = [
Sahel Sharify38cabdc2020-01-16 00:40:01221 LLVM_COV_PATH, 'show', '-format={}'.format(output_format),
Abhishek Arya1ec832c2017-12-05 18:06:59222 '-output-dir={}'.format(OUTPUT_DIR),
223 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
224 ]
225 subprocess_cmd.extend(
226 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29227 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18228 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17229 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42230 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59231 if ignore_filename_regex:
232 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
233
Yuke Liao506e8822017-12-04 16:52:54234 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34235
Abhishek Aryafb70b532018-05-06 17:47:40236 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54237
238
Max Moroz7c5354f2018-05-06 00:03:48239def _GetLogsDirectoryPath():
240 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18241 return os.path.join(
242 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48243
244
245def _GetProfdataFilePath():
246 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18247 return os.path.join(
248 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
249 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48250
251
252def _GetSummaryFilePath():
253 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18254 return os.path.join(
255 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
256 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33257
258
Yuke Liao506e8822017-12-04 16:52:54259def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
260 """Builds and runs target to generate the coverage profile data.
261
262 Args:
263 targets: A list of targets to build with coverage instrumentation.
264 commands: A list of commands used to run the targets.
265 jobs_count: Number of jobs to run in parallel for building. If None, a
266 default value is derived based on CPUs availability.
267
268 Returns:
269 A relative path to the generated profdata file.
270 """
271 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02272 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59273 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02274 coverage_profdata_file_path = (
275 _CreateCoverageProfileDataFromTargetProfDataFiles(
276 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54277
Abhishek Aryac19bc5ef2018-05-04 22:10:02278 for target_profdata_file_path in target_profdata_file_paths:
279 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52280
Abhishek Aryac19bc5ef2018-05-04 22:10:02281 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54282
283
284def _BuildTargets(targets, jobs_count):
285 """Builds target with Clang coverage instrumentation.
286
287 This function requires current working directory to be the root of checkout.
288
289 Args:
290 targets: A list of targets to build with coverage instrumentation.
291 jobs_count: Number of jobs to run in parallel for compilation. If None, a
292 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54293 """
Abhishek Aryafb70b532018-05-06 17:47:40294 logging.info('Building %s.', str(targets))
Brent McBrideb25b177a42020-05-11 18:13:06295 autoninja = 'autoninja'
296 if coverage_utils.GetHostPlatform() == 'win':
297 autoninja += '.bat'
Yuke Liao506e8822017-12-04 16:52:54298
Brent McBrideb25b177a42020-05-11 18:13:06299 subprocess_cmd = [autoninja, '-C', BUILD_DIR]
Yuke Liao506e8822017-12-04 16:52:54300 if jobs_count is not None:
301 subprocess_cmd.append('-j' + str(jobs_count))
302
303 subprocess_cmd.extend(targets)
304 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40305 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54306
307
Abhishek Aryac19bc5ef2018-05-04 22:10:02308def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54309 """Runs commands and returns the relative paths to the profraw data files.
310
311 Args:
312 targets: A list of targets built with coverage instrumentation.
313 commands: A list of commands used to run the targets.
314
315 Returns:
316 A list of relative paths to the generated profraw data files.
317 """
Abhishek Aryafb70b532018-05-06 17:47:40318 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10319
Yuke Liao506e8822017-12-04 16:52:54320 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18321 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
322 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54323 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18324 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48325
326 # Ensure that logs directory exists.
327 if not os.path.exists(_GetLogsDirectoryPath()):
328 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54329
Abhishek Aryac19bc5ef2018-05-04 22:10:02330 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10331
Yuke Liaod4a9865202018-01-12 23:17:52332 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54333 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48334 output_file_name = os.extsep.join([target + '_output', 'log'])
335 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10336
Abhishek Aryac19bc5ef2018-05-04 22:10:02337 profdata_file_path = None
338 for _ in xrange(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40339 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02340 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10341
Abhishek Aryac19bc5ef2018-05-04 22:10:02342 if _IsIOSCommand(command):
343 # On iOS platform, due to lack of write permissions, profraw files are
344 # generated outside of the OUTPUT_DIR, and the exact paths are contained
345 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35346 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02347 else:
348 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35349 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02350
351 profraw_file_paths = []
352 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57353 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Abhishek Aryac19bc5ef2018-05-04 22:10:02354 else:
Max Moroz1de68d72018-08-21 13:38:18355 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02356 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48357 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18358 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02359
360 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40361 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04362 'please make sure the binary exists, is properly instrumented and '
363 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02364
Yuke Liao9c2c70b2018-05-23 15:37:57365 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18366 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
367 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57368
Abhishek Aryac19bc5ef2018-05-04 22:10:02369 try:
370 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
371 target, profraw_file_paths)
372 break
373 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04374 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02375 finally:
376 # Remove profraw files now so that they are not used in next iteration.
377 for profraw_file_path in profraw_file_paths:
378 os.remove(profraw_file_path)
379
380 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04381 'Failed to merge target "%s" profraw files after %d retries. %s' %
382 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02383 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54384
Abhishek Aryafb70b532018-05-06 17:47:40385 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10386
Abhishek Aryac19bc5ef2018-05-04 22:10:02387 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54388
389
Abhishek Arya03911092018-05-21 16:42:35390def _GetEnvironmentVars(profraw_file_path):
391 """Return environment vars for subprocess, given a profraw file path."""
392 env = os.environ.copy()
393 env.update({
394 'LLVM_PROFILE_FILE': profraw_file_path,
395 'PATH': _GetPathWithLLVMSymbolizerDir()
396 })
397 return env
398
399
Sajjad Mirza0b96e002020-11-10 19:32:55400def _SplitCommand(command):
401 """Split a command string into parts in a platform-specific way."""
402 if coverage_utils.GetHostPlatform() == 'win':
403 return command.split()
404 return shlex.split(command)
405
406
Abhishek Arya03911092018-05-21 16:42:35407def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10408 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52409 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01410 #
Max Morozd73e45f2018-04-24 18:32:47411 # "%p" expands out to the process ID. It's not used by this scripts due to:
412 # 1) If a target program spawns too many processess, it may exhaust all disk
413 # space available. For example, unit_tests writes thousands of .profraw
414 # files each of size 1GB+.
415 # 2) If a target binary uses shared libraries, coverage profile data for them
416 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01417 #
Yuke Liaod4a9865202018-01-12 23:17:52418 # "%Nm" expands out to the instrumented binary's signature. When this pattern
419 # is specified, the runtime creates a pool of N raw profiles which are used
420 # for on-line profile merging. The runtime takes care of selecting a raw
421 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52422 # N must be between 1 and 9. The merge pool specifier can only occur once per
423 # filename pattern.
424 #
Max Morozd73e45f2018-04-24 18:32:47425 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01426 #
Max Morozd73e45f2018-04-24 18:32:47427 # For other cases, "%4m" is chosen as it creates some level of parallelism,
428 # but it's not too big to consume too much computing resource or disk space.
429 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59430 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01431 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18432 expected_profraw_file_path = os.path.join(
433 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
434 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24435 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
436 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54437
Yuke Liaoa0c8c2f2018-02-28 20:14:10438 try:
Max Moroz7c5354f2018-05-06 00:03:48439 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35440 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55441 subprocess.check_call(_SplitCommand(command),
442 stdout=output_file_handle,
443 stderr=subprocess.STDOUT,
444 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10445 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35446 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10447
Abhishek Arya03911092018-05-21 16:42:35448 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10449
450
Yuke Liao27349c92018-03-22 21:10:01451def _IsFuzzerTarget(target):
452 """Returns true if the target is a fuzzer target."""
453 build_args = _GetBuildArgs()
454 use_libfuzzer = ('use_libfuzzer' in build_args and
455 build_args['use_libfuzzer'] == 'true')
456 return use_libfuzzer and target.endswith('_fuzzer')
457
458
Abhishek Arya03911092018-05-21 16:42:35459def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10460 """Runs a single iOS command and generates a profraw data file.
461
462 iOS application doesn't have write access to folders outside of the app, so
463 it's impossible to instruct the app to flush the profraw data file to the
464 desired location. The profraw data file will be generated somewhere within the
465 application's Documents folder, and the full path can be obtained by parsing
466 the output.
467 """
Yuke Liaob2926832018-03-02 17:34:29468 assert _IsIOSCommand(command)
469
470 # After running tests, iossim generates a profraw data file, it won't be
471 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
472 # checkout.
473 iossim_profraw_file_path = os.path.join(
474 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24475 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
476 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10477
478 try:
Abhishek Arya03911092018-05-21 16:42:35479 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55480 subprocess.check_call(_SplitCommand(command),
481 stdout=output_file_handle,
482 stderr=subprocess.STDOUT,
483 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10484 except subprocess.CalledProcessError as e:
485 # iossim emits non-zero return code even if tests run successfully, so
486 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35487 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10488
Abhishek Arya03911092018-05-21 16:42:35489 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10490
491
492def _GetProfrawDataFileByParsingOutput(output):
493 """Returns the path to the profraw data file obtained by parsing the output.
494
495 The output of running the test target has no format, but it is guaranteed to
496 have a single line containing the path to the generated profraw data file.
497 NOTE: This should only be called when target os is iOS.
498 """
Yuke Liaob2926832018-03-02 17:34:29499 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10500
Yuke Liaob2926832018-03-02 17:34:29501 output_by_lines = ''.join(output).splitlines()
502 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10503
504 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29505 result = profraw_file_pattern.match(line)
506 if result:
507 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10508
509 assert False, ('No profraw data file was generated, did you call '
510 'coverage_util::ConfigureCoverageReportPath() in test setup? '
511 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54512
513
Abhishek Aryac19bc5ef2018-05-04 22:10:02514def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
515 """Returns a relative path to coverage profdata file by merging target
516 profdata files.
Yuke Liao506e8822017-12-04 16:52:54517
518 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02519 profdata_file_paths: A list of relative paths to the profdata data files
520 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54521
522 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02523 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54524
525 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02526 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54527 """
Abhishek Aryafb70b532018-05-06 17:47:40528 logging.info('Creating the coverage profile data file.')
529 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48530 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54531 try:
Abhishek Arya1ec832c2017-12-05 18:06:59532 subprocess_cmd = [
533 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
534 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02535 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01536
537 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18538 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02539 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04540 logging.error(
541 'Failed to merge target profdata files to create coverage profdata. %s',
542 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02543 raise error
544
Abhishek Aryafb70b532018-05-06 17:47:40545 logging.debug('Finished merging target profdata files.')
546 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02547 profdata_file_path)
548 return profdata_file_path
549
550
551def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
552 """Returns a relative path to target profdata file by merging target
553 profraw files.
554
555 Args:
556 profraw_file_paths: A list of relative paths to the profdata data files
557 that are to be merged.
558
559 Returns:
560 A relative path to the merged coverage profdata file.
561
562 Raises:
563 CalledProcessError: An error occurred merging profdata files.
564 """
Abhishek Aryafb70b532018-05-06 17:47:40565 logging.info('Creating target profile data file.')
566 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02567 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
568
569 try:
570 subprocess_cmd = [
571 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
572 ]
Yuke Liao506e8822017-12-04 16:52:54573 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01574
575 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18576 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54577 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04578 logging.error(
579 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54580 raise error
581
Abhishek Aryafb70b532018-05-06 17:47:40582 logging.debug('Finished merging target profraw files.')
583 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10584 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54585 return profdata_file_path
586
587
Yuke Liao0e4c8682018-04-18 21:06:59588def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
589 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33590 """Generates per file coverage summary using "llvm-cov export" command."""
591 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
592 # [[-object BIN]] [SOURCES].
593 # NOTE: For object files, the first one is specified as a positional argument,
594 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10595 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40596 'export -summary-only" command.')
Sajjad Mirza07f52332020-11-11 01:50:47597 for path in binary_paths:
598 if not os.path.exists(path):
599 logging.error("Binary %s does not exist", path)
Yuke Liaoea228d02018-01-05 19:10:33600 subprocess_cmd = [
601 LLVM_COV_PATH, 'export', '-summary-only',
602 '-instr-profile=' + profdata_file_path, binary_paths[0]
603 ]
604 subprocess_cmd.extend(
605 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29606 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33607 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59608 if ignore_filename_regex:
609 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33610
Max Moroz7c5354f2018-05-06 00:03:48611 export_output = subprocess.check_output(subprocess_cmd)
612
613 # Write output on the disk to be used by code coverage bot.
614 with open(_GetSummaryFilePath(), 'w') as f:
615 f.write(export_output)
616
Max Moroz1de68d72018-08-21 13:38:18617 return export_output
Yuke Liaoea228d02018-01-05 19:10:33618
619
Yuke Liaob2926832018-03-02 17:34:29620def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
621 """Appends -arch arguments to the command list if it's ios platform.
622
623 iOS binaries are universal binaries, and require specifying the architecture
624 to use, and one architecture needs to be specified for each binary.
625 """
626 if _IsIOS():
627 cmd_list.extend(['-arch=x86_64'] * num_archs)
628
629
Yuke Liao506e8822017-12-04 16:52:54630def _GetBinaryPath(command):
631 """Returns a relative path to the binary to be run by the command.
632
Yuke Liao545db322018-02-15 17:12:01633 Currently, following types of commands are supported (e.g. url_unittests):
634 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
635 2. Use xvfb.
636 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
637 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37638 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
639 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10640 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37641 <iossim_arguments> -c <app_arguments>
642 out/Coverage-iphonesimulator/url_unittests.app"
643
Yuke Liao506e8822017-12-04 16:52:54644 Args:
645 command: A command used to run a target.
646
647 Returns:
648 A relative path to the binary.
649 """
Yuke Liao545db322018-02-15 17:12:01650 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
651
Sajjad Mirza0b96e002020-11-10 19:32:55652 command_parts = _SplitCommand(command)
Yuke Liao545db322018-02-15 17:12:01653 if os.path.basename(command_parts[0]) == 'python':
654 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40655 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01656 return command_parts[2]
657
658 if os.path.basename(command_parts[0]) == xvfb_script_name:
659 return command_parts[1]
660
Yuke Liaob2926832018-03-02 17:34:29661 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10662 # For a given application bundle, the binary resides in the bundle and has
663 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02664 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10665 app_name = os.path.splitext(os.path.basename(app_path))[0]
666 return os.path.join(app_path, app_name)
667
Sajjad Mirza07f52332020-11-11 01:50:47668 if coverage_utils.GetHostPlatform() == 'win' \
669 and not command_parts[0].endswith('.exe'):
670 return command_parts[0] + '.exe'
671
Yuke Liaob2926832018-03-02 17:34:29672 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54673
674
Yuke Liaob2926832018-03-02 17:34:29675def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10676 """Returns true if command is used to run tests on iOS platform."""
Sajjad Mirza0b96e002020-11-10 19:32:55677 return os.path.basename(_SplitCommand(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10678
679
Yuke Liao95d13d72017-12-07 18:18:50680def _VerifyTargetExecutablesAreInBuildDirectory(commands):
681 """Verifies that the target executables specified in the commands are inside
682 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54683 for command in commands:
684 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18685 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35686 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50687 'Target executable "%s" in command: "%s" is outside of '
688 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54689
690
691def _ValidateBuildingWithClangCoverage():
692 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20693 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54694
695 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
696 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59697 assert False, ('\'{} = true\' is required in args.gn.'
698 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54699
700
Yuke Liaoc60b2d02018-03-02 21:40:43701def _ValidateCurrentPlatformIsSupported():
702 """Asserts that this script suports running on the current platform"""
703 target_os = _GetTargetOS()
704 if target_os:
705 current_platform = target_os
706 else:
Max Moroz1de68d72018-08-21 13:38:18707 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43708
709 assert current_platform in [
Brent McBrideb25b177a42020-05-11 18:13:06710 'linux', 'mac', 'chromeos', 'ios', 'win'
711 ], ('Coverage is only supported on linux, mac, chromeos, ios and win.')
Yuke Liaoc60b2d02018-03-02 21:40:43712
713
Yuke Liao80afff32018-03-07 01:26:20714def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54715 """Parses args.gn file and returns results as a dictionary.
716
717 Returns:
718 A dictionary representing the build args.
719 """
Yuke Liao80afff32018-03-07 01:26:20720 global _BUILD_ARGS
721 if _BUILD_ARGS is not None:
722 return _BUILD_ARGS
723
724 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54725 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
726 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
727 'missing args.gn file.' % BUILD_DIR)
728 with open(build_args_path) as build_args_file:
729 build_args_lines = build_args_file.readlines()
730
Yuke Liao506e8822017-12-04 16:52:54731 for build_arg_line in build_args_lines:
732 build_arg_without_comments = build_arg_line.split('#')[0]
733 key_value_pair = build_arg_without_comments.split('=')
734 if len(key_value_pair) != 2:
735 continue
736
737 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43738
739 # Values are wrapped within a pair of double-quotes, so remove the leading
740 # and trailing double-quotes.
741 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20742 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54743
Yuke Liao80afff32018-03-07 01:26:20744 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54745
746
Abhishek Arya16f059a2017-12-07 17:47:32747def _VerifyPathsAndReturnAbsolutes(paths):
748 """Verifies that the paths specified in |paths| exist and returns absolute
749 versions.
Yuke Liao66da1732017-12-05 22:19:42750
751 Args:
752 paths: A list of files or directories.
753 """
Abhishek Arya16f059a2017-12-07 17:47:32754 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42755 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32756 absolute_path = os.path.join(SRC_ROOT_PATH, path)
757 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
758
759 absolute_paths.append(absolute_path)
760
761 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42762
763
Abhishek Arya64636af2018-05-04 14:42:13764def _GetBinaryPathsFromTargets(targets, build_dir):
765 """Return binary paths from target names."""
766 # FIXME: Derive output binary from target build definitions rather than
767 # assuming that it is always the same name.
768 binary_paths = []
769 for target in targets:
770 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18771 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13772 binary_path += '.exe'
773
774 if os.path.exists(binary_path):
775 binary_paths.append(binary_path)
776 else:
777 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40778 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13779 os.path.basename(binary_path))
780
781 return binary_paths
782
783
Abhishek Arya03911092018-05-21 16:42:35784def _GetCommandForWebTests(arguments):
785 """Return command to run for blink web tests."""
786 command_list = [
787 'python', 'testing/xvfb.py', 'python',
788 'third_party/blink/tools/run_web_tests.py',
789 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24790 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Robert Ma339fc472018-12-14 21:22:42791 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Abhishek Arya03911092018-05-21 16:42:35792 '--child-processes=%d' % max(1, int(multiprocessing.cpu_count() / 2)),
Abhishek Aryabd0655d2018-05-21 19:55:24793 '--disable-breakpad', '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35794 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
795 ]
796 if arguments.strip():
797 command_list.append(arguments)
798 return ' '.join(command_list)
799
800
801def _GetBinaryPathForWebTests():
802 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18803 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35804 if host_platform == 'win':
805 return os.path.join(BUILD_DIR, 'content_shell.exe')
806 elif host_platform == 'linux':
807 return os.path.join(BUILD_DIR, 'content_shell')
808 elif host_platform == 'mac':
809 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
810 'Content Shell')
811 else:
812 assert False, 'This platform is not supported for web tests.'
813
814
Abhishek Aryae5811afa2018-05-24 03:56:01815def _SetupOutputDir():
816 """Setup output directory."""
817 if os.path.exists(OUTPUT_DIR):
818 shutil.rmtree(OUTPUT_DIR)
819
820 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18821 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01822
823
Yuke Liaoabfbba42019-06-11 16:03:59824def _SetMacXcodePath():
825 """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
826 if sys.platform != 'darwin':
827 return
828
829 xcode_path = os.path.join(SRC_ROOT_PATH, 'build', 'mac_files', 'Xcode.app')
830 if os.path.exists(xcode_path):
831 os.environ['DEVELOPER_DIR'] = xcode_path
832
833
Yuke Liao506e8822017-12-04 16:52:54834def _ParseCommandArguments():
835 """Adds and parses relevant arguments for tool comands.
836
837 Returns:
838 A dictionary representing the arguments.
839 """
840 arg_parser = argparse.ArgumentParser()
841 arg_parser.usage = __doc__
842
Abhishek Arya1ec832c2017-12-05 18:06:59843 arg_parser.add_argument(
844 '-b',
845 '--build-dir',
846 type=str,
847 required=True,
848 help='The build directory, the path needs to be relative to the root of '
849 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54850
Abhishek Arya1ec832c2017-12-05 18:06:59851 arg_parser.add_argument(
852 '-o',
853 '--output-dir',
854 type=str,
855 required=True,
856 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54857
Abhishek Arya1ec832c2017-12-05 18:06:59858 arg_parser.add_argument(
859 '-c',
860 '--command',
861 action='append',
Abhishek Arya64636af2018-05-04 14:42:13862 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59863 help='Commands used to run test targets, one test target needs one and '
864 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13865 'current working directory is the root of the checkout. This option is '
866 'incompatible with -p/--profdata-file option.')
867
868 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35869 '-wt',
870 '--web-tests',
871 nargs='?',
872 type=str,
873 const=' ',
874 required=False,
875 help='Run blink web tests. Support passing arguments to run_web_tests.py')
876
877 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13878 '-p',
879 '--profdata-file',
880 type=str,
881 required=False,
882 help='Path to profdata file to use for generating code coverage reports. '
883 'This can be useful if you generated the profdata file seperately in '
884 'your own test harness. This option is ignored if run command(s) are '
885 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54886
Abhishek Arya1ec832c2017-12-05 18:06:59887 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42888 '-f',
889 '--filters',
890 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32891 required=False,
Yuke Liao66da1732017-12-05 22:19:42892 help='Directories or files to get code coverage for, and all files under '
893 'the directories are included recursively.')
894
895 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59896 '-i',
897 '--ignore-filename-regex',
898 type=str,
899 help='Skip source code files with file paths that match the given '
900 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
901 'to exclude files in third_party/ and out/ folders from the report.')
902
903 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32904 '--no-file-view',
905 action='store_true',
906 help='Don\'t generate the file view in the coverage report. When there '
907 'are large number of html files, the file view becomes heavy and may '
908 'cause the browser to freeze, and this argument comes handy.')
909
910 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18911 '--no-component-view',
912 action='store_true',
913 help='Don\'t generate the component view in the coverage report.')
914
915 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:40916 '--coverage-tools-dir',
917 type=str,
918 help='Path of the directory where LLVM coverage tools (llvm-cov, '
919 'llvm-profdata) exist. This should be only needed if you are testing '
920 'against a custom built clang revision. Otherwise, we pick coverage '
921 'tools automatically from your current source checkout.')
922
923 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:59924 '-j',
925 '--jobs',
926 type=int,
927 default=None,
928 help='Run N jobs to build in parallel. If not specified, a default value '
Max Moroz06576292019-01-03 19:22:52929 'will be derived based on CPUs and goma availability. Please refer to '
930 '\'autoninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:54931
Abhishek Arya1ec832c2017-12-05 18:06:59932 arg_parser.add_argument(
Sahel Sharify38cabdc2020-01-16 00:40:01933 '--format',
934 type=str,
935 default='html',
936 help='Output format of the "llvm-cov show" command. The supported '
937 'formats are "text" and "html".')
938
939 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:10940 '-v',
941 '--verbose',
942 action='store_true',
943 help='Prints additional output for diagnostics.')
944
945 arg_parser.add_argument(
946 '-l', '--log_file', type=str, help='Redirects logs to a file.')
947
948 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:02949 'targets',
950 nargs='+',
951 help='The names of the test targets to run. If multiple run commands are '
952 'specified using the -c/--command option, then the order of targets and '
953 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:54954
955 args = arg_parser.parse_args()
956 return args
957
958
959def Main():
960 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:40961
Abhishek Arya64636af2018-05-04 14:42:13962 # Change directory to source root to aid in relative paths calculations.
963 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:11964
pasthanaa4844112020-05-21 18:03:55965 # Setup coverage binaries even when script is called with empty params. This
966 # is used by coverage bot for initial setup.
967 if len(sys.argv) == 1:
968 subprocess.check_call(
969 ['tools/clang/scripts/update.py', '--package', 'coverage_tools'])
970 print(__doc__)
971 return
972
Yuke Liao506e8822017-12-04 16:52:54973 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:18974 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:40975 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:13976
Yuke Liao506e8822017-12-04 16:52:54977 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:18978 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:01979
Yuke Liao506e8822017-12-04 16:52:54980 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:18981 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:54982
Abhishek Arya03911092018-05-21 16:42:35983 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:13984 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:35985 'provide prof-data file as input using -p/--profdata-file option OR '
986 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:43987
Abhishek Arya64636af2018-05-04 14:42:13988 assert not args.command or (len(args.targets) == len(args.command)), (
989 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:43990
Abhishek Arya1ec832c2017-12-05 18:06:59991 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:40992 'Build directory: "%s" doesn\'t exist. '
993 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:13994
Yuke Liaoc60b2d02018-03-02 21:40:43995 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:54996 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:32997
998 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:42999 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:321000 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:421001
Abhishek Aryae5811afa2018-05-24 03:56:011002 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:541003
Abhishek Arya03911092018-05-21 16:42:351004 # Get .profdata file and list of binary paths.
1005 if args.web_tests:
1006 commands = [_GetCommandForWebTests(args.web_tests)]
1007 profdata_file_path = _CreateCoverageProfileDataForTargets(
1008 args.targets, commands, args.jobs)
1009 binary_paths = [_GetBinaryPathForWebTests()]
1010 elif args.command:
1011 for i in range(len(args.command)):
1012 assert not 'run_web_tests.py' in args.command[i], (
1013 'run_web_tests.py is not supported via --command argument. '
1014 'Please use --run-web-tests argument instead.')
1015
Abhishek Arya64636af2018-05-04 14:42:131016 # A list of commands are provided. Run them to generate profdata file, and
1017 # create a list of binary paths from parsing commands.
1018 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1019 profdata_file_path = _CreateCoverageProfileDataForTargets(
1020 args.targets, args.command, args.jobs)
1021 binary_paths = [_GetBinaryPath(command) for command in args.command]
1022 else:
1023 # An input prof-data file is already provided. Just calculate binary paths.
1024 profdata_file_path = args.profdata_file
1025 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331026
Erik Chen283b92c72019-07-22 16:37:391027 # If the checkout uses the hermetic xcode binaries, then otool must be
1028 # directly invoked. The indirection via /usr/bin/otool won't work unless
1029 # there's an actual system install of Xcode.
1030 otool_path = None
1031 if sys.platform == 'darwin':
1032 hermetic_otool_path = os.path.join(
1033 SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents',
1034 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin',
1035 'otool')
1036 if os.path.exists(hermetic_otool_path):
1037 otool_path = hermetic_otool_path
Brent McBrideb25b177a42020-05-11 18:13:061038 if sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
1039 binary_paths.extend(
1040 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
Abhishek Arya78120bc2018-05-07 20:53:541041
Sahel Sharify38cabdc2020-01-16 00:40:011042 assert args.format == 'html' or args.format == 'text', (
1043 '%s is not a valid output format for "llvm-cov show". Only "text" and '
1044 '"html" formats are supported.' % (args.format))
1045 logging.info('Generating code coverage report in %s (this can take a while '
1046 'depending on size of target!).' % (args.format))
Max Moroz1de68d72018-08-21 13:38:181047 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591048 binary_paths, profdata_file_path, absolute_filter_paths,
1049 args.ignore_filename_regex)
Sahel Sharify38cabdc2020-01-16 00:40:011050 _GeneratePerFileLineByLineCoverageInFormat(
1051 binary_paths, profdata_file_path, absolute_filter_paths,
1052 args.ignore_filename_regex, args.format)
Max Moroz1de68d72018-08-21 13:38:181053 component_mappings = None
1054 if not args.no_component_view:
1055 component_mappings = json.load(urllib2.urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371056
Max Moroz1de68d72018-08-21 13:38:181057 # Call prepare here.
1058 processor = coverage_utils.CoverageReportPostProcessor(
1059 OUTPUT_DIR,
1060 SRC_ROOT_PATH,
1061 per_file_summary_data,
1062 no_component_view=args.no_component_view,
1063 no_file_view=args.no_file_view,
1064 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371065
Sahel Sharify38cabdc2020-01-16 00:40:011066 if args.format == 'html':
1067 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541068
Abhishek Arya1ec832c2017-12-05 18:06:591069
Yuke Liao506e8822017-12-04 16:52:541070if __name__ == '__main__':
1071 sys.exit(Main())