blob: dda605f16f04cc9f5cf8d19b77509ce5d90ce2d3 [file] [log] [blame]
Prakhar65d63832021-06-16 23:01:371#!/usr/bin/env vpython
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
Prakhar507de0b2021-07-02 18:02:4720 vpython 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
Prakhar507de0b2021-07-02 18:02:4735 vpython tools/code_coverage/coverage.py unit_tests -b out/coverage \\
Yuke Liao545db322018-02-15 17:12:0136 -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
Prakhar507de0b2021-07-02 18:02:4743 vpython tools/code_coverage/coverage.py pdfium_fuzzer \\
Abhishek Arya16f059a2017-12-07 17:47:3244 -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
Prakhar507de0b2021-07-02 18:02:4756 vpython tools/code_coverage/coverage.py blink_tests \\
Abhishek Arya03911092018-05-21 16:42:3557 -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
Choongwoo Hanbd1aa952021-06-09 22:25:3882import six
83
84if six.PY2:
85 from urllib2 import urlopen
86else:
87 from urllib.request import urlopen
88
Abhishek Arya1ec832c2017-12-05 18:06:5989sys.path.append(
90 os.path.join(
Yuke Liaoea228d02018-01-05 19:10:3391 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
96
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.
pasthanab37d5bfd2020-05-28 12:18:3199# Absolute path to the root of the checkout.
100SRC_ROOT_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)),
101 os.path.pardir, os.path.pardir)
102LLVM_BIN_DIR = os.path.join(
103 os.path.join(SRC_ROOT_PATH, 'third_party', 'llvm-build', 'Release+Asserts'),
104 'bin')
Abhishek Arya1c97ea542018-05-10 03:53:19105LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
106LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:54107
Abhishek Arya03911092018-05-21 16:42:35108
Yuke Liao506e8822017-12-04 16:52:54109# Build directory, the value is parsed from command line arguments.
110BUILD_DIR = None
111
112# Output directory for generated artifacts, the value is parsed from command
113# line arguemnts.
114OUTPUT_DIR = None
115
Yuke Liao506e8822017-12-04 16:52:54116# Name of the file extension for profraw data files.
117PROFRAW_FILE_EXTENSION = 'profraw'
118
119# Name of the final profdata file, and this file needs to be passed to
120# "llvm-cov" command in order to call "llvm-cov show" to inspect the
121# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48122PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
123
124# Name of the file with summary information generated by llvm-cov export.
125SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54126
127# Build arg required for generating code coverage data.
128CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
129
Max Moroz7c5354f2018-05-06 00:03:48130LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37131
132# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19133COMPONENT_MAPPING_URL = (
134 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37135
Yuke Liao80afff32018-03-07 01:26:20136# Caches the results returned by _GetBuildArgs, don't use this variable
137# directly, call _GetBuildArgs instead.
138_BUILD_ARGS = None
139
Abhishek Aryac19bc5ef2018-05-04 22:10:02140# Retry failed merges.
141MERGE_RETRIES = 3
142
Abhishek Aryad35de7e2018-05-10 22:23:04143# Message to guide user to file a bug when everything else fails.
144FILE_BUG_MESSAGE = (
145 'If it persists, please file a bug with the command you used, git revision '
146 'and args.gn config here: '
147 'https://bugs.chromium.org/p/chromium/issues/entry?'
Yuke Liao03c644072019-07-30 18:33:40148 'components=Infra%3ETest%3ECodeCoverage')
Abhishek Aryad35de7e2018-05-10 22:23:04149
Abhishek Aryabd0655d2018-05-21 19:55:24150# String to replace with actual llvm profile path.
151LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
152
Yuke Liao082e99632018-05-18 15:40:40153def _ConfigureLLVMCoverageTools(args):
154 """Configures llvm coverage tools."""
155 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18156 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40157 global LLVM_COV_PATH
158 global LLVM_PROFDATA_PATH
159 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
160 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
161 else:
Choongwoo Hanbd1aa952021-06-09 22:25:38162 subprocess.check_call([
163 'python', 'tools/clang/scripts/update.py', '--package', 'coverage_tools'
164 ])
Brent McBrideb25b177a42020-05-11 18:13:06165
166 if coverage_utils.GetHostPlatform() == 'win':
167 LLVM_COV_PATH += '.exe'
168 LLVM_PROFDATA_PATH += '.exe'
Yuke Liao082e99632018-05-18 15:40:40169
170 coverage_tools_exist = (
171 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
172 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
173 'both \'%s\' and \'%s\' exist.') % (
174 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
175
Abhishek Arya2f261182019-04-24 17:06:45176
Abhishek Arya1c97ea542018-05-10 03:53:19177def _GetPathWithLLVMSymbolizerDir():
178 """Add llvm-symbolizer directory to path for symbolized stacks."""
179 path = os.getenv('PATH')
180 dirs = path.split(os.pathsep)
181 if LLVM_BIN_DIR in dirs:
182 return path
183
184 return path + os.pathsep + LLVM_BIN_DIR
185
186
Yuke Liaoc60b2d02018-03-02 21:40:43187def _GetTargetOS():
188 """Returns the target os specified in args.gn file.
189
190 Returns an empty string is target_os is not specified.
191 """
Yuke Liao80afff32018-03-07 01:26:20192 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43193 return build_args['target_os'] if 'target_os' in build_args else ''
194
195
Ben Joyce88282362021-01-29 23:53:31196def _IsAndroid():
197 """Returns true if the target_os specified in args.gn file is android"""
198 return _GetTargetOS() == 'android'
199
200
Yuke Liaob2926832018-03-02 17:34:29201def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10202 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43203 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10204
205
Sahel Sharify38cabdc2020-01-16 00:40:01206def _GeneratePerFileLineByLineCoverageInFormat(binary_paths, profdata_file_path,
207 filters, ignore_filename_regex,
208 output_format):
209 """Generates per file line-by-line coverage in html or text using
210 'llvm-cov show'.
Yuke Liao506e8822017-12-04 16:52:54211
Sahel Sharify38cabdc2020-01-16 00:40:01212 For a file with absolute path /a/b/x.cc, a html/txt report is generated as:
213 OUTPUT_DIR/coverage/a/b/x.cc.[html|txt]. For html format, an index html file
214 is also generated as: OUTPUT_DIR/index.html.
Yuke Liao506e8822017-12-04 16:52:54215
216 Args:
217 binary_paths: A list of paths to the instrumented binaries.
218 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42219 filters: A list of directories and files to get coverage for.
Sahel Sharify38cabdc2020-01-16 00:40:01220 ignore_filename_regex: A regular expression for skipping source code files
221 with certain file paths.
222 output_format: The output format of generated report files.
Yuke Liao506e8822017-12-04 16:52:54223 """
Yuke Liao506e8822017-12-04 16:52:54224 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
225 # [[-object BIN]] [SOURCES]
226 # NOTE: For object files, the first one is specified as a positional argument,
227 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10228 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40229 '"llvm-cov show" command.')
Sahel Sharify38cabdc2020-01-16 00:40:01230
Abhishek Arya1ec832c2017-12-05 18:06:59231 subprocess_cmd = [
Sahel Sharify38cabdc2020-01-16 00:40:01232 LLVM_COV_PATH, 'show', '-format={}'.format(output_format),
Choongwoo Han56752522021-06-10 17:38:34233 '-compilation-dir={}'.format(BUILD_DIR),
Abhishek Arya1ec832c2017-12-05 18:06:59234 '-output-dir={}'.format(OUTPUT_DIR),
235 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
236 ]
237 subprocess_cmd.extend(
238 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29239 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18240 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17241 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42242 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59243 if ignore_filename_regex:
244 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
245
Yuke Liao506e8822017-12-04 16:52:54246 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34247
Abhishek Aryafb70b532018-05-06 17:47:40248 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54249
250
Max Moroz7c5354f2018-05-06 00:03:48251def _GetLogsDirectoryPath():
252 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18253 return os.path.join(
254 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48255
256
257def _GetProfdataFilePath():
258 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18259 return os.path.join(
260 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
261 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48262
263
264def _GetSummaryFilePath():
265 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18266 return os.path.join(
267 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
268 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33269
270
Yuke Liao506e8822017-12-04 16:52:54271def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
272 """Builds and runs target to generate the coverage profile data.
273
274 Args:
275 targets: A list of targets to build with coverage instrumentation.
276 commands: A list of commands used to run the targets.
277 jobs_count: Number of jobs to run in parallel for building. If None, a
278 default value is derived based on CPUs availability.
279
280 Returns:
281 A relative path to the generated profdata file.
282 """
283 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02284 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59285 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02286 coverage_profdata_file_path = (
287 _CreateCoverageProfileDataFromTargetProfDataFiles(
288 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54289
Abhishek Aryac19bc5ef2018-05-04 22:10:02290 for target_profdata_file_path in target_profdata_file_paths:
291 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52292
Abhishek Aryac19bc5ef2018-05-04 22:10:02293 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54294
295
296def _BuildTargets(targets, jobs_count):
297 """Builds target with Clang coverage instrumentation.
298
299 This function requires current working directory to be the root of checkout.
300
301 Args:
302 targets: A list of targets to build with coverage instrumentation.
303 jobs_count: Number of jobs to run in parallel for compilation. If None, a
304 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54305 """
Abhishek Aryafb70b532018-05-06 17:47:40306 logging.info('Building %s.', str(targets))
Brent McBrideb25b177a42020-05-11 18:13:06307 autoninja = 'autoninja'
308 if coverage_utils.GetHostPlatform() == 'win':
309 autoninja += '.bat'
Yuke Liao506e8822017-12-04 16:52:54310
Brent McBrideb25b177a42020-05-11 18:13:06311 subprocess_cmd = [autoninja, '-C', BUILD_DIR]
Yuke Liao506e8822017-12-04 16:52:54312 if jobs_count is not None:
313 subprocess_cmd.append('-j' + str(jobs_count))
314
315 subprocess_cmd.extend(targets)
316 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40317 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54318
319
Abhishek Aryac19bc5ef2018-05-04 22:10:02320def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54321 """Runs commands and returns the relative paths to the profraw data files.
322
323 Args:
324 targets: A list of targets built with coverage instrumentation.
325 commands: A list of commands used to run the targets.
326
327 Returns:
328 A list of relative paths to the generated profraw data files.
329 """
Abhishek Aryafb70b532018-05-06 17:47:40330 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10331
Yuke Liao506e8822017-12-04 16:52:54332 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18333 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
334 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54335 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18336 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48337
338 # Ensure that logs directory exists.
339 if not os.path.exists(_GetLogsDirectoryPath()):
340 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54341
Abhishek Aryac19bc5ef2018-05-04 22:10:02342 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10343
Yuke Liaod4a9865202018-01-12 23:17:52344 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54345 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48346 output_file_name = os.extsep.join([target + '_output', 'log'])
347 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10348
Abhishek Aryac19bc5ef2018-05-04 22:10:02349 profdata_file_path = None
Prakhar65d63832021-06-16 23:01:37350 for _ in range(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40351 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02352 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10353
Abhishek Aryac19bc5ef2018-05-04 22:10:02354 if _IsIOSCommand(command):
355 # On iOS platform, due to lack of write permissions, profraw files are
356 # generated outside of the OUTPUT_DIR, and the exact paths are contained
357 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35358 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02359 else:
360 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35361 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02362
363 profraw_file_paths = []
364 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57365 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Ben Joyce88282362021-01-29 23:53:31366 elif _IsAndroid():
367 android_coverage_dir = os.path.join(BUILD_DIR, 'coverage')
368 for r, _, files in os.walk(android_coverage_dir):
369 for f in files:
370 if f.endswith(PROFRAW_FILE_EXTENSION):
371 profraw_file_paths.append(os.path.join(r, f))
Abhishek Aryac19bc5ef2018-05-04 22:10:02372 else:
Max Moroz1de68d72018-08-21 13:38:18373 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02374 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48375 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18376 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02377
378 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40379 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04380 'please make sure the binary exists, is properly instrumented and '
381 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02382
Yuke Liao9c2c70b2018-05-23 15:37:57383 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18384 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
385 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57386
Abhishek Aryac19bc5ef2018-05-04 22:10:02387 try:
388 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
389 target, profraw_file_paths)
390 break
391 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04392 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02393 finally:
394 # Remove profraw files now so that they are not used in next iteration.
395 for profraw_file_path in profraw_file_paths:
396 os.remove(profraw_file_path)
397
398 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04399 'Failed to merge target "%s" profraw files after %d retries. %s' %
400 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02401 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54402
Abhishek Aryafb70b532018-05-06 17:47:40403 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10404
Abhishek Aryac19bc5ef2018-05-04 22:10:02405 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54406
407
Abhishek Arya03911092018-05-21 16:42:35408def _GetEnvironmentVars(profraw_file_path):
409 """Return environment vars for subprocess, given a profraw file path."""
410 env = os.environ.copy()
411 env.update({
412 'LLVM_PROFILE_FILE': profraw_file_path,
413 'PATH': _GetPathWithLLVMSymbolizerDir()
414 })
415 return env
416
417
Sajjad Mirza0b96e002020-11-10 19:32:55418def _SplitCommand(command):
419 """Split a command string into parts in a platform-specific way."""
420 if coverage_utils.GetHostPlatform() == 'win':
421 return command.split()
422 return shlex.split(command)
423
424
Abhishek Arya03911092018-05-21 16:42:35425def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10426 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52427 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01428 #
Max Morozd73e45f2018-04-24 18:32:47429 # "%p" expands out to the process ID. It's not used by this scripts due to:
430 # 1) If a target program spawns too many processess, it may exhaust all disk
431 # space available. For example, unit_tests writes thousands of .profraw
432 # files each of size 1GB+.
433 # 2) If a target binary uses shared libraries, coverage profile data for them
434 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01435 #
Yuke Liaod4a9865202018-01-12 23:17:52436 # "%Nm" expands out to the instrumented binary's signature. When this pattern
437 # is specified, the runtime creates a pool of N raw profiles which are used
438 # for on-line profile merging. The runtime takes care of selecting a raw
439 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52440 # N must be between 1 and 9. The merge pool specifier can only occur once per
441 # filename pattern.
442 #
Max Morozd73e45f2018-04-24 18:32:47443 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01444 #
Max Morozd73e45f2018-04-24 18:32:47445 # For other cases, "%4m" is chosen as it creates some level of parallelism,
446 # but it's not too big to consume too much computing resource or disk space.
447 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59448 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01449 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18450 expected_profraw_file_path = os.path.join(
451 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
452 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24453 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
454 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54455
Yuke Liaoa0c8c2f2018-02-28 20:14:10456 try:
Max Moroz7c5354f2018-05-06 00:03:48457 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35458 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55459 subprocess.check_call(_SplitCommand(command),
460 stdout=output_file_handle,
461 stderr=subprocess.STDOUT,
462 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10463 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35464 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10465
Abhishek Arya03911092018-05-21 16:42:35466 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10467
468
Yuke Liao27349c92018-03-22 21:10:01469def _IsFuzzerTarget(target):
470 """Returns true if the target is a fuzzer target."""
471 build_args = _GetBuildArgs()
472 use_libfuzzer = ('use_libfuzzer' in build_args and
473 build_args['use_libfuzzer'] == 'true')
474 return use_libfuzzer and target.endswith('_fuzzer')
475
476
Abhishek Arya03911092018-05-21 16:42:35477def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10478 """Runs a single iOS command and generates a profraw data file.
479
480 iOS application doesn't have write access to folders outside of the app, so
481 it's impossible to instruct the app to flush the profraw data file to the
482 desired location. The profraw data file will be generated somewhere within the
483 application's Documents folder, and the full path can be obtained by parsing
484 the output.
485 """
Yuke Liaob2926832018-03-02 17:34:29486 assert _IsIOSCommand(command)
487
488 # After running tests, iossim generates a profraw data file, it won't be
489 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
490 # checkout.
491 iossim_profraw_file_path = os.path.join(
492 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24493 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
494 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10495
496 try:
Abhishek Arya03911092018-05-21 16:42:35497 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55498 subprocess.check_call(_SplitCommand(command),
499 stdout=output_file_handle,
500 stderr=subprocess.STDOUT,
501 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10502 except subprocess.CalledProcessError as e:
503 # iossim emits non-zero return code even if tests run successfully, so
504 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35505 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10506
Abhishek Arya03911092018-05-21 16:42:35507 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10508
509
510def _GetProfrawDataFileByParsingOutput(output):
511 """Returns the path to the profraw data file obtained by parsing the output.
512
513 The output of running the test target has no format, but it is guaranteed to
514 have a single line containing the path to the generated profraw data file.
515 NOTE: This should only be called when target os is iOS.
516 """
Yuke Liaob2926832018-03-02 17:34:29517 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10518
Yuke Liaob2926832018-03-02 17:34:29519 output_by_lines = ''.join(output).splitlines()
520 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10521
522 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29523 result = profraw_file_pattern.match(line)
524 if result:
525 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10526
527 assert False, ('No profraw data file was generated, did you call '
528 'coverage_util::ConfigureCoverageReportPath() in test setup? '
529 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54530
531
Abhishek Aryac19bc5ef2018-05-04 22:10:02532def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
533 """Returns a relative path to coverage profdata file by merging target
534 profdata files.
Yuke Liao506e8822017-12-04 16:52:54535
536 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02537 profdata_file_paths: A list of relative paths to the profdata data files
538 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54539
540 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02541 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54542
543 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02544 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54545 """
Abhishek Aryafb70b532018-05-06 17:47:40546 logging.info('Creating the coverage profile data file.')
547 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48548 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54549 try:
Abhishek Arya1ec832c2017-12-05 18:06:59550 subprocess_cmd = [
551 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
552 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02553 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01554
555 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18556 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02557 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04558 logging.error(
559 'Failed to merge target profdata files to create coverage profdata. %s',
560 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02561 raise error
562
Abhishek Aryafb70b532018-05-06 17:47:40563 logging.debug('Finished merging target profdata files.')
564 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02565 profdata_file_path)
566 return profdata_file_path
567
568
569def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
570 """Returns a relative path to target profdata file by merging target
571 profraw files.
572
573 Args:
574 profraw_file_paths: A list of relative paths to the profdata data files
575 that are to be merged.
576
577 Returns:
578 A relative path to the merged coverage profdata file.
579
580 Raises:
581 CalledProcessError: An error occurred merging profdata files.
582 """
Abhishek Aryafb70b532018-05-06 17:47:40583 logging.info('Creating target profile data file.')
584 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02585 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
586
587 try:
588 subprocess_cmd = [
589 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
590 ]
Yuke Liao506e8822017-12-04 16:52:54591 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01592 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18593 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54594 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04595 logging.error(
596 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54597 raise error
598
Abhishek Aryafb70b532018-05-06 17:47:40599 logging.debug('Finished merging target profraw files.')
600 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10601 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54602 return profdata_file_path
603
604
Yuke Liao0e4c8682018-04-18 21:06:59605def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
606 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33607 """Generates per file coverage summary using "llvm-cov export" command."""
608 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
609 # [[-object BIN]] [SOURCES].
610 # NOTE: For object files, the first one is specified as a positional argument,
611 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10612 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40613 'export -summary-only" command.')
Sajjad Mirza07f52332020-11-11 01:50:47614 for path in binary_paths:
615 if not os.path.exists(path):
616 logging.error("Binary %s does not exist", path)
Yuke Liaoea228d02018-01-05 19:10:33617 subprocess_cmd = [
618 LLVM_COV_PATH, 'export', '-summary-only',
Choongwoo Han56752522021-06-10 17:38:34619 '-compilation-dir={}'.format(BUILD_DIR),
Yuke Liaoea228d02018-01-05 19:10:33620 '-instr-profile=' + profdata_file_path, binary_paths[0]
621 ]
622 subprocess_cmd.extend(
623 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29624 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33625 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59626 if ignore_filename_regex:
627 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33628
Max Moroz7c5354f2018-05-06 00:03:48629 export_output = subprocess.check_output(subprocess_cmd)
630
631 # Write output on the disk to be used by code coverage bot.
Prakhar65d63832021-06-16 23:01:37632 with open(_GetSummaryFilePath(), 'wb') as f:
Max Moroz7c5354f2018-05-06 00:03:48633 f.write(export_output)
634
Max Moroz1de68d72018-08-21 13:38:18635 return export_output
Yuke Liaoea228d02018-01-05 19:10:33636
637
Yuke Liaob2926832018-03-02 17:34:29638def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
639 """Appends -arch arguments to the command list if it's ios platform.
640
641 iOS binaries are universal binaries, and require specifying the architecture
642 to use, and one architecture needs to be specified for each binary.
643 """
644 if _IsIOS():
645 cmd_list.extend(['-arch=x86_64'] * num_archs)
646
647
Yuke Liao506e8822017-12-04 16:52:54648def _GetBinaryPath(command):
649 """Returns a relative path to the binary to be run by the command.
650
Yuke Liao545db322018-02-15 17:12:01651 Currently, following types of commands are supported (e.g. url_unittests):
652 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
653 2. Use xvfb.
654 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
655 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37656 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
657 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10658 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37659 <iossim_arguments> -c <app_arguments>
660 out/Coverage-iphonesimulator/url_unittests.app"
661
Yuke Liao506e8822017-12-04 16:52:54662 Args:
663 command: A command used to run a target.
664
665 Returns:
666 A relative path to the binary.
667 """
Yuke Liao545db322018-02-15 17:12:01668 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
669
Sajjad Mirza0b96e002020-11-10 19:32:55670 command_parts = _SplitCommand(command)
Yuke Liao545db322018-02-15 17:12:01671 if os.path.basename(command_parts[0]) == 'python':
672 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40673 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01674 return command_parts[2]
675
676 if os.path.basename(command_parts[0]) == xvfb_script_name:
677 return command_parts[1]
678
Yuke Liaob2926832018-03-02 17:34:29679 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10680 # For a given application bundle, the binary resides in the bundle and has
681 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02682 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10683 app_name = os.path.splitext(os.path.basename(app_path))[0]
684 return os.path.join(app_path, app_name)
685
Sajjad Mirza07f52332020-11-11 01:50:47686 if coverage_utils.GetHostPlatform() == 'win' \
687 and not command_parts[0].endswith('.exe'):
688 return command_parts[0] + '.exe'
689
Yuke Liaob2926832018-03-02 17:34:29690 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54691
692
Yuke Liaob2926832018-03-02 17:34:29693def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10694 """Returns true if command is used to run tests on iOS platform."""
Sajjad Mirza0b96e002020-11-10 19:32:55695 return os.path.basename(_SplitCommand(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10696
697
Yuke Liao95d13d72017-12-07 18:18:50698def _VerifyTargetExecutablesAreInBuildDirectory(commands):
699 """Verifies that the target executables specified in the commands are inside
700 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54701 for command in commands:
702 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18703 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35704 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50705 'Target executable "%s" in command: "%s" is outside of '
706 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54707
708
709def _ValidateBuildingWithClangCoverage():
710 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20711 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54712
713 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
714 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59715 assert False, ('\'{} = true\' is required in args.gn.'
716 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54717
718
Yuke Liaoc60b2d02018-03-02 21:40:43719def _ValidateCurrentPlatformIsSupported():
720 """Asserts that this script suports running on the current platform"""
721 target_os = _GetTargetOS()
722 if target_os:
723 current_platform = target_os
724 else:
Max Moroz1de68d72018-08-21 13:38:18725 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43726
Ben Joyce88282362021-01-29 23:53:31727 supported_platforms = ['android', 'chromeos', 'ios', 'linux', 'mac', 'win']
728 assert current_platform in supported_platforms, ('Coverage is only'
729 'supported on %s' %
730 supported_platforms)
Yuke Liaoc60b2d02018-03-02 21:40:43731
732
Yuke Liao80afff32018-03-07 01:26:20733def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54734 """Parses args.gn file and returns results as a dictionary.
735
736 Returns:
737 A dictionary representing the build args.
738 """
Yuke Liao80afff32018-03-07 01:26:20739 global _BUILD_ARGS
740 if _BUILD_ARGS is not None:
741 return _BUILD_ARGS
742
743 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54744 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
745 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
746 'missing args.gn file.' % BUILD_DIR)
747 with open(build_args_path) as build_args_file:
748 build_args_lines = build_args_file.readlines()
749
Yuke Liao506e8822017-12-04 16:52:54750 for build_arg_line in build_args_lines:
751 build_arg_without_comments = build_arg_line.split('#')[0]
752 key_value_pair = build_arg_without_comments.split('=')
753 if len(key_value_pair) != 2:
754 continue
755
756 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43757
758 # Values are wrapped within a pair of double-quotes, so remove the leading
759 # and trailing double-quotes.
760 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20761 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54762
Yuke Liao80afff32018-03-07 01:26:20763 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54764
765
Abhishek Arya16f059a2017-12-07 17:47:32766def _VerifyPathsAndReturnAbsolutes(paths):
767 """Verifies that the paths specified in |paths| exist and returns absolute
768 versions.
Yuke Liao66da1732017-12-05 22:19:42769
770 Args:
771 paths: A list of files or directories.
772 """
Abhishek Arya16f059a2017-12-07 17:47:32773 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42774 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32775 absolute_path = os.path.join(SRC_ROOT_PATH, path)
776 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
777
778 absolute_paths.append(absolute_path)
779
780 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42781
782
Abhishek Arya64636af2018-05-04 14:42:13783def _GetBinaryPathsFromTargets(targets, build_dir):
784 """Return binary paths from target names."""
Ben Joyce88282362021-01-29 23:53:31785 # TODO(crbug.com/899974): Derive output binary from target build definitions
786 # rather than assuming that it is always the same name.
Abhishek Arya64636af2018-05-04 14:42:13787 binary_paths = []
788 for target in targets:
789 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18790 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13791 binary_path += '.exe'
792
793 if os.path.exists(binary_path):
794 binary_paths.append(binary_path)
795 else:
796 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40797 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13798 os.path.basename(binary_path))
799
800 return binary_paths
801
802
Abhishek Arya03911092018-05-21 16:42:35803def _GetCommandForWebTests(arguments):
804 """Return command to run for blink web tests."""
Dirk Pranke34c093a42021-03-25 19:19:05805 cpu_count = multiprocessing.cpu_count()
806 if sys.platform == 'win32':
807 # TODO(crbug.com/1190269) - we can't use more than 56
808 # cores on Windows or Python3 may hang.
809 cpu_count = min(cpu_count, 56)
810 cpu_count = max(1, cpu_count // 2)
811
Abhishek Arya03911092018-05-21 16:42:35812 command_list = [
813 'python', 'testing/xvfb.py', 'python',
814 'third_party/blink/tools/run_web_tests.py',
815 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24816 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Robert Ma339fc472018-12-14 21:22:42817 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Dirk Pranke34c093a42021-03-25 19:19:05818 '--child-processes=%d' % cpu_count, '--disable-breakpad',
819 '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35820 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
821 ]
822 if arguments.strip():
823 command_list.append(arguments)
824 return ' '.join(command_list)
825
826
Ben Joyce88282362021-01-29 23:53:31827def _GetBinaryPathsForAndroid(targets):
828 """Return binary paths used when running android tests."""
829 # TODO(crbug.com/899974): Implement approach that doesn't assume .so file is
830 # based on the target's name.
831 android_binaries = set()
832 for target in targets:
833 so_library_path = os.path.join(BUILD_DIR, 'lib.unstripped',
834 'lib%s__library.so' % target)
835 if os.path.exists(so_library_path):
836 android_binaries.add(so_library_path)
837
838 return list(android_binaries)
839
840
Abhishek Arya03911092018-05-21 16:42:35841def _GetBinaryPathForWebTests():
842 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18843 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35844 if host_platform == 'win':
845 return os.path.join(BUILD_DIR, 'content_shell.exe')
846 elif host_platform == 'linux':
847 return os.path.join(BUILD_DIR, 'content_shell')
848 elif host_platform == 'mac':
849 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
850 'Content Shell')
851 else:
852 assert False, 'This platform is not supported for web tests.'
853
854
Abhishek Aryae5811afa2018-05-24 03:56:01855def _SetupOutputDir():
856 """Setup output directory."""
857 if os.path.exists(OUTPUT_DIR):
858 shutil.rmtree(OUTPUT_DIR)
859
860 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18861 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01862
863
Yuke Liaoabfbba42019-06-11 16:03:59864def _SetMacXcodePath():
865 """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
866 if sys.platform != 'darwin':
867 return
868
869 xcode_path = os.path.join(SRC_ROOT_PATH, 'build', 'mac_files', 'Xcode.app')
870 if os.path.exists(xcode_path):
871 os.environ['DEVELOPER_DIR'] = xcode_path
872
873
Yuke Liao506e8822017-12-04 16:52:54874def _ParseCommandArguments():
875 """Adds and parses relevant arguments for tool comands.
876
877 Returns:
878 A dictionary representing the arguments.
879 """
880 arg_parser = argparse.ArgumentParser()
881 arg_parser.usage = __doc__
882
Abhishek Arya1ec832c2017-12-05 18:06:59883 arg_parser.add_argument(
884 '-b',
885 '--build-dir',
886 type=str,
887 required=True,
888 help='The build directory, the path needs to be relative to the root of '
889 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54890
Abhishek Arya1ec832c2017-12-05 18:06:59891 arg_parser.add_argument(
892 '-o',
893 '--output-dir',
894 type=str,
895 required=True,
896 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54897
Abhishek Arya1ec832c2017-12-05 18:06:59898 arg_parser.add_argument(
899 '-c',
900 '--command',
901 action='append',
Abhishek Arya64636af2018-05-04 14:42:13902 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59903 help='Commands used to run test targets, one test target needs one and '
904 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13905 'current working directory is the root of the checkout. This option is '
906 'incompatible with -p/--profdata-file option.')
907
908 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35909 '-wt',
910 '--web-tests',
911 nargs='?',
912 type=str,
913 const=' ',
914 required=False,
915 help='Run blink web tests. Support passing arguments to run_web_tests.py')
916
917 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13918 '-p',
919 '--profdata-file',
920 type=str,
921 required=False,
922 help='Path to profdata file to use for generating code coverage reports. '
923 'This can be useful if you generated the profdata file seperately in '
924 'your own test harness. This option is ignored if run command(s) are '
925 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54926
Abhishek Arya1ec832c2017-12-05 18:06:59927 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42928 '-f',
929 '--filters',
930 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32931 required=False,
Yuke Liao66da1732017-12-05 22:19:42932 help='Directories or files to get code coverage for, and all files under '
933 'the directories are included recursively.')
934
935 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59936 '-i',
937 '--ignore-filename-regex',
938 type=str,
939 help='Skip source code files with file paths that match the given '
940 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
941 'to exclude files in third_party/ and out/ folders from the report.')
942
943 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32944 '--no-file-view',
945 action='store_true',
946 help='Don\'t generate the file view in the coverage report. When there '
947 'are large number of html files, the file view becomes heavy and may '
948 'cause the browser to freeze, and this argument comes handy.')
949
950 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18951 '--no-component-view',
952 action='store_true',
953 help='Don\'t generate the component view in the coverage report.')
954
955 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:40956 '--coverage-tools-dir',
957 type=str,
958 help='Path of the directory where LLVM coverage tools (llvm-cov, '
959 'llvm-profdata) exist. This should be only needed if you are testing '
960 'against a custom built clang revision. Otherwise, we pick coverage '
961 'tools automatically from your current source checkout.')
962
963 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:59964 '-j',
965 '--jobs',
966 type=int,
967 default=None,
968 help='Run N jobs to build in parallel. If not specified, a default value '
Max Moroz06576292019-01-03 19:22:52969 'will be derived based on CPUs and goma availability. Please refer to '
970 '\'autoninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:54971
Abhishek Arya1ec832c2017-12-05 18:06:59972 arg_parser.add_argument(
Sahel Sharify38cabdc2020-01-16 00:40:01973 '--format',
974 type=str,
975 default='html',
976 help='Output format of the "llvm-cov show" command. The supported '
977 'formats are "text" and "html".')
978
979 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:10980 '-v',
981 '--verbose',
982 action='store_true',
983 help='Prints additional output for diagnostics.')
984
985 arg_parser.add_argument(
986 '-l', '--log_file', type=str, help='Redirects logs to a file.')
987
988 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:02989 'targets',
990 nargs='+',
991 help='The names of the test targets to run. If multiple run commands are '
992 'specified using the -c/--command option, then the order of targets and '
993 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:54994
995 args = arg_parser.parse_args()
996 return args
997
998
999def Main():
1000 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:401001
Abhishek Arya64636af2018-05-04 14:42:131002 # Change directory to source root to aid in relative paths calculations.
1003 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:111004
pasthanaa4844112020-05-21 18:03:551005 # Setup coverage binaries even when script is called with empty params. This
1006 # is used by coverage bot for initial setup.
1007 if len(sys.argv) == 1:
Choongwoo Hanbd1aa952021-06-09 22:25:381008 subprocess.check_call([
1009 'python', 'tools/clang/scripts/update.py', '--package', 'coverage_tools'
1010 ])
pasthanaa4844112020-05-21 18:03:551011 print(__doc__)
1012 return
1013
Yuke Liao506e8822017-12-04 16:52:541014 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:181015 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:401016 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:131017
Yuke Liao506e8822017-12-04 16:52:541018 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:181019 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:011020
Yuke Liao506e8822017-12-04 16:52:541021 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:181022 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:541023
Abhishek Arya03911092018-05-21 16:42:351024 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:131025 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:351026 'provide prof-data file as input using -p/--profdata-file option OR '
1027 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:431028
Abhishek Arya64636af2018-05-04 14:42:131029 assert not args.command or (len(args.targets) == len(args.command)), (
1030 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:431031
Abhishek Arya1ec832c2017-12-05 18:06:591032 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:401033 'Build directory: "%s" doesn\'t exist. '
1034 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:131035
Yuke Liaoc60b2d02018-03-02 21:40:431036 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:541037 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:321038
1039 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:421040 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:321041 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:421042
Abhishek Aryae5811afa2018-05-24 03:56:011043 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:541044
Abhishek Arya03911092018-05-21 16:42:351045 # Get .profdata file and list of binary paths.
1046 if args.web_tests:
1047 commands = [_GetCommandForWebTests(args.web_tests)]
1048 profdata_file_path = _CreateCoverageProfileDataForTargets(
1049 args.targets, commands, args.jobs)
1050 binary_paths = [_GetBinaryPathForWebTests()]
1051 elif args.command:
1052 for i in range(len(args.command)):
1053 assert not 'run_web_tests.py' in args.command[i], (
1054 'run_web_tests.py is not supported via --command argument. '
1055 'Please use --run-web-tests argument instead.')
1056
Abhishek Arya64636af2018-05-04 14:42:131057 # A list of commands are provided. Run them to generate profdata file, and
1058 # create a list of binary paths from parsing commands.
1059 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1060 profdata_file_path = _CreateCoverageProfileDataForTargets(
1061 args.targets, args.command, args.jobs)
1062 binary_paths = [_GetBinaryPath(command) for command in args.command]
1063 else:
1064 # An input prof-data file is already provided. Just calculate binary paths.
1065 profdata_file_path = args.profdata_file
1066 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331067
Erik Chen283b92c72019-07-22 16:37:391068 # If the checkout uses the hermetic xcode binaries, then otool must be
1069 # directly invoked. The indirection via /usr/bin/otool won't work unless
1070 # there's an actual system install of Xcode.
1071 otool_path = None
1072 if sys.platform == 'darwin':
1073 hermetic_otool_path = os.path.join(
1074 SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents',
1075 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin',
1076 'otool')
1077 if os.path.exists(hermetic_otool_path):
1078 otool_path = hermetic_otool_path
Ben Joyce88282362021-01-29 23:53:311079
1080 if _IsAndroid():
1081 binary_paths = _GetBinaryPathsForAndroid(args.targets)
1082 elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
Brent McBrideb25b177a42020-05-11 18:13:061083 binary_paths.extend(
1084 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
Abhishek Arya78120bc2018-05-07 20:53:541085
Sahel Sharify38cabdc2020-01-16 00:40:011086 assert args.format == 'html' or args.format == 'text', (
1087 '%s is not a valid output format for "llvm-cov show". Only "text" and '
1088 '"html" formats are supported.' % (args.format))
1089 logging.info('Generating code coverage report in %s (this can take a while '
1090 'depending on size of target!).' % (args.format))
Max Moroz1de68d72018-08-21 13:38:181091 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591092 binary_paths, profdata_file_path, absolute_filter_paths,
1093 args.ignore_filename_regex)
Sahel Sharify38cabdc2020-01-16 00:40:011094 _GeneratePerFileLineByLineCoverageInFormat(
1095 binary_paths, profdata_file_path, absolute_filter_paths,
1096 args.ignore_filename_regex, args.format)
Max Moroz1de68d72018-08-21 13:38:181097 component_mappings = None
1098 if not args.no_component_view:
Choongwoo Hanbd1aa952021-06-09 22:25:381099 component_mappings = json.load(urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371100
Max Moroz1de68d72018-08-21 13:38:181101 # Call prepare here.
1102 processor = coverage_utils.CoverageReportPostProcessor(
1103 OUTPUT_DIR,
1104 SRC_ROOT_PATH,
1105 per_file_summary_data,
1106 no_component_view=args.no_component_view,
1107 no_file_view=args.no_file_view,
1108 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371109
Sahel Sharify38cabdc2020-01-16 00:40:011110 if args.format == 'html':
1111 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541112
Abhishek Arya1ec832c2017-12-05 18:06:591113
Yuke Liao506e8822017-12-04 16:52:541114if __name__ == '__main__':
1115 sys.exit(Main())