blob: 714e5afe5916f68c6e7660e231edbb96b9258147 [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'))
Roberto Carrillo36312722018-10-17 02:18:4587from update import LLVM_BUILD_DIR
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
Roberto Carrillo36312722018-10-17 02:18:4596import update_clang_coverage_tools
Max Moroz1de68d72018-08-21 13:38:1897
Yuke Liao082e99632018-05-18 15:40:4098# Absolute path to the code coverage tools binary. These paths can be
99# overwritten by user specified coverage tool paths.
Abhishek Arya1c97ea542018-05-10 03:53:19100LLVM_BIN_DIR = os.path.join(LLVM_BUILD_DIR, 'bin')
101LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
102LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:54103
Abhishek Arya03911092018-05-21 16:42:35104# Absolute path to the root of the checkout.
105SRC_ROOT_PATH = None
106
Yuke Liao506e8822017-12-04 16:52:54107# Build directory, the value is parsed from command line arguments.
108BUILD_DIR = None
109
110# Output directory for generated artifacts, the value is parsed from command
111# line arguemnts.
112OUTPUT_DIR = None
113
Yuke Liao506e8822017-12-04 16:52:54114# Name of the file extension for profraw data files.
115PROFRAW_FILE_EXTENSION = 'profraw'
116
117# Name of the final profdata file, and this file needs to be passed to
118# "llvm-cov" command in order to call "llvm-cov show" to inspect the
119# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48120PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
121
122# Name of the file with summary information generated by llvm-cov export.
123SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54124
125# Build arg required for generating code coverage data.
126CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
127
Max Moroz7c5354f2018-05-06 00:03:48128LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37129
130# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19131COMPONENT_MAPPING_URL = (
132 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37133
Yuke Liao80afff32018-03-07 01:26:20134# Caches the results returned by _GetBuildArgs, don't use this variable
135# directly, call _GetBuildArgs instead.
136_BUILD_ARGS = None
137
Abhishek Aryac19bc5ef2018-05-04 22:10:02138# Retry failed merges.
139MERGE_RETRIES = 3
140
Abhishek Aryad35de7e2018-05-10 22:23:04141# Message to guide user to file a bug when everything else fails.
142FILE_BUG_MESSAGE = (
143 'If it persists, please file a bug with the command you used, git revision '
144 'and args.gn config here: '
145 'https://bugs.chromium.org/p/chromium/issues/entry?'
146 'components=Tools%3ECodeCoverage')
147
Abhishek Aryabd0655d2018-05-21 19:55:24148# String to replace with actual llvm profile path.
149LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
150
Yuke Liaoea228d02018-01-05 19:10:33151
Yuke Liao082e99632018-05-18 15:40:40152def _ConfigureLLVMCoverageTools(args):
153 """Configures llvm coverage tools."""
154 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18155 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40156 global LLVM_COV_PATH
157 global LLVM_PROFDATA_PATH
158 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
159 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
160 else:
Roberto Carrillo36312722018-10-17 02:18:45161 update_clang_coverage_tools.DownloadCoverageToolsIfNeeded()
Yuke Liao082e99632018-05-18 15:40:40162
163 coverage_tools_exist = (
164 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
165 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
166 'both \'%s\' and \'%s\' exist.') % (
167 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
168
Abhishek Arya2f261182019-04-24 17:06:45169
Abhishek Arya1c97ea542018-05-10 03:53:19170def _GetPathWithLLVMSymbolizerDir():
171 """Add llvm-symbolizer directory to path for symbolized stacks."""
172 path = os.getenv('PATH')
173 dirs = path.split(os.pathsep)
174 if LLVM_BIN_DIR in dirs:
175 return path
176
177 return path + os.pathsep + LLVM_BIN_DIR
178
179
Yuke Liaoc60b2d02018-03-02 21:40:43180def _GetTargetOS():
181 """Returns the target os specified in args.gn file.
182
183 Returns an empty string is target_os is not specified.
184 """
Yuke Liao80afff32018-03-07 01:26:20185 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43186 return build_args['target_os'] if 'target_os' in build_args else ''
187
188
Yuke Liaob2926832018-03-02 17:34:29189def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10190 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43191 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10192
193
Yuke Liaodd1ec0592018-02-02 01:26:37194def _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:59195 filters, ignore_filename_regex):
Yuke Liao506e8822017-12-04 16:52:54196 """Generates per file line-by-line coverage in html using 'llvm-cov show'.
197
198 For a file with absolute path /a/b/x.cc, a html report is generated as:
199 OUTPUT_DIR/coverage/a/b/x.cc.html. An index html file is also generated as:
200 OUTPUT_DIR/index.html.
201
202 Args:
203 binary_paths: A list of paths to the instrumented binaries.
204 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42205 filters: A list of directories and files to get coverage for.
Yuke Liao506e8822017-12-04 16:52:54206 """
Yuke Liao506e8822017-12-04 16:52:54207 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
208 # [[-object BIN]] [SOURCES]
209 # NOTE: For object files, the first one is specified as a positional argument,
210 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10211 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40212 '"llvm-cov show" command.')
Abhishek Arya1ec832c2017-12-05 18:06:59213 subprocess_cmd = [
214 LLVM_COV_PATH, 'show', '-format=html',
215 '-output-dir={}'.format(OUTPUT_DIR),
216 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
217 ]
218 subprocess_cmd.extend(
219 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29220 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18221 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17222 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42223 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59224 if ignore_filename_regex:
225 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
226
Yuke Liao506e8822017-12-04 16:52:54227 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34228
Abhishek Aryafb70b532018-05-06 17:47:40229 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54230
231
Max Moroz7c5354f2018-05-06 00:03:48232def _GetLogsDirectoryPath():
233 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18234 return os.path.join(
235 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48236
237
238def _GetProfdataFilePath():
239 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18240 return os.path.join(
241 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
242 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48243
244
245def _GetSummaryFilePath():
246 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18247 return os.path.join(
248 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
249 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33250
251
Yuke Liao506e8822017-12-04 16:52:54252def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
253 """Builds and runs target to generate the coverage profile data.
254
255 Args:
256 targets: A list of targets to build with coverage instrumentation.
257 commands: A list of commands used to run the targets.
258 jobs_count: Number of jobs to run in parallel for building. If None, a
259 default value is derived based on CPUs availability.
260
261 Returns:
262 A relative path to the generated profdata file.
263 """
264 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02265 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59266 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02267 coverage_profdata_file_path = (
268 _CreateCoverageProfileDataFromTargetProfDataFiles(
269 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54270
Abhishek Aryac19bc5ef2018-05-04 22:10:02271 for target_profdata_file_path in target_profdata_file_paths:
272 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52273
Abhishek Aryac19bc5ef2018-05-04 22:10:02274 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54275
276
277def _BuildTargets(targets, jobs_count):
278 """Builds target with Clang coverage instrumentation.
279
280 This function requires current working directory to be the root of checkout.
281
282 Args:
283 targets: A list of targets to build with coverage instrumentation.
284 jobs_count: Number of jobs to run in parallel for compilation. If None, a
285 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54286 """
Abhishek Aryafb70b532018-05-06 17:47:40287 logging.info('Building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54288
Max Moroz06576292019-01-03 19:22:52289 subprocess_cmd = ['autoninja', '-C', BUILD_DIR]
Yuke Liao506e8822017-12-04 16:52:54290 if jobs_count is not None:
291 subprocess_cmd.append('-j' + str(jobs_count))
292
293 subprocess_cmd.extend(targets)
294 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40295 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54296
297
Abhishek Aryac19bc5ef2018-05-04 22:10:02298def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54299 """Runs commands and returns the relative paths to the profraw data files.
300
301 Args:
302 targets: A list of targets built with coverage instrumentation.
303 commands: A list of commands used to run the targets.
304
305 Returns:
306 A list of relative paths to the generated profraw data files.
307 """
Abhishek Aryafb70b532018-05-06 17:47:40308 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10309
Yuke Liao506e8822017-12-04 16:52:54310 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18311 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
312 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54313 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18314 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48315
316 # Ensure that logs directory exists.
317 if not os.path.exists(_GetLogsDirectoryPath()):
318 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54319
Abhishek Aryac19bc5ef2018-05-04 22:10:02320 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10321
Yuke Liaod4a9865202018-01-12 23:17:52322 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54323 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48324 output_file_name = os.extsep.join([target + '_output', 'log'])
325 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10326
Abhishek Aryac19bc5ef2018-05-04 22:10:02327 profdata_file_path = None
328 for _ in xrange(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40329 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02330 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10331
Abhishek Aryac19bc5ef2018-05-04 22:10:02332 if _IsIOSCommand(command):
333 # On iOS platform, due to lack of write permissions, profraw files are
334 # generated outside of the OUTPUT_DIR, and the exact paths are contained
335 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35336 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02337 else:
338 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35339 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02340
341 profraw_file_paths = []
342 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57343 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Abhishek Aryac19bc5ef2018-05-04 22:10:02344 else:
Max Moroz1de68d72018-08-21 13:38:18345 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02346 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48347 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18348 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02349
350 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40351 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04352 'please make sure the binary exists, is properly instrumented and '
353 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02354
Yuke Liao9c2c70b2018-05-23 15:37:57355 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18356 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
357 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57358
Abhishek Aryac19bc5ef2018-05-04 22:10:02359 try:
360 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
361 target, profraw_file_paths)
362 break
363 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04364 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02365 finally:
366 # Remove profraw files now so that they are not used in next iteration.
367 for profraw_file_path in profraw_file_paths:
368 os.remove(profraw_file_path)
369
370 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04371 'Failed to merge target "%s" profraw files after %d retries. %s' %
372 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02373 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54374
Abhishek Aryafb70b532018-05-06 17:47:40375 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10376
Abhishek Aryac19bc5ef2018-05-04 22:10:02377 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54378
379
Abhishek Arya03911092018-05-21 16:42:35380def _GetEnvironmentVars(profraw_file_path):
381 """Return environment vars for subprocess, given a profraw file path."""
382 env = os.environ.copy()
383 env.update({
384 'LLVM_PROFILE_FILE': profraw_file_path,
385 'PATH': _GetPathWithLLVMSymbolizerDir()
386 })
387 return env
388
389
390def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10391 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52392 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01393 #
Max Morozd73e45f2018-04-24 18:32:47394 # "%p" expands out to the process ID. It's not used by this scripts due to:
395 # 1) If a target program spawns too many processess, it may exhaust all disk
396 # space available. For example, unit_tests writes thousands of .profraw
397 # files each of size 1GB+.
398 # 2) If a target binary uses shared libraries, coverage profile data for them
399 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01400 #
Yuke Liaod4a9865202018-01-12 23:17:52401 # "%Nm" expands out to the instrumented binary's signature. When this pattern
402 # is specified, the runtime creates a pool of N raw profiles which are used
403 # for on-line profile merging. The runtime takes care of selecting a raw
404 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52405 # N must be between 1 and 9. The merge pool specifier can only occur once per
406 # filename pattern.
407 #
Max Morozd73e45f2018-04-24 18:32:47408 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01409 #
Max Morozd73e45f2018-04-24 18:32:47410 # For other cases, "%4m" is chosen as it creates some level of parallelism,
411 # but it's not too big to consume too much computing resource or disk space.
412 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59413 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01414 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18415 expected_profraw_file_path = os.path.join(
416 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
417 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24418 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
419 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54420
Yuke Liaoa0c8c2f2018-02-28 20:14:10421 try:
Max Moroz7c5354f2018-05-06 00:03:48422 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35423 with open(output_file_path, 'wb') as output_file_handle:
424 subprocess.check_call(
425 shlex.split(command),
426 stdout=output_file_handle,
427 stderr=subprocess.STDOUT,
428 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10429 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35430 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10431
Abhishek Arya03911092018-05-21 16:42:35432 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10433
434
Yuke Liao27349c92018-03-22 21:10:01435def _IsFuzzerTarget(target):
436 """Returns true if the target is a fuzzer target."""
437 build_args = _GetBuildArgs()
438 use_libfuzzer = ('use_libfuzzer' in build_args and
439 build_args['use_libfuzzer'] == 'true')
440 return use_libfuzzer and target.endswith('_fuzzer')
441
442
Abhishek Arya03911092018-05-21 16:42:35443def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10444 """Runs a single iOS command and generates a profraw data file.
445
446 iOS application doesn't have write access to folders outside of the app, so
447 it's impossible to instruct the app to flush the profraw data file to the
448 desired location. The profraw data file will be generated somewhere within the
449 application's Documents folder, and the full path can be obtained by parsing
450 the output.
451 """
Yuke Liaob2926832018-03-02 17:34:29452 assert _IsIOSCommand(command)
453
454 # After running tests, iossim generates a profraw data file, it won't be
455 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
456 # checkout.
457 iossim_profraw_file_path = os.path.join(
458 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24459 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
460 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10461
462 try:
Abhishek Arya03911092018-05-21 16:42:35463 with open(output_file_path, 'wb') as output_file_handle:
464 subprocess.check_call(
465 shlex.split(command),
466 stdout=output_file_handle,
467 stderr=subprocess.STDOUT,
468 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10469 except subprocess.CalledProcessError as e:
470 # iossim emits non-zero return code even if tests run successfully, so
471 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35472 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10473
Abhishek Arya03911092018-05-21 16:42:35474 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10475
476
477def _GetProfrawDataFileByParsingOutput(output):
478 """Returns the path to the profraw data file obtained by parsing the output.
479
480 The output of running the test target has no format, but it is guaranteed to
481 have a single line containing the path to the generated profraw data file.
482 NOTE: This should only be called when target os is iOS.
483 """
Yuke Liaob2926832018-03-02 17:34:29484 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10485
Yuke Liaob2926832018-03-02 17:34:29486 output_by_lines = ''.join(output).splitlines()
487 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10488
489 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29490 result = profraw_file_pattern.match(line)
491 if result:
492 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10493
494 assert False, ('No profraw data file was generated, did you call '
495 'coverage_util::ConfigureCoverageReportPath() in test setup? '
496 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54497
498
Abhishek Aryac19bc5ef2018-05-04 22:10:02499def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
500 """Returns a relative path to coverage profdata file by merging target
501 profdata files.
Yuke Liao506e8822017-12-04 16:52:54502
503 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02504 profdata_file_paths: A list of relative paths to the profdata data files
505 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54506
507 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02508 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54509
510 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02511 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54512 """
Abhishek Aryafb70b532018-05-06 17:47:40513 logging.info('Creating the coverage profile data file.')
514 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48515 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54516 try:
Abhishek Arya1ec832c2017-12-05 18:06:59517 subprocess_cmd = [
518 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
519 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02520 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01521
522 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18523 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02524 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04525 logging.error(
526 'Failed to merge target profdata files to create coverage profdata. %s',
527 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02528 raise error
529
Abhishek Aryafb70b532018-05-06 17:47:40530 logging.debug('Finished merging target profdata files.')
531 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02532 profdata_file_path)
533 return profdata_file_path
534
535
536def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
537 """Returns a relative path to target profdata file by merging target
538 profraw files.
539
540 Args:
541 profraw_file_paths: A list of relative paths to the profdata data files
542 that are to be merged.
543
544 Returns:
545 A relative path to the merged coverage profdata file.
546
547 Raises:
548 CalledProcessError: An error occurred merging profdata files.
549 """
Abhishek Aryafb70b532018-05-06 17:47:40550 logging.info('Creating target profile data file.')
551 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02552 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
553
554 try:
555 subprocess_cmd = [
556 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
557 ]
Yuke Liao506e8822017-12-04 16:52:54558 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01559
560 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18561 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54562 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04563 logging.error(
564 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54565 raise error
566
Abhishek Aryafb70b532018-05-06 17:47:40567 logging.debug('Finished merging target profraw files.')
568 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10569 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54570 return profdata_file_path
571
572
Yuke Liao0e4c8682018-04-18 21:06:59573def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
574 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33575 """Generates per file coverage summary using "llvm-cov export" command."""
576 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
577 # [[-object BIN]] [SOURCES].
578 # NOTE: For object files, the first one is specified as a positional argument,
579 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10580 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40581 'export -summary-only" command.')
Yuke Liaoea228d02018-01-05 19:10:33582 subprocess_cmd = [
583 LLVM_COV_PATH, 'export', '-summary-only',
584 '-instr-profile=' + profdata_file_path, binary_paths[0]
585 ]
586 subprocess_cmd.extend(
587 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29588 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33589 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59590 if ignore_filename_regex:
591 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33592
Max Moroz7c5354f2018-05-06 00:03:48593 export_output = subprocess.check_output(subprocess_cmd)
594
595 # Write output on the disk to be used by code coverage bot.
596 with open(_GetSummaryFilePath(), 'w') as f:
597 f.write(export_output)
598
Max Moroz1de68d72018-08-21 13:38:18599 return export_output
Yuke Liaoea228d02018-01-05 19:10:33600
601
Yuke Liaob2926832018-03-02 17:34:29602def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
603 """Appends -arch arguments to the command list if it's ios platform.
604
605 iOS binaries are universal binaries, and require specifying the architecture
606 to use, and one architecture needs to be specified for each binary.
607 """
608 if _IsIOS():
609 cmd_list.extend(['-arch=x86_64'] * num_archs)
610
611
Yuke Liao506e8822017-12-04 16:52:54612def _GetBinaryPath(command):
613 """Returns a relative path to the binary to be run by the command.
614
Yuke Liao545db322018-02-15 17:12:01615 Currently, following types of commands are supported (e.g. url_unittests):
616 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
617 2. Use xvfb.
618 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
619 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37620 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
621 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10622 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37623 <iossim_arguments> -c <app_arguments>
624 out/Coverage-iphonesimulator/url_unittests.app"
625
Yuke Liao506e8822017-12-04 16:52:54626 Args:
627 command: A command used to run a target.
628
629 Returns:
630 A relative path to the binary.
631 """
Yuke Liao545db322018-02-15 17:12:01632 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
633
Yuke Liaob2926832018-03-02 17:34:29634 command_parts = shlex.split(command)
Yuke Liao545db322018-02-15 17:12:01635 if os.path.basename(command_parts[0]) == 'python':
636 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40637 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01638 return command_parts[2]
639
640 if os.path.basename(command_parts[0]) == xvfb_script_name:
641 return command_parts[1]
642
Yuke Liaob2926832018-03-02 17:34:29643 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10644 # For a given application bundle, the binary resides in the bundle and has
645 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02646 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10647 app_name = os.path.splitext(os.path.basename(app_path))[0]
648 return os.path.join(app_path, app_name)
649
Yuke Liaob2926832018-03-02 17:34:29650 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54651
652
Yuke Liaob2926832018-03-02 17:34:29653def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10654 """Returns true if command is used to run tests on iOS platform."""
Yuke Liaob2926832018-03-02 17:34:29655 return os.path.basename(shlex.split(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10656
657
Yuke Liao95d13d72017-12-07 18:18:50658def _VerifyTargetExecutablesAreInBuildDirectory(commands):
659 """Verifies that the target executables specified in the commands are inside
660 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54661 for command in commands:
662 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18663 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35664 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50665 'Target executable "%s" in command: "%s" is outside of '
666 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54667
668
669def _ValidateBuildingWithClangCoverage():
670 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20671 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54672
673 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
674 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59675 assert False, ('\'{} = true\' is required in args.gn.'
676 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54677
678
Yuke Liaoc60b2d02018-03-02 21:40:43679def _ValidateCurrentPlatformIsSupported():
680 """Asserts that this script suports running on the current platform"""
681 target_os = _GetTargetOS()
682 if target_os:
683 current_platform = target_os
684 else:
Max Moroz1de68d72018-08-21 13:38:18685 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43686
687 assert current_platform in [
688 'linux', 'mac', 'chromeos', 'ios'
689 ], ('Coverage is only supported on linux, mac, chromeos and ios.')
690
691
Yuke Liao80afff32018-03-07 01:26:20692def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54693 """Parses args.gn file and returns results as a dictionary.
694
695 Returns:
696 A dictionary representing the build args.
697 """
Yuke Liao80afff32018-03-07 01:26:20698 global _BUILD_ARGS
699 if _BUILD_ARGS is not None:
700 return _BUILD_ARGS
701
702 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54703 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
704 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
705 'missing args.gn file.' % BUILD_DIR)
706 with open(build_args_path) as build_args_file:
707 build_args_lines = build_args_file.readlines()
708
Yuke Liao506e8822017-12-04 16:52:54709 for build_arg_line in build_args_lines:
710 build_arg_without_comments = build_arg_line.split('#')[0]
711 key_value_pair = build_arg_without_comments.split('=')
712 if len(key_value_pair) != 2:
713 continue
714
715 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43716
717 # Values are wrapped within a pair of double-quotes, so remove the leading
718 # and trailing double-quotes.
719 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20720 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54721
Yuke Liao80afff32018-03-07 01:26:20722 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54723
724
Abhishek Arya16f059a2017-12-07 17:47:32725def _VerifyPathsAndReturnAbsolutes(paths):
726 """Verifies that the paths specified in |paths| exist and returns absolute
727 versions.
Yuke Liao66da1732017-12-05 22:19:42728
729 Args:
730 paths: A list of files or directories.
731 """
Abhishek Arya16f059a2017-12-07 17:47:32732 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42733 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32734 absolute_path = os.path.join(SRC_ROOT_PATH, path)
735 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
736
737 absolute_paths.append(absolute_path)
738
739 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42740
741
Abhishek Arya64636af2018-05-04 14:42:13742def _GetBinaryPathsFromTargets(targets, build_dir):
743 """Return binary paths from target names."""
744 # FIXME: Derive output binary from target build definitions rather than
745 # assuming that it is always the same name.
746 binary_paths = []
747 for target in targets:
748 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18749 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13750 binary_path += '.exe'
751
752 if os.path.exists(binary_path):
753 binary_paths.append(binary_path)
754 else:
755 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40756 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13757 os.path.basename(binary_path))
758
759 return binary_paths
760
761
Abhishek Arya03911092018-05-21 16:42:35762def _GetCommandForWebTests(arguments):
763 """Return command to run for blink web tests."""
764 command_list = [
765 'python', 'testing/xvfb.py', 'python',
766 'third_party/blink/tools/run_web_tests.py',
767 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24768 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Robert Ma339fc472018-12-14 21:22:42769 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Abhishek Arya03911092018-05-21 16:42:35770 '--child-processes=%d' % max(1, int(multiprocessing.cpu_count() / 2)),
Abhishek Aryabd0655d2018-05-21 19:55:24771 '--disable-breakpad', '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35772 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
773 ]
774 if arguments.strip():
775 command_list.append(arguments)
776 return ' '.join(command_list)
777
778
779def _GetBinaryPathForWebTests():
780 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18781 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35782 if host_platform == 'win':
783 return os.path.join(BUILD_DIR, 'content_shell.exe')
784 elif host_platform == 'linux':
785 return os.path.join(BUILD_DIR, 'content_shell')
786 elif host_platform == 'mac':
787 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
788 'Content Shell')
789 else:
790 assert False, 'This platform is not supported for web tests.'
791
792
Abhishek Aryae5811afa2018-05-24 03:56:01793def _SetupOutputDir():
794 """Setup output directory."""
795 if os.path.exists(OUTPUT_DIR):
796 shutil.rmtree(OUTPUT_DIR)
797
798 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18799 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01800
801
Yuke Liaoabfbba42019-06-11 16:03:59802def _SetMacXcodePath():
803 """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
804 if sys.platform != 'darwin':
805 return
806
807 xcode_path = os.path.join(SRC_ROOT_PATH, 'build', 'mac_files', 'Xcode.app')
808 if os.path.exists(xcode_path):
809 os.environ['DEVELOPER_DIR'] = xcode_path
810
811
Yuke Liao506e8822017-12-04 16:52:54812def _ParseCommandArguments():
813 """Adds and parses relevant arguments for tool comands.
814
815 Returns:
816 A dictionary representing the arguments.
817 """
818 arg_parser = argparse.ArgumentParser()
819 arg_parser.usage = __doc__
820
Abhishek Arya1ec832c2017-12-05 18:06:59821 arg_parser.add_argument(
822 '-b',
823 '--build-dir',
824 type=str,
825 required=True,
826 help='The build directory, the path needs to be relative to the root of '
827 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54828
Abhishek Arya1ec832c2017-12-05 18:06:59829 arg_parser.add_argument(
830 '-o',
831 '--output-dir',
832 type=str,
833 required=True,
834 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54835
Abhishek Arya1ec832c2017-12-05 18:06:59836 arg_parser.add_argument(
837 '-c',
838 '--command',
839 action='append',
Abhishek Arya64636af2018-05-04 14:42:13840 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59841 help='Commands used to run test targets, one test target needs one and '
842 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13843 'current working directory is the root of the checkout. This option is '
844 'incompatible with -p/--profdata-file option.')
845
846 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35847 '-wt',
848 '--web-tests',
849 nargs='?',
850 type=str,
851 const=' ',
852 required=False,
853 help='Run blink web tests. Support passing arguments to run_web_tests.py')
854
855 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13856 '-p',
857 '--profdata-file',
858 type=str,
859 required=False,
860 help='Path to profdata file to use for generating code coverage reports. '
861 'This can be useful if you generated the profdata file seperately in '
862 'your own test harness. This option is ignored if run command(s) are '
863 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54864
Abhishek Arya1ec832c2017-12-05 18:06:59865 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42866 '-f',
867 '--filters',
868 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32869 required=False,
Yuke Liao66da1732017-12-05 22:19:42870 help='Directories or files to get code coverage for, and all files under '
871 'the directories are included recursively.')
872
873 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59874 '-i',
875 '--ignore-filename-regex',
876 type=str,
877 help='Skip source code files with file paths that match the given '
878 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
879 'to exclude files in third_party/ and out/ folders from the report.')
880
881 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32882 '--no-file-view',
883 action='store_true',
884 help='Don\'t generate the file view in the coverage report. When there '
885 'are large number of html files, the file view becomes heavy and may '
886 'cause the browser to freeze, and this argument comes handy.')
887
888 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18889 '--no-component-view',
890 action='store_true',
891 help='Don\'t generate the component view in the coverage report.')
892
893 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:40894 '--coverage-tools-dir',
895 type=str,
896 help='Path of the directory where LLVM coverage tools (llvm-cov, '
897 'llvm-profdata) exist. This should be only needed if you are testing '
898 'against a custom built clang revision. Otherwise, we pick coverage '
899 'tools automatically from your current source checkout.')
900
901 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:59902 '-j',
903 '--jobs',
904 type=int,
905 default=None,
906 help='Run N jobs to build in parallel. If not specified, a default value '
Max Moroz06576292019-01-03 19:22:52907 'will be derived based on CPUs and goma availability. Please refer to '
908 '\'autoninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:54909
Abhishek Arya1ec832c2017-12-05 18:06:59910 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:10911 '-v',
912 '--verbose',
913 action='store_true',
914 help='Prints additional output for diagnostics.')
915
916 arg_parser.add_argument(
917 '-l', '--log_file', type=str, help='Redirects logs to a file.')
918
919 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:02920 'targets',
921 nargs='+',
922 help='The names of the test targets to run. If multiple run commands are '
923 'specified using the -c/--command option, then the order of targets and '
924 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:54925
926 args = arg_parser.parse_args()
927 return args
928
929
930def Main():
931 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:40932 # Setup coverage binaries even when script is called with empty params. This
933 # is used by coverage bot for initial setup.
934 if len(sys.argv) == 1:
Roberto Carrillo36312722018-10-17 02:18:45935 update_clang_coverage_tools.DownloadCoverageToolsIfNeeded()
Yuke Liao082e99632018-05-18 15:40:40936 print(__doc__)
937 return
938
Abhishek Arya64636af2018-05-04 14:42:13939 # Change directory to source root to aid in relative paths calculations.
Abhishek Arya03911092018-05-21 16:42:35940 global SRC_ROOT_PATH
Max Moroz1de68d72018-08-21 13:38:18941 SRC_ROOT_PATH = coverage_utils.GetFullPath(
Abhishek Arya03911092018-05-21 16:42:35942 os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
Abhishek Arya64636af2018-05-04 14:42:13943 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:11944
Yuke Liao506e8822017-12-04 16:52:54945 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:18946 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:40947 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:13948
Yuke Liao506e8822017-12-04 16:52:54949 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:18950 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:01951
Yuke Liao506e8822017-12-04 16:52:54952 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:18953 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:54954
Abhishek Arya03911092018-05-21 16:42:35955 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:13956 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:35957 'provide prof-data file as input using -p/--profdata-file option OR '
958 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:43959
Abhishek Arya64636af2018-05-04 14:42:13960 assert not args.command or (len(args.targets) == len(args.command)), (
961 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:43962
Abhishek Arya1ec832c2017-12-05 18:06:59963 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:40964 'Build directory: "%s" doesn\'t exist. '
965 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:13966
Yuke Liaoc60b2d02018-03-02 21:40:43967 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:54968 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:32969
970 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:42971 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:32972 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:42973
Abhishek Aryae5811afa2018-05-24 03:56:01974 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:54975
Abhishek Arya03911092018-05-21 16:42:35976 # Get .profdata file and list of binary paths.
977 if args.web_tests:
978 commands = [_GetCommandForWebTests(args.web_tests)]
979 profdata_file_path = _CreateCoverageProfileDataForTargets(
980 args.targets, commands, args.jobs)
981 binary_paths = [_GetBinaryPathForWebTests()]
982 elif args.command:
983 for i in range(len(args.command)):
984 assert not 'run_web_tests.py' in args.command[i], (
985 'run_web_tests.py is not supported via --command argument. '
986 'Please use --run-web-tests argument instead.')
987
Abhishek Arya64636af2018-05-04 14:42:13988 # A list of commands are provided. Run them to generate profdata file, and
989 # create a list of binary paths from parsing commands.
990 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
991 profdata_file_path = _CreateCoverageProfileDataForTargets(
992 args.targets, args.command, args.jobs)
993 binary_paths = [_GetBinaryPath(command) for command in args.command]
994 else:
995 # An input prof-data file is already provided. Just calculate binary paths.
996 profdata_file_path = args.profdata_file
997 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:33998
Erik Chen283b92c72019-07-22 16:37:39999 # If the checkout uses the hermetic xcode binaries, then otool must be
1000 # directly invoked. The indirection via /usr/bin/otool won't work unless
1001 # there's an actual system install of Xcode.
1002 otool_path = None
1003 if sys.platform == 'darwin':
1004 hermetic_otool_path = os.path.join(
1005 SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents',
1006 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin',
1007 'otool')
1008 if os.path.exists(hermetic_otool_path):
1009 otool_path = hermetic_otool_path
Max Moroz1de68d72018-08-21 13:38:181010 binary_paths.extend(
Erik Chen283b92c72019-07-22 16:37:391011 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
Abhishek Arya78120bc2018-05-07 20:53:541012
Yuke Liao481d3482018-01-29 19:17:101013 logging.info('Generating code coverage report in html (this can take a while '
Abhishek Aryafb70b532018-05-06 17:47:401014 'depending on size of target!).')
Max Moroz1de68d72018-08-21 13:38:181015 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591016 binary_paths, profdata_file_path, absolute_filter_paths,
1017 args.ignore_filename_regex)
Yuke Liaodd1ec0592018-02-02 01:26:371018 _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:591019 absolute_filter_paths,
1020 args.ignore_filename_regex)
Max Moroz1de68d72018-08-21 13:38:181021 component_mappings = None
1022 if not args.no_component_view:
1023 component_mappings = json.load(urllib2.urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371024
Max Moroz1de68d72018-08-21 13:38:181025 # Call prepare here.
1026 processor = coverage_utils.CoverageReportPostProcessor(
1027 OUTPUT_DIR,
1028 SRC_ROOT_PATH,
1029 per_file_summary_data,
1030 no_component_view=args.no_component_view,
1031 no_file_view=args.no_file_view,
1032 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371033
Max Moroz1de68d72018-08-21 13:38:181034 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541035
Abhishek Arya1ec832c2017-12-05 18:06:591036
Yuke Liao506e8822017-12-04 16:52:541037if __name__ == '__main__':
1038 sys.exit(Main())