blob: b239b24a55f8e86878d82b4abd1dab4406042147 [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 Arya4a9494f2018-05-22 01:39:4162 Note: Generating coverage over entire suite can take minimum of 3 hours due to
63 --batch-size=1 argument added by default. This is needed since otherwise any
64 crash will cause us to lose coverage from prior successful test runs.
65
Abhishek Arya1ec832c2017-12-05 18:06:5966 For more options, please refer to tools/code_coverage/coverage.py -h.
Yuke Liao8e209fe82018-04-18 20:36:3867
68 For an overview of how code coverage works in Chromium, please refer to
69 https://chromium.googlesource.com/chromium/src/+/master/docs/code_coverage.md
Yuke Liao506e8822017-12-04 16:52:5470"""
71
72from __future__ import print_function
73
74import sys
75
76import argparse
Yuke Liaoea228d02018-01-05 19:10:3377import json
Yuke Liao481d3482018-01-29 19:17:1078import logging
Abhishek Arya03911092018-05-21 16:42:3579import multiprocessing
Yuke Liao506e8822017-12-04 16:52:5480import os
Yuke Liaob2926832018-03-02 17:34:2981import re
82import shlex
Max Moroz025d8952018-05-03 16:33:3483import shutil
Yuke Liao506e8822017-12-04 16:52:5484import subprocess
Yuke Liao506e8822017-12-04 16:52:5485import urllib2
86
Abhishek Arya1ec832c2017-12-05 18:06:5987sys.path.append(
88 os.path.join(
89 os.path.dirname(__file__), os.path.pardir, os.path.pardir, 'tools',
90 'clang', 'scripts'))
Roberto Carrillo36312722018-10-17 02:18:4591from update import LLVM_BUILD_DIR
Yuke Liao506e8822017-12-04 16:52:5492
Yuke Liaoea228d02018-01-05 19:10:3393sys.path.append(
94 os.path.join(
95 os.path.dirname(__file__), os.path.pardir, os.path.pardir,
96 'third_party'))
Yuke Liaoea228d02018-01-05 19:10:3397from collections import defaultdict
98
Max Moroz1de68d72018-08-21 13:38:1899import coverage_utils
Roberto Carrillo36312722018-10-17 02:18:45100import update_clang_coverage_tools
Max Moroz1de68d72018-08-21 13:38:18101
Yuke Liao082e99632018-05-18 15:40:40102# Absolute path to the code coverage tools binary. These paths can be
103# overwritten by user specified coverage tool paths.
Abhishek Arya1c97ea542018-05-10 03:53:19104LLVM_BIN_DIR = os.path.join(LLVM_BUILD_DIR, 'bin')
105LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
106LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:54107
Abhishek Arya03911092018-05-21 16:42:35108# Absolute path to the root of the checkout.
109SRC_ROOT_PATH = None
110
Yuke Liao506e8822017-12-04 16:52:54111# Build directory, the value is parsed from command line arguments.
112BUILD_DIR = None
113
114# Output directory for generated artifacts, the value is parsed from command
115# line arguemnts.
116OUTPUT_DIR = None
117
118# Default number of jobs used to build when goma is configured and enabled.
119DEFAULT_GOMA_JOBS = 100
120
121# Name of the file extension for profraw data files.
122PROFRAW_FILE_EXTENSION = 'profraw'
123
124# Name of the final profdata file, and this file needs to be passed to
125# "llvm-cov" command in order to call "llvm-cov show" to inspect the
126# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48127PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
128
129# Name of the file with summary information generated by llvm-cov export.
130SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54131
132# Build arg required for generating code coverage data.
133CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
134
Max Moroz7c5354f2018-05-06 00:03:48135LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37136
137# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19138COMPONENT_MAPPING_URL = (
139 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37140
Yuke Liao80afff32018-03-07 01:26:20141# Caches the results returned by _GetBuildArgs, don't use this variable
142# directly, call _GetBuildArgs instead.
143_BUILD_ARGS = None
144
Abhishek Aryac19bc5ef2018-05-04 22:10:02145# Retry failed merges.
146MERGE_RETRIES = 3
147
Abhishek Aryad35de7e2018-05-10 22:23:04148# Message to guide user to file a bug when everything else fails.
149FILE_BUG_MESSAGE = (
150 'If it persists, please file a bug with the command you used, git revision '
151 'and args.gn config here: '
152 'https://bugs.chromium.org/p/chromium/issues/entry?'
153 'components=Tools%3ECodeCoverage')
154
Abhishek Aryabd0655d2018-05-21 19:55:24155# String to replace with actual llvm profile path.
156LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
157
Yuke Liaoea228d02018-01-05 19:10:33158
Yuke Liao082e99632018-05-18 15:40:40159def _ConfigureLLVMCoverageTools(args):
160 """Configures llvm coverage tools."""
161 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18162 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40163 global LLVM_COV_PATH
164 global LLVM_PROFDATA_PATH
165 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
166 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
167 else:
Roberto Carrillo36312722018-10-17 02:18:45168 update_clang_coverage_tools.DownloadCoverageToolsIfNeeded()
Yuke Liao082e99632018-05-18 15:40:40169
170 coverage_tools_exist = (
171 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
172 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
173 'both \'%s\' and \'%s\' exist.') % (
174 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
175
Abhishek Arya1c97ea542018-05-10 03:53:19176def _GetPathWithLLVMSymbolizerDir():
177 """Add llvm-symbolizer directory to path for symbolized stacks."""
178 path = os.getenv('PATH')
179 dirs = path.split(os.pathsep)
180 if LLVM_BIN_DIR in dirs:
181 return path
182
183 return path + os.pathsep + LLVM_BIN_DIR
184
185
Yuke Liaoc60b2d02018-03-02 21:40:43186def _GetTargetOS():
187 """Returns the target os specified in args.gn file.
188
189 Returns an empty string is target_os is not specified.
190 """
Yuke Liao80afff32018-03-07 01:26:20191 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43192 return build_args['target_os'] if 'target_os' in build_args else ''
193
194
Yuke Liaob2926832018-03-02 17:34:29195def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10196 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43197 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10198
199
Yuke Liao506e8822017-12-04 16:52:54200
Yuke Liaodd1ec0592018-02-02 01:26:37201def _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:59202 filters, ignore_filename_regex):
Yuke Liao506e8822017-12-04 16:52:54203 """Generates per file line-by-line coverage in html using 'llvm-cov show'.
204
205 For a file with absolute path /a/b/x.cc, a html report is generated as:
206 OUTPUT_DIR/coverage/a/b/x.cc.html. An index html file is also generated as:
207 OUTPUT_DIR/index.html.
208
209 Args:
210 binary_paths: A list of paths to the instrumented binaries.
211 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42212 filters: A list of directories and files to get coverage for.
Yuke Liao506e8822017-12-04 16:52:54213 """
Yuke Liao506e8822017-12-04 16:52:54214 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
215 # [[-object BIN]] [SOURCES]
216 # NOTE: For object files, the first one is specified as a positional argument,
217 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10218 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40219 '"llvm-cov show" command.')
Abhishek Arya1ec832c2017-12-05 18:06:59220 subprocess_cmd = [
221 LLVM_COV_PATH, 'show', '-format=html',
222 '-output-dir={}'.format(OUTPUT_DIR),
223 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
224 ]
225 subprocess_cmd.extend(
226 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29227 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18228 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17229 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42230 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59231 if ignore_filename_regex:
232 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
233
Yuke Liao506e8822017-12-04 16:52:54234 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34235
Abhishek Aryafb70b532018-05-06 17:47:40236 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54237
238
Max Moroz7c5354f2018-05-06 00:03:48239def _GetLogsDirectoryPath():
240 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18241 return os.path.join(
242 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48243
244
245def _GetProfdataFilePath():
246 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18247 return os.path.join(
248 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
249 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48250
251
252def _GetSummaryFilePath():
253 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18254 return os.path.join(
255 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
256 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33257
258
Yuke Liao506e8822017-12-04 16:52:54259def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
260 """Builds and runs target to generate the coverage profile data.
261
262 Args:
263 targets: A list of targets to build with coverage instrumentation.
264 commands: A list of commands used to run the targets.
265 jobs_count: Number of jobs to run in parallel for building. If None, a
266 default value is derived based on CPUs availability.
267
268 Returns:
269 A relative path to the generated profdata file.
270 """
271 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02272 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59273 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02274 coverage_profdata_file_path = (
275 _CreateCoverageProfileDataFromTargetProfDataFiles(
276 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54277
Abhishek Aryac19bc5ef2018-05-04 22:10:02278 for target_profdata_file_path in target_profdata_file_paths:
279 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52280
Abhishek Aryac19bc5ef2018-05-04 22:10:02281 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54282
283
284def _BuildTargets(targets, jobs_count):
285 """Builds target with Clang coverage instrumentation.
286
287 This function requires current working directory to be the root of checkout.
288
289 Args:
290 targets: A list of targets to build with coverage instrumentation.
291 jobs_count: Number of jobs to run in parallel for compilation. If None, a
292 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54293 """
Abhishek Arya1ec832c2017-12-05 18:06:59294
Yuke Liao506e8822017-12-04 16:52:54295 def _IsGomaConfigured():
296 """Returns True if goma is enabled in the gn build args.
297
298 Returns:
299 A boolean indicates whether goma is configured for building or not.
300 """
Yuke Liao80afff32018-03-07 01:26:20301 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54302 return 'use_goma' in build_args and build_args['use_goma'] == 'true'
303
Abhishek Aryafb70b532018-05-06 17:47:40304 logging.info('Building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54305 if jobs_count is None and _IsGomaConfigured():
306 jobs_count = DEFAULT_GOMA_JOBS
307
308 subprocess_cmd = ['ninja', '-C', BUILD_DIR]
309 if jobs_count is not None:
310 subprocess_cmd.append('-j' + str(jobs_count))
311
312 subprocess_cmd.extend(targets)
313 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40314 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54315
316
Abhishek Aryac19bc5ef2018-05-04 22:10:02317def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54318 """Runs commands and returns the relative paths to the profraw data files.
319
320 Args:
321 targets: A list of targets built with coverage instrumentation.
322 commands: A list of commands used to run the targets.
323
324 Returns:
325 A list of relative paths to the generated profraw data files.
326 """
Abhishek Aryafb70b532018-05-06 17:47:40327 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10328
Yuke Liao506e8822017-12-04 16:52:54329 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18330 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
331 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54332 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18333 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48334
335 # Ensure that logs directory exists.
336 if not os.path.exists(_GetLogsDirectoryPath()):
337 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54338
Abhishek Aryac19bc5ef2018-05-04 22:10:02339 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10340
Yuke Liaod4a9865202018-01-12 23:17:52341 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54342 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48343 output_file_name = os.extsep.join([target + '_output', 'log'])
344 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10345
Abhishek Aryac19bc5ef2018-05-04 22:10:02346 profdata_file_path = None
347 for _ in xrange(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40348 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02349 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10350
Abhishek Aryac19bc5ef2018-05-04 22:10:02351 if _IsIOSCommand(command):
352 # On iOS platform, due to lack of write permissions, profraw files are
353 # generated outside of the OUTPUT_DIR, and the exact paths are contained
354 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35355 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02356 else:
357 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35358 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02359
360 profraw_file_paths = []
361 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57362 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Abhishek Aryac19bc5ef2018-05-04 22:10:02363 else:
Max Moroz1de68d72018-08-21 13:38:18364 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02365 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48366 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18367 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02368
369 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40370 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04371 'please make sure the binary exists, is properly instrumented and '
372 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02373
Yuke Liao9c2c70b2018-05-23 15:37:57374 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18375 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
376 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57377
Abhishek Aryac19bc5ef2018-05-04 22:10:02378 try:
379 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
380 target, profraw_file_paths)
381 break
382 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04383 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02384 finally:
385 # Remove profraw files now so that they are not used in next iteration.
386 for profraw_file_path in profraw_file_paths:
387 os.remove(profraw_file_path)
388
389 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04390 'Failed to merge target "%s" profraw files after %d retries. %s' %
391 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02392 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54393
Abhishek Aryafb70b532018-05-06 17:47:40394 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10395
Abhishek Aryac19bc5ef2018-05-04 22:10:02396 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54397
398
Abhishek Arya03911092018-05-21 16:42:35399def _GetEnvironmentVars(profraw_file_path):
400 """Return environment vars for subprocess, given a profraw file path."""
401 env = os.environ.copy()
402 env.update({
403 'LLVM_PROFILE_FILE': profraw_file_path,
404 'PATH': _GetPathWithLLVMSymbolizerDir()
405 })
406 return env
407
408
409def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10410 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52411 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01412 #
Max Morozd73e45f2018-04-24 18:32:47413 # "%p" expands out to the process ID. It's not used by this scripts due to:
414 # 1) If a target program spawns too many processess, it may exhaust all disk
415 # space available. For example, unit_tests writes thousands of .profraw
416 # files each of size 1GB+.
417 # 2) If a target binary uses shared libraries, coverage profile data for them
418 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01419 #
Yuke Liaod4a9865202018-01-12 23:17:52420 # "%Nm" expands out to the instrumented binary's signature. When this pattern
421 # is specified, the runtime creates a pool of N raw profiles which are used
422 # for on-line profile merging. The runtime takes care of selecting a raw
423 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52424 # N must be between 1 and 9. The merge pool specifier can only occur once per
425 # filename pattern.
426 #
Max Morozd73e45f2018-04-24 18:32:47427 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01428 #
Max Morozd73e45f2018-04-24 18:32:47429 # For other cases, "%4m" is chosen as it creates some level of parallelism,
430 # but it's not too big to consume too much computing resource or disk space.
431 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59432 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01433 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18434 expected_profraw_file_path = os.path.join(
435 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
436 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24437 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
438 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54439
Yuke Liaoa0c8c2f2018-02-28 20:14:10440 try:
Max Moroz7c5354f2018-05-06 00:03:48441 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35442 with open(output_file_path, 'wb') as output_file_handle:
443 subprocess.check_call(
444 shlex.split(command),
445 stdout=output_file_handle,
446 stderr=subprocess.STDOUT,
447 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10448 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35449 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10450
Abhishek Arya03911092018-05-21 16:42:35451 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10452
453
Yuke Liao27349c92018-03-22 21:10:01454def _IsFuzzerTarget(target):
455 """Returns true if the target is a fuzzer target."""
456 build_args = _GetBuildArgs()
457 use_libfuzzer = ('use_libfuzzer' in build_args and
458 build_args['use_libfuzzer'] == 'true')
459 return use_libfuzzer and target.endswith('_fuzzer')
460
461
Abhishek Arya03911092018-05-21 16:42:35462def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10463 """Runs a single iOS command and generates a profraw data file.
464
465 iOS application doesn't have write access to folders outside of the app, so
466 it's impossible to instruct the app to flush the profraw data file to the
467 desired location. The profraw data file will be generated somewhere within the
468 application's Documents folder, and the full path can be obtained by parsing
469 the output.
470 """
Yuke Liaob2926832018-03-02 17:34:29471 assert _IsIOSCommand(command)
472
473 # After running tests, iossim generates a profraw data file, it won't be
474 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
475 # checkout.
476 iossim_profraw_file_path = os.path.join(
477 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24478 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
479 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10480
481 try:
Abhishek Arya03911092018-05-21 16:42:35482 with open(output_file_path, 'wb') as output_file_handle:
483 subprocess.check_call(
484 shlex.split(command),
485 stdout=output_file_handle,
486 stderr=subprocess.STDOUT,
487 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10488 except subprocess.CalledProcessError as e:
489 # iossim emits non-zero return code even if tests run successfully, so
490 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35491 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10492
Abhishek Arya03911092018-05-21 16:42:35493 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10494
495
496def _GetProfrawDataFileByParsingOutput(output):
497 """Returns the path to the profraw data file obtained by parsing the output.
498
499 The output of running the test target has no format, but it is guaranteed to
500 have a single line containing the path to the generated profraw data file.
501 NOTE: This should only be called when target os is iOS.
502 """
Yuke Liaob2926832018-03-02 17:34:29503 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10504
Yuke Liaob2926832018-03-02 17:34:29505 output_by_lines = ''.join(output).splitlines()
506 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10507
508 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29509 result = profraw_file_pattern.match(line)
510 if result:
511 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10512
513 assert False, ('No profraw data file was generated, did you call '
514 'coverage_util::ConfigureCoverageReportPath() in test setup? '
515 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54516
517
Abhishek Aryac19bc5ef2018-05-04 22:10:02518def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
519 """Returns a relative path to coverage profdata file by merging target
520 profdata files.
Yuke Liao506e8822017-12-04 16:52:54521
522 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02523 profdata_file_paths: A list of relative paths to the profdata data files
524 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54525
526 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02527 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54528
529 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02530 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54531 """
Abhishek Aryafb70b532018-05-06 17:47:40532 logging.info('Creating the coverage profile data file.')
533 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48534 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54535 try:
Abhishek Arya1ec832c2017-12-05 18:06:59536 subprocess_cmd = [
537 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
538 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02539 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01540
541 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18542 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02543 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04544 logging.error(
545 'Failed to merge target profdata files to create coverage profdata. %s',
546 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02547 raise error
548
Abhishek Aryafb70b532018-05-06 17:47:40549 logging.debug('Finished merging target profdata files.')
550 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02551 profdata_file_path)
552 return profdata_file_path
553
554
555def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
556 """Returns a relative path to target profdata file by merging target
557 profraw files.
558
559 Args:
560 profraw_file_paths: A list of relative paths to the profdata data files
561 that are to be merged.
562
563 Returns:
564 A relative path to the merged coverage profdata file.
565
566 Raises:
567 CalledProcessError: An error occurred merging profdata files.
568 """
Abhishek Aryafb70b532018-05-06 17:47:40569 logging.info('Creating target profile data file.')
570 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02571 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
572
573 try:
574 subprocess_cmd = [
575 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
576 ]
Yuke Liao506e8822017-12-04 16:52:54577 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01578
579 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18580 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54581 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04582 logging.error(
583 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54584 raise error
585
Abhishek Aryafb70b532018-05-06 17:47:40586 logging.debug('Finished merging target profraw files.')
587 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10588 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54589 return profdata_file_path
590
591
Yuke Liao0e4c8682018-04-18 21:06:59592def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
593 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33594 """Generates per file coverage summary using "llvm-cov export" command."""
595 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
596 # [[-object BIN]] [SOURCES].
597 # NOTE: For object files, the first one is specified as a positional argument,
598 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10599 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40600 'export -summary-only" command.')
Yuke Liaoea228d02018-01-05 19:10:33601 subprocess_cmd = [
602 LLVM_COV_PATH, 'export', '-summary-only',
603 '-instr-profile=' + profdata_file_path, binary_paths[0]
604 ]
605 subprocess_cmd.extend(
606 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29607 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33608 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59609 if ignore_filename_regex:
610 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33611
Max Moroz7c5354f2018-05-06 00:03:48612 export_output = subprocess.check_output(subprocess_cmd)
613
614 # Write output on the disk to be used by code coverage bot.
615 with open(_GetSummaryFilePath(), 'w') as f:
616 f.write(export_output)
617
Max Moroz1de68d72018-08-21 13:38:18618 return export_output
Yuke Liaoea228d02018-01-05 19:10:33619
620
Yuke Liaob2926832018-03-02 17:34:29621def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
622 """Appends -arch arguments to the command list if it's ios platform.
623
624 iOS binaries are universal binaries, and require specifying the architecture
625 to use, and one architecture needs to be specified for each binary.
626 """
627 if _IsIOS():
628 cmd_list.extend(['-arch=x86_64'] * num_archs)
629
630
Yuke Liao506e8822017-12-04 16:52:54631def _GetBinaryPath(command):
632 """Returns a relative path to the binary to be run by the command.
633
Yuke Liao545db322018-02-15 17:12:01634 Currently, following types of commands are supported (e.g. url_unittests):
635 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
636 2. Use xvfb.
637 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
638 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37639 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
640 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10641 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37642 <iossim_arguments> -c <app_arguments>
643 out/Coverage-iphonesimulator/url_unittests.app"
644
Yuke Liao506e8822017-12-04 16:52:54645 Args:
646 command: A command used to run a target.
647
648 Returns:
649 A relative path to the binary.
650 """
Yuke Liao545db322018-02-15 17:12:01651 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
652
Yuke Liaob2926832018-03-02 17:34:29653 command_parts = shlex.split(command)
Yuke Liao545db322018-02-15 17:12:01654 if os.path.basename(command_parts[0]) == 'python':
655 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40656 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01657 return command_parts[2]
658
659 if os.path.basename(command_parts[0]) == xvfb_script_name:
660 return command_parts[1]
661
Yuke Liaob2926832018-03-02 17:34:29662 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10663 # For a given application bundle, the binary resides in the bundle and has
664 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02665 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10666 app_name = os.path.splitext(os.path.basename(app_path))[0]
667 return os.path.join(app_path, app_name)
668
Yuke Liaob2926832018-03-02 17:34:29669 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54670
671
Yuke Liaob2926832018-03-02 17:34:29672def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10673 """Returns true if command is used to run tests on iOS platform."""
Yuke Liaob2926832018-03-02 17:34:29674 return os.path.basename(shlex.split(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10675
676
Yuke Liao95d13d72017-12-07 18:18:50677def _VerifyTargetExecutablesAreInBuildDirectory(commands):
678 """Verifies that the target executables specified in the commands are inside
679 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54680 for command in commands:
681 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18682 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35683 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50684 'Target executable "%s" in command: "%s" is outside of '
685 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54686
687
688def _ValidateBuildingWithClangCoverage():
689 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20690 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54691
692 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
693 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59694 assert False, ('\'{} = true\' is required in args.gn.'
695 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54696
697
Yuke Liaoc60b2d02018-03-02 21:40:43698def _ValidateCurrentPlatformIsSupported():
699 """Asserts that this script suports running on the current platform"""
700 target_os = _GetTargetOS()
701 if target_os:
702 current_platform = target_os
703 else:
Max Moroz1de68d72018-08-21 13:38:18704 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43705
706 assert current_platform in [
707 'linux', 'mac', 'chromeos', 'ios'
708 ], ('Coverage is only supported on linux, mac, chromeos and ios.')
709
710
Yuke Liao80afff32018-03-07 01:26:20711def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54712 """Parses args.gn file and returns results as a dictionary.
713
714 Returns:
715 A dictionary representing the build args.
716 """
Yuke Liao80afff32018-03-07 01:26:20717 global _BUILD_ARGS
718 if _BUILD_ARGS is not None:
719 return _BUILD_ARGS
720
721 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54722 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
723 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
724 'missing args.gn file.' % BUILD_DIR)
725 with open(build_args_path) as build_args_file:
726 build_args_lines = build_args_file.readlines()
727
Yuke Liao506e8822017-12-04 16:52:54728 for build_arg_line in build_args_lines:
729 build_arg_without_comments = build_arg_line.split('#')[0]
730 key_value_pair = build_arg_without_comments.split('=')
731 if len(key_value_pair) != 2:
732 continue
733
734 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43735
736 # Values are wrapped within a pair of double-quotes, so remove the leading
737 # and trailing double-quotes.
738 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20739 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54740
Yuke Liao80afff32018-03-07 01:26:20741 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54742
743
Abhishek Arya16f059a2017-12-07 17:47:32744def _VerifyPathsAndReturnAbsolutes(paths):
745 """Verifies that the paths specified in |paths| exist and returns absolute
746 versions.
Yuke Liao66da1732017-12-05 22:19:42747
748 Args:
749 paths: A list of files or directories.
750 """
Abhishek Arya16f059a2017-12-07 17:47:32751 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42752 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32753 absolute_path = os.path.join(SRC_ROOT_PATH, path)
754 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
755
756 absolute_paths.append(absolute_path)
757
758 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42759
760
Abhishek Arya64636af2018-05-04 14:42:13761def _GetBinaryPathsFromTargets(targets, build_dir):
762 """Return binary paths from target names."""
763 # FIXME: Derive output binary from target build definitions rather than
764 # assuming that it is always the same name.
765 binary_paths = []
766 for target in targets:
767 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18768 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13769 binary_path += '.exe'
770
771 if os.path.exists(binary_path):
772 binary_paths.append(binary_path)
773 else:
774 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40775 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13776 os.path.basename(binary_path))
777
778 return binary_paths
779
780
Abhishek Arya03911092018-05-21 16:42:35781def _GetCommandForWebTests(arguments):
782 """Return command to run for blink web tests."""
783 command_list = [
784 'python', 'testing/xvfb.py', 'python',
785 'third_party/blink/tools/run_web_tests.py',
786 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24787 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Max Moroz1de68d72018-08-21 13:38:18788 LLVM_PROFILE_FILE_PATH_SUBSTITUTION, '--batch-size=1',
Abhishek Arya03911092018-05-21 16:42:35789 '--child-processes=%d' % max(1, int(multiprocessing.cpu_count() / 2)),
Abhishek Aryabd0655d2018-05-21 19:55:24790 '--disable-breakpad', '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35791 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
792 ]
793 if arguments.strip():
794 command_list.append(arguments)
795 return ' '.join(command_list)
796
797
798def _GetBinaryPathForWebTests():
799 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18800 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35801 if host_platform == 'win':
802 return os.path.join(BUILD_DIR, 'content_shell.exe')
803 elif host_platform == 'linux':
804 return os.path.join(BUILD_DIR, 'content_shell')
805 elif host_platform == 'mac':
806 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
807 'Content Shell')
808 else:
809 assert False, 'This platform is not supported for web tests.'
810
811
Abhishek Aryae5811afa2018-05-24 03:56:01812def _SetupOutputDir():
813 """Setup output directory."""
814 if os.path.exists(OUTPUT_DIR):
815 shutil.rmtree(OUTPUT_DIR)
816
817 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18818 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01819
820
Yuke Liao506e8822017-12-04 16:52:54821def _ParseCommandArguments():
822 """Adds and parses relevant arguments for tool comands.
823
824 Returns:
825 A dictionary representing the arguments.
826 """
827 arg_parser = argparse.ArgumentParser()
828 arg_parser.usage = __doc__
829
Abhishek Arya1ec832c2017-12-05 18:06:59830 arg_parser.add_argument(
831 '-b',
832 '--build-dir',
833 type=str,
834 required=True,
835 help='The build directory, the path needs to be relative to the root of '
836 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54837
Abhishek Arya1ec832c2017-12-05 18:06:59838 arg_parser.add_argument(
839 '-o',
840 '--output-dir',
841 type=str,
842 required=True,
843 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54844
Abhishek Arya1ec832c2017-12-05 18:06:59845 arg_parser.add_argument(
846 '-c',
847 '--command',
848 action='append',
Abhishek Arya64636af2018-05-04 14:42:13849 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59850 help='Commands used to run test targets, one test target needs one and '
851 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13852 'current working directory is the root of the checkout. This option is '
853 'incompatible with -p/--profdata-file option.')
854
855 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35856 '-wt',
857 '--web-tests',
858 nargs='?',
859 type=str,
860 const=' ',
861 required=False,
862 help='Run blink web tests. Support passing arguments to run_web_tests.py')
863
864 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13865 '-p',
866 '--profdata-file',
867 type=str,
868 required=False,
869 help='Path to profdata file to use for generating code coverage reports. '
870 'This can be useful if you generated the profdata file seperately in '
871 'your own test harness. This option is ignored if run command(s) are '
872 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54873
Abhishek Arya1ec832c2017-12-05 18:06:59874 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42875 '-f',
876 '--filters',
877 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32878 required=False,
Yuke Liao66da1732017-12-05 22:19:42879 help='Directories or files to get code coverage for, and all files under '
880 'the directories are included recursively.')
881
882 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59883 '-i',
884 '--ignore-filename-regex',
885 type=str,
886 help='Skip source code files with file paths that match the given '
887 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
888 'to exclude files in third_party/ and out/ folders from the report.')
889
890 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32891 '--no-file-view',
892 action='store_true',
893 help='Don\'t generate the file view in the coverage report. When there '
894 'are large number of html files, the file view becomes heavy and may '
895 'cause the browser to freeze, and this argument comes handy.')
896
897 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18898 '--no-component-view',
899 action='store_true',
900 help='Don\'t generate the component view in the coverage report.')
901
902 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:40903 '--coverage-tools-dir',
904 type=str,
905 help='Path of the directory where LLVM coverage tools (llvm-cov, '
906 'llvm-profdata) exist. This should be only needed if you are testing '
907 'against a custom built clang revision. Otherwise, we pick coverage '
908 'tools automatically from your current source checkout.')
909
910 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:59911 '-j',
912 '--jobs',
913 type=int,
914 default=None,
915 help='Run N jobs to build in parallel. If not specified, a default value '
916 'will be derived based on CPUs availability. Please refer to '
917 '\'ninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:54918
Abhishek Arya1ec832c2017-12-05 18:06:59919 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:10920 '-v',
921 '--verbose',
922 action='store_true',
923 help='Prints additional output for diagnostics.')
924
925 arg_parser.add_argument(
926 '-l', '--log_file', type=str, help='Redirects logs to a file.')
927
928 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:02929 'targets',
930 nargs='+',
931 help='The names of the test targets to run. If multiple run commands are '
932 'specified using the -c/--command option, then the order of targets and '
933 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:54934
935 args = arg_parser.parse_args()
936 return args
937
938
939def Main():
940 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:40941 # Setup coverage binaries even when script is called with empty params. This
942 # is used by coverage bot for initial setup.
943 if len(sys.argv) == 1:
Roberto Carrillo36312722018-10-17 02:18:45944 update_clang_coverage_tools.DownloadCoverageToolsIfNeeded()
Yuke Liao082e99632018-05-18 15:40:40945 print(__doc__)
946 return
947
Abhishek Arya64636af2018-05-04 14:42:13948 # Change directory to source root to aid in relative paths calculations.
Abhishek Arya03911092018-05-21 16:42:35949 global SRC_ROOT_PATH
Max Moroz1de68d72018-08-21 13:38:18950 SRC_ROOT_PATH = coverage_utils.GetFullPath(
Abhishek Arya03911092018-05-21 16:42:35951 os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
Abhishek Arya64636af2018-05-04 14:42:13952 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:11953
Yuke Liao506e8822017-12-04 16:52:54954 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:18955 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:40956 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:13957
Yuke Liao506e8822017-12-04 16:52:54958 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:18959 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:01960
Yuke Liao506e8822017-12-04 16:52:54961 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:18962 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:54963
Abhishek Arya03911092018-05-21 16:42:35964 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:13965 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:35966 'provide prof-data file as input using -p/--profdata-file option OR '
967 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:43968
Abhishek Arya64636af2018-05-04 14:42:13969 assert not args.command or (len(args.targets) == len(args.command)), (
970 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:43971
Abhishek Arya1ec832c2017-12-05 18:06:59972 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:40973 'Build directory: "%s" doesn\'t exist. '
974 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:13975
Yuke Liaoc60b2d02018-03-02 21:40:43976 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:54977 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:32978
979 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:42980 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:32981 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:42982
Abhishek Aryae5811afa2018-05-24 03:56:01983 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:54984
Abhishek Arya03911092018-05-21 16:42:35985 # Get .profdata file and list of binary paths.
986 if args.web_tests:
987 commands = [_GetCommandForWebTests(args.web_tests)]
988 profdata_file_path = _CreateCoverageProfileDataForTargets(
989 args.targets, commands, args.jobs)
990 binary_paths = [_GetBinaryPathForWebTests()]
991 elif args.command:
992 for i in range(len(args.command)):
993 assert not 'run_web_tests.py' in args.command[i], (
994 'run_web_tests.py is not supported via --command argument. '
995 'Please use --run-web-tests argument instead.')
996
Abhishek Arya64636af2018-05-04 14:42:13997 # A list of commands are provided. Run them to generate profdata file, and
998 # create a list of binary paths from parsing commands.
999 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1000 profdata_file_path = _CreateCoverageProfileDataForTargets(
1001 args.targets, args.command, args.jobs)
1002 binary_paths = [_GetBinaryPath(command) for command in args.command]
1003 else:
1004 # An input prof-data file is already provided. Just calculate binary paths.
1005 profdata_file_path = args.profdata_file
1006 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331007
Max Moroz1de68d72018-08-21 13:38:181008 binary_paths.extend(
1009 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR))
Abhishek Arya78120bc2018-05-07 20:53:541010
Yuke Liao481d3482018-01-29 19:17:101011 logging.info('Generating code coverage report in html (this can take a while '
Abhishek Aryafb70b532018-05-06 17:47:401012 'depending on size of target!).')
Max Moroz1de68d72018-08-21 13:38:181013 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591014 binary_paths, profdata_file_path, absolute_filter_paths,
1015 args.ignore_filename_regex)
Yuke Liaodd1ec0592018-02-02 01:26:371016 _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:591017 absolute_filter_paths,
1018 args.ignore_filename_regex)
Max Moroz1de68d72018-08-21 13:38:181019 component_mappings = None
1020 if not args.no_component_view:
1021 component_mappings = json.load(urllib2.urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371022
Max Moroz1de68d72018-08-21 13:38:181023 # Call prepare here.
1024 processor = coverage_utils.CoverageReportPostProcessor(
1025 OUTPUT_DIR,
1026 SRC_ROOT_PATH,
1027 per_file_summary_data,
1028 no_component_view=args.no_component_view,
1029 no_file_view=args.no_file_view,
1030 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371031
Max Moroz1de68d72018-08-21 13:38:181032 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541033
Abhishek Arya1ec832c2017-12-05 18:06:591034
Yuke Liao506e8822017-12-04 16:52:541035if __name__ == '__main__':
1036 sys.exit(Main())