blob: 126f48c7309d910b92208a24cda8c6ef02b30af9 [file] [log] [blame]
Yuke Liao506e8822017-12-04 16:52:541#!/usr/bin/python
2# Copyright 2017 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
Abhishek Arya1ec832c2017-12-05 18:06:595"""This script helps to generate code coverage report.
Yuke Liao506e8822017-12-04 16:52:546
Abhishek Arya1ec832c2017-12-05 18:06:597 It uses Clang Source-based Code Coverage -
8 https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
Yuke Liao506e8822017-12-04 16:52:549
Abhishek Arya16f059a2017-12-07 17:47:3210 In order to generate code coverage report, you need to first add
Yuke Liaoab9c44e2018-02-21 00:24:4011 "use_clang_coverage=true" and "is_component_build=false" GN flags to args.gn
12 file in your build output directory (e.g. out/coverage).
Yuke Liao506e8822017-12-04 16:52:5413
Abhishek Arya03911092018-05-21 16:42:3514 * Example usage:
Abhishek Arya1ec832c2017-12-05 18:06:5915
Max Moroza5a95272018-08-31 16:20:5516 gn gen out/coverage \\
Abhishek Arya2f261182019-04-24 17:06:4517 --args="use_clang_coverage=true is_component_build=false\\
18 is_debug=false dcheck_always_on=true"
Abhishek Arya16f059a2017-12-07 17:47:3219 gclient runhooks
Abhishek Arya1ec832c2017-12-05 18:06:5920 python tools/code_coverage/coverage.py crypto_unittests url_unittests \\
Abhishek Arya16f059a2017-12-07 17:47:3221 -b out/coverage -o out/report -c 'out/coverage/crypto_unittests' \\
22 -c 'out/coverage/url_unittests --gtest_filter=URLParser.PathURL' \\
23 -f url/ -f crypto/
Abhishek Arya1ec832c2017-12-05 18:06:5924
Abhishek Arya16f059a2017-12-07 17:47:3225 The command above builds crypto_unittests and url_unittests targets and then
26 runs them with specified command line arguments. For url_unittests, it only
27 runs the test URLParser.PathURL. The coverage report is filtered to include
28 only files and sub-directories under url/ and crypto/ directories.
Abhishek Arya1ec832c2017-12-05 18:06:5929
Yuke Liao545db322018-02-15 17:12:0130 If you want to run tests that try to draw to the screen but don't have a
31 display connected, you can run tests in headless mode with xvfb.
32
Abhishek Arya03911092018-05-21 16:42:3533 * Sample flow for running a test target with xvfb (e.g. unit_tests):
Yuke Liao545db322018-02-15 17:12:0134
35 python tools/code_coverage/coverage.py unit_tests -b out/coverage \\
36 -o out/report -c 'python testing/xvfb.py out/coverage/unit_tests'
37
Abhishek Arya1ec832c2017-12-05 18:06:5938 If you are building a fuzz target, you need to add "use_libfuzzer=true" GN
39 flag as well.
40
Abhishek Arya03911092018-05-21 16:42:3541 * Sample workflow for a fuzz target (e.g. pdfium_fuzzer):
Abhishek Arya1ec832c2017-12-05 18:06:5942
Abhishek Arya16f059a2017-12-07 17:47:3243 python tools/code_coverage/coverage.py pdfium_fuzzer \\
44 -b out/coverage -o out/report \\
Max Moroz13c23182018-11-17 00:23:2245 -c 'out/coverage/pdfium_fuzzer -runs=0 <corpus_dir>' \\
Abhishek Arya16f059a2017-12-07 17:47:3246 -f third_party/pdfium
Abhishek Arya1ec832c2017-12-05 18:06:5947
48 where:
49 <corpus_dir> - directory containing samples files for this format.
Max Moroz13c23182018-11-17 00:23:2250
51 To learn more about generating code coverage reports for fuzz targets, see
52 https://chromium.googlesource.com/chromium/src/+/master/testing/libfuzzer/efficient_fuzzer.md#Code-Coverage
Abhishek Arya1ec832c2017-12-05 18:06:5953
Abhishek Arya03911092018-05-21 16:42:3554 * Sample workflow for running Blink web tests:
55
56 python tools/code_coverage/coverage.py blink_tests \\
57 -wt -b out/coverage -o out/report -f third_party/blink
58
59 If you need to pass arguments to run_web_tests.py, use
60 -wt='arguments to run_web_tests.py e.g. test directories'
61
Abhishek Arya1ec832c2017-12-05 18:06:5962 For more options, please refer to tools/code_coverage/coverage.py -h.
Yuke Liao8e209fe82018-04-18 20:36:3863
64 For an overview of how code coverage works in Chromium, please refer to
Yuke Liao43bbbcd52019-06-21 19:34:5065 https://chromium.googlesource.com/chromium/src/+/master/docs/testing/code_coverage.md
Yuke Liao506e8822017-12-04 16:52:5466"""
67
68from __future__ import print_function
69
70import sys
71
72import argparse
Yuke Liaoea228d02018-01-05 19:10:3373import json
Yuke Liao481d3482018-01-29 19:17:1074import logging
Abhishek Arya03911092018-05-21 16:42:3575import multiprocessing
Yuke Liao506e8822017-12-04 16:52:5476import os
Yuke Liaob2926832018-03-02 17:34:2977import re
78import shlex
Max Moroz025d8952018-05-03 16:33:3479import shutil
Yuke Liao506e8822017-12-04 16:52:5480import subprocess
Yuke Liao506e8822017-12-04 16:52:5481import urllib2
82
Abhishek Arya1ec832c2017-12-05 18:06:5983sys.path.append(
84 os.path.join(
85 os.path.dirname(__file__), os.path.pardir, os.path.pardir, 'tools',
86 'clang', 'scripts'))
Hans Wennborg8ee64a12019-11-05 17:31:3087import update
Yuke Liao506e8822017-12-04 16:52:5488
Yuke Liaoea228d02018-01-05 19:10:3389sys.path.append(
90 os.path.join(
91 os.path.dirname(__file__), os.path.pardir, os.path.pardir,
92 'third_party'))
Yuke Liaoea228d02018-01-05 19:10:3393from collections import defaultdict
94
Max Moroz1de68d72018-08-21 13:38:1895import coverage_utils
Max Moroz1de68d72018-08-21 13:38:1896
Yuke Liao082e99632018-05-18 15:40:4097# Absolute path to the code coverage tools binary. These paths can be
98# overwritten by user specified coverage tool paths.
Hans Wennborg8ee64a12019-11-05 17:31:3099LLVM_BIN_DIR = os.path.join(update.LLVM_BUILD_DIR, 'bin')
Abhishek Arya1c97ea542018-05-10 03:53:19100LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
101LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:54102
Abhishek Arya03911092018-05-21 16:42:35103# Absolute path to the root of the checkout.
104SRC_ROOT_PATH = None
105
Yuke Liao506e8822017-12-04 16:52:54106# Build directory, the value is parsed from command line arguments.
107BUILD_DIR = None
108
109# Output directory for generated artifacts, the value is parsed from command
110# line arguemnts.
111OUTPUT_DIR = None
112
Yuke Liao506e8822017-12-04 16:52:54113# Name of the file extension for profraw data files.
114PROFRAW_FILE_EXTENSION = 'profraw'
115
116# Name of the final profdata file, and this file needs to be passed to
117# "llvm-cov" command in order to call "llvm-cov show" to inspect the
118# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48119PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
120
121# Name of the file with summary information generated by llvm-cov export.
122SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54123
124# Build arg required for generating code coverage data.
125CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
126
Max Moroz7c5354f2018-05-06 00:03:48127LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37128
129# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19130COMPONENT_MAPPING_URL = (
131 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37132
Yuke Liao80afff32018-03-07 01:26:20133# Caches the results returned by _GetBuildArgs, don't use this variable
134# directly, call _GetBuildArgs instead.
135_BUILD_ARGS = None
136
Abhishek Aryac19bc5ef2018-05-04 22:10:02137# Retry failed merges.
138MERGE_RETRIES = 3
139
Abhishek Aryad35de7e2018-05-10 22:23:04140# Message to guide user to file a bug when everything else fails.
141FILE_BUG_MESSAGE = (
142 'If it persists, please file a bug with the command you used, git revision '
143 'and args.gn config here: '
144 'https://bugs.chromium.org/p/chromium/issues/entry?'
Yuke Liao03c644072019-07-30 18:33:40145 'components=Infra%3ETest%3ECodeCoverage')
Abhishek Aryad35de7e2018-05-10 22:23:04146
Abhishek Aryabd0655d2018-05-21 19:55:24147# String to replace with actual llvm profile path.
148LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
149
Yuke Liaoea228d02018-01-05 19:10:33150
Yuke Liao082e99632018-05-18 15:40:40151def _ConfigureLLVMCoverageTools(args):
152 """Configures llvm coverage tools."""
153 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18154 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40155 global LLVM_COV_PATH
156 global LLVM_PROFDATA_PATH
157 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
158 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
159 else:
Hans Wennborg8ee64a12019-11-05 17:31:30160 update.UpdatePackage('coverage_tools')
Yuke Liao082e99632018-05-18 15:40:40161
162 coverage_tools_exist = (
163 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
164 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
165 'both \'%s\' and \'%s\' exist.') % (
166 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
167
Abhishek Arya2f261182019-04-24 17:06:45168
Abhishek Arya1c97ea542018-05-10 03:53:19169def _GetPathWithLLVMSymbolizerDir():
170 """Add llvm-symbolizer directory to path for symbolized stacks."""
171 path = os.getenv('PATH')
172 dirs = path.split(os.pathsep)
173 if LLVM_BIN_DIR in dirs:
174 return path
175
176 return path + os.pathsep + LLVM_BIN_DIR
177
178
Yuke Liaoc60b2d02018-03-02 21:40:43179def _GetTargetOS():
180 """Returns the target os specified in args.gn file.
181
182 Returns an empty string is target_os is not specified.
183 """
Yuke Liao80afff32018-03-07 01:26:20184 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43185 return build_args['target_os'] if 'target_os' in build_args else ''
186
187
Yuke Liaob2926832018-03-02 17:34:29188def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10189 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43190 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10191
192
Yuke Liaodd1ec0592018-02-02 01:26:37193def _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:59194 filters, ignore_filename_regex):
Yuke Liao506e8822017-12-04 16:52:54195 """Generates per file line-by-line coverage in html using 'llvm-cov show'.
196
197 For a file with absolute path /a/b/x.cc, a html report is generated as:
198 OUTPUT_DIR/coverage/a/b/x.cc.html. An index html file is also generated as:
199 OUTPUT_DIR/index.html.
200
201 Args:
202 binary_paths: A list of paths to the instrumented binaries.
203 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42204 filters: A list of directories and files to get coverage for.
Yuke Liao506e8822017-12-04 16:52:54205 """
Yuke Liao506e8822017-12-04 16:52:54206 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
207 # [[-object BIN]] [SOURCES]
208 # NOTE: For object files, the first one is specified as a positional argument,
209 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10210 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40211 '"llvm-cov show" command.')
Abhishek Arya1ec832c2017-12-05 18:06:59212 subprocess_cmd = [
213 LLVM_COV_PATH, 'show', '-format=html',
214 '-output-dir={}'.format(OUTPUT_DIR),
215 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
216 ]
217 subprocess_cmd.extend(
218 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29219 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18220 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17221 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42222 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59223 if ignore_filename_regex:
224 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
225
Yuke Liao506e8822017-12-04 16:52:54226 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34227
Abhishek Aryafb70b532018-05-06 17:47:40228 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54229
230
Max Moroz7c5354f2018-05-06 00:03:48231def _GetLogsDirectoryPath():
232 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18233 return os.path.join(
234 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48235
236
237def _GetProfdataFilePath():
238 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18239 return os.path.join(
240 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
241 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48242
243
244def _GetSummaryFilePath():
245 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18246 return os.path.join(
247 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
248 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33249
250
Yuke Liao506e8822017-12-04 16:52:54251def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
252 """Builds and runs target to generate the coverage profile data.
253
254 Args:
255 targets: A list of targets to build with coverage instrumentation.
256 commands: A list of commands used to run the targets.
257 jobs_count: Number of jobs to run in parallel for building. If None, a
258 default value is derived based on CPUs availability.
259
260 Returns:
261 A relative path to the generated profdata file.
262 """
263 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02264 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59265 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02266 coverage_profdata_file_path = (
267 _CreateCoverageProfileDataFromTargetProfDataFiles(
268 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54269
Abhishek Aryac19bc5ef2018-05-04 22:10:02270 for target_profdata_file_path in target_profdata_file_paths:
271 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52272
Abhishek Aryac19bc5ef2018-05-04 22:10:02273 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54274
275
276def _BuildTargets(targets, jobs_count):
277 """Builds target with Clang coverage instrumentation.
278
279 This function requires current working directory to be the root of checkout.
280
281 Args:
282 targets: A list of targets to build with coverage instrumentation.
283 jobs_count: Number of jobs to run in parallel for compilation. If None, a
284 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54285 """
Abhishek Aryafb70b532018-05-06 17:47:40286 logging.info('Building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54287
Max Moroz06576292019-01-03 19:22:52288 subprocess_cmd = ['autoninja', '-C', BUILD_DIR]
Yuke Liao506e8822017-12-04 16:52:54289 if jobs_count is not None:
290 subprocess_cmd.append('-j' + str(jobs_count))
291
292 subprocess_cmd.extend(targets)
293 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40294 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54295
296
Abhishek Aryac19bc5ef2018-05-04 22:10:02297def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54298 """Runs commands and returns the relative paths to the profraw data files.
299
300 Args:
301 targets: A list of targets built with coverage instrumentation.
302 commands: A list of commands used to run the targets.
303
304 Returns:
305 A list of relative paths to the generated profraw data files.
306 """
Abhishek Aryafb70b532018-05-06 17:47:40307 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10308
Yuke Liao506e8822017-12-04 16:52:54309 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18310 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
311 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54312 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18313 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48314
315 # Ensure that logs directory exists.
316 if not os.path.exists(_GetLogsDirectoryPath()):
317 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54318
Abhishek Aryac19bc5ef2018-05-04 22:10:02319 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10320
Yuke Liaod4a9865202018-01-12 23:17:52321 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54322 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48323 output_file_name = os.extsep.join([target + '_output', 'log'])
324 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10325
Abhishek Aryac19bc5ef2018-05-04 22:10:02326 profdata_file_path = None
327 for _ in xrange(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40328 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02329 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10330
Abhishek Aryac19bc5ef2018-05-04 22:10:02331 if _IsIOSCommand(command):
332 # On iOS platform, due to lack of write permissions, profraw files are
333 # generated outside of the OUTPUT_DIR, and the exact paths are contained
334 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35335 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02336 else:
337 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35338 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02339
340 profraw_file_paths = []
341 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57342 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Abhishek Aryac19bc5ef2018-05-04 22:10:02343 else:
Max Moroz1de68d72018-08-21 13:38:18344 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02345 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48346 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18347 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02348
349 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40350 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04351 'please make sure the binary exists, is properly instrumented and '
352 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02353
Yuke Liao9c2c70b2018-05-23 15:37:57354 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18355 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
356 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57357
Abhishek Aryac19bc5ef2018-05-04 22:10:02358 try:
359 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
360 target, profraw_file_paths)
361 break
362 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04363 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02364 finally:
365 # Remove profraw files now so that they are not used in next iteration.
366 for profraw_file_path in profraw_file_paths:
367 os.remove(profraw_file_path)
368
369 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04370 'Failed to merge target "%s" profraw files after %d retries. %s' %
371 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02372 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54373
Abhishek Aryafb70b532018-05-06 17:47:40374 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10375
Abhishek Aryac19bc5ef2018-05-04 22:10:02376 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54377
378
Abhishek Arya03911092018-05-21 16:42:35379def _GetEnvironmentVars(profraw_file_path):
380 """Return environment vars for subprocess, given a profraw file path."""
381 env = os.environ.copy()
382 env.update({
383 'LLVM_PROFILE_FILE': profraw_file_path,
384 'PATH': _GetPathWithLLVMSymbolizerDir()
385 })
386 return env
387
388
389def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10390 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52391 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01392 #
Max Morozd73e45f2018-04-24 18:32:47393 # "%p" expands out to the process ID. It's not used by this scripts due to:
394 # 1) If a target program spawns too many processess, it may exhaust all disk
395 # space available. For example, unit_tests writes thousands of .profraw
396 # files each of size 1GB+.
397 # 2) If a target binary uses shared libraries, coverage profile data for them
398 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01399 #
Yuke Liaod4a9865202018-01-12 23:17:52400 # "%Nm" expands out to the instrumented binary's signature. When this pattern
401 # is specified, the runtime creates a pool of N raw profiles which are used
402 # for on-line profile merging. The runtime takes care of selecting a raw
403 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52404 # N must be between 1 and 9. The merge pool specifier can only occur once per
405 # filename pattern.
406 #
Max Morozd73e45f2018-04-24 18:32:47407 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01408 #
Max Morozd73e45f2018-04-24 18:32:47409 # For other cases, "%4m" is chosen as it creates some level of parallelism,
410 # but it's not too big to consume too much computing resource or disk space.
411 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59412 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01413 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18414 expected_profraw_file_path = os.path.join(
415 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
416 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24417 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
418 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54419
Yuke Liaoa0c8c2f2018-02-28 20:14:10420 try:
Max Moroz7c5354f2018-05-06 00:03:48421 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35422 with open(output_file_path, 'wb') as output_file_handle:
423 subprocess.check_call(
424 shlex.split(command),
425 stdout=output_file_handle,
426 stderr=subprocess.STDOUT,
427 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10428 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35429 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10430
Abhishek Arya03911092018-05-21 16:42:35431 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10432
433
Yuke Liao27349c92018-03-22 21:10:01434def _IsFuzzerTarget(target):
435 """Returns true if the target is a fuzzer target."""
436 build_args = _GetBuildArgs()
437 use_libfuzzer = ('use_libfuzzer' in build_args and
438 build_args['use_libfuzzer'] == 'true')
439 return use_libfuzzer and target.endswith('_fuzzer')
440
441
Abhishek Arya03911092018-05-21 16:42:35442def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10443 """Runs a single iOS command and generates a profraw data file.
444
445 iOS application doesn't have write access to folders outside of the app, so
446 it's impossible to instruct the app to flush the profraw data file to the
447 desired location. The profraw data file will be generated somewhere within the
448 application's Documents folder, and the full path can be obtained by parsing
449 the output.
450 """
Yuke Liaob2926832018-03-02 17:34:29451 assert _IsIOSCommand(command)
452
453 # After running tests, iossim generates a profraw data file, it won't be
454 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
455 # checkout.
456 iossim_profraw_file_path = os.path.join(
457 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24458 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
459 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10460
461 try:
Abhishek Arya03911092018-05-21 16:42:35462 with open(output_file_path, 'wb') as output_file_handle:
463 subprocess.check_call(
464 shlex.split(command),
465 stdout=output_file_handle,
466 stderr=subprocess.STDOUT,
467 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10468 except subprocess.CalledProcessError as e:
469 # iossim emits non-zero return code even if tests run successfully, so
470 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35471 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10472
Abhishek Arya03911092018-05-21 16:42:35473 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10474
475
476def _GetProfrawDataFileByParsingOutput(output):
477 """Returns the path to the profraw data file obtained by parsing the output.
478
479 The output of running the test target has no format, but it is guaranteed to
480 have a single line containing the path to the generated profraw data file.
481 NOTE: This should only be called when target os is iOS.
482 """
Yuke Liaob2926832018-03-02 17:34:29483 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10484
Yuke Liaob2926832018-03-02 17:34:29485 output_by_lines = ''.join(output).splitlines()
486 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10487
488 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29489 result = profraw_file_pattern.match(line)
490 if result:
491 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10492
493 assert False, ('No profraw data file was generated, did you call '
494 'coverage_util::ConfigureCoverageReportPath() in test setup? '
495 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54496
497
Abhishek Aryac19bc5ef2018-05-04 22:10:02498def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
499 """Returns a relative path to coverage profdata file by merging target
500 profdata files.
Yuke Liao506e8822017-12-04 16:52:54501
502 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02503 profdata_file_paths: A list of relative paths to the profdata data files
504 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54505
506 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02507 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54508
509 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02510 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54511 """
Abhishek Aryafb70b532018-05-06 17:47:40512 logging.info('Creating the coverage profile data file.')
513 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48514 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54515 try:
Abhishek Arya1ec832c2017-12-05 18:06:59516 subprocess_cmd = [
517 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
518 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02519 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01520
521 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18522 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02523 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04524 logging.error(
525 'Failed to merge target profdata files to create coverage profdata. %s',
526 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02527 raise error
528
Abhishek Aryafb70b532018-05-06 17:47:40529 logging.debug('Finished merging target profdata files.')
530 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02531 profdata_file_path)
532 return profdata_file_path
533
534
535def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
536 """Returns a relative path to target profdata file by merging target
537 profraw files.
538
539 Args:
540 profraw_file_paths: A list of relative paths to the profdata data files
541 that are to be merged.
542
543 Returns:
544 A relative path to the merged coverage profdata file.
545
546 Raises:
547 CalledProcessError: An error occurred merging profdata files.
548 """
Abhishek Aryafb70b532018-05-06 17:47:40549 logging.info('Creating target profile data file.')
550 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02551 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
552
553 try:
554 subprocess_cmd = [
555 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
556 ]
Yuke Liao506e8822017-12-04 16:52:54557 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01558
559 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18560 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54561 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04562 logging.error(
563 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54564 raise error
565
Abhishek Aryafb70b532018-05-06 17:47:40566 logging.debug('Finished merging target profraw files.')
567 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10568 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54569 return profdata_file_path
570
571
Yuke Liao0e4c8682018-04-18 21:06:59572def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
573 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33574 """Generates per file coverage summary using "llvm-cov export" command."""
575 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
576 # [[-object BIN]] [SOURCES].
577 # NOTE: For object files, the first one is specified as a positional argument,
578 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10579 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40580 'export -summary-only" command.')
Yuke Liaoea228d02018-01-05 19:10:33581 subprocess_cmd = [
582 LLVM_COV_PATH, 'export', '-summary-only',
583 '-instr-profile=' + profdata_file_path, binary_paths[0]
584 ]
585 subprocess_cmd.extend(
586 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29587 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33588 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59589 if ignore_filename_regex:
590 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33591
Max Moroz7c5354f2018-05-06 00:03:48592 export_output = subprocess.check_output(subprocess_cmd)
593
594 # Write output on the disk to be used by code coverage bot.
595 with open(_GetSummaryFilePath(), 'w') as f:
596 f.write(export_output)
597
Max Moroz1de68d72018-08-21 13:38:18598 return export_output
Yuke Liaoea228d02018-01-05 19:10:33599
600
Yuke Liaob2926832018-03-02 17:34:29601def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
602 """Appends -arch arguments to the command list if it's ios platform.
603
604 iOS binaries are universal binaries, and require specifying the architecture
605 to use, and one architecture needs to be specified for each binary.
606 """
607 if _IsIOS():
608 cmd_list.extend(['-arch=x86_64'] * num_archs)
609
610
Yuke Liao506e8822017-12-04 16:52:54611def _GetBinaryPath(command):
612 """Returns a relative path to the binary to be run by the command.
613
Yuke Liao545db322018-02-15 17:12:01614 Currently, following types of commands are supported (e.g. url_unittests):
615 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
616 2. Use xvfb.
617 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
618 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37619 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
620 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10621 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37622 <iossim_arguments> -c <app_arguments>
623 out/Coverage-iphonesimulator/url_unittests.app"
624
Yuke Liao506e8822017-12-04 16:52:54625 Args:
626 command: A command used to run a target.
627
628 Returns:
629 A relative path to the binary.
630 """
Yuke Liao545db322018-02-15 17:12:01631 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
632
Yuke Liaob2926832018-03-02 17:34:29633 command_parts = shlex.split(command)
Yuke Liao545db322018-02-15 17:12:01634 if os.path.basename(command_parts[0]) == 'python':
635 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40636 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01637 return command_parts[2]
638
639 if os.path.basename(command_parts[0]) == xvfb_script_name:
640 return command_parts[1]
641
Yuke Liaob2926832018-03-02 17:34:29642 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10643 # For a given application bundle, the binary resides in the bundle and has
644 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02645 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10646 app_name = os.path.splitext(os.path.basename(app_path))[0]
647 return os.path.join(app_path, app_name)
648
Yuke Liaob2926832018-03-02 17:34:29649 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54650
651
Yuke Liaob2926832018-03-02 17:34:29652def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10653 """Returns true if command is used to run tests on iOS platform."""
Yuke Liaob2926832018-03-02 17:34:29654 return os.path.basename(shlex.split(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10655
656
Yuke Liao95d13d72017-12-07 18:18:50657def _VerifyTargetExecutablesAreInBuildDirectory(commands):
658 """Verifies that the target executables specified in the commands are inside
659 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54660 for command in commands:
661 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18662 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35663 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50664 'Target executable "%s" in command: "%s" is outside of '
665 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54666
667
668def _ValidateBuildingWithClangCoverage():
669 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20670 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54671
672 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
673 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59674 assert False, ('\'{} = true\' is required in args.gn.'
675 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54676
677
Yuke Liaoc60b2d02018-03-02 21:40:43678def _ValidateCurrentPlatformIsSupported():
679 """Asserts that this script suports running on the current platform"""
680 target_os = _GetTargetOS()
681 if target_os:
682 current_platform = target_os
683 else:
Max Moroz1de68d72018-08-21 13:38:18684 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43685
686 assert current_platform in [
687 'linux', 'mac', 'chromeos', 'ios'
688 ], ('Coverage is only supported on linux, mac, chromeos and ios.')
689
690
Yuke Liao80afff32018-03-07 01:26:20691def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54692 """Parses args.gn file and returns results as a dictionary.
693
694 Returns:
695 A dictionary representing the build args.
696 """
Yuke Liao80afff32018-03-07 01:26:20697 global _BUILD_ARGS
698 if _BUILD_ARGS is not None:
699 return _BUILD_ARGS
700
701 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54702 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
703 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
704 'missing args.gn file.' % BUILD_DIR)
705 with open(build_args_path) as build_args_file:
706 build_args_lines = build_args_file.readlines()
707
Yuke Liao506e8822017-12-04 16:52:54708 for build_arg_line in build_args_lines:
709 build_arg_without_comments = build_arg_line.split('#')[0]
710 key_value_pair = build_arg_without_comments.split('=')
711 if len(key_value_pair) != 2:
712 continue
713
714 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43715
716 # Values are wrapped within a pair of double-quotes, so remove the leading
717 # and trailing double-quotes.
718 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20719 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54720
Yuke Liao80afff32018-03-07 01:26:20721 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54722
723
Abhishek Arya16f059a2017-12-07 17:47:32724def _VerifyPathsAndReturnAbsolutes(paths):
725 """Verifies that the paths specified in |paths| exist and returns absolute
726 versions.
Yuke Liao66da1732017-12-05 22:19:42727
728 Args:
729 paths: A list of files or directories.
730 """
Abhishek Arya16f059a2017-12-07 17:47:32731 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42732 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32733 absolute_path = os.path.join(SRC_ROOT_PATH, path)
734 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
735
736 absolute_paths.append(absolute_path)
737
738 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42739
740
Abhishek Arya64636af2018-05-04 14:42:13741def _GetBinaryPathsFromTargets(targets, build_dir):
742 """Return binary paths from target names."""
743 # FIXME: Derive output binary from target build definitions rather than
744 # assuming that it is always the same name.
745 binary_paths = []
746 for target in targets:
747 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18748 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13749 binary_path += '.exe'
750
751 if os.path.exists(binary_path):
752 binary_paths.append(binary_path)
753 else:
754 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40755 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13756 os.path.basename(binary_path))
757
758 return binary_paths
759
760
Abhishek Arya03911092018-05-21 16:42:35761def _GetCommandForWebTests(arguments):
762 """Return command to run for blink web tests."""
763 command_list = [
764 'python', 'testing/xvfb.py', 'python',
765 'third_party/blink/tools/run_web_tests.py',
766 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24767 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Robert Ma339fc472018-12-14 21:22:42768 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Abhishek Arya03911092018-05-21 16:42:35769 '--child-processes=%d' % max(1, int(multiprocessing.cpu_count() / 2)),
Abhishek Aryabd0655d2018-05-21 19:55:24770 '--disable-breakpad', '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35771 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
772 ]
773 if arguments.strip():
774 command_list.append(arguments)
775 return ' '.join(command_list)
776
777
778def _GetBinaryPathForWebTests():
779 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18780 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35781 if host_platform == 'win':
782 return os.path.join(BUILD_DIR, 'content_shell.exe')
783 elif host_platform == 'linux':
784 return os.path.join(BUILD_DIR, 'content_shell')
785 elif host_platform == 'mac':
786 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
787 'Content Shell')
788 else:
789 assert False, 'This platform is not supported for web tests.'
790
791
Abhishek Aryae5811afa2018-05-24 03:56:01792def _SetupOutputDir():
793 """Setup output directory."""
794 if os.path.exists(OUTPUT_DIR):
795 shutil.rmtree(OUTPUT_DIR)
796
797 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18798 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01799
800
Yuke Liaoabfbba42019-06-11 16:03:59801def _SetMacXcodePath():
802 """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
803 if sys.platform != 'darwin':
804 return
805
806 xcode_path = os.path.join(SRC_ROOT_PATH, 'build', 'mac_files', 'Xcode.app')
807 if os.path.exists(xcode_path):
808 os.environ['DEVELOPER_DIR'] = xcode_path
809
810
Yuke Liao506e8822017-12-04 16:52:54811def _ParseCommandArguments():
812 """Adds and parses relevant arguments for tool comands.
813
814 Returns:
815 A dictionary representing the arguments.
816 """
817 arg_parser = argparse.ArgumentParser()
818 arg_parser.usage = __doc__
819
Abhishek Arya1ec832c2017-12-05 18:06:59820 arg_parser.add_argument(
821 '-b',
822 '--build-dir',
823 type=str,
824 required=True,
825 help='The build directory, the path needs to be relative to the root of '
826 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54827
Abhishek Arya1ec832c2017-12-05 18:06:59828 arg_parser.add_argument(
829 '-o',
830 '--output-dir',
831 type=str,
832 required=True,
833 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54834
Abhishek Arya1ec832c2017-12-05 18:06:59835 arg_parser.add_argument(
836 '-c',
837 '--command',
838 action='append',
Abhishek Arya64636af2018-05-04 14:42:13839 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59840 help='Commands used to run test targets, one test target needs one and '
841 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13842 'current working directory is the root of the checkout. This option is '
843 'incompatible with -p/--profdata-file option.')
844
845 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35846 '-wt',
847 '--web-tests',
848 nargs='?',
849 type=str,
850 const=' ',
851 required=False,
852 help='Run blink web tests. Support passing arguments to run_web_tests.py')
853
854 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13855 '-p',
856 '--profdata-file',
857 type=str,
858 required=False,
859 help='Path to profdata file to use for generating code coverage reports. '
860 'This can be useful if you generated the profdata file seperately in '
861 'your own test harness. This option is ignored if run command(s) are '
862 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54863
Abhishek Arya1ec832c2017-12-05 18:06:59864 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42865 '-f',
866 '--filters',
867 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32868 required=False,
Yuke Liao66da1732017-12-05 22:19:42869 help='Directories or files to get code coverage for, and all files under '
870 'the directories are included recursively.')
871
872 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59873 '-i',
874 '--ignore-filename-regex',
875 type=str,
876 help='Skip source code files with file paths that match the given '
877 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
878 'to exclude files in third_party/ and out/ folders from the report.')
879
880 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32881 '--no-file-view',
882 action='store_true',
883 help='Don\'t generate the file view in the coverage report. When there '
884 'are large number of html files, the file view becomes heavy and may '
885 'cause the browser to freeze, and this argument comes handy.')
886
887 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18888 '--no-component-view',
889 action='store_true',
890 help='Don\'t generate the component view in the coverage report.')
891
892 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:40893 '--coverage-tools-dir',
894 type=str,
895 help='Path of the directory where LLVM coverage tools (llvm-cov, '
896 'llvm-profdata) exist. This should be only needed if you are testing '
897 'against a custom built clang revision. Otherwise, we pick coverage '
898 'tools automatically from your current source checkout.')
899
900 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:59901 '-j',
902 '--jobs',
903 type=int,
904 default=None,
905 help='Run N jobs to build in parallel. If not specified, a default value '
Max Moroz06576292019-01-03 19:22:52906 'will be derived based on CPUs and goma availability. Please refer to '
907 '\'autoninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:54908
Abhishek Arya1ec832c2017-12-05 18:06:59909 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:10910 '-v',
911 '--verbose',
912 action='store_true',
913 help='Prints additional output for diagnostics.')
914
915 arg_parser.add_argument(
916 '-l', '--log_file', type=str, help='Redirects logs to a file.')
917
918 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:02919 'targets',
920 nargs='+',
921 help='The names of the test targets to run. If multiple run commands are '
922 'specified using the -c/--command option, then the order of targets and '
923 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:54924
925 args = arg_parser.parse_args()
926 return args
927
928
929def Main():
930 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:40931 # Setup coverage binaries even when script is called with empty params. This
932 # is used by coverage bot for initial setup.
933 if len(sys.argv) == 1:
Hans Wennborg8ee64a12019-11-05 17:31:30934 update.UpdatePackage('coverage_tools')
Yuke Liao082e99632018-05-18 15:40:40935 print(__doc__)
936 return
937
Abhishek Arya64636af2018-05-04 14:42:13938 # Change directory to source root to aid in relative paths calculations.
Abhishek Arya03911092018-05-21 16:42:35939 global SRC_ROOT_PATH
Max Moroz1de68d72018-08-21 13:38:18940 SRC_ROOT_PATH = coverage_utils.GetFullPath(
Abhishek Arya03911092018-05-21 16:42:35941 os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
Abhishek Arya64636af2018-05-04 14:42:13942 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:11943
Yuke Liao506e8822017-12-04 16:52:54944 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:18945 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:40946 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:13947
Yuke Liao506e8822017-12-04 16:52:54948 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:18949 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:01950
Yuke Liao506e8822017-12-04 16:52:54951 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:18952 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:54953
Abhishek Arya03911092018-05-21 16:42:35954 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:13955 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:35956 'provide prof-data file as input using -p/--profdata-file option OR '
957 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:43958
Abhishek Arya64636af2018-05-04 14:42:13959 assert not args.command or (len(args.targets) == len(args.command)), (
960 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:43961
Abhishek Arya1ec832c2017-12-05 18:06:59962 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:40963 'Build directory: "%s" doesn\'t exist. '
964 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:13965
Yuke Liaoc60b2d02018-03-02 21:40:43966 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:54967 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:32968
969 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:42970 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:32971 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:42972
Abhishek Aryae5811afa2018-05-24 03:56:01973 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:54974
Abhishek Arya03911092018-05-21 16:42:35975 # Get .profdata file and list of binary paths.
976 if args.web_tests:
977 commands = [_GetCommandForWebTests(args.web_tests)]
978 profdata_file_path = _CreateCoverageProfileDataForTargets(
979 args.targets, commands, args.jobs)
980 binary_paths = [_GetBinaryPathForWebTests()]
981 elif args.command:
982 for i in range(len(args.command)):
983 assert not 'run_web_tests.py' in args.command[i], (
984 'run_web_tests.py is not supported via --command argument. '
985 'Please use --run-web-tests argument instead.')
986
Abhishek Arya64636af2018-05-04 14:42:13987 # A list of commands are provided. Run them to generate profdata file, and
988 # create a list of binary paths from parsing commands.
989 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
990 profdata_file_path = _CreateCoverageProfileDataForTargets(
991 args.targets, args.command, args.jobs)
992 binary_paths = [_GetBinaryPath(command) for command in args.command]
993 else:
994 # An input prof-data file is already provided. Just calculate binary paths.
995 profdata_file_path = args.profdata_file
996 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:33997
Erik Chen283b92c72019-07-22 16:37:39998 # If the checkout uses the hermetic xcode binaries, then otool must be
999 # directly invoked. The indirection via /usr/bin/otool won't work unless
1000 # there's an actual system install of Xcode.
1001 otool_path = None
1002 if sys.platform == 'darwin':
1003 hermetic_otool_path = os.path.join(
1004 SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents',
1005 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin',
1006 'otool')
1007 if os.path.exists(hermetic_otool_path):
1008 otool_path = hermetic_otool_path
Max Moroz1de68d72018-08-21 13:38:181009 binary_paths.extend(
Erik Chen283b92c72019-07-22 16:37:391010 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
Abhishek Arya78120bc2018-05-07 20:53:541011
Yuke Liao481d3482018-01-29 19:17:101012 logging.info('Generating code coverage report in html (this can take a while '
Abhishek Aryafb70b532018-05-06 17:47:401013 'depending on size of target!).')
Max Moroz1de68d72018-08-21 13:38:181014 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591015 binary_paths, profdata_file_path, absolute_filter_paths,
1016 args.ignore_filename_regex)
Yuke Liaodd1ec0592018-02-02 01:26:371017 _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:591018 absolute_filter_paths,
1019 args.ignore_filename_regex)
Max Moroz1de68d72018-08-21 13:38:181020 component_mappings = None
1021 if not args.no_component_view:
1022 component_mappings = json.load(urllib2.urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371023
Max Moroz1de68d72018-08-21 13:38:181024 # Call prepare here.
1025 processor = coverage_utils.CoverageReportPostProcessor(
1026 OUTPUT_DIR,
1027 SRC_ROOT_PATH,
1028 per_file_summary_data,
1029 no_component_view=args.no_component_view,
1030 no_file_view=args.no_file_view,
1031 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371032
Max Moroz1de68d72018-08-21 13:38:181033 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541034
Abhishek Arya1ec832c2017-12-05 18:06:591035
Yuke Liao506e8822017-12-04 16:52:541036if __name__ == '__main__':
1037 sys.exit(Main())