blob: a8ee5029bfec3428e1c082cb418b3793a2da0fae [file] [log] [blame]
Dirk Pranke4d164bb2021-03-24 06:52:401#!/usr/bin/env python
Yuke Liao506e8822017-12-04 16:52:542# 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
John Palmerab8812a2021-05-21 17:03:4352 https://chromium.googlesource.com/chromium/src/+/main/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
John Palmerab8812a2021-05-21 17:03:4365 https://chromium.googlesource.com/chromium/src/+/main/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
Sajjad Mirza0b96e002020-11-10 19:32:5577import platform
Yuke Liaob2926832018-03-02 17:34:2978import re
79import shlex
Max Moroz025d8952018-05-03 16:33:3480import shutil
Yuke Liao506e8822017-12-04 16:52:5481import subprocess
Yuke Liao506e8822017-12-04 16:52:5482import urllib2
83
Choongwoo Hanbd1aa952021-06-09 22:25:3884import six
85
86if six.PY2:
87 from urllib2 import urlopen
88else:
89 from urllib.request import urlopen
90
Abhishek Arya1ec832c2017-12-05 18:06:5991sys.path.append(
92 os.path.join(
Yuke Liaoea228d02018-01-05 19:10:3393 os.path.dirname(__file__), os.path.pardir, os.path.pardir,
94 'third_party'))
Yuke Liaoea228d02018-01-05 19:10:3395from collections import defaultdict
96
Max Moroz1de68d72018-08-21 13:38:1897import coverage_utils
98
Yuke Liao082e99632018-05-18 15:40:4099# Absolute path to the code coverage tools binary. These paths can be
100# overwritten by user specified coverage tool paths.
pasthanab37d5bfd2020-05-28 12:18:31101# Absolute path to the root of the checkout.
102SRC_ROOT_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)),
103 os.path.pardir, os.path.pardir)
104LLVM_BIN_DIR = os.path.join(
105 os.path.join(SRC_ROOT_PATH, 'third_party', 'llvm-build', 'Release+Asserts'),
106 'bin')
Abhishek Arya1c97ea542018-05-10 03:53:19107LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
108LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:54109
Abhishek Arya03911092018-05-21 16:42:35110
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
Yuke Liao506e8822017-12-04 16:52:54118# Name of the file extension for profraw data files.
119PROFRAW_FILE_EXTENSION = 'profraw'
120
121# Name of the final profdata file, and this file needs to be passed to
122# "llvm-cov" command in order to call "llvm-cov show" to inspect the
123# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48124PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
125
126# Name of the file with summary information generated by llvm-cov export.
127SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54128
129# Build arg required for generating code coverage data.
130CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
131
Max Moroz7c5354f2018-05-06 00:03:48132LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37133
134# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19135COMPONENT_MAPPING_URL = (
136 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37137
Yuke Liao80afff32018-03-07 01:26:20138# Caches the results returned by _GetBuildArgs, don't use this variable
139# directly, call _GetBuildArgs instead.
140_BUILD_ARGS = None
141
Abhishek Aryac19bc5ef2018-05-04 22:10:02142# Retry failed merges.
143MERGE_RETRIES = 3
144
Abhishek Aryad35de7e2018-05-10 22:23:04145# Message to guide user to file a bug when everything else fails.
146FILE_BUG_MESSAGE = (
147 'If it persists, please file a bug with the command you used, git revision '
148 'and args.gn config here: '
149 'https://bugs.chromium.org/p/chromium/issues/entry?'
Yuke Liao03c644072019-07-30 18:33:40150 'components=Infra%3ETest%3ECodeCoverage')
Abhishek Aryad35de7e2018-05-10 22:23:04151
Abhishek Aryabd0655d2018-05-21 19:55:24152# String to replace with actual llvm profile path.
153LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
154
Yuke Liao082e99632018-05-18 15:40:40155def _ConfigureLLVMCoverageTools(args):
156 """Configures llvm coverage tools."""
157 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18158 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40159 global LLVM_COV_PATH
160 global LLVM_PROFDATA_PATH
161 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
162 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
163 else:
Choongwoo Hanbd1aa952021-06-09 22:25:38164 subprocess.check_call([
165 'python', 'tools/clang/scripts/update.py', '--package', 'coverage_tools'
166 ])
Brent McBrideb25b177a42020-05-11 18:13:06167
168 if coverage_utils.GetHostPlatform() == 'win':
169 LLVM_COV_PATH += '.exe'
170 LLVM_PROFDATA_PATH += '.exe'
Yuke Liao082e99632018-05-18 15:40:40171
172 coverage_tools_exist = (
173 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
174 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
175 'both \'%s\' and \'%s\' exist.') % (
176 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
177
Abhishek Arya2f261182019-04-24 17:06:45178
Abhishek Arya1c97ea542018-05-10 03:53:19179def _GetPathWithLLVMSymbolizerDir():
180 """Add llvm-symbolizer directory to path for symbolized stacks."""
181 path = os.getenv('PATH')
182 dirs = path.split(os.pathsep)
183 if LLVM_BIN_DIR in dirs:
184 return path
185
186 return path + os.pathsep + LLVM_BIN_DIR
187
188
Yuke Liaoc60b2d02018-03-02 21:40:43189def _GetTargetOS():
190 """Returns the target os specified in args.gn file.
191
192 Returns an empty string is target_os is not specified.
193 """
Yuke Liao80afff32018-03-07 01:26:20194 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43195 return build_args['target_os'] if 'target_os' in build_args else ''
196
197
Ben Joyce88282362021-01-29 23:53:31198def _IsAndroid():
199 """Returns true if the target_os specified in args.gn file is android"""
200 return _GetTargetOS() == 'android'
201
202
Yuke Liaob2926832018-03-02 17:34:29203def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10204 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43205 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10206
207
Sahel Sharify38cabdc2020-01-16 00:40:01208def _GeneratePerFileLineByLineCoverageInFormat(binary_paths, profdata_file_path,
209 filters, ignore_filename_regex,
210 output_format):
211 """Generates per file line-by-line coverage in html or text using
212 'llvm-cov show'.
Yuke Liao506e8822017-12-04 16:52:54213
Sahel Sharify38cabdc2020-01-16 00:40:01214 For a file with absolute path /a/b/x.cc, a html/txt report is generated as:
215 OUTPUT_DIR/coverage/a/b/x.cc.[html|txt]. For html format, an index html file
216 is also generated as: OUTPUT_DIR/index.html.
Yuke Liao506e8822017-12-04 16:52:54217
218 Args:
219 binary_paths: A list of paths to the instrumented binaries.
220 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42221 filters: A list of directories and files to get coverage for.
Sahel Sharify38cabdc2020-01-16 00:40:01222 ignore_filename_regex: A regular expression for skipping source code files
223 with certain file paths.
224 output_format: The output format of generated report files.
Yuke Liao506e8822017-12-04 16:52:54225 """
Yuke Liao506e8822017-12-04 16:52:54226 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
227 # [[-object BIN]] [SOURCES]
228 # NOTE: For object files, the first one is specified as a positional argument,
229 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10230 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40231 '"llvm-cov show" command.')
Sahel Sharify38cabdc2020-01-16 00:40:01232
Abhishek Arya1ec832c2017-12-05 18:06:59233 subprocess_cmd = [
Sahel Sharify38cabdc2020-01-16 00:40:01234 LLVM_COV_PATH, 'show', '-format={}'.format(output_format),
Choongwoo Han56752522021-06-10 17:38:34235 '-compilation-dir={}'.format(BUILD_DIR),
Abhishek Arya1ec832c2017-12-05 18:06:59236 '-output-dir={}'.format(OUTPUT_DIR),
237 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
238 ]
239 subprocess_cmd.extend(
240 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29241 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18242 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17243 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42244 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59245 if ignore_filename_regex:
246 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
247
Yuke Liao506e8822017-12-04 16:52:54248 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34249
Abhishek Aryafb70b532018-05-06 17:47:40250 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54251
252
Max Moroz7c5354f2018-05-06 00:03:48253def _GetLogsDirectoryPath():
254 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18255 return os.path.join(
256 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48257
258
259def _GetProfdataFilePath():
260 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18261 return os.path.join(
262 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
263 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48264
265
266def _GetSummaryFilePath():
267 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18268 return os.path.join(
269 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
270 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33271
272
Yuke Liao506e8822017-12-04 16:52:54273def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
274 """Builds and runs target to generate the coverage profile data.
275
276 Args:
277 targets: A list of targets to build with coverage instrumentation.
278 commands: A list of commands used to run the targets.
279 jobs_count: Number of jobs to run in parallel for building. If None, a
280 default value is derived based on CPUs availability.
281
282 Returns:
283 A relative path to the generated profdata file.
284 """
285 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02286 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59287 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02288 coverage_profdata_file_path = (
289 _CreateCoverageProfileDataFromTargetProfDataFiles(
290 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54291
Abhishek Aryac19bc5ef2018-05-04 22:10:02292 for target_profdata_file_path in target_profdata_file_paths:
293 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52294
Abhishek Aryac19bc5ef2018-05-04 22:10:02295 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54296
297
298def _BuildTargets(targets, jobs_count):
299 """Builds target with Clang coverage instrumentation.
300
301 This function requires current working directory to be the root of checkout.
302
303 Args:
304 targets: A list of targets to build with coverage instrumentation.
305 jobs_count: Number of jobs to run in parallel for compilation. If None, a
306 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54307 """
Abhishek Aryafb70b532018-05-06 17:47:40308 logging.info('Building %s.', str(targets))
Brent McBrideb25b177a42020-05-11 18:13:06309 autoninja = 'autoninja'
310 if coverage_utils.GetHostPlatform() == 'win':
311 autoninja += '.bat'
Yuke Liao506e8822017-12-04 16:52:54312
Brent McBrideb25b177a42020-05-11 18:13:06313 subprocess_cmd = [autoninja, '-C', BUILD_DIR]
Yuke Liao506e8822017-12-04 16:52:54314 if jobs_count is not None:
315 subprocess_cmd.append('-j' + str(jobs_count))
316
317 subprocess_cmd.extend(targets)
318 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40319 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54320
321
Abhishek Aryac19bc5ef2018-05-04 22:10:02322def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54323 """Runs commands and returns the relative paths to the profraw data files.
324
325 Args:
326 targets: A list of targets built with coverage instrumentation.
327 commands: A list of commands used to run the targets.
328
329 Returns:
330 A list of relative paths to the generated profraw data files.
331 """
Abhishek Aryafb70b532018-05-06 17:47:40332 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10333
Yuke Liao506e8822017-12-04 16:52:54334 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18335 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
336 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54337 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18338 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48339
340 # Ensure that logs directory exists.
341 if not os.path.exists(_GetLogsDirectoryPath()):
342 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54343
Abhishek Aryac19bc5ef2018-05-04 22:10:02344 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10345
Yuke Liaod4a9865202018-01-12 23:17:52346 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54347 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48348 output_file_name = os.extsep.join([target + '_output', 'log'])
349 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10350
Abhishek Aryac19bc5ef2018-05-04 22:10:02351 profdata_file_path = None
352 for _ in xrange(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40353 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02354 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10355
Abhishek Aryac19bc5ef2018-05-04 22:10:02356 if _IsIOSCommand(command):
357 # On iOS platform, due to lack of write permissions, profraw files are
358 # generated outside of the OUTPUT_DIR, and the exact paths are contained
359 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35360 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02361 else:
362 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35363 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02364
365 profraw_file_paths = []
366 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57367 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Ben Joyce88282362021-01-29 23:53:31368 elif _IsAndroid():
369 android_coverage_dir = os.path.join(BUILD_DIR, 'coverage')
370 for r, _, files in os.walk(android_coverage_dir):
371 for f in files:
372 if f.endswith(PROFRAW_FILE_EXTENSION):
373 profraw_file_paths.append(os.path.join(r, f))
Abhishek Aryac19bc5ef2018-05-04 22:10:02374 else:
Max Moroz1de68d72018-08-21 13:38:18375 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02376 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48377 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18378 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02379
380 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40381 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04382 'please make sure the binary exists, is properly instrumented and '
383 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02384
Yuke Liao9c2c70b2018-05-23 15:37:57385 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18386 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
387 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57388
Abhishek Aryac19bc5ef2018-05-04 22:10:02389 try:
390 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
391 target, profraw_file_paths)
392 break
393 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04394 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02395 finally:
396 # Remove profraw files now so that they are not used in next iteration.
397 for profraw_file_path in profraw_file_paths:
398 os.remove(profraw_file_path)
399
400 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04401 'Failed to merge target "%s" profraw files after %d retries. %s' %
402 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02403 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54404
Abhishek Aryafb70b532018-05-06 17:47:40405 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10406
Abhishek Aryac19bc5ef2018-05-04 22:10:02407 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54408
409
Abhishek Arya03911092018-05-21 16:42:35410def _GetEnvironmentVars(profraw_file_path):
411 """Return environment vars for subprocess, given a profraw file path."""
412 env = os.environ.copy()
413 env.update({
414 'LLVM_PROFILE_FILE': profraw_file_path,
415 'PATH': _GetPathWithLLVMSymbolizerDir()
416 })
417 return env
418
419
Sajjad Mirza0b96e002020-11-10 19:32:55420def _SplitCommand(command):
421 """Split a command string into parts in a platform-specific way."""
422 if coverage_utils.GetHostPlatform() == 'win':
423 return command.split()
424 return shlex.split(command)
425
426
Abhishek Arya03911092018-05-21 16:42:35427def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10428 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52429 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01430 #
Max Morozd73e45f2018-04-24 18:32:47431 # "%p" expands out to the process ID. It's not used by this scripts due to:
432 # 1) If a target program spawns too many processess, it may exhaust all disk
433 # space available. For example, unit_tests writes thousands of .profraw
434 # files each of size 1GB+.
435 # 2) If a target binary uses shared libraries, coverage profile data for them
436 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01437 #
Yuke Liaod4a9865202018-01-12 23:17:52438 # "%Nm" expands out to the instrumented binary's signature. When this pattern
439 # is specified, the runtime creates a pool of N raw profiles which are used
440 # for on-line profile merging. The runtime takes care of selecting a raw
441 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52442 # N must be between 1 and 9. The merge pool specifier can only occur once per
443 # filename pattern.
444 #
Max Morozd73e45f2018-04-24 18:32:47445 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01446 #
Max Morozd73e45f2018-04-24 18:32:47447 # For other cases, "%4m" is chosen as it creates some level of parallelism,
448 # but it's not too big to consume too much computing resource or disk space.
449 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59450 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01451 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18452 expected_profraw_file_path = os.path.join(
453 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
454 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24455 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
456 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54457
Yuke Liaoa0c8c2f2018-02-28 20:14:10458 try:
Max Moroz7c5354f2018-05-06 00:03:48459 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35460 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55461 subprocess.check_call(_SplitCommand(command),
462 stdout=output_file_handle,
463 stderr=subprocess.STDOUT,
464 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10465 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35466 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10467
Abhishek Arya03911092018-05-21 16:42:35468 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10469
470
Yuke Liao27349c92018-03-22 21:10:01471def _IsFuzzerTarget(target):
472 """Returns true if the target is a fuzzer target."""
473 build_args = _GetBuildArgs()
474 use_libfuzzer = ('use_libfuzzer' in build_args and
475 build_args['use_libfuzzer'] == 'true')
476 return use_libfuzzer and target.endswith('_fuzzer')
477
478
Abhishek Arya03911092018-05-21 16:42:35479def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10480 """Runs a single iOS command and generates a profraw data file.
481
482 iOS application doesn't have write access to folders outside of the app, so
483 it's impossible to instruct the app to flush the profraw data file to the
484 desired location. The profraw data file will be generated somewhere within the
485 application's Documents folder, and the full path can be obtained by parsing
486 the output.
487 """
Yuke Liaob2926832018-03-02 17:34:29488 assert _IsIOSCommand(command)
489
490 # After running tests, iossim generates a profraw data file, it won't be
491 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
492 # checkout.
493 iossim_profraw_file_path = os.path.join(
494 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24495 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
496 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10497
498 try:
Abhishek Arya03911092018-05-21 16:42:35499 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55500 subprocess.check_call(_SplitCommand(command),
501 stdout=output_file_handle,
502 stderr=subprocess.STDOUT,
503 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10504 except subprocess.CalledProcessError as e:
505 # iossim emits non-zero return code even if tests run successfully, so
506 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35507 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10508
Abhishek Arya03911092018-05-21 16:42:35509 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10510
511
512def _GetProfrawDataFileByParsingOutput(output):
513 """Returns the path to the profraw data file obtained by parsing the output.
514
515 The output of running the test target has no format, but it is guaranteed to
516 have a single line containing the path to the generated profraw data file.
517 NOTE: This should only be called when target os is iOS.
518 """
Yuke Liaob2926832018-03-02 17:34:29519 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10520
Yuke Liaob2926832018-03-02 17:34:29521 output_by_lines = ''.join(output).splitlines()
522 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10523
524 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29525 result = profraw_file_pattern.match(line)
526 if result:
527 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10528
529 assert False, ('No profraw data file was generated, did you call '
530 'coverage_util::ConfigureCoverageReportPath() in test setup? '
531 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54532
533
Abhishek Aryac19bc5ef2018-05-04 22:10:02534def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
535 """Returns a relative path to coverage profdata file by merging target
536 profdata files.
Yuke Liao506e8822017-12-04 16:52:54537
538 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02539 profdata_file_paths: A list of relative paths to the profdata data files
540 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54541
542 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02543 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54544
545 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02546 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54547 """
Abhishek Aryafb70b532018-05-06 17:47:40548 logging.info('Creating the coverage profile data file.')
549 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48550 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54551 try:
Abhishek Arya1ec832c2017-12-05 18:06:59552 subprocess_cmd = [
553 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
554 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02555 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01556
557 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18558 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02559 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04560 logging.error(
561 'Failed to merge target profdata files to create coverage profdata. %s',
562 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02563 raise error
564
Abhishek Aryafb70b532018-05-06 17:47:40565 logging.debug('Finished merging target profdata files.')
566 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02567 profdata_file_path)
568 return profdata_file_path
569
570
571def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
572 """Returns a relative path to target profdata file by merging target
573 profraw files.
574
575 Args:
576 profraw_file_paths: A list of relative paths to the profdata data files
577 that are to be merged.
578
579 Returns:
580 A relative path to the merged coverage profdata file.
581
582 Raises:
583 CalledProcessError: An error occurred merging profdata files.
584 """
Abhishek Aryafb70b532018-05-06 17:47:40585 logging.info('Creating target profile data file.')
586 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02587 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
588
589 try:
590 subprocess_cmd = [
591 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
592 ]
Yuke Liao506e8822017-12-04 16:52:54593 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01594 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18595 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54596 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04597 logging.error(
598 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54599 raise error
600
Abhishek Aryafb70b532018-05-06 17:47:40601 logging.debug('Finished merging target profraw files.')
602 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10603 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54604 return profdata_file_path
605
606
Yuke Liao0e4c8682018-04-18 21:06:59607def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
608 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33609 """Generates per file coverage summary using "llvm-cov export" command."""
610 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
611 # [[-object BIN]] [SOURCES].
612 # NOTE: For object files, the first one is specified as a positional argument,
613 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10614 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40615 'export -summary-only" command.')
Sajjad Mirza07f52332020-11-11 01:50:47616 for path in binary_paths:
617 if not os.path.exists(path):
618 logging.error("Binary %s does not exist", path)
Yuke Liaoea228d02018-01-05 19:10:33619 subprocess_cmd = [
620 LLVM_COV_PATH, 'export', '-summary-only',
Choongwoo Han56752522021-06-10 17:38:34621 '-compilation-dir={}'.format(BUILD_DIR),
Yuke Liaoea228d02018-01-05 19:10:33622 '-instr-profile=' + profdata_file_path, binary_paths[0]
623 ]
624 subprocess_cmd.extend(
625 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29626 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33627 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59628 if ignore_filename_regex:
629 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33630
Max Moroz7c5354f2018-05-06 00:03:48631 export_output = subprocess.check_output(subprocess_cmd)
632
633 # Write output on the disk to be used by code coverage bot.
634 with open(_GetSummaryFilePath(), 'w') as f:
635 f.write(export_output)
636
Max Moroz1de68d72018-08-21 13:38:18637 return export_output
Yuke Liaoea228d02018-01-05 19:10:33638
639
Yuke Liaob2926832018-03-02 17:34:29640def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
641 """Appends -arch arguments to the command list if it's ios platform.
642
643 iOS binaries are universal binaries, and require specifying the architecture
644 to use, and one architecture needs to be specified for each binary.
645 """
646 if _IsIOS():
647 cmd_list.extend(['-arch=x86_64'] * num_archs)
648
649
Yuke Liao506e8822017-12-04 16:52:54650def _GetBinaryPath(command):
651 """Returns a relative path to the binary to be run by the command.
652
Yuke Liao545db322018-02-15 17:12:01653 Currently, following types of commands are supported (e.g. url_unittests):
654 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
655 2. Use xvfb.
656 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
657 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37658 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
659 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10660 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37661 <iossim_arguments> -c <app_arguments>
662 out/Coverage-iphonesimulator/url_unittests.app"
663
Yuke Liao506e8822017-12-04 16:52:54664 Args:
665 command: A command used to run a target.
666
667 Returns:
668 A relative path to the binary.
669 """
Yuke Liao545db322018-02-15 17:12:01670 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
671
Sajjad Mirza0b96e002020-11-10 19:32:55672 command_parts = _SplitCommand(command)
Yuke Liao545db322018-02-15 17:12:01673 if os.path.basename(command_parts[0]) == 'python':
674 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40675 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01676 return command_parts[2]
677
678 if os.path.basename(command_parts[0]) == xvfb_script_name:
679 return command_parts[1]
680
Yuke Liaob2926832018-03-02 17:34:29681 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10682 # For a given application bundle, the binary resides in the bundle and has
683 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02684 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10685 app_name = os.path.splitext(os.path.basename(app_path))[0]
686 return os.path.join(app_path, app_name)
687
Sajjad Mirza07f52332020-11-11 01:50:47688 if coverage_utils.GetHostPlatform() == 'win' \
689 and not command_parts[0].endswith('.exe'):
690 return command_parts[0] + '.exe'
691
Yuke Liaob2926832018-03-02 17:34:29692 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54693
694
Yuke Liaob2926832018-03-02 17:34:29695def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10696 """Returns true if command is used to run tests on iOS platform."""
Sajjad Mirza0b96e002020-11-10 19:32:55697 return os.path.basename(_SplitCommand(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10698
699
Yuke Liao95d13d72017-12-07 18:18:50700def _VerifyTargetExecutablesAreInBuildDirectory(commands):
701 """Verifies that the target executables specified in the commands are inside
702 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54703 for command in commands:
704 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18705 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35706 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50707 'Target executable "%s" in command: "%s" is outside of '
708 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54709
710
711def _ValidateBuildingWithClangCoverage():
712 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20713 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54714
715 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
716 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59717 assert False, ('\'{} = true\' is required in args.gn.'
718 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54719
720
Yuke Liaoc60b2d02018-03-02 21:40:43721def _ValidateCurrentPlatformIsSupported():
722 """Asserts that this script suports running on the current platform"""
723 target_os = _GetTargetOS()
724 if target_os:
725 current_platform = target_os
726 else:
Max Moroz1de68d72018-08-21 13:38:18727 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43728
Ben Joyce88282362021-01-29 23:53:31729 supported_platforms = ['android', 'chromeos', 'ios', 'linux', 'mac', 'win']
730 assert current_platform in supported_platforms, ('Coverage is only'
731 'supported on %s' %
732 supported_platforms)
Yuke Liaoc60b2d02018-03-02 21:40:43733
734
Yuke Liao80afff32018-03-07 01:26:20735def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54736 """Parses args.gn file and returns results as a dictionary.
737
738 Returns:
739 A dictionary representing the build args.
740 """
Yuke Liao80afff32018-03-07 01:26:20741 global _BUILD_ARGS
742 if _BUILD_ARGS is not None:
743 return _BUILD_ARGS
744
745 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54746 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
747 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
748 'missing args.gn file.' % BUILD_DIR)
749 with open(build_args_path) as build_args_file:
750 build_args_lines = build_args_file.readlines()
751
Yuke Liao506e8822017-12-04 16:52:54752 for build_arg_line in build_args_lines:
753 build_arg_without_comments = build_arg_line.split('#')[0]
754 key_value_pair = build_arg_without_comments.split('=')
755 if len(key_value_pair) != 2:
756 continue
757
758 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43759
760 # Values are wrapped within a pair of double-quotes, so remove the leading
761 # and trailing double-quotes.
762 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20763 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54764
Yuke Liao80afff32018-03-07 01:26:20765 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54766
767
Abhishek Arya16f059a2017-12-07 17:47:32768def _VerifyPathsAndReturnAbsolutes(paths):
769 """Verifies that the paths specified in |paths| exist and returns absolute
770 versions.
Yuke Liao66da1732017-12-05 22:19:42771
772 Args:
773 paths: A list of files or directories.
774 """
Abhishek Arya16f059a2017-12-07 17:47:32775 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42776 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32777 absolute_path = os.path.join(SRC_ROOT_PATH, path)
778 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
779
780 absolute_paths.append(absolute_path)
781
782 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42783
784
Abhishek Arya64636af2018-05-04 14:42:13785def _GetBinaryPathsFromTargets(targets, build_dir):
786 """Return binary paths from target names."""
Ben Joyce88282362021-01-29 23:53:31787 # TODO(crbug.com/899974): Derive output binary from target build definitions
788 # rather than assuming that it is always the same name.
Abhishek Arya64636af2018-05-04 14:42:13789 binary_paths = []
790 for target in targets:
791 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18792 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13793 binary_path += '.exe'
794
795 if os.path.exists(binary_path):
796 binary_paths.append(binary_path)
797 else:
798 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40799 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13800 os.path.basename(binary_path))
801
802 return binary_paths
803
804
Abhishek Arya03911092018-05-21 16:42:35805def _GetCommandForWebTests(arguments):
806 """Return command to run for blink web tests."""
Dirk Pranke34c093a42021-03-25 19:19:05807 cpu_count = multiprocessing.cpu_count()
808 if sys.platform == 'win32':
809 # TODO(crbug.com/1190269) - we can't use more than 56
810 # cores on Windows or Python3 may hang.
811 cpu_count = min(cpu_count, 56)
812 cpu_count = max(1, cpu_count // 2)
813
Abhishek Arya03911092018-05-21 16:42:35814 command_list = [
815 'python', 'testing/xvfb.py', 'python',
816 'third_party/blink/tools/run_web_tests.py',
817 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24818 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Robert Ma339fc472018-12-14 21:22:42819 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Dirk Pranke34c093a42021-03-25 19:19:05820 '--child-processes=%d' % cpu_count, '--disable-breakpad',
821 '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35822 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
823 ]
824 if arguments.strip():
825 command_list.append(arguments)
826 return ' '.join(command_list)
827
828
Ben Joyce88282362021-01-29 23:53:31829def _GetBinaryPathsForAndroid(targets):
830 """Return binary paths used when running android tests."""
831 # TODO(crbug.com/899974): Implement approach that doesn't assume .so file is
832 # based on the target's name.
833 android_binaries = set()
834 for target in targets:
835 so_library_path = os.path.join(BUILD_DIR, 'lib.unstripped',
836 'lib%s__library.so' % target)
837 if os.path.exists(so_library_path):
838 android_binaries.add(so_library_path)
839
840 return list(android_binaries)
841
842
Abhishek Arya03911092018-05-21 16:42:35843def _GetBinaryPathForWebTests():
844 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18845 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35846 if host_platform == 'win':
847 return os.path.join(BUILD_DIR, 'content_shell.exe')
848 elif host_platform == 'linux':
849 return os.path.join(BUILD_DIR, 'content_shell')
850 elif host_platform == 'mac':
851 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
852 'Content Shell')
853 else:
854 assert False, 'This platform is not supported for web tests.'
855
856
Abhishek Aryae5811afa2018-05-24 03:56:01857def _SetupOutputDir():
858 """Setup output directory."""
859 if os.path.exists(OUTPUT_DIR):
860 shutil.rmtree(OUTPUT_DIR)
861
862 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18863 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01864
865
Yuke Liaoabfbba42019-06-11 16:03:59866def _SetMacXcodePath():
867 """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
868 if sys.platform != 'darwin':
869 return
870
871 xcode_path = os.path.join(SRC_ROOT_PATH, 'build', 'mac_files', 'Xcode.app')
872 if os.path.exists(xcode_path):
873 os.environ['DEVELOPER_DIR'] = xcode_path
874
875
Yuke Liao506e8822017-12-04 16:52:54876def _ParseCommandArguments():
877 """Adds and parses relevant arguments for tool comands.
878
879 Returns:
880 A dictionary representing the arguments.
881 """
882 arg_parser = argparse.ArgumentParser()
883 arg_parser.usage = __doc__
884
Abhishek Arya1ec832c2017-12-05 18:06:59885 arg_parser.add_argument(
886 '-b',
887 '--build-dir',
888 type=str,
889 required=True,
890 help='The build directory, the path needs to be relative to the root of '
891 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54892
Abhishek Arya1ec832c2017-12-05 18:06:59893 arg_parser.add_argument(
894 '-o',
895 '--output-dir',
896 type=str,
897 required=True,
898 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54899
Abhishek Arya1ec832c2017-12-05 18:06:59900 arg_parser.add_argument(
901 '-c',
902 '--command',
903 action='append',
Abhishek Arya64636af2018-05-04 14:42:13904 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59905 help='Commands used to run test targets, one test target needs one and '
906 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13907 'current working directory is the root of the checkout. This option is '
908 'incompatible with -p/--profdata-file option.')
909
910 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35911 '-wt',
912 '--web-tests',
913 nargs='?',
914 type=str,
915 const=' ',
916 required=False,
917 help='Run blink web tests. Support passing arguments to run_web_tests.py')
918
919 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13920 '-p',
921 '--profdata-file',
922 type=str,
923 required=False,
924 help='Path to profdata file to use for generating code coverage reports. '
925 'This can be useful if you generated the profdata file seperately in '
926 'your own test harness. This option is ignored if run command(s) are '
927 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54928
Abhishek Arya1ec832c2017-12-05 18:06:59929 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42930 '-f',
931 '--filters',
932 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32933 required=False,
Yuke Liao66da1732017-12-05 22:19:42934 help='Directories or files to get code coverage for, and all files under '
935 'the directories are included recursively.')
936
937 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59938 '-i',
939 '--ignore-filename-regex',
940 type=str,
941 help='Skip source code files with file paths that match the given '
942 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
943 'to exclude files in third_party/ and out/ folders from the report.')
944
945 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32946 '--no-file-view',
947 action='store_true',
948 help='Don\'t generate the file view in the coverage report. When there '
949 'are large number of html files, the file view becomes heavy and may '
950 'cause the browser to freeze, and this argument comes handy.')
951
952 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18953 '--no-component-view',
954 action='store_true',
955 help='Don\'t generate the component view in the coverage report.')
956
957 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:40958 '--coverage-tools-dir',
959 type=str,
960 help='Path of the directory where LLVM coverage tools (llvm-cov, '
961 'llvm-profdata) exist. This should be only needed if you are testing '
962 'against a custom built clang revision. Otherwise, we pick coverage '
963 'tools automatically from your current source checkout.')
964
965 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:59966 '-j',
967 '--jobs',
968 type=int,
969 default=None,
970 help='Run N jobs to build in parallel. If not specified, a default value '
Max Moroz06576292019-01-03 19:22:52971 'will be derived based on CPUs and goma availability. Please refer to '
972 '\'autoninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:54973
Abhishek Arya1ec832c2017-12-05 18:06:59974 arg_parser.add_argument(
Sahel Sharify38cabdc2020-01-16 00:40:01975 '--format',
976 type=str,
977 default='html',
978 help='Output format of the "llvm-cov show" command. The supported '
979 'formats are "text" and "html".')
980
981 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:10982 '-v',
983 '--verbose',
984 action='store_true',
985 help='Prints additional output for diagnostics.')
986
987 arg_parser.add_argument(
988 '-l', '--log_file', type=str, help='Redirects logs to a file.')
989
990 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:02991 'targets',
992 nargs='+',
993 help='The names of the test targets to run. If multiple run commands are '
994 'specified using the -c/--command option, then the order of targets and '
995 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:54996
997 args = arg_parser.parse_args()
998 return args
999
1000
1001def Main():
1002 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:401003
Abhishek Arya64636af2018-05-04 14:42:131004 # Change directory to source root to aid in relative paths calculations.
1005 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:111006
pasthanaa4844112020-05-21 18:03:551007 # Setup coverage binaries even when script is called with empty params. This
1008 # is used by coverage bot for initial setup.
1009 if len(sys.argv) == 1:
Choongwoo Hanbd1aa952021-06-09 22:25:381010 subprocess.check_call([
1011 'python', 'tools/clang/scripts/update.py', '--package', 'coverage_tools'
1012 ])
pasthanaa4844112020-05-21 18:03:551013 print(__doc__)
1014 return
1015
Yuke Liao506e8822017-12-04 16:52:541016 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:181017 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:401018 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:131019
Yuke Liao506e8822017-12-04 16:52:541020 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:181021 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:011022
Yuke Liao506e8822017-12-04 16:52:541023 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:181024 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:541025
Abhishek Arya03911092018-05-21 16:42:351026 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:131027 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:351028 'provide prof-data file as input using -p/--profdata-file option OR '
1029 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:431030
Abhishek Arya64636af2018-05-04 14:42:131031 assert not args.command or (len(args.targets) == len(args.command)), (
1032 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:431033
Abhishek Arya1ec832c2017-12-05 18:06:591034 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:401035 'Build directory: "%s" doesn\'t exist. '
1036 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:131037
Yuke Liaoc60b2d02018-03-02 21:40:431038 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:541039 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:321040
1041 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:421042 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:321043 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:421044
Abhishek Aryae5811afa2018-05-24 03:56:011045 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:541046
Abhishek Arya03911092018-05-21 16:42:351047 # Get .profdata file and list of binary paths.
1048 if args.web_tests:
1049 commands = [_GetCommandForWebTests(args.web_tests)]
1050 profdata_file_path = _CreateCoverageProfileDataForTargets(
1051 args.targets, commands, args.jobs)
1052 binary_paths = [_GetBinaryPathForWebTests()]
1053 elif args.command:
1054 for i in range(len(args.command)):
1055 assert not 'run_web_tests.py' in args.command[i], (
1056 'run_web_tests.py is not supported via --command argument. '
1057 'Please use --run-web-tests argument instead.')
1058
Abhishek Arya64636af2018-05-04 14:42:131059 # A list of commands are provided. Run them to generate profdata file, and
1060 # create a list of binary paths from parsing commands.
1061 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1062 profdata_file_path = _CreateCoverageProfileDataForTargets(
1063 args.targets, args.command, args.jobs)
1064 binary_paths = [_GetBinaryPath(command) for command in args.command]
1065 else:
1066 # An input prof-data file is already provided. Just calculate binary paths.
1067 profdata_file_path = args.profdata_file
1068 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331069
Erik Chen283b92c72019-07-22 16:37:391070 # If the checkout uses the hermetic xcode binaries, then otool must be
1071 # directly invoked. The indirection via /usr/bin/otool won't work unless
1072 # there's an actual system install of Xcode.
1073 otool_path = None
1074 if sys.platform == 'darwin':
1075 hermetic_otool_path = os.path.join(
1076 SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents',
1077 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin',
1078 'otool')
1079 if os.path.exists(hermetic_otool_path):
1080 otool_path = hermetic_otool_path
Ben Joyce88282362021-01-29 23:53:311081
1082 if _IsAndroid():
1083 binary_paths = _GetBinaryPathsForAndroid(args.targets)
1084 elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
Brent McBrideb25b177a42020-05-11 18:13:061085 binary_paths.extend(
1086 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
Abhishek Arya78120bc2018-05-07 20:53:541087
Sahel Sharify38cabdc2020-01-16 00:40:011088 assert args.format == 'html' or args.format == 'text', (
1089 '%s is not a valid output format for "llvm-cov show". Only "text" and '
1090 '"html" formats are supported.' % (args.format))
1091 logging.info('Generating code coverage report in %s (this can take a while '
1092 'depending on size of target!).' % (args.format))
Max Moroz1de68d72018-08-21 13:38:181093 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591094 binary_paths, profdata_file_path, absolute_filter_paths,
1095 args.ignore_filename_regex)
Sahel Sharify38cabdc2020-01-16 00:40:011096 _GeneratePerFileLineByLineCoverageInFormat(
1097 binary_paths, profdata_file_path, absolute_filter_paths,
1098 args.ignore_filename_regex, args.format)
Max Moroz1de68d72018-08-21 13:38:181099 component_mappings = None
1100 if not args.no_component_view:
Choongwoo Hanbd1aa952021-06-09 22:25:381101 component_mappings = json.load(urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371102
Max Moroz1de68d72018-08-21 13:38:181103 # Call prepare here.
1104 processor = coverage_utils.CoverageReportPostProcessor(
1105 OUTPUT_DIR,
1106 SRC_ROOT_PATH,
1107 per_file_summary_data,
1108 no_component_view=args.no_component_view,
1109 no_file_view=args.no_file_view,
1110 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371111
Sahel Sharify38cabdc2020-01-16 00:40:011112 if args.format == 'html':
1113 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541114
Abhishek Arya1ec832c2017-12-05 18:06:591115
Yuke Liao506e8822017-12-04 16:52:541116if __name__ == '__main__':
1117 sys.exit(Main())