blob: e0ca15e2ab4627edf0814feabf4cd9a618176b50 [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 \\
17 --args='use_clang_coverage=true is_component_build=false \\
18 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 \\
45 -c 'out/coverage/pdfium_fuzzer -runs=<runs> <corpus_dir>' \\
46 -f third_party/pdfium
Abhishek Arya1ec832c2017-12-05 18:06:5947
48 where:
49 <corpus_dir> - directory containing samples files for this format.
50 <runs> - number of times to fuzz target function. Should be 0 when you just
51 want to see the coverage on corpus and don't want to fuzz at all.
52
Abhishek Arya03911092018-05-21 16:42:3553 * Sample workflow for running Blink web tests:
54
55 python tools/code_coverage/coverage.py blink_tests \\
56 -wt -b out/coverage -o out/report -f third_party/blink
57
58 If you need to pass arguments to run_web_tests.py, use
59 -wt='arguments to run_web_tests.py e.g. test directories'
60
Abhishek Arya4a9494f2018-05-22 01:39:4161 Note: Generating coverage over entire suite can take minimum of 3 hours due to
62 --batch-size=1 argument added by default. This is needed since otherwise any
63 crash will cause us to lose coverage from prior successful test runs.
64
Abhishek Arya1ec832c2017-12-05 18:06:5965 For more options, please refer to tools/code_coverage/coverage.py -h.
Yuke Liao8e209fe82018-04-18 20:36:3866
67 For an overview of how code coverage works in Chromium, please refer to
68 https://chromium.googlesource.com/chromium/src/+/master/docs/code_coverage.md
Yuke Liao506e8822017-12-04 16:52:5469"""
70
71from __future__ import print_function
72
73import sys
74
75import argparse
Yuke Liaoea228d02018-01-05 19:10:3376import json
Yuke Liao481d3482018-01-29 19:17:1077import logging
Abhishek Arya03911092018-05-21 16:42:3578import multiprocessing
Yuke Liao506e8822017-12-04 16:52:5479import os
Yuke Liaob2926832018-03-02 17:34:2980import re
81import shlex
Max Moroz025d8952018-05-03 16:33:3482import shutil
Yuke Liao506e8822017-12-04 16:52:5483import subprocess
Yuke Liao506e8822017-12-04 16:52:5484import urllib2
85
Abhishek Arya1ec832c2017-12-05 18:06:5986sys.path.append(
87 os.path.join(
88 os.path.dirname(__file__), os.path.pardir, os.path.pardir, 'tools',
89 'clang', 'scripts'))
Roberto Carrillo36312722018-10-17 02:18:4590from update import LLVM_BUILD_DIR
Yuke Liao506e8822017-12-04 16:52:5491
Yuke Liaoea228d02018-01-05 19:10:3392sys.path.append(
93 os.path.join(
94 os.path.dirname(__file__), os.path.pardir, os.path.pardir,
95 'third_party'))
Yuke Liaoea228d02018-01-05 19:10:3396from collections import defaultdict
97
Max Moroz1de68d72018-08-21 13:38:1898import coverage_utils
Roberto Carrillo36312722018-10-17 02:18:4599import update_clang_coverage_tools
Max Moroz1de68d72018-08-21 13:38:18100
Yuke Liao082e99632018-05-18 15:40:40101# Absolute path to the code coverage tools binary. These paths can be
102# overwritten by user specified coverage tool paths.
Abhishek Arya1c97ea542018-05-10 03:53:19103LLVM_BIN_DIR = os.path.join(LLVM_BUILD_DIR, 'bin')
104LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
105LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:54106
Abhishek Arya03911092018-05-21 16:42:35107# Absolute path to the root of the checkout.
108SRC_ROOT_PATH = None
109
Yuke Liao506e8822017-12-04 16:52:54110# Build directory, the value is parsed from command line arguments.
111BUILD_DIR = None
112
113# Output directory for generated artifacts, the value is parsed from command
114# line arguemnts.
115OUTPUT_DIR = None
116
117# Default number of jobs used to build when goma is configured and enabled.
118DEFAULT_GOMA_JOBS = 100
119
120# Name of the file extension for profraw data files.
121PROFRAW_FILE_EXTENSION = 'profraw'
122
123# Name of the final profdata file, and this file needs to be passed to
124# "llvm-cov" command in order to call "llvm-cov show" to inspect the
125# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48126PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
127
128# Name of the file with summary information generated by llvm-cov export.
129SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54130
131# Build arg required for generating code coverage data.
132CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
133
Max Moroz7c5354f2018-05-06 00:03:48134LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37135
136# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19137COMPONENT_MAPPING_URL = (
138 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37139
Yuke Liao80afff32018-03-07 01:26:20140# Caches the results returned by _GetBuildArgs, don't use this variable
141# directly, call _GetBuildArgs instead.
142_BUILD_ARGS = None
143
Abhishek Aryac19bc5ef2018-05-04 22:10:02144# Retry failed merges.
145MERGE_RETRIES = 3
146
Abhishek Aryad35de7e2018-05-10 22:23:04147# Message to guide user to file a bug when everything else fails.
148FILE_BUG_MESSAGE = (
149 'If it persists, please file a bug with the command you used, git revision '
150 'and args.gn config here: '
151 'https://bugs.chromium.org/p/chromium/issues/entry?'
152 'components=Tools%3ECodeCoverage')
153
Abhishek Aryabd0655d2018-05-21 19:55:24154# String to replace with actual llvm profile path.
155LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
156
Yuke Liaoea228d02018-01-05 19:10:33157
Yuke Liao082e99632018-05-18 15:40:40158def _ConfigureLLVMCoverageTools(args):
159 """Configures llvm coverage tools."""
160 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18161 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40162 global LLVM_COV_PATH
163 global LLVM_PROFDATA_PATH
164 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
165 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
166 else:
Roberto Carrillo36312722018-10-17 02:18:45167 update_clang_coverage_tools.DownloadCoverageToolsIfNeeded()
Yuke Liao082e99632018-05-18 15:40:40168
169 coverage_tools_exist = (
170 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
171 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
172 'both \'%s\' and \'%s\' exist.') % (
173 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
174
Abhishek Arya1c97ea542018-05-10 03:53:19175def _GetPathWithLLVMSymbolizerDir():
176 """Add llvm-symbolizer directory to path for symbolized stacks."""
177 path = os.getenv('PATH')
178 dirs = path.split(os.pathsep)
179 if LLVM_BIN_DIR in dirs:
180 return path
181
182 return path + os.pathsep + LLVM_BIN_DIR
183
184
Yuke Liaoc60b2d02018-03-02 21:40:43185def _GetTargetOS():
186 """Returns the target os specified in args.gn file.
187
188 Returns an empty string is target_os is not specified.
189 """
Yuke Liao80afff32018-03-07 01:26:20190 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43191 return build_args['target_os'] if 'target_os' in build_args else ''
192
193
Yuke Liaob2926832018-03-02 17:34:29194def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10195 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43196 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10197
198
Yuke Liao506e8822017-12-04 16:52:54199
Yuke Liaodd1ec0592018-02-02 01:26:37200def _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:59201 filters, ignore_filename_regex):
Yuke Liao506e8822017-12-04 16:52:54202 """Generates per file line-by-line coverage in html using 'llvm-cov show'.
203
204 For a file with absolute path /a/b/x.cc, a html report is generated as:
205 OUTPUT_DIR/coverage/a/b/x.cc.html. An index html file is also generated as:
206 OUTPUT_DIR/index.html.
207
208 Args:
209 binary_paths: A list of paths to the instrumented binaries.
210 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42211 filters: A list of directories and files to get coverage for.
Yuke Liao506e8822017-12-04 16:52:54212 """
Yuke Liao506e8822017-12-04 16:52:54213 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
214 # [[-object BIN]] [SOURCES]
215 # NOTE: For object files, the first one is specified as a positional argument,
216 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10217 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40218 '"llvm-cov show" command.')
Abhishek Arya1ec832c2017-12-05 18:06:59219 subprocess_cmd = [
220 LLVM_COV_PATH, 'show', '-format=html',
221 '-output-dir={}'.format(OUTPUT_DIR),
222 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
223 ]
224 subprocess_cmd.extend(
225 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29226 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18227 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17228 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42229 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59230 if ignore_filename_regex:
231 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
232
Yuke Liao506e8822017-12-04 16:52:54233 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34234
Abhishek Aryafb70b532018-05-06 17:47:40235 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54236
237
Max Moroz7c5354f2018-05-06 00:03:48238def _GetLogsDirectoryPath():
239 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18240 return os.path.join(
241 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48242
243
244def _GetProfdataFilePath():
245 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18246 return os.path.join(
247 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
248 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48249
250
251def _GetSummaryFilePath():
252 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18253 return os.path.join(
254 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
255 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33256
257
Yuke Liao506e8822017-12-04 16:52:54258def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
259 """Builds and runs target to generate the coverage profile data.
260
261 Args:
262 targets: A list of targets to build with coverage instrumentation.
263 commands: A list of commands used to run the targets.
264 jobs_count: Number of jobs to run in parallel for building. If None, a
265 default value is derived based on CPUs availability.
266
267 Returns:
268 A relative path to the generated profdata file.
269 """
270 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02271 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59272 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02273 coverage_profdata_file_path = (
274 _CreateCoverageProfileDataFromTargetProfDataFiles(
275 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54276
Abhishek Aryac19bc5ef2018-05-04 22:10:02277 for target_profdata_file_path in target_profdata_file_paths:
278 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52279
Abhishek Aryac19bc5ef2018-05-04 22:10:02280 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54281
282
283def _BuildTargets(targets, jobs_count):
284 """Builds target with Clang coverage instrumentation.
285
286 This function requires current working directory to be the root of checkout.
287
288 Args:
289 targets: A list of targets to build with coverage instrumentation.
290 jobs_count: Number of jobs to run in parallel for compilation. If None, a
291 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54292 """
Abhishek Arya1ec832c2017-12-05 18:06:59293
Yuke Liao506e8822017-12-04 16:52:54294 def _IsGomaConfigured():
295 """Returns True if goma is enabled in the gn build args.
296
297 Returns:
298 A boolean indicates whether goma is configured for building or not.
299 """
Yuke Liao80afff32018-03-07 01:26:20300 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54301 return 'use_goma' in build_args and build_args['use_goma'] == 'true'
302
Abhishek Aryafb70b532018-05-06 17:47:40303 logging.info('Building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54304 if jobs_count is None and _IsGomaConfigured():
305 jobs_count = DEFAULT_GOMA_JOBS
306
307 subprocess_cmd = ['ninja', '-C', BUILD_DIR]
308 if jobs_count is not None:
309 subprocess_cmd.append('-j' + str(jobs_count))
310
311 subprocess_cmd.extend(targets)
312 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40313 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54314
315
Abhishek Aryac19bc5ef2018-05-04 22:10:02316def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54317 """Runs commands and returns the relative paths to the profraw data files.
318
319 Args:
320 targets: A list of targets built with coverage instrumentation.
321 commands: A list of commands used to run the targets.
322
323 Returns:
324 A list of relative paths to the generated profraw data files.
325 """
Abhishek Aryafb70b532018-05-06 17:47:40326 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10327
Yuke Liao506e8822017-12-04 16:52:54328 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18329 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
330 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54331 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18332 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48333
334 # Ensure that logs directory exists.
335 if not os.path.exists(_GetLogsDirectoryPath()):
336 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54337
Abhishek Aryac19bc5ef2018-05-04 22:10:02338 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10339
Yuke Liaod4a9865202018-01-12 23:17:52340 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54341 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48342 output_file_name = os.extsep.join([target + '_output', 'log'])
343 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10344
Abhishek Aryac19bc5ef2018-05-04 22:10:02345 profdata_file_path = None
346 for _ in xrange(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40347 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02348 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10349
Abhishek Aryac19bc5ef2018-05-04 22:10:02350 if _IsIOSCommand(command):
351 # On iOS platform, due to lack of write permissions, profraw files are
352 # generated outside of the OUTPUT_DIR, and the exact paths are contained
353 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35354 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02355 else:
356 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35357 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02358
359 profraw_file_paths = []
360 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57361 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Abhishek Aryac19bc5ef2018-05-04 22:10:02362 else:
Max Moroz1de68d72018-08-21 13:38:18363 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02364 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48365 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18366 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02367
368 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40369 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04370 'please make sure the binary exists, is properly instrumented and '
371 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02372
Yuke Liao9c2c70b2018-05-23 15:37:57373 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18374 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
375 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57376
Abhishek Aryac19bc5ef2018-05-04 22:10:02377 try:
378 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
379 target, profraw_file_paths)
380 break
381 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04382 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02383 finally:
384 # Remove profraw files now so that they are not used in next iteration.
385 for profraw_file_path in profraw_file_paths:
386 os.remove(profraw_file_path)
387
388 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04389 'Failed to merge target "%s" profraw files after %d retries. %s' %
390 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02391 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54392
Abhishek Aryafb70b532018-05-06 17:47:40393 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10394
Abhishek Aryac19bc5ef2018-05-04 22:10:02395 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54396
397
Abhishek Arya03911092018-05-21 16:42:35398def _GetEnvironmentVars(profraw_file_path):
399 """Return environment vars for subprocess, given a profraw file path."""
400 env = os.environ.copy()
401 env.update({
402 'LLVM_PROFILE_FILE': profraw_file_path,
403 'PATH': _GetPathWithLLVMSymbolizerDir()
404 })
405 return env
406
407
408def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10409 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52410 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01411 #
Max Morozd73e45f2018-04-24 18:32:47412 # "%p" expands out to the process ID. It's not used by this scripts due to:
413 # 1) If a target program spawns too many processess, it may exhaust all disk
414 # space available. For example, unit_tests writes thousands of .profraw
415 # files each of size 1GB+.
416 # 2) If a target binary uses shared libraries, coverage profile data for them
417 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01418 #
Yuke Liaod4a9865202018-01-12 23:17:52419 # "%Nm" expands out to the instrumented binary's signature. When this pattern
420 # is specified, the runtime creates a pool of N raw profiles which are used
421 # for on-line profile merging. The runtime takes care of selecting a raw
422 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52423 # N must be between 1 and 9. The merge pool specifier can only occur once per
424 # filename pattern.
425 #
Max Morozd73e45f2018-04-24 18:32:47426 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01427 #
Max Morozd73e45f2018-04-24 18:32:47428 # For other cases, "%4m" is chosen as it creates some level of parallelism,
429 # but it's not too big to consume too much computing resource or disk space.
430 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59431 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01432 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18433 expected_profraw_file_path = os.path.join(
434 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
435 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24436 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
437 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54438
Yuke Liaoa0c8c2f2018-02-28 20:14:10439 try:
Max Moroz7c5354f2018-05-06 00:03:48440 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35441 with open(output_file_path, 'wb') as output_file_handle:
442 subprocess.check_call(
443 shlex.split(command),
444 stdout=output_file_handle,
445 stderr=subprocess.STDOUT,
446 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10447 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35448 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10449
Abhishek Arya03911092018-05-21 16:42:35450 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10451
452
Yuke Liao27349c92018-03-22 21:10:01453def _IsFuzzerTarget(target):
454 """Returns true if the target is a fuzzer target."""
455 build_args = _GetBuildArgs()
456 use_libfuzzer = ('use_libfuzzer' in build_args and
457 build_args['use_libfuzzer'] == 'true')
458 return use_libfuzzer and target.endswith('_fuzzer')
459
460
Abhishek Arya03911092018-05-21 16:42:35461def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10462 """Runs a single iOS command and generates a profraw data file.
463
464 iOS application doesn't have write access to folders outside of the app, so
465 it's impossible to instruct the app to flush the profraw data file to the
466 desired location. The profraw data file will be generated somewhere within the
467 application's Documents folder, and the full path can be obtained by parsing
468 the output.
469 """
Yuke Liaob2926832018-03-02 17:34:29470 assert _IsIOSCommand(command)
471
472 # After running tests, iossim generates a profraw data file, it won't be
473 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
474 # checkout.
475 iossim_profraw_file_path = os.path.join(
476 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24477 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
478 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10479
480 try:
Abhishek Arya03911092018-05-21 16:42:35481 with open(output_file_path, 'wb') as output_file_handle:
482 subprocess.check_call(
483 shlex.split(command),
484 stdout=output_file_handle,
485 stderr=subprocess.STDOUT,
486 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10487 except subprocess.CalledProcessError as e:
488 # iossim emits non-zero return code even if tests run successfully, so
489 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35490 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10491
Abhishek Arya03911092018-05-21 16:42:35492 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10493
494
495def _GetProfrawDataFileByParsingOutput(output):
496 """Returns the path to the profraw data file obtained by parsing the output.
497
498 The output of running the test target has no format, but it is guaranteed to
499 have a single line containing the path to the generated profraw data file.
500 NOTE: This should only be called when target os is iOS.
501 """
Yuke Liaob2926832018-03-02 17:34:29502 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10503
Yuke Liaob2926832018-03-02 17:34:29504 output_by_lines = ''.join(output).splitlines()
505 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10506
507 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29508 result = profraw_file_pattern.match(line)
509 if result:
510 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10511
512 assert False, ('No profraw data file was generated, did you call '
513 'coverage_util::ConfigureCoverageReportPath() in test setup? '
514 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54515
516
Abhishek Aryac19bc5ef2018-05-04 22:10:02517def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
518 """Returns a relative path to coverage profdata file by merging target
519 profdata files.
Yuke Liao506e8822017-12-04 16:52:54520
521 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02522 profdata_file_paths: A list of relative paths to the profdata data files
523 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54524
525 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02526 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54527
528 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02529 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54530 """
Abhishek Aryafb70b532018-05-06 17:47:40531 logging.info('Creating the coverage profile data file.')
532 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48533 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54534 try:
Abhishek Arya1ec832c2017-12-05 18:06:59535 subprocess_cmd = [
536 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
537 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02538 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01539
540 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18541 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02542 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04543 logging.error(
544 'Failed to merge target profdata files to create coverage profdata. %s',
545 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02546 raise error
547
Abhishek Aryafb70b532018-05-06 17:47:40548 logging.debug('Finished merging target profdata files.')
549 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02550 profdata_file_path)
551 return profdata_file_path
552
553
554def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
555 """Returns a relative path to target profdata file by merging target
556 profraw files.
557
558 Args:
559 profraw_file_paths: A list of relative paths to the profdata data files
560 that are to be merged.
561
562 Returns:
563 A relative path to the merged coverage profdata file.
564
565 Raises:
566 CalledProcessError: An error occurred merging profdata files.
567 """
Abhishek Aryafb70b532018-05-06 17:47:40568 logging.info('Creating target profile data file.')
569 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02570 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
571
572 try:
573 subprocess_cmd = [
574 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
575 ]
Yuke Liao506e8822017-12-04 16:52:54576 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01577
578 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18579 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54580 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04581 logging.error(
582 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54583 raise error
584
Abhishek Aryafb70b532018-05-06 17:47:40585 logging.debug('Finished merging target profraw files.')
586 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10587 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54588 return profdata_file_path
589
590
Yuke Liao0e4c8682018-04-18 21:06:59591def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
592 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33593 """Generates per file coverage summary using "llvm-cov export" command."""
594 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
595 # [[-object BIN]] [SOURCES].
596 # NOTE: For object files, the first one is specified as a positional argument,
597 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10598 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40599 'export -summary-only" command.')
Yuke Liaoea228d02018-01-05 19:10:33600 subprocess_cmd = [
601 LLVM_COV_PATH, 'export', '-summary-only',
602 '-instr-profile=' + profdata_file_path, binary_paths[0]
603 ]
604 subprocess_cmd.extend(
605 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29606 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33607 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59608 if ignore_filename_regex:
609 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33610
Max Moroz7c5354f2018-05-06 00:03:48611 export_output = subprocess.check_output(subprocess_cmd)
612
613 # Write output on the disk to be used by code coverage bot.
614 with open(_GetSummaryFilePath(), 'w') as f:
615 f.write(export_output)
616
Max Moroz1de68d72018-08-21 13:38:18617 return export_output
Yuke Liaoea228d02018-01-05 19:10:33618
619
Yuke Liaob2926832018-03-02 17:34:29620def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
621 """Appends -arch arguments to the command list if it's ios platform.
622
623 iOS binaries are universal binaries, and require specifying the architecture
624 to use, and one architecture needs to be specified for each binary.
625 """
626 if _IsIOS():
627 cmd_list.extend(['-arch=x86_64'] * num_archs)
628
629
Yuke Liao506e8822017-12-04 16:52:54630def _GetBinaryPath(command):
631 """Returns a relative path to the binary to be run by the command.
632
Yuke Liao545db322018-02-15 17:12:01633 Currently, following types of commands are supported (e.g. url_unittests):
634 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
635 2. Use xvfb.
636 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
637 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37638 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
639 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10640 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37641 <iossim_arguments> -c <app_arguments>
642 out/Coverage-iphonesimulator/url_unittests.app"
643
Yuke Liao506e8822017-12-04 16:52:54644 Args:
645 command: A command used to run a target.
646
647 Returns:
648 A relative path to the binary.
649 """
Yuke Liao545db322018-02-15 17:12:01650 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
651
Yuke Liaob2926832018-03-02 17:34:29652 command_parts = shlex.split(command)
Yuke Liao545db322018-02-15 17:12:01653 if os.path.basename(command_parts[0]) == 'python':
654 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40655 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01656 return command_parts[2]
657
658 if os.path.basename(command_parts[0]) == xvfb_script_name:
659 return command_parts[1]
660
Yuke Liaob2926832018-03-02 17:34:29661 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10662 # For a given application bundle, the binary resides in the bundle and has
663 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02664 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10665 app_name = os.path.splitext(os.path.basename(app_path))[0]
666 return os.path.join(app_path, app_name)
667
Yuke Liaob2926832018-03-02 17:34:29668 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54669
670
Yuke Liaob2926832018-03-02 17:34:29671def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10672 """Returns true if command is used to run tests on iOS platform."""
Yuke Liaob2926832018-03-02 17:34:29673 return os.path.basename(shlex.split(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10674
675
Yuke Liao95d13d72017-12-07 18:18:50676def _VerifyTargetExecutablesAreInBuildDirectory(commands):
677 """Verifies that the target executables specified in the commands are inside
678 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54679 for command in commands:
680 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18681 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35682 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50683 'Target executable "%s" in command: "%s" is outside of '
684 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54685
686
687def _ValidateBuildingWithClangCoverage():
688 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20689 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54690
691 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
692 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59693 assert False, ('\'{} = true\' is required in args.gn.'
694 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54695
696
Yuke Liaoc60b2d02018-03-02 21:40:43697def _ValidateCurrentPlatformIsSupported():
698 """Asserts that this script suports running on the current platform"""
699 target_os = _GetTargetOS()
700 if target_os:
701 current_platform = target_os
702 else:
Max Moroz1de68d72018-08-21 13:38:18703 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43704
705 assert current_platform in [
706 'linux', 'mac', 'chromeos', 'ios'
707 ], ('Coverage is only supported on linux, mac, chromeos and ios.')
708
709
Yuke Liao80afff32018-03-07 01:26:20710def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54711 """Parses args.gn file and returns results as a dictionary.
712
713 Returns:
714 A dictionary representing the build args.
715 """
Yuke Liao80afff32018-03-07 01:26:20716 global _BUILD_ARGS
717 if _BUILD_ARGS is not None:
718 return _BUILD_ARGS
719
720 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54721 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
722 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
723 'missing args.gn file.' % BUILD_DIR)
724 with open(build_args_path) as build_args_file:
725 build_args_lines = build_args_file.readlines()
726
Yuke Liao506e8822017-12-04 16:52:54727 for build_arg_line in build_args_lines:
728 build_arg_without_comments = build_arg_line.split('#')[0]
729 key_value_pair = build_arg_without_comments.split('=')
730 if len(key_value_pair) != 2:
731 continue
732
733 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43734
735 # Values are wrapped within a pair of double-quotes, so remove the leading
736 # and trailing double-quotes.
737 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20738 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54739
Yuke Liao80afff32018-03-07 01:26:20740 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54741
742
Abhishek Arya16f059a2017-12-07 17:47:32743def _VerifyPathsAndReturnAbsolutes(paths):
744 """Verifies that the paths specified in |paths| exist and returns absolute
745 versions.
Yuke Liao66da1732017-12-05 22:19:42746
747 Args:
748 paths: A list of files or directories.
749 """
Abhishek Arya16f059a2017-12-07 17:47:32750 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42751 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32752 absolute_path = os.path.join(SRC_ROOT_PATH, path)
753 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
754
755 absolute_paths.append(absolute_path)
756
757 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42758
759
Abhishek Arya64636af2018-05-04 14:42:13760def _GetBinaryPathsFromTargets(targets, build_dir):
761 """Return binary paths from target names."""
762 # FIXME: Derive output binary from target build definitions rather than
763 # assuming that it is always the same name.
764 binary_paths = []
765 for target in targets:
766 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18767 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13768 binary_path += '.exe'
769
770 if os.path.exists(binary_path):
771 binary_paths.append(binary_path)
772 else:
773 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40774 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13775 os.path.basename(binary_path))
776
777 return binary_paths
778
779
Abhishek Arya03911092018-05-21 16:42:35780def _GetCommandForWebTests(arguments):
781 """Return command to run for blink web tests."""
782 command_list = [
783 'python', 'testing/xvfb.py', 'python',
784 'third_party/blink/tools/run_web_tests.py',
785 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24786 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Max Moroz1de68d72018-08-21 13:38:18787 LLVM_PROFILE_FILE_PATH_SUBSTITUTION, '--batch-size=1',
Abhishek Arya03911092018-05-21 16:42:35788 '--child-processes=%d' % max(1, int(multiprocessing.cpu_count() / 2)),
Abhishek Aryabd0655d2018-05-21 19:55:24789 '--disable-breakpad', '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35790 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
791 ]
792 if arguments.strip():
793 command_list.append(arguments)
794 return ' '.join(command_list)
795
796
797def _GetBinaryPathForWebTests():
798 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18799 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35800 if host_platform == 'win':
801 return os.path.join(BUILD_DIR, 'content_shell.exe')
802 elif host_platform == 'linux':
803 return os.path.join(BUILD_DIR, 'content_shell')
804 elif host_platform == 'mac':
805 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
806 'Content Shell')
807 else:
808 assert False, 'This platform is not supported for web tests.'
809
810
Abhishek Aryae5811afa2018-05-24 03:56:01811def _SetupOutputDir():
812 """Setup output directory."""
813 if os.path.exists(OUTPUT_DIR):
814 shutil.rmtree(OUTPUT_DIR)
815
816 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18817 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01818
819
Yuke Liao506e8822017-12-04 16:52:54820def _ParseCommandArguments():
821 """Adds and parses relevant arguments for tool comands.
822
823 Returns:
824 A dictionary representing the arguments.
825 """
826 arg_parser = argparse.ArgumentParser()
827 arg_parser.usage = __doc__
828
Abhishek Arya1ec832c2017-12-05 18:06:59829 arg_parser.add_argument(
830 '-b',
831 '--build-dir',
832 type=str,
833 required=True,
834 help='The build directory, the path needs to be relative to the root of '
835 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54836
Abhishek Arya1ec832c2017-12-05 18:06:59837 arg_parser.add_argument(
838 '-o',
839 '--output-dir',
840 type=str,
841 required=True,
842 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54843
Abhishek Arya1ec832c2017-12-05 18:06:59844 arg_parser.add_argument(
845 '-c',
846 '--command',
847 action='append',
Abhishek Arya64636af2018-05-04 14:42:13848 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59849 help='Commands used to run test targets, one test target needs one and '
850 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13851 'current working directory is the root of the checkout. This option is '
852 'incompatible with -p/--profdata-file option.')
853
854 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35855 '-wt',
856 '--web-tests',
857 nargs='?',
858 type=str,
859 const=' ',
860 required=False,
861 help='Run blink web tests. Support passing arguments to run_web_tests.py')
862
863 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13864 '-p',
865 '--profdata-file',
866 type=str,
867 required=False,
868 help='Path to profdata file to use for generating code coverage reports. '
869 'This can be useful if you generated the profdata file seperately in '
870 'your own test harness. This option is ignored if run command(s) are '
871 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54872
Abhishek Arya1ec832c2017-12-05 18:06:59873 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42874 '-f',
875 '--filters',
876 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32877 required=False,
Yuke Liao66da1732017-12-05 22:19:42878 help='Directories or files to get code coverage for, and all files under '
879 'the directories are included recursively.')
880
881 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59882 '-i',
883 '--ignore-filename-regex',
884 type=str,
885 help='Skip source code files with file paths that match the given '
886 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
887 'to exclude files in third_party/ and out/ folders from the report.')
888
889 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32890 '--no-file-view',
891 action='store_true',
892 help='Don\'t generate the file view in the coverage report. When there '
893 'are large number of html files, the file view becomes heavy and may '
894 'cause the browser to freeze, and this argument comes handy.')
895
896 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18897 '--no-component-view',
898 action='store_true',
899 help='Don\'t generate the component view in the coverage report.')
900
901 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:40902 '--coverage-tools-dir',
903 type=str,
904 help='Path of the directory where LLVM coverage tools (llvm-cov, '
905 'llvm-profdata) exist. This should be only needed if you are testing '
906 'against a custom built clang revision. Otherwise, we pick coverage '
907 'tools automatically from your current source checkout.')
908
909 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:59910 '-j',
911 '--jobs',
912 type=int,
913 default=None,
914 help='Run N jobs to build in parallel. If not specified, a default value '
915 'will be derived based on CPUs availability. Please refer to '
916 '\'ninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:54917
Abhishek Arya1ec832c2017-12-05 18:06:59918 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:10919 '-v',
920 '--verbose',
921 action='store_true',
922 help='Prints additional output for diagnostics.')
923
924 arg_parser.add_argument(
925 '-l', '--log_file', type=str, help='Redirects logs to a file.')
926
927 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:02928 'targets',
929 nargs='+',
930 help='The names of the test targets to run. If multiple run commands are '
931 'specified using the -c/--command option, then the order of targets and '
932 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:54933
934 args = arg_parser.parse_args()
935 return args
936
937
938def Main():
939 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:40940 # Setup coverage binaries even when script is called with empty params. This
941 # is used by coverage bot for initial setup.
942 if len(sys.argv) == 1:
Roberto Carrillo36312722018-10-17 02:18:45943 update_clang_coverage_tools.DownloadCoverageToolsIfNeeded()
Yuke Liao082e99632018-05-18 15:40:40944 print(__doc__)
945 return
946
Abhishek Arya64636af2018-05-04 14:42:13947 # Change directory to source root to aid in relative paths calculations.
Abhishek Arya03911092018-05-21 16:42:35948 global SRC_ROOT_PATH
Max Moroz1de68d72018-08-21 13:38:18949 SRC_ROOT_PATH = coverage_utils.GetFullPath(
Abhishek Arya03911092018-05-21 16:42:35950 os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
Abhishek Arya64636af2018-05-04 14:42:13951 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:11952
Yuke Liao506e8822017-12-04 16:52:54953 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:18954 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:40955 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:13956
Yuke Liao506e8822017-12-04 16:52:54957 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:18958 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:01959
Yuke Liao506e8822017-12-04 16:52:54960 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:18961 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:54962
Abhishek Arya03911092018-05-21 16:42:35963 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:13964 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:35965 'provide prof-data file as input using -p/--profdata-file option OR '
966 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:43967
Abhishek Arya64636af2018-05-04 14:42:13968 assert not args.command or (len(args.targets) == len(args.command)), (
969 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:43970
Abhishek Arya1ec832c2017-12-05 18:06:59971 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:40972 'Build directory: "%s" doesn\'t exist. '
973 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:13974
Yuke Liaoc60b2d02018-03-02 21:40:43975 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:54976 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:32977
978 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:42979 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:32980 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:42981
Abhishek Aryae5811afa2018-05-24 03:56:01982 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:54983
Abhishek Arya03911092018-05-21 16:42:35984 # Get .profdata file and list of binary paths.
985 if args.web_tests:
986 commands = [_GetCommandForWebTests(args.web_tests)]
987 profdata_file_path = _CreateCoverageProfileDataForTargets(
988 args.targets, commands, args.jobs)
989 binary_paths = [_GetBinaryPathForWebTests()]
990 elif args.command:
991 for i in range(len(args.command)):
992 assert not 'run_web_tests.py' in args.command[i], (
993 'run_web_tests.py is not supported via --command argument. '
994 'Please use --run-web-tests argument instead.')
995
Abhishek Arya64636af2018-05-04 14:42:13996 # A list of commands are provided. Run them to generate profdata file, and
997 # create a list of binary paths from parsing commands.
998 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
999 profdata_file_path = _CreateCoverageProfileDataForTargets(
1000 args.targets, args.command, args.jobs)
1001 binary_paths = [_GetBinaryPath(command) for command in args.command]
1002 else:
1003 # An input prof-data file is already provided. Just calculate binary paths.
1004 profdata_file_path = args.profdata_file
1005 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331006
Max Moroz1de68d72018-08-21 13:38:181007 binary_paths.extend(
1008 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR))
Abhishek Arya78120bc2018-05-07 20:53:541009
Yuke Liao481d3482018-01-29 19:17:101010 logging.info('Generating code coverage report in html (this can take a while '
Abhishek Aryafb70b532018-05-06 17:47:401011 'depending on size of target!).')
Max Moroz1de68d72018-08-21 13:38:181012 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591013 binary_paths, profdata_file_path, absolute_filter_paths,
1014 args.ignore_filename_regex)
Yuke Liaodd1ec0592018-02-02 01:26:371015 _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:591016 absolute_filter_paths,
1017 args.ignore_filename_regex)
Max Moroz1de68d72018-08-21 13:38:181018 component_mappings = None
1019 if not args.no_component_view:
1020 component_mappings = json.load(urllib2.urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371021
Max Moroz1de68d72018-08-21 13:38:181022 # Call prepare here.
1023 processor = coverage_utils.CoverageReportPostProcessor(
1024 OUTPUT_DIR,
1025 SRC_ROOT_PATH,
1026 per_file_summary_data,
1027 no_component_view=args.no_component_view,
1028 no_file_view=args.no_file_view,
1029 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371030
Max Moroz1de68d72018-08-21 13:38:181031 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541032
Abhishek Arya1ec832c2017-12-05 18:06:591033
Yuke Liao506e8822017-12-04 16:52:541034if __name__ == '__main__':
1035 sys.exit(Main())