blob: 8b3b7a40f784d9bce9eef508b029a9645e53474c [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:
Hans Wennborg8ee64a12019-11-05 17:31:30160 update.UpdatePackage('coverage_tools')
Yuke Liao082e99632018-05-18 15:40:40161
162 coverage_tools_exist = (
163 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
164 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
165 'both \'%s\' and \'%s\' exist.') % (
166 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
167
Abhishek Arya2f261182019-04-24 17:06:45168
Abhishek Arya1c97ea542018-05-10 03:53:19169def _GetPathWithLLVMSymbolizerDir():
170 """Add llvm-symbolizer directory to path for symbolized stacks."""
171 path = os.getenv('PATH')
172 dirs = path.split(os.pathsep)
173 if LLVM_BIN_DIR in dirs:
174 return path
175
176 return path + os.pathsep + LLVM_BIN_DIR
177
178
Yuke Liaoc60b2d02018-03-02 21:40:43179def _GetTargetOS():
180 """Returns the target os specified in args.gn file.
181
182 Returns an empty string is target_os is not specified.
183 """
Yuke Liao80afff32018-03-07 01:26:20184 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43185 return build_args['target_os'] if 'target_os' in build_args else ''
186
187
Yuke Liaob2926832018-03-02 17:34:29188def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10189 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43190 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10191
192
Sahel Sharify38cabdc2020-01-16 00:40:01193def _GeneratePerFileLineByLineCoverageInFormat(binary_paths, profdata_file_path,
194 filters, ignore_filename_regex,
195 output_format):
196 """Generates per file line-by-line coverage in html or text using
197 'llvm-cov show'.
Yuke Liao506e8822017-12-04 16:52:54198
Sahel Sharify38cabdc2020-01-16 00:40:01199 For a file with absolute path /a/b/x.cc, a html/txt report is generated as:
200 OUTPUT_DIR/coverage/a/b/x.cc.[html|txt]. For html format, an index html file
201 is also generated as: OUTPUT_DIR/index.html.
Yuke Liao506e8822017-12-04 16:52:54202
203 Args:
204 binary_paths: A list of paths to the instrumented binaries.
205 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42206 filters: A list of directories and files to get coverage for.
Sahel Sharify38cabdc2020-01-16 00:40:01207 ignore_filename_regex: A regular expression for skipping source code files
208 with certain file paths.
209 output_format: The output format of generated report files.
Yuke Liao506e8822017-12-04 16:52:54210 """
Yuke Liao506e8822017-12-04 16:52:54211 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
212 # [[-object BIN]] [SOURCES]
213 # NOTE: For object files, the first one is specified as a positional argument,
214 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10215 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40216 '"llvm-cov show" command.')
Sahel Sharify38cabdc2020-01-16 00:40:01217
Abhishek Arya1ec832c2017-12-05 18:06:59218 subprocess_cmd = [
Sahel Sharify38cabdc2020-01-16 00:40:01219 LLVM_COV_PATH, 'show', '-format={}'.format(output_format),
Abhishek Arya1ec832c2017-12-05 18:06:59220 '-output-dir={}'.format(OUTPUT_DIR),
221 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
222 ]
223 subprocess_cmd.extend(
224 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29225 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18226 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17227 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42228 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59229 if ignore_filename_regex:
230 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
231
Yuke Liao506e8822017-12-04 16:52:54232 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34233
Abhishek Aryafb70b532018-05-06 17:47:40234 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54235
236
Max Moroz7c5354f2018-05-06 00:03:48237def _GetLogsDirectoryPath():
238 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18239 return os.path.join(
240 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48241
242
243def _GetProfdataFilePath():
244 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18245 return os.path.join(
246 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
247 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48248
249
250def _GetSummaryFilePath():
251 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18252 return os.path.join(
253 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
254 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33255
256
Yuke Liao506e8822017-12-04 16:52:54257def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
258 """Builds and runs target to generate the coverage profile data.
259
260 Args:
261 targets: A list of targets to build with coverage instrumentation.
262 commands: A list of commands used to run the targets.
263 jobs_count: Number of jobs to run in parallel for building. If None, a
264 default value is derived based on CPUs availability.
265
266 Returns:
267 A relative path to the generated profdata file.
268 """
269 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02270 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59271 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02272 coverage_profdata_file_path = (
273 _CreateCoverageProfileDataFromTargetProfDataFiles(
274 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54275
Abhishek Aryac19bc5ef2018-05-04 22:10:02276 for target_profdata_file_path in target_profdata_file_paths:
277 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52278
Abhishek Aryac19bc5ef2018-05-04 22:10:02279 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54280
281
282def _BuildTargets(targets, jobs_count):
283 """Builds target with Clang coverage instrumentation.
284
285 This function requires current working directory to be the root of checkout.
286
287 Args:
288 targets: A list of targets to build with coverage instrumentation.
289 jobs_count: Number of jobs to run in parallel for compilation. If None, a
290 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54291 """
Abhishek Aryafb70b532018-05-06 17:47:40292 logging.info('Building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54293
Max Moroz06576292019-01-03 19:22:52294 subprocess_cmd = ['autoninja', '-C', BUILD_DIR]
Yuke Liao506e8822017-12-04 16:52:54295 if jobs_count is not None:
296 subprocess_cmd.append('-j' + str(jobs_count))
297
298 subprocess_cmd.extend(targets)
299 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40300 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54301
302
Abhishek Aryac19bc5ef2018-05-04 22:10:02303def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54304 """Runs commands and returns the relative paths to the profraw data files.
305
306 Args:
307 targets: A list of targets built with coverage instrumentation.
308 commands: A list of commands used to run the targets.
309
310 Returns:
311 A list of relative paths to the generated profraw data files.
312 """
Abhishek Aryafb70b532018-05-06 17:47:40313 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10314
Yuke Liao506e8822017-12-04 16:52:54315 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18316 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
317 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54318 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18319 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48320
321 # Ensure that logs directory exists.
322 if not os.path.exists(_GetLogsDirectoryPath()):
323 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54324
Abhishek Aryac19bc5ef2018-05-04 22:10:02325 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10326
Yuke Liaod4a9865202018-01-12 23:17:52327 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54328 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48329 output_file_name = os.extsep.join([target + '_output', 'log'])
330 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10331
Abhishek Aryac19bc5ef2018-05-04 22:10:02332 profdata_file_path = None
333 for _ in xrange(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40334 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02335 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10336
Abhishek Aryac19bc5ef2018-05-04 22:10:02337 if _IsIOSCommand(command):
338 # On iOS platform, due to lack of write permissions, profraw files are
339 # generated outside of the OUTPUT_DIR, and the exact paths are contained
340 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35341 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02342 else:
343 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35344 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02345
346 profraw_file_paths = []
347 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57348 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Abhishek Aryac19bc5ef2018-05-04 22:10:02349 else:
Max Moroz1de68d72018-08-21 13:38:18350 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02351 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48352 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18353 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02354
355 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40356 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04357 'please make sure the binary exists, is properly instrumented and '
358 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02359
Yuke Liao9c2c70b2018-05-23 15:37:57360 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18361 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
362 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57363
Abhishek Aryac19bc5ef2018-05-04 22:10:02364 try:
365 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
366 target, profraw_file_paths)
367 break
368 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04369 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02370 finally:
371 # Remove profraw files now so that they are not used in next iteration.
372 for profraw_file_path in profraw_file_paths:
373 os.remove(profraw_file_path)
374
375 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04376 'Failed to merge target "%s" profraw files after %d retries. %s' %
377 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02378 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54379
Abhishek Aryafb70b532018-05-06 17:47:40380 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10381
Abhishek Aryac19bc5ef2018-05-04 22:10:02382 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54383
384
Abhishek Arya03911092018-05-21 16:42:35385def _GetEnvironmentVars(profraw_file_path):
386 """Return environment vars for subprocess, given a profraw file path."""
387 env = os.environ.copy()
388 env.update({
389 'LLVM_PROFILE_FILE': profraw_file_path,
390 'PATH': _GetPathWithLLVMSymbolizerDir()
391 })
392 return env
393
394
395def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10396 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52397 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01398 #
Max Morozd73e45f2018-04-24 18:32:47399 # "%p" expands out to the process ID. It's not used by this scripts due to:
400 # 1) If a target program spawns too many processess, it may exhaust all disk
401 # space available. For example, unit_tests writes thousands of .profraw
402 # files each of size 1GB+.
403 # 2) If a target binary uses shared libraries, coverage profile data for them
404 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01405 #
Yuke Liaod4a9865202018-01-12 23:17:52406 # "%Nm" expands out to the instrumented binary's signature. When this pattern
407 # is specified, the runtime creates a pool of N raw profiles which are used
408 # for on-line profile merging. The runtime takes care of selecting a raw
409 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52410 # N must be between 1 and 9. The merge pool specifier can only occur once per
411 # filename pattern.
412 #
Max Morozd73e45f2018-04-24 18:32:47413 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01414 #
Max Morozd73e45f2018-04-24 18:32:47415 # For other cases, "%4m" is chosen as it creates some level of parallelism,
416 # but it's not too big to consume too much computing resource or disk space.
417 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59418 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01419 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18420 expected_profraw_file_path = os.path.join(
421 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
422 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24423 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
424 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54425
Yuke Liaoa0c8c2f2018-02-28 20:14:10426 try:
Max Moroz7c5354f2018-05-06 00:03:48427 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35428 with open(output_file_path, 'wb') as output_file_handle:
429 subprocess.check_call(
430 shlex.split(command),
431 stdout=output_file_handle,
432 stderr=subprocess.STDOUT,
433 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10434 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35435 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10436
Abhishek Arya03911092018-05-21 16:42:35437 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10438
439
Yuke Liao27349c92018-03-22 21:10:01440def _IsFuzzerTarget(target):
441 """Returns true if the target is a fuzzer target."""
442 build_args = _GetBuildArgs()
443 use_libfuzzer = ('use_libfuzzer' in build_args and
444 build_args['use_libfuzzer'] == 'true')
445 return use_libfuzzer and target.endswith('_fuzzer')
446
447
Abhishek Arya03911092018-05-21 16:42:35448def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10449 """Runs a single iOS command and generates a profraw data file.
450
451 iOS application doesn't have write access to folders outside of the app, so
452 it's impossible to instruct the app to flush the profraw data file to the
453 desired location. The profraw data file will be generated somewhere within the
454 application's Documents folder, and the full path can be obtained by parsing
455 the output.
456 """
Yuke Liaob2926832018-03-02 17:34:29457 assert _IsIOSCommand(command)
458
459 # After running tests, iossim generates a profraw data file, it won't be
460 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
461 # checkout.
462 iossim_profraw_file_path = os.path.join(
463 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24464 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
465 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10466
467 try:
Abhishek Arya03911092018-05-21 16:42:35468 with open(output_file_path, 'wb') as output_file_handle:
469 subprocess.check_call(
470 shlex.split(command),
471 stdout=output_file_handle,
472 stderr=subprocess.STDOUT,
473 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10474 except subprocess.CalledProcessError as e:
475 # iossim emits non-zero return code even if tests run successfully, so
476 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35477 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10478
Abhishek Arya03911092018-05-21 16:42:35479 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10480
481
482def _GetProfrawDataFileByParsingOutput(output):
483 """Returns the path to the profraw data file obtained by parsing the output.
484
485 The output of running the test target has no format, but it is guaranteed to
486 have a single line containing the path to the generated profraw data file.
487 NOTE: This should only be called when target os is iOS.
488 """
Yuke Liaob2926832018-03-02 17:34:29489 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10490
Yuke Liaob2926832018-03-02 17:34:29491 output_by_lines = ''.join(output).splitlines()
492 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10493
494 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29495 result = profraw_file_pattern.match(line)
496 if result:
497 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10498
499 assert False, ('No profraw data file was generated, did you call '
500 'coverage_util::ConfigureCoverageReportPath() in test setup? '
501 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54502
503
Abhishek Aryac19bc5ef2018-05-04 22:10:02504def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
505 """Returns a relative path to coverage profdata file by merging target
506 profdata files.
Yuke Liao506e8822017-12-04 16:52:54507
508 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02509 profdata_file_paths: A list of relative paths to the profdata data files
510 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54511
512 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02513 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54514
515 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02516 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54517 """
Abhishek Aryafb70b532018-05-06 17:47:40518 logging.info('Creating the coverage profile data file.')
519 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48520 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54521 try:
Abhishek Arya1ec832c2017-12-05 18:06:59522 subprocess_cmd = [
523 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
524 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02525 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01526
527 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18528 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02529 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04530 logging.error(
531 'Failed to merge target profdata files to create coverage profdata. %s',
532 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02533 raise error
534
Abhishek Aryafb70b532018-05-06 17:47:40535 logging.debug('Finished merging target profdata files.')
536 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02537 profdata_file_path)
538 return profdata_file_path
539
540
541def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
542 """Returns a relative path to target profdata file by merging target
543 profraw files.
544
545 Args:
546 profraw_file_paths: A list of relative paths to the profdata data files
547 that are to be merged.
548
549 Returns:
550 A relative path to the merged coverage profdata file.
551
552 Raises:
553 CalledProcessError: An error occurred merging profdata files.
554 """
Abhishek Aryafb70b532018-05-06 17:47:40555 logging.info('Creating target profile data file.')
556 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02557 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
558
559 try:
560 subprocess_cmd = [
561 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
562 ]
Yuke Liao506e8822017-12-04 16:52:54563 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01564
565 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18566 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54567 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04568 logging.error(
569 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54570 raise error
571
Abhishek Aryafb70b532018-05-06 17:47:40572 logging.debug('Finished merging target profraw files.')
573 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10574 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54575 return profdata_file_path
576
577
Yuke Liao0e4c8682018-04-18 21:06:59578def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
579 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33580 """Generates per file coverage summary using "llvm-cov export" command."""
581 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
582 # [[-object BIN]] [SOURCES].
583 # NOTE: For object files, the first one is specified as a positional argument,
584 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10585 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40586 'export -summary-only" command.')
Yuke Liaoea228d02018-01-05 19:10:33587 subprocess_cmd = [
588 LLVM_COV_PATH, 'export', '-summary-only',
589 '-instr-profile=' + profdata_file_path, binary_paths[0]
590 ]
591 subprocess_cmd.extend(
592 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29593 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33594 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59595 if ignore_filename_regex:
596 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33597
Max Moroz7c5354f2018-05-06 00:03:48598 export_output = subprocess.check_output(subprocess_cmd)
599
600 # Write output on the disk to be used by code coverage bot.
601 with open(_GetSummaryFilePath(), 'w') as f:
602 f.write(export_output)
603
Max Moroz1de68d72018-08-21 13:38:18604 return export_output
Yuke Liaoea228d02018-01-05 19:10:33605
606
Yuke Liaob2926832018-03-02 17:34:29607def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
608 """Appends -arch arguments to the command list if it's ios platform.
609
610 iOS binaries are universal binaries, and require specifying the architecture
611 to use, and one architecture needs to be specified for each binary.
612 """
613 if _IsIOS():
614 cmd_list.extend(['-arch=x86_64'] * num_archs)
615
616
Yuke Liao506e8822017-12-04 16:52:54617def _GetBinaryPath(command):
618 """Returns a relative path to the binary to be run by the command.
619
Yuke Liao545db322018-02-15 17:12:01620 Currently, following types of commands are supported (e.g. url_unittests):
621 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
622 2. Use xvfb.
623 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
624 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37625 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
626 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10627 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37628 <iossim_arguments> -c <app_arguments>
629 out/Coverage-iphonesimulator/url_unittests.app"
630
Yuke Liao506e8822017-12-04 16:52:54631 Args:
632 command: A command used to run a target.
633
634 Returns:
635 A relative path to the binary.
636 """
Yuke Liao545db322018-02-15 17:12:01637 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
638
Yuke Liaob2926832018-03-02 17:34:29639 command_parts = shlex.split(command)
Yuke Liao545db322018-02-15 17:12:01640 if os.path.basename(command_parts[0]) == 'python':
641 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40642 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01643 return command_parts[2]
644
645 if os.path.basename(command_parts[0]) == xvfb_script_name:
646 return command_parts[1]
647
Yuke Liaob2926832018-03-02 17:34:29648 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10649 # For a given application bundle, the binary resides in the bundle and has
650 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02651 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10652 app_name = os.path.splitext(os.path.basename(app_path))[0]
653 return os.path.join(app_path, app_name)
654
Yuke Liaob2926832018-03-02 17:34:29655 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54656
657
Yuke Liaob2926832018-03-02 17:34:29658def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10659 """Returns true if command is used to run tests on iOS platform."""
Yuke Liaob2926832018-03-02 17:34:29660 return os.path.basename(shlex.split(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10661
662
Yuke Liao95d13d72017-12-07 18:18:50663def _VerifyTargetExecutablesAreInBuildDirectory(commands):
664 """Verifies that the target executables specified in the commands are inside
665 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54666 for command in commands:
667 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18668 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35669 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50670 'Target executable "%s" in command: "%s" is outside of '
671 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54672
673
674def _ValidateBuildingWithClangCoverage():
675 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20676 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54677
678 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
679 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59680 assert False, ('\'{} = true\' is required in args.gn.'
681 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54682
683
Yuke Liaoc60b2d02018-03-02 21:40:43684def _ValidateCurrentPlatformIsSupported():
685 """Asserts that this script suports running on the current platform"""
686 target_os = _GetTargetOS()
687 if target_os:
688 current_platform = target_os
689 else:
Max Moroz1de68d72018-08-21 13:38:18690 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43691
692 assert current_platform in [
693 'linux', 'mac', 'chromeos', 'ios'
694 ], ('Coverage is only supported on linux, mac, chromeos and ios.')
695
696
Yuke Liao80afff32018-03-07 01:26:20697def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54698 """Parses args.gn file and returns results as a dictionary.
699
700 Returns:
701 A dictionary representing the build args.
702 """
Yuke Liao80afff32018-03-07 01:26:20703 global _BUILD_ARGS
704 if _BUILD_ARGS is not None:
705 return _BUILD_ARGS
706
707 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54708 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
709 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
710 'missing args.gn file.' % BUILD_DIR)
711 with open(build_args_path) as build_args_file:
712 build_args_lines = build_args_file.readlines()
713
Yuke Liao506e8822017-12-04 16:52:54714 for build_arg_line in build_args_lines:
715 build_arg_without_comments = build_arg_line.split('#')[0]
716 key_value_pair = build_arg_without_comments.split('=')
717 if len(key_value_pair) != 2:
718 continue
719
720 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43721
722 # Values are wrapped within a pair of double-quotes, so remove the leading
723 # and trailing double-quotes.
724 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20725 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54726
Yuke Liao80afff32018-03-07 01:26:20727 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54728
729
Abhishek Arya16f059a2017-12-07 17:47:32730def _VerifyPathsAndReturnAbsolutes(paths):
731 """Verifies that the paths specified in |paths| exist and returns absolute
732 versions.
Yuke Liao66da1732017-12-05 22:19:42733
734 Args:
735 paths: A list of files or directories.
736 """
Abhishek Arya16f059a2017-12-07 17:47:32737 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42738 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32739 absolute_path = os.path.join(SRC_ROOT_PATH, path)
740 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
741
742 absolute_paths.append(absolute_path)
743
744 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42745
746
Abhishek Arya64636af2018-05-04 14:42:13747def _GetBinaryPathsFromTargets(targets, build_dir):
748 """Return binary paths from target names."""
749 # FIXME: Derive output binary from target build definitions rather than
750 # assuming that it is always the same name.
751 binary_paths = []
752 for target in targets:
753 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18754 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13755 binary_path += '.exe'
756
757 if os.path.exists(binary_path):
758 binary_paths.append(binary_path)
759 else:
760 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40761 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13762 os.path.basename(binary_path))
763
764 return binary_paths
765
766
Abhishek Arya03911092018-05-21 16:42:35767def _GetCommandForWebTests(arguments):
768 """Return command to run for blink web tests."""
769 command_list = [
770 'python', 'testing/xvfb.py', 'python',
771 'third_party/blink/tools/run_web_tests.py',
772 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24773 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Robert Ma339fc472018-12-14 21:22:42774 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Abhishek Arya03911092018-05-21 16:42:35775 '--child-processes=%d' % max(1, int(multiprocessing.cpu_count() / 2)),
Abhishek Aryabd0655d2018-05-21 19:55:24776 '--disable-breakpad', '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35777 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
778 ]
779 if arguments.strip():
780 command_list.append(arguments)
781 return ' '.join(command_list)
782
783
784def _GetBinaryPathForWebTests():
785 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18786 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35787 if host_platform == 'win':
788 return os.path.join(BUILD_DIR, 'content_shell.exe')
789 elif host_platform == 'linux':
790 return os.path.join(BUILD_DIR, 'content_shell')
791 elif host_platform == 'mac':
792 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
793 'Content Shell')
794 else:
795 assert False, 'This platform is not supported for web tests.'
796
797
Abhishek Aryae5811afa2018-05-24 03:56:01798def _SetupOutputDir():
799 """Setup output directory."""
800 if os.path.exists(OUTPUT_DIR):
801 shutil.rmtree(OUTPUT_DIR)
802
803 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18804 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01805
806
Yuke Liaoabfbba42019-06-11 16:03:59807def _SetMacXcodePath():
808 """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
809 if sys.platform != 'darwin':
810 return
811
812 xcode_path = os.path.join(SRC_ROOT_PATH, 'build', 'mac_files', 'Xcode.app')
813 if os.path.exists(xcode_path):
814 os.environ['DEVELOPER_DIR'] = xcode_path
815
816
Yuke Liao506e8822017-12-04 16:52:54817def _ParseCommandArguments():
818 """Adds and parses relevant arguments for tool comands.
819
820 Returns:
821 A dictionary representing the arguments.
822 """
823 arg_parser = argparse.ArgumentParser()
824 arg_parser.usage = __doc__
825
Abhishek Arya1ec832c2017-12-05 18:06:59826 arg_parser.add_argument(
827 '-b',
828 '--build-dir',
829 type=str,
830 required=True,
831 help='The build directory, the path needs to be relative to the root of '
832 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54833
Abhishek Arya1ec832c2017-12-05 18:06:59834 arg_parser.add_argument(
835 '-o',
836 '--output-dir',
837 type=str,
838 required=True,
839 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54840
Abhishek Arya1ec832c2017-12-05 18:06:59841 arg_parser.add_argument(
842 '-c',
843 '--command',
844 action='append',
Abhishek Arya64636af2018-05-04 14:42:13845 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59846 help='Commands used to run test targets, one test target needs one and '
847 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13848 'current working directory is the root of the checkout. This option is '
849 'incompatible with -p/--profdata-file option.')
850
851 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35852 '-wt',
853 '--web-tests',
854 nargs='?',
855 type=str,
856 const=' ',
857 required=False,
858 help='Run blink web tests. Support passing arguments to run_web_tests.py')
859
860 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13861 '-p',
862 '--profdata-file',
863 type=str,
864 required=False,
865 help='Path to profdata file to use for generating code coverage reports. '
866 'This can be useful if you generated the profdata file seperately in '
867 'your own test harness. This option is ignored if run command(s) are '
868 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54869
Abhishek Arya1ec832c2017-12-05 18:06:59870 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42871 '-f',
872 '--filters',
873 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32874 required=False,
Yuke Liao66da1732017-12-05 22:19:42875 help='Directories or files to get code coverage for, and all files under '
876 'the directories are included recursively.')
877
878 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59879 '-i',
880 '--ignore-filename-regex',
881 type=str,
882 help='Skip source code files with file paths that match the given '
883 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
884 'to exclude files in third_party/ and out/ folders from the report.')
885
886 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32887 '--no-file-view',
888 action='store_true',
889 help='Don\'t generate the file view in the coverage report. When there '
890 'are large number of html files, the file view becomes heavy and may '
891 'cause the browser to freeze, and this argument comes handy.')
892
893 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18894 '--no-component-view',
895 action='store_true',
896 help='Don\'t generate the component view in the coverage report.')
897
898 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:40899 '--coverage-tools-dir',
900 type=str,
901 help='Path of the directory where LLVM coverage tools (llvm-cov, '
902 'llvm-profdata) exist. This should be only needed if you are testing '
903 'against a custom built clang revision. Otherwise, we pick coverage '
904 'tools automatically from your current source checkout.')
905
906 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:59907 '-j',
908 '--jobs',
909 type=int,
910 default=None,
911 help='Run N jobs to build in parallel. If not specified, a default value '
Max Moroz06576292019-01-03 19:22:52912 'will be derived based on CPUs and goma availability. Please refer to '
913 '\'autoninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:54914
Abhishek Arya1ec832c2017-12-05 18:06:59915 arg_parser.add_argument(
Sahel Sharify38cabdc2020-01-16 00:40:01916 '--format',
917 type=str,
918 default='html',
919 help='Output format of the "llvm-cov show" command. The supported '
920 'formats are "text" and "html".')
921
922 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:10923 '-v',
924 '--verbose',
925 action='store_true',
926 help='Prints additional output for diagnostics.')
927
928 arg_parser.add_argument(
929 '-l', '--log_file', type=str, help='Redirects logs to a file.')
930
931 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:02932 'targets',
933 nargs='+',
934 help='The names of the test targets to run. If multiple run commands are '
935 'specified using the -c/--command option, then the order of targets and '
936 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:54937
938 args = arg_parser.parse_args()
939 return args
940
941
942def Main():
943 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:40944 # Setup coverage binaries even when script is called with empty params. This
945 # is used by coverage bot for initial setup.
946 if len(sys.argv) == 1:
Hans Wennborg8ee64a12019-11-05 17:31:30947 update.UpdatePackage('coverage_tools')
Yuke Liao082e99632018-05-18 15:40:40948 print(__doc__)
949 return
950
Abhishek Arya64636af2018-05-04 14:42:13951 # Change directory to source root to aid in relative paths calculations.
Abhishek Arya03911092018-05-21 16:42:35952 global SRC_ROOT_PATH
Max Moroz1de68d72018-08-21 13:38:18953 SRC_ROOT_PATH = coverage_utils.GetFullPath(
Abhishek Arya03911092018-05-21 16:42:35954 os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
Abhishek Arya64636af2018-05-04 14:42:13955 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:11956
Yuke Liao506e8822017-12-04 16:52:54957 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:18958 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:40959 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:13960
Yuke Liao506e8822017-12-04 16:52:54961 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:18962 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:01963
Yuke Liao506e8822017-12-04 16:52:54964 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:18965 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:54966
Abhishek Arya03911092018-05-21 16:42:35967 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:13968 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:35969 'provide prof-data file as input using -p/--profdata-file option OR '
970 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:43971
Abhishek Arya64636af2018-05-04 14:42:13972 assert not args.command or (len(args.targets) == len(args.command)), (
973 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:43974
Abhishek Arya1ec832c2017-12-05 18:06:59975 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:40976 'Build directory: "%s" doesn\'t exist. '
977 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:13978
Yuke Liaoc60b2d02018-03-02 21:40:43979 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:54980 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:32981
982 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:42983 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:32984 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:42985
Abhishek Aryae5811afa2018-05-24 03:56:01986 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:54987
Abhishek Arya03911092018-05-21 16:42:35988 # Get .profdata file and list of binary paths.
989 if args.web_tests:
990 commands = [_GetCommandForWebTests(args.web_tests)]
991 profdata_file_path = _CreateCoverageProfileDataForTargets(
992 args.targets, commands, args.jobs)
993 binary_paths = [_GetBinaryPathForWebTests()]
994 elif args.command:
995 for i in range(len(args.command)):
996 assert not 'run_web_tests.py' in args.command[i], (
997 'run_web_tests.py is not supported via --command argument. '
998 'Please use --run-web-tests argument instead.')
999
Abhishek Arya64636af2018-05-04 14:42:131000 # A list of commands are provided. Run them to generate profdata file, and
1001 # create a list of binary paths from parsing commands.
1002 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1003 profdata_file_path = _CreateCoverageProfileDataForTargets(
1004 args.targets, args.command, args.jobs)
1005 binary_paths = [_GetBinaryPath(command) for command in args.command]
1006 else:
1007 # An input prof-data file is already provided. Just calculate binary paths.
1008 profdata_file_path = args.profdata_file
1009 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331010
Erik Chen283b92c72019-07-22 16:37:391011 # If the checkout uses the hermetic xcode binaries, then otool must be
1012 # directly invoked. The indirection via /usr/bin/otool won't work unless
1013 # there's an actual system install of Xcode.
1014 otool_path = None
1015 if sys.platform == 'darwin':
1016 hermetic_otool_path = os.path.join(
1017 SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents',
1018 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin',
1019 'otool')
1020 if os.path.exists(hermetic_otool_path):
1021 otool_path = hermetic_otool_path
Max Moroz1de68d72018-08-21 13:38:181022 binary_paths.extend(
Erik Chen283b92c72019-07-22 16:37:391023 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
Abhishek Arya78120bc2018-05-07 20:53:541024
Sahel Sharify38cabdc2020-01-16 00:40:011025 assert args.format == 'html' or args.format == 'text', (
1026 '%s is not a valid output format for "llvm-cov show". Only "text" and '
1027 '"html" formats are supported.' % (args.format))
1028 logging.info('Generating code coverage report in %s (this can take a while '
1029 'depending on size of target!).' % (args.format))
Max Moroz1de68d72018-08-21 13:38:181030 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591031 binary_paths, profdata_file_path, absolute_filter_paths,
1032 args.ignore_filename_regex)
Sahel Sharify38cabdc2020-01-16 00:40:011033 _GeneratePerFileLineByLineCoverageInFormat(
1034 binary_paths, profdata_file_path, absolute_filter_paths,
1035 args.ignore_filename_regex, args.format)
Max Moroz1de68d72018-08-21 13:38:181036 component_mappings = None
1037 if not args.no_component_view:
1038 component_mappings = json.load(urllib2.urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371039
Max Moroz1de68d72018-08-21 13:38:181040 # Call prepare here.
1041 processor = coverage_utils.CoverageReportPostProcessor(
1042 OUTPUT_DIR,
1043 SRC_ROOT_PATH,
1044 per_file_summary_data,
1045 no_component_view=args.no_component_view,
1046 no_file_view=args.no_file_view,
1047 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371048
Sahel Sharify38cabdc2020-01-16 00:40:011049 if args.format == 'html':
1050 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541051
Abhishek Arya1ec832c2017-12-05 18:06:591052
Yuke Liao506e8822017-12-04 16:52:541053if __name__ == '__main__':
1054 sys.exit(Main())