blob: eb7dd346d677f463f1adc5e8f2a3333e1529fd2a [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 \\
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
65 https://chromium.googlesource.com/chromium/src/+/master/docs/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
114# Default number of jobs used to build when goma is configured and enabled.
115DEFAULT_GOMA_JOBS = 100
116
117# Name of the file extension for profraw data files.
118PROFRAW_FILE_EXTENSION = 'profraw'
119
120# Name of the final profdata file, and this file needs to be passed to
121# "llvm-cov" command in order to call "llvm-cov show" to inspect the
122# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48123PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
124
125# Name of the file with summary information generated by llvm-cov export.
126SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54127
128# Build arg required for generating code coverage data.
129CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
130
Max Moroz7c5354f2018-05-06 00:03:48131LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37132
133# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19134COMPONENT_MAPPING_URL = (
135 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37136
Yuke Liao80afff32018-03-07 01:26:20137# Caches the results returned by _GetBuildArgs, don't use this variable
138# directly, call _GetBuildArgs instead.
139_BUILD_ARGS = None
140
Abhishek Aryac19bc5ef2018-05-04 22:10:02141# Retry failed merges.
142MERGE_RETRIES = 3
143
Abhishek Aryad35de7e2018-05-10 22:23:04144# Message to guide user to file a bug when everything else fails.
145FILE_BUG_MESSAGE = (
146 'If it persists, please file a bug with the command you used, git revision '
147 'and args.gn config here: '
148 'https://bugs.chromium.org/p/chromium/issues/entry?'
149 'components=Tools%3ECodeCoverage')
150
Abhishek Aryabd0655d2018-05-21 19:55:24151# String to replace with actual llvm profile path.
152LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
153
Yuke Liaoea228d02018-01-05 19:10:33154
Yuke Liao082e99632018-05-18 15:40:40155def _ConfigureLLVMCoverageTools(args):
156 """Configures llvm coverage tools."""
157 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18158 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40159 global LLVM_COV_PATH
160 global LLVM_PROFDATA_PATH
161 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
162 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
163 else:
Roberto Carrillo36312722018-10-17 02:18:45164 update_clang_coverage_tools.DownloadCoverageToolsIfNeeded()
Yuke Liao082e99632018-05-18 15:40:40165
166 coverage_tools_exist = (
167 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
168 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
169 'both \'%s\' and \'%s\' exist.') % (
170 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
171
Abhishek Arya1c97ea542018-05-10 03:53:19172def _GetPathWithLLVMSymbolizerDir():
173 """Add llvm-symbolizer directory to path for symbolized stacks."""
174 path = os.getenv('PATH')
175 dirs = path.split(os.pathsep)
176 if LLVM_BIN_DIR in dirs:
177 return path
178
179 return path + os.pathsep + LLVM_BIN_DIR
180
181
Yuke Liaoc60b2d02018-03-02 21:40:43182def _GetTargetOS():
183 """Returns the target os specified in args.gn file.
184
185 Returns an empty string is target_os is not specified.
186 """
Yuke Liao80afff32018-03-07 01:26:20187 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43188 return build_args['target_os'] if 'target_os' in build_args else ''
189
190
Yuke Liaob2926832018-03-02 17:34:29191def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10192 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43193 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10194
195
Yuke Liao506e8822017-12-04 16:52:54196
Yuke Liaodd1ec0592018-02-02 01:26:37197def _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:59198 filters, ignore_filename_regex):
Yuke Liao506e8822017-12-04 16:52:54199 """Generates per file line-by-line coverage in html using 'llvm-cov show'.
200
201 For a file with absolute path /a/b/x.cc, a html report is generated as:
202 OUTPUT_DIR/coverage/a/b/x.cc.html. An index html file is also generated as:
203 OUTPUT_DIR/index.html.
204
205 Args:
206 binary_paths: A list of paths to the instrumented binaries.
207 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42208 filters: A list of directories and files to get coverage for.
Yuke Liao506e8822017-12-04 16:52:54209 """
Yuke Liao506e8822017-12-04 16:52:54210 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
211 # [[-object BIN]] [SOURCES]
212 # NOTE: For object files, the first one is specified as a positional argument,
213 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10214 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40215 '"llvm-cov show" command.')
Abhishek Arya1ec832c2017-12-05 18:06:59216 subprocess_cmd = [
217 LLVM_COV_PATH, 'show', '-format=html',
218 '-output-dir={}'.format(OUTPUT_DIR),
219 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
220 ]
221 subprocess_cmd.extend(
222 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29223 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18224 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17225 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42226 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59227 if ignore_filename_regex:
228 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
229
Yuke Liao506e8822017-12-04 16:52:54230 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34231
Abhishek Aryafb70b532018-05-06 17:47:40232 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54233
234
Max Moroz7c5354f2018-05-06 00:03:48235def _GetLogsDirectoryPath():
236 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18237 return os.path.join(
238 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48239
240
241def _GetProfdataFilePath():
242 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18243 return os.path.join(
244 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
245 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48246
247
248def _GetSummaryFilePath():
249 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18250 return os.path.join(
251 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
252 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33253
254
Yuke Liao506e8822017-12-04 16:52:54255def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
256 """Builds and runs target to generate the coverage profile data.
257
258 Args:
259 targets: A list of targets to build with coverage instrumentation.
260 commands: A list of commands used to run the targets.
261 jobs_count: Number of jobs to run in parallel for building. If None, a
262 default value is derived based on CPUs availability.
263
264 Returns:
265 A relative path to the generated profdata file.
266 """
267 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02268 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59269 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02270 coverage_profdata_file_path = (
271 _CreateCoverageProfileDataFromTargetProfDataFiles(
272 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54273
Abhishek Aryac19bc5ef2018-05-04 22:10:02274 for target_profdata_file_path in target_profdata_file_paths:
275 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52276
Abhishek Aryac19bc5ef2018-05-04 22:10:02277 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54278
279
280def _BuildTargets(targets, jobs_count):
281 """Builds target with Clang coverage instrumentation.
282
283 This function requires current working directory to be the root of checkout.
284
285 Args:
286 targets: A list of targets to build with coverage instrumentation.
287 jobs_count: Number of jobs to run in parallel for compilation. If None, a
288 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54289 """
Abhishek Arya1ec832c2017-12-05 18:06:59290
Yuke Liao506e8822017-12-04 16:52:54291 def _IsGomaConfigured():
292 """Returns True if goma is enabled in the gn build args.
293
294 Returns:
295 A boolean indicates whether goma is configured for building or not.
296 """
Yuke Liao80afff32018-03-07 01:26:20297 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54298 return 'use_goma' in build_args and build_args['use_goma'] == 'true'
299
Abhishek Aryafb70b532018-05-06 17:47:40300 logging.info('Building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54301 if jobs_count is None and _IsGomaConfigured():
302 jobs_count = DEFAULT_GOMA_JOBS
303
304 subprocess_cmd = ['ninja', '-C', BUILD_DIR]
305 if jobs_count is not None:
306 subprocess_cmd.append('-j' + str(jobs_count))
307
308 subprocess_cmd.extend(targets)
309 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40310 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54311
312
Abhishek Aryac19bc5ef2018-05-04 22:10:02313def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54314 """Runs commands and returns the relative paths to the profraw data files.
315
316 Args:
317 targets: A list of targets built with coverage instrumentation.
318 commands: A list of commands used to run the targets.
319
320 Returns:
321 A list of relative paths to the generated profraw data files.
322 """
Abhishek Aryafb70b532018-05-06 17:47:40323 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10324
Yuke Liao506e8822017-12-04 16:52:54325 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18326 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
327 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54328 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18329 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48330
331 # Ensure that logs directory exists.
332 if not os.path.exists(_GetLogsDirectoryPath()):
333 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54334
Abhishek Aryac19bc5ef2018-05-04 22:10:02335 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10336
Yuke Liaod4a9865202018-01-12 23:17:52337 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54338 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48339 output_file_name = os.extsep.join([target + '_output', 'log'])
340 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10341
Abhishek Aryac19bc5ef2018-05-04 22:10:02342 profdata_file_path = None
343 for _ in xrange(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40344 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02345 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10346
Abhishek Aryac19bc5ef2018-05-04 22:10:02347 if _IsIOSCommand(command):
348 # On iOS platform, due to lack of write permissions, profraw files are
349 # generated outside of the OUTPUT_DIR, and the exact paths are contained
350 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35351 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02352 else:
353 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35354 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02355
356 profraw_file_paths = []
357 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57358 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Abhishek Aryac19bc5ef2018-05-04 22:10:02359 else:
Max Moroz1de68d72018-08-21 13:38:18360 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02361 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48362 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18363 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02364
365 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40366 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04367 'please make sure the binary exists, is properly instrumented and '
368 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02369
Yuke Liao9c2c70b2018-05-23 15:37:57370 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18371 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
372 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57373
Abhishek Aryac19bc5ef2018-05-04 22:10:02374 try:
375 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
376 target, profraw_file_paths)
377 break
378 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04379 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02380 finally:
381 # Remove profraw files now so that they are not used in next iteration.
382 for profraw_file_path in profraw_file_paths:
383 os.remove(profraw_file_path)
384
385 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04386 'Failed to merge target "%s" profraw files after %d retries. %s' %
387 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02388 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54389
Abhishek Aryafb70b532018-05-06 17:47:40390 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10391
Abhishek Aryac19bc5ef2018-05-04 22:10:02392 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54393
394
Abhishek Arya03911092018-05-21 16:42:35395def _GetEnvironmentVars(profraw_file_path):
396 """Return environment vars for subprocess, given a profraw file path."""
397 env = os.environ.copy()
398 env.update({
399 'LLVM_PROFILE_FILE': profraw_file_path,
400 'PATH': _GetPathWithLLVMSymbolizerDir()
401 })
402 return env
403
404
405def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10406 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52407 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01408 #
Max Morozd73e45f2018-04-24 18:32:47409 # "%p" expands out to the process ID. It's not used by this scripts due to:
410 # 1) If a target program spawns too many processess, it may exhaust all disk
411 # space available. For example, unit_tests writes thousands of .profraw
412 # files each of size 1GB+.
413 # 2) If a target binary uses shared libraries, coverage profile data for them
414 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01415 #
Yuke Liaod4a9865202018-01-12 23:17:52416 # "%Nm" expands out to the instrumented binary's signature. When this pattern
417 # is specified, the runtime creates a pool of N raw profiles which are used
418 # for on-line profile merging. The runtime takes care of selecting a raw
419 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52420 # N must be between 1 and 9. The merge pool specifier can only occur once per
421 # filename pattern.
422 #
Max Morozd73e45f2018-04-24 18:32:47423 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01424 #
Max Morozd73e45f2018-04-24 18:32:47425 # For other cases, "%4m" is chosen as it creates some level of parallelism,
426 # but it's not too big to consume too much computing resource or disk space.
427 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59428 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01429 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18430 expected_profraw_file_path = os.path.join(
431 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
432 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24433 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
434 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54435
Yuke Liaoa0c8c2f2018-02-28 20:14:10436 try:
Max Moroz7c5354f2018-05-06 00:03:48437 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35438 with open(output_file_path, 'wb') as output_file_handle:
439 subprocess.check_call(
440 shlex.split(command),
441 stdout=output_file_handle,
442 stderr=subprocess.STDOUT,
443 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10444 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35445 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10446
Abhishek Arya03911092018-05-21 16:42:35447 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10448
449
Yuke Liao27349c92018-03-22 21:10:01450def _IsFuzzerTarget(target):
451 """Returns true if the target is a fuzzer target."""
452 build_args = _GetBuildArgs()
453 use_libfuzzer = ('use_libfuzzer' in build_args and
454 build_args['use_libfuzzer'] == 'true')
455 return use_libfuzzer and target.endswith('_fuzzer')
456
457
Abhishek Arya03911092018-05-21 16:42:35458def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10459 """Runs a single iOS command and generates a profraw data file.
460
461 iOS application doesn't have write access to folders outside of the app, so
462 it's impossible to instruct the app to flush the profraw data file to the
463 desired location. The profraw data file will be generated somewhere within the
464 application's Documents folder, and the full path can be obtained by parsing
465 the output.
466 """
Yuke Liaob2926832018-03-02 17:34:29467 assert _IsIOSCommand(command)
468
469 # After running tests, iossim generates a profraw data file, it won't be
470 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
471 # checkout.
472 iossim_profraw_file_path = os.path.join(
473 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24474 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
475 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10476
477 try:
Abhishek Arya03911092018-05-21 16:42:35478 with open(output_file_path, 'wb') as output_file_handle:
479 subprocess.check_call(
480 shlex.split(command),
481 stdout=output_file_handle,
482 stderr=subprocess.STDOUT,
483 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10484 except subprocess.CalledProcessError as e:
485 # iossim emits non-zero return code even if tests run successfully, so
486 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35487 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10488
Abhishek Arya03911092018-05-21 16:42:35489 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10490
491
492def _GetProfrawDataFileByParsingOutput(output):
493 """Returns the path to the profraw data file obtained by parsing the output.
494
495 The output of running the test target has no format, but it is guaranteed to
496 have a single line containing the path to the generated profraw data file.
497 NOTE: This should only be called when target os is iOS.
498 """
Yuke Liaob2926832018-03-02 17:34:29499 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10500
Yuke Liaob2926832018-03-02 17:34:29501 output_by_lines = ''.join(output).splitlines()
502 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10503
504 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29505 result = profraw_file_pattern.match(line)
506 if result:
507 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10508
509 assert False, ('No profraw data file was generated, did you call '
510 'coverage_util::ConfigureCoverageReportPath() in test setup? '
511 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54512
513
Abhishek Aryac19bc5ef2018-05-04 22:10:02514def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
515 """Returns a relative path to coverage profdata file by merging target
516 profdata files.
Yuke Liao506e8822017-12-04 16:52:54517
518 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02519 profdata_file_paths: A list of relative paths to the profdata data files
520 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54521
522 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02523 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54524
525 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02526 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54527 """
Abhishek Aryafb70b532018-05-06 17:47:40528 logging.info('Creating the coverage profile data file.')
529 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48530 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54531 try:
Abhishek Arya1ec832c2017-12-05 18:06:59532 subprocess_cmd = [
533 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
534 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02535 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01536
537 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18538 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02539 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04540 logging.error(
541 'Failed to merge target profdata files to create coverage profdata. %s',
542 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02543 raise error
544
Abhishek Aryafb70b532018-05-06 17:47:40545 logging.debug('Finished merging target profdata files.')
546 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02547 profdata_file_path)
548 return profdata_file_path
549
550
551def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
552 """Returns a relative path to target profdata file by merging target
553 profraw files.
554
555 Args:
556 profraw_file_paths: A list of relative paths to the profdata data files
557 that are to be merged.
558
559 Returns:
560 A relative path to the merged coverage profdata file.
561
562 Raises:
563 CalledProcessError: An error occurred merging profdata files.
564 """
Abhishek Aryafb70b532018-05-06 17:47:40565 logging.info('Creating target profile data file.')
566 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02567 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
568
569 try:
570 subprocess_cmd = [
571 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
572 ]
Yuke Liao506e8822017-12-04 16:52:54573 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01574
575 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18576 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54577 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04578 logging.error(
579 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54580 raise error
581
Abhishek Aryafb70b532018-05-06 17:47:40582 logging.debug('Finished merging target profraw files.')
583 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10584 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54585 return profdata_file_path
586
587
Yuke Liao0e4c8682018-04-18 21:06:59588def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
589 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33590 """Generates per file coverage summary using "llvm-cov export" command."""
591 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
592 # [[-object BIN]] [SOURCES].
593 # NOTE: For object files, the first one is specified as a positional argument,
594 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10595 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40596 'export -summary-only" command.')
Yuke Liaoea228d02018-01-05 19:10:33597 subprocess_cmd = [
598 LLVM_COV_PATH, 'export', '-summary-only',
599 '-instr-profile=' + profdata_file_path, binary_paths[0]
600 ]
601 subprocess_cmd.extend(
602 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29603 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33604 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59605 if ignore_filename_regex:
606 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33607
Max Moroz7c5354f2018-05-06 00:03:48608 export_output = subprocess.check_output(subprocess_cmd)
609
610 # Write output on the disk to be used by code coverage bot.
611 with open(_GetSummaryFilePath(), 'w') as f:
612 f.write(export_output)
613
Max Moroz1de68d72018-08-21 13:38:18614 return export_output
Yuke Liaoea228d02018-01-05 19:10:33615
616
Yuke Liaob2926832018-03-02 17:34:29617def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
618 """Appends -arch arguments to the command list if it's ios platform.
619
620 iOS binaries are universal binaries, and require specifying the architecture
621 to use, and one architecture needs to be specified for each binary.
622 """
623 if _IsIOS():
624 cmd_list.extend(['-arch=x86_64'] * num_archs)
625
626
Yuke Liao506e8822017-12-04 16:52:54627def _GetBinaryPath(command):
628 """Returns a relative path to the binary to be run by the command.
629
Yuke Liao545db322018-02-15 17:12:01630 Currently, following types of commands are supported (e.g. url_unittests):
631 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
632 2. Use xvfb.
633 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
634 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37635 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
636 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10637 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37638 <iossim_arguments> -c <app_arguments>
639 out/Coverage-iphonesimulator/url_unittests.app"
640
Yuke Liao506e8822017-12-04 16:52:54641 Args:
642 command: A command used to run a target.
643
644 Returns:
645 A relative path to the binary.
646 """
Yuke Liao545db322018-02-15 17:12:01647 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
648
Yuke Liaob2926832018-03-02 17:34:29649 command_parts = shlex.split(command)
Yuke Liao545db322018-02-15 17:12:01650 if os.path.basename(command_parts[0]) == 'python':
651 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40652 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01653 return command_parts[2]
654
655 if os.path.basename(command_parts[0]) == xvfb_script_name:
656 return command_parts[1]
657
Yuke Liaob2926832018-03-02 17:34:29658 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10659 # For a given application bundle, the binary resides in the bundle and has
660 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02661 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10662 app_name = os.path.splitext(os.path.basename(app_path))[0]
663 return os.path.join(app_path, app_name)
664
Yuke Liaob2926832018-03-02 17:34:29665 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54666
667
Yuke Liaob2926832018-03-02 17:34:29668def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10669 """Returns true if command is used to run tests on iOS platform."""
Yuke Liaob2926832018-03-02 17:34:29670 return os.path.basename(shlex.split(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10671
672
Yuke Liao95d13d72017-12-07 18:18:50673def _VerifyTargetExecutablesAreInBuildDirectory(commands):
674 """Verifies that the target executables specified in the commands are inside
675 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54676 for command in commands:
677 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18678 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35679 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50680 'Target executable "%s" in command: "%s" is outside of '
681 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54682
683
684def _ValidateBuildingWithClangCoverage():
685 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20686 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54687
688 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
689 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59690 assert False, ('\'{} = true\' is required in args.gn.'
691 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54692
693
Yuke Liaoc60b2d02018-03-02 21:40:43694def _ValidateCurrentPlatformIsSupported():
695 """Asserts that this script suports running on the current platform"""
696 target_os = _GetTargetOS()
697 if target_os:
698 current_platform = target_os
699 else:
Max Moroz1de68d72018-08-21 13:38:18700 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43701
702 assert current_platform in [
703 'linux', 'mac', 'chromeos', 'ios'
704 ], ('Coverage is only supported on linux, mac, chromeos and ios.')
705
706
Yuke Liao80afff32018-03-07 01:26:20707def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54708 """Parses args.gn file and returns results as a dictionary.
709
710 Returns:
711 A dictionary representing the build args.
712 """
Yuke Liao80afff32018-03-07 01:26:20713 global _BUILD_ARGS
714 if _BUILD_ARGS is not None:
715 return _BUILD_ARGS
716
717 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54718 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
719 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
720 'missing args.gn file.' % BUILD_DIR)
721 with open(build_args_path) as build_args_file:
722 build_args_lines = build_args_file.readlines()
723
Yuke Liao506e8822017-12-04 16:52:54724 for build_arg_line in build_args_lines:
725 build_arg_without_comments = build_arg_line.split('#')[0]
726 key_value_pair = build_arg_without_comments.split('=')
727 if len(key_value_pair) != 2:
728 continue
729
730 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43731
732 # Values are wrapped within a pair of double-quotes, so remove the leading
733 # and trailing double-quotes.
734 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20735 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54736
Yuke Liao80afff32018-03-07 01:26:20737 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54738
739
Abhishek Arya16f059a2017-12-07 17:47:32740def _VerifyPathsAndReturnAbsolutes(paths):
741 """Verifies that the paths specified in |paths| exist and returns absolute
742 versions.
Yuke Liao66da1732017-12-05 22:19:42743
744 Args:
745 paths: A list of files or directories.
746 """
Abhishek Arya16f059a2017-12-07 17:47:32747 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42748 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32749 absolute_path = os.path.join(SRC_ROOT_PATH, path)
750 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
751
752 absolute_paths.append(absolute_path)
753
754 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42755
756
Abhishek Arya64636af2018-05-04 14:42:13757def _GetBinaryPathsFromTargets(targets, build_dir):
758 """Return binary paths from target names."""
759 # FIXME: Derive output binary from target build definitions rather than
760 # assuming that it is always the same name.
761 binary_paths = []
762 for target in targets:
763 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18764 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13765 binary_path += '.exe'
766
767 if os.path.exists(binary_path):
768 binary_paths.append(binary_path)
769 else:
770 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40771 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13772 os.path.basename(binary_path))
773
774 return binary_paths
775
776
Abhishek Arya03911092018-05-21 16:42:35777def _GetCommandForWebTests(arguments):
778 """Return command to run for blink web tests."""
779 command_list = [
780 'python', 'testing/xvfb.py', 'python',
781 'third_party/blink/tools/run_web_tests.py',
782 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24783 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Robert Ma339fc472018-12-14 21:22:42784 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Abhishek Arya03911092018-05-21 16:42:35785 '--child-processes=%d' % max(1, int(multiprocessing.cpu_count() / 2)),
Abhishek Aryabd0655d2018-05-21 19:55:24786 '--disable-breakpad', '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35787 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
788 ]
789 if arguments.strip():
790 command_list.append(arguments)
791 return ' '.join(command_list)
792
793
794def _GetBinaryPathForWebTests():
795 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18796 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35797 if host_platform == 'win':
798 return os.path.join(BUILD_DIR, 'content_shell.exe')
799 elif host_platform == 'linux':
800 return os.path.join(BUILD_DIR, 'content_shell')
801 elif host_platform == 'mac':
802 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
803 'Content Shell')
804 else:
805 assert False, 'This platform is not supported for web tests.'
806
807
Abhishek Aryae5811afa2018-05-24 03:56:01808def _SetupOutputDir():
809 """Setup output directory."""
810 if os.path.exists(OUTPUT_DIR):
811 shutil.rmtree(OUTPUT_DIR)
812
813 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18814 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01815
816
Yuke Liao506e8822017-12-04 16:52:54817def _ParseCommandArguments():
818 """Adds and parses relevant arguments for tool comands.
819
820 Returns:
821 A dictionary representing the arguments.
822 """
823 arg_parser = argparse.ArgumentParser()
824 arg_parser.usage = __doc__
825
Abhishek Arya1ec832c2017-12-05 18:06:59826 arg_parser.add_argument(
827 '-b',
828 '--build-dir',
829 type=str,
830 required=True,
831 help='The build directory, the path needs to be relative to the root of '
832 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54833
Abhishek Arya1ec832c2017-12-05 18:06:59834 arg_parser.add_argument(
835 '-o',
836 '--output-dir',
837 type=str,
838 required=True,
839 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54840
Abhishek Arya1ec832c2017-12-05 18:06:59841 arg_parser.add_argument(
842 '-c',
843 '--command',
844 action='append',
Abhishek Arya64636af2018-05-04 14:42:13845 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59846 help='Commands used to run test targets, one test target needs one and '
847 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13848 'current working directory is the root of the checkout. This option is '
849 'incompatible with -p/--profdata-file option.')
850
851 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35852 '-wt',
853 '--web-tests',
854 nargs='?',
855 type=str,
856 const=' ',
857 required=False,
858 help='Run blink web tests. Support passing arguments to run_web_tests.py')
859
860 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13861 '-p',
862 '--profdata-file',
863 type=str,
864 required=False,
865 help='Path to profdata file to use for generating code coverage reports. '
866 'This can be useful if you generated the profdata file seperately in '
867 'your own test harness. This option is ignored if run command(s) are '
868 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54869
Abhishek Arya1ec832c2017-12-05 18:06:59870 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42871 '-f',
872 '--filters',
873 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32874 required=False,
Yuke Liao66da1732017-12-05 22:19:42875 help='Directories or files to get code coverage for, and all files under '
876 'the directories are included recursively.')
877
878 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59879 '-i',
880 '--ignore-filename-regex',
881 type=str,
882 help='Skip source code files with file paths that match the given '
883 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
884 'to exclude files in third_party/ and out/ folders from the report.')
885
886 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32887 '--no-file-view',
888 action='store_true',
889 help='Don\'t generate the file view in the coverage report. When there '
890 'are large number of html files, the file view becomes heavy and may '
891 'cause the browser to freeze, and this argument comes handy.')
892
893 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18894 '--no-component-view',
895 action='store_true',
896 help='Don\'t generate the component view in the coverage report.')
897
898 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:40899 '--coverage-tools-dir',
900 type=str,
901 help='Path of the directory where LLVM coverage tools (llvm-cov, '
902 'llvm-profdata) exist. This should be only needed if you are testing '
903 'against a custom built clang revision. Otherwise, we pick coverage '
904 'tools automatically from your current source checkout.')
905
906 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:59907 '-j',
908 '--jobs',
909 type=int,
910 default=None,
911 help='Run N jobs to build in parallel. If not specified, a default value '
912 'will be derived based on CPUs availability. Please refer to '
913 '\'ninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:54914
Abhishek Arya1ec832c2017-12-05 18:06:59915 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:10916 '-v',
917 '--verbose',
918 action='store_true',
919 help='Prints additional output for diagnostics.')
920
921 arg_parser.add_argument(
922 '-l', '--log_file', type=str, help='Redirects logs to a file.')
923
924 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:02925 'targets',
926 nargs='+',
927 help='The names of the test targets to run. If multiple run commands are '
928 'specified using the -c/--command option, then the order of targets and '
929 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:54930
931 args = arg_parser.parse_args()
932 return args
933
934
935def Main():
936 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:40937 # Setup coverage binaries even when script is called with empty params. This
938 # is used by coverage bot for initial setup.
939 if len(sys.argv) == 1:
Roberto Carrillo36312722018-10-17 02:18:45940 update_clang_coverage_tools.DownloadCoverageToolsIfNeeded()
Yuke Liao082e99632018-05-18 15:40:40941 print(__doc__)
942 return
943
Abhishek Arya64636af2018-05-04 14:42:13944 # Change directory to source root to aid in relative paths calculations.
Abhishek Arya03911092018-05-21 16:42:35945 global SRC_ROOT_PATH
Max Moroz1de68d72018-08-21 13:38:18946 SRC_ROOT_PATH = coverage_utils.GetFullPath(
Abhishek Arya03911092018-05-21 16:42:35947 os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
Abhishek Arya64636af2018-05-04 14:42:13948 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:11949
Yuke Liao506e8822017-12-04 16:52:54950 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:18951 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:40952 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:13953
Yuke Liao506e8822017-12-04 16:52:54954 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:18955 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:01956
Yuke Liao506e8822017-12-04 16:52:54957 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:18958 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:54959
Abhishek Arya03911092018-05-21 16:42:35960 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:13961 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:35962 'provide prof-data file as input using -p/--profdata-file option OR '
963 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:43964
Abhishek Arya64636af2018-05-04 14:42:13965 assert not args.command or (len(args.targets) == len(args.command)), (
966 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:43967
Abhishek Arya1ec832c2017-12-05 18:06:59968 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:40969 'Build directory: "%s" doesn\'t exist. '
970 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:13971
Yuke Liaoc60b2d02018-03-02 21:40:43972 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:54973 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:32974
975 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:42976 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:32977 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:42978
Abhishek Aryae5811afa2018-05-24 03:56:01979 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:54980
Abhishek Arya03911092018-05-21 16:42:35981 # Get .profdata file and list of binary paths.
982 if args.web_tests:
983 commands = [_GetCommandForWebTests(args.web_tests)]
984 profdata_file_path = _CreateCoverageProfileDataForTargets(
985 args.targets, commands, args.jobs)
986 binary_paths = [_GetBinaryPathForWebTests()]
987 elif args.command:
988 for i in range(len(args.command)):
989 assert not 'run_web_tests.py' in args.command[i], (
990 'run_web_tests.py is not supported via --command argument. '
991 'Please use --run-web-tests argument instead.')
992
Abhishek Arya64636af2018-05-04 14:42:13993 # A list of commands are provided. Run them to generate profdata file, and
994 # create a list of binary paths from parsing commands.
995 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
996 profdata_file_path = _CreateCoverageProfileDataForTargets(
997 args.targets, args.command, args.jobs)
998 binary_paths = [_GetBinaryPath(command) for command in args.command]
999 else:
1000 # An input prof-data file is already provided. Just calculate binary paths.
1001 profdata_file_path = args.profdata_file
1002 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331003
Max Moroz1de68d72018-08-21 13:38:181004 binary_paths.extend(
1005 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR))
Abhishek Arya78120bc2018-05-07 20:53:541006
Yuke Liao481d3482018-01-29 19:17:101007 logging.info('Generating code coverage report in html (this can take a while '
Abhishek Aryafb70b532018-05-06 17:47:401008 'depending on size of target!).')
Max Moroz1de68d72018-08-21 13:38:181009 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591010 binary_paths, profdata_file_path, absolute_filter_paths,
1011 args.ignore_filename_regex)
Yuke Liaodd1ec0592018-02-02 01:26:371012 _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:591013 absolute_filter_paths,
1014 args.ignore_filename_regex)
Max Moroz1de68d72018-08-21 13:38:181015 component_mappings = None
1016 if not args.no_component_view:
1017 component_mappings = json.load(urllib2.urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371018
Max Moroz1de68d72018-08-21 13:38:181019 # Call prepare here.
1020 processor = coverage_utils.CoverageReportPostProcessor(
1021 OUTPUT_DIR,
1022 SRC_ROOT_PATH,
1023 per_file_summary_data,
1024 no_component_view=args.no_component_view,
1025 no_file_view=args.no_file_view,
1026 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371027
Max Moroz1de68d72018-08-21 13:38:181028 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541029
Abhishek Arya1ec832c2017-12-05 18:06:591030
Yuke Liao506e8822017-12-04 16:52:541031if __name__ == '__main__':
1032 sys.exit(Main())