blob: 826496ac40806a6252908c384f65b3a4c7b7de35 [file] [log] [blame]
Lei Zhang42a5b51a2022-03-07 19:16:161#!/usr/bin/env vpython3
Avi Drissmandfd880852022-09-15 20:11:092# Copyright 2017 The Chromium Authors
Yuke Liao506e8822017-12-04 16:52:543# 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
Fabrice de Gans0b5511e72022-09-16 22:07:2020 vpython3 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
Fabrice de Gans0b5511e72022-09-16 22:07:2035 vpython3 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
Fabrice de Gans0b5511e72022-09-16 22:07:2043 vpython3 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
Fabrice de Gans0b5511e72022-09-16 22:07:2056 vpython3 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:3882
Lei Zhang20e2ab752022-10-11 22:11:0083from urllib.request import urlopen
Choongwoo Hanbd1aa952021-06-09 22:25:3884
Abhishek Arya1ec832c2017-12-05 18:06:5985sys.path.append(
86 os.path.join(
Yuke Liaoea228d02018-01-05 19:10:3387 os.path.dirname(__file__), os.path.pardir, os.path.pardir,
88 'third_party'))
Yuke Liaoea228d02018-01-05 19:10:3389from collections import defaultdict
90
Max Moroz1de68d72018-08-21 13:38:1891import coverage_utils
92
Yuke Liao082e99632018-05-18 15:40:4093# Absolute path to the code coverage tools binary. These paths can be
94# overwritten by user specified coverage tool paths.
pasthanab37d5bfd2020-05-28 12:18:3195# Absolute path to the root of the checkout.
96SRC_ROOT_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)),
97 os.path.pardir, os.path.pardir)
98LLVM_BIN_DIR = os.path.join(
99 os.path.join(SRC_ROOT_PATH, 'third_party', 'llvm-build', 'Release+Asserts'),
100 'bin')
Abhishek Arya1c97ea542018-05-10 03:53:19101LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
102LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:54103
Abhishek Arya03911092018-05-21 16:42:35104
Yuke Liao506e8822017-12-04 16:52:54105# Build directory, the value is parsed from command line arguments.
106BUILD_DIR = None
107
108# Output directory for generated artifacts, the value is parsed from command
109# line arguemnts.
110OUTPUT_DIR = None
111
Yuke Liao506e8822017-12-04 16:52:54112# Name of the file extension for profraw data files.
113PROFRAW_FILE_EXTENSION = 'profraw'
114
115# Name of the final profdata file, and this file needs to be passed to
116# "llvm-cov" command in order to call "llvm-cov show" to inspect the
117# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48118PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
119
120# Name of the file with summary information generated by llvm-cov export.
121SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54122
Akekawit Jitprasertf9cb6622021-08-24 17:48:02123# Name of the coverage file in lcov format generated by llvm-cov export.
124LCOV_FILE_NAME = os.extsep.join(['coverage', 'lcov'])
125
Yuke Liao506e8822017-12-04 16:52:54126# Build arg required for generating code coverage data.
127CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
128
Max Moroz7c5354f2018-05-06 00:03:48129LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37130
131# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19132COMPONENT_MAPPING_URL = (
133 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37134
Yuke Liao80afff32018-03-07 01:26:20135# Caches the results returned by _GetBuildArgs, don't use this variable
136# directly, call _GetBuildArgs instead.
137_BUILD_ARGS = None
138
Abhishek Aryac19bc5ef2018-05-04 22:10:02139# Retry failed merges.
140MERGE_RETRIES = 3
141
Abhishek Aryad35de7e2018-05-10 22:23:04142# Message to guide user to file a bug when everything else fails.
143FILE_BUG_MESSAGE = (
144 'If it persists, please file a bug with the command you used, git revision '
145 'and args.gn config here: '
146 'https://bugs.chromium.org/p/chromium/issues/entry?'
Yuke Liao03c644072019-07-30 18:33:40147 'components=Infra%3ETest%3ECodeCoverage')
Abhishek Aryad35de7e2018-05-10 22:23:04148
Abhishek Aryabd0655d2018-05-21 19:55:24149# String to replace with actual llvm profile path.
150LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
151
Yuke Liao082e99632018-05-18 15:40:40152def _ConfigureLLVMCoverageTools(args):
153 """Configures llvm coverage tools."""
154 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18155 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40156 global LLVM_COV_PATH
157 global LLVM_PROFDATA_PATH
158 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
159 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
160 else:
Choongwoo Hanbd1aa952021-06-09 22:25:38161 subprocess.check_call([
Akekawit Jitprasert928671e2021-09-20 18:40:58162 sys.executable, 'tools/clang/scripts/update.py', '--package',
163 'coverage_tools'
Choongwoo Hanbd1aa952021-06-09 22:25:38164 ])
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
Lei Zhang42a5b51a2022-03-07 19:16:16251def _GeneratePerFileLineByLineCoverageInLcov(binary_paths, profdata_file_path,
252 filters, ignore_filename_regex):
Akekawit Jitprasertf9cb6622021-08-24 17:48:02253 """Generates per file line-by-line coverage using "llvm-cov export".
254
255 Args:
256 binary_paths: A list of paths to the instrumented binaries.
257 profdata_file_path: A path to the profdata file.
258 filters: A list of directories and files to get coverage for.
259 ignore_filename_regex: A regular expression for skipping source code files
260 with certain file paths.
261 """
262 logging.debug('Generating per file line by line coverage reports using '
263 '"llvm-cov export" command.')
264 for path in binary_paths:
265 if not os.path.exists(path):
266 logging.error("Binary %s does not exist", path)
267 subprocess_cmd = [
268 LLVM_COV_PATH, 'export', '-format=lcov',
269 '-instr-profile=' + profdata_file_path, binary_paths[0]
270 ]
271 subprocess_cmd.extend(
272 ['-object=' + binary_path for binary_path in binary_paths[1:]])
273 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
274 subprocess_cmd.extend(filters)
275 if ignore_filename_regex:
276 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
277
278 # Write output on the disk to be used by code coverage bot.
279 with open(_GetLcovFilePath(), 'w') as f:
280 subprocess.check_call(subprocess_cmd, stdout=f)
281
282 logging.debug('Finished running "llvm-cov export" command.')
283
284
Max Moroz7c5354f2018-05-06 00:03:48285def _GetLogsDirectoryPath():
286 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18287 return os.path.join(
288 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48289
290
291def _GetProfdataFilePath():
292 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18293 return os.path.join(
294 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
295 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48296
297
298def _GetSummaryFilePath():
299 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18300 return os.path.join(
301 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
302 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33303
304
Akekawit Jitprasertf9cb6622021-08-24 17:48:02305def _GetLcovFilePath():
306 """The LCOV file that contains coverage data written by llvm-cov export."""
307 return os.path.join(
308 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
309 LCOV_FILE_NAME)
310
311
Yuke Liao506e8822017-12-04 16:52:54312def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
313 """Builds and runs target to generate the coverage profile data.
314
315 Args:
316 targets: A list of targets to build with coverage instrumentation.
317 commands: A list of commands used to run the targets.
318 jobs_count: Number of jobs to run in parallel for building. If None, a
319 default value is derived based on CPUs availability.
320
321 Returns:
322 A relative path to the generated profdata file.
323 """
324 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02325 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59326 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02327 coverage_profdata_file_path = (
328 _CreateCoverageProfileDataFromTargetProfDataFiles(
329 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54330
Abhishek Aryac19bc5ef2018-05-04 22:10:02331 for target_profdata_file_path in target_profdata_file_paths:
332 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52333
Abhishek Aryac19bc5ef2018-05-04 22:10:02334 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54335
336
337def _BuildTargets(targets, jobs_count):
338 """Builds target with Clang coverage instrumentation.
339
340 This function requires current working directory to be the root of checkout.
341
342 Args:
343 targets: A list of targets to build with coverage instrumentation.
344 jobs_count: Number of jobs to run in parallel for compilation. If None, a
345 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54346 """
Abhishek Aryafb70b532018-05-06 17:47:40347 logging.info('Building %s.', str(targets))
Brent McBrideb25b177a42020-05-11 18:13:06348 autoninja = 'autoninja'
349 if coverage_utils.GetHostPlatform() == 'win':
350 autoninja += '.bat'
Yuke Liao506e8822017-12-04 16:52:54351
Brent McBrideb25b177a42020-05-11 18:13:06352 subprocess_cmd = [autoninja, '-C', BUILD_DIR]
Yuke Liao506e8822017-12-04 16:52:54353 if jobs_count is not None:
354 subprocess_cmd.append('-j' + str(jobs_count))
355
356 subprocess_cmd.extend(targets)
357 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40358 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54359
360
Abhishek Aryac19bc5ef2018-05-04 22:10:02361def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54362 """Runs commands and returns the relative paths to the profraw data files.
363
364 Args:
365 targets: A list of targets built with coverage instrumentation.
366 commands: A list of commands used to run the targets.
367
368 Returns:
369 A list of relative paths to the generated profraw data files.
370 """
Abhishek Aryafb70b532018-05-06 17:47:40371 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10372
Yuke Liao506e8822017-12-04 16:52:54373 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18374 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
375 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54376 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18377 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48378
379 # Ensure that logs directory exists.
380 if not os.path.exists(_GetLogsDirectoryPath()):
381 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54382
Abhishek Aryac19bc5ef2018-05-04 22:10:02383 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10384
Yuke Liaod4a9865202018-01-12 23:17:52385 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54386 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48387 output_file_name = os.extsep.join([target + '_output', 'log'])
388 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10389
Abhishek Aryac19bc5ef2018-05-04 22:10:02390 profdata_file_path = None
Prakhar65d63832021-06-16 23:01:37391 for _ in range(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40392 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02393 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10394
Abhishek Aryac19bc5ef2018-05-04 22:10:02395 if _IsIOSCommand(command):
396 # On iOS platform, due to lack of write permissions, profraw files are
397 # generated outside of the OUTPUT_DIR, and the exact paths are contained
398 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35399 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02400 else:
401 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35402 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02403
404 profraw_file_paths = []
405 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57406 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Ben Joyce88282362021-01-29 23:53:31407 elif _IsAndroid():
408 android_coverage_dir = os.path.join(BUILD_DIR, 'coverage')
409 for r, _, files in os.walk(android_coverage_dir):
410 for f in files:
411 if f.endswith(PROFRAW_FILE_EXTENSION):
412 profraw_file_paths.append(os.path.join(r, f))
Abhishek Aryac19bc5ef2018-05-04 22:10:02413 else:
Max Moroz1de68d72018-08-21 13:38:18414 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02415 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48416 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18417 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02418
419 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40420 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04421 'please make sure the binary exists, is properly instrumented and '
422 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02423
Yuke Liao9c2c70b2018-05-23 15:37:57424 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18425 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
426 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57427
Abhishek Aryac19bc5ef2018-05-04 22:10:02428 try:
429 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
430 target, profraw_file_paths)
431 break
432 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04433 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02434 finally:
435 # Remove profraw files now so that they are not used in next iteration.
436 for profraw_file_path in profraw_file_paths:
437 os.remove(profraw_file_path)
438
439 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04440 'Failed to merge target "%s" profraw files after %d retries. %s' %
441 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02442 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54443
Abhishek Aryafb70b532018-05-06 17:47:40444 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10445
Abhishek Aryac19bc5ef2018-05-04 22:10:02446 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54447
448
Abhishek Arya03911092018-05-21 16:42:35449def _GetEnvironmentVars(profraw_file_path):
450 """Return environment vars for subprocess, given a profraw file path."""
451 env = os.environ.copy()
452 env.update({
453 'LLVM_PROFILE_FILE': profraw_file_path,
454 'PATH': _GetPathWithLLVMSymbolizerDir()
455 })
456 return env
457
458
Sajjad Mirza0b96e002020-11-10 19:32:55459def _SplitCommand(command):
460 """Split a command string into parts in a platform-specific way."""
461 if coverage_utils.GetHostPlatform() == 'win':
462 return command.split()
463 return shlex.split(command)
464
465
Abhishek Arya03911092018-05-21 16:42:35466def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10467 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52468 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01469 #
Max Morozd73e45f2018-04-24 18:32:47470 # "%p" expands out to the process ID. It's not used by this scripts due to:
471 # 1) If a target program spawns too many processess, it may exhaust all disk
472 # space available. For example, unit_tests writes thousands of .profraw
473 # files each of size 1GB+.
474 # 2) If a target binary uses shared libraries, coverage profile data for them
475 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01476 #
Yuke Liaod4a9865202018-01-12 23:17:52477 # "%Nm" expands out to the instrumented binary's signature. When this pattern
478 # is specified, the runtime creates a pool of N raw profiles which are used
479 # for on-line profile merging. The runtime takes care of selecting a raw
480 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52481 # N must be between 1 and 9. The merge pool specifier can only occur once per
482 # filename pattern.
483 #
Max Morozd73e45f2018-04-24 18:32:47484 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01485 #
Max Morozd73e45f2018-04-24 18:32:47486 # For other cases, "%4m" is chosen as it creates some level of parallelism,
487 # but it's not too big to consume too much computing resource or disk space.
488 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59489 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01490 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18491 expected_profraw_file_path = os.path.join(
492 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
493 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24494 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
495 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54496
Yuke Liaoa0c8c2f2018-02-28 20:14:10497 try:
Max Moroz7c5354f2018-05-06 00:03:48498 # Some fuzz targets or tests may write into stderr, redirect it as well.
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(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10504 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35505 logging.warning('Command: "%s" exited with non-zero return code.', command)
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
Yuke Liao27349c92018-03-22 21:10:01510def _IsFuzzerTarget(target):
511 """Returns true if the target is a fuzzer target."""
512 build_args = _GetBuildArgs()
513 use_libfuzzer = ('use_libfuzzer' in build_args and
514 build_args['use_libfuzzer'] == 'true')
515 return use_libfuzzer and target.endswith('_fuzzer')
516
517
Abhishek Arya03911092018-05-21 16:42:35518def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10519 """Runs a single iOS command and generates a profraw data file.
520
521 iOS application doesn't have write access to folders outside of the app, so
522 it's impossible to instruct the app to flush the profraw data file to the
523 desired location. The profraw data file will be generated somewhere within the
524 application's Documents folder, and the full path can be obtained by parsing
525 the output.
526 """
Yuke Liaob2926832018-03-02 17:34:29527 assert _IsIOSCommand(command)
528
529 # After running tests, iossim generates a profraw data file, it won't be
530 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
531 # checkout.
532 iossim_profraw_file_path = os.path.join(
533 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24534 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
535 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10536
537 try:
Abhishek Arya03911092018-05-21 16:42:35538 with open(output_file_path, 'wb') as output_file_handle:
Sajjad Mirza0b96e002020-11-10 19:32:55539 subprocess.check_call(_SplitCommand(command),
540 stdout=output_file_handle,
541 stderr=subprocess.STDOUT,
542 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10543 except subprocess.CalledProcessError as e:
544 # iossim emits non-zero return code even if tests run successfully, so
545 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35546 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10547
Abhishek Arya03911092018-05-21 16:42:35548 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10549
550
551def _GetProfrawDataFileByParsingOutput(output):
552 """Returns the path to the profraw data file obtained by parsing the output.
553
554 The output of running the test target has no format, but it is guaranteed to
555 have a single line containing the path to the generated profraw data file.
556 NOTE: This should only be called when target os is iOS.
557 """
Yuke Liaob2926832018-03-02 17:34:29558 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10559
Yuke Liaob2926832018-03-02 17:34:29560 output_by_lines = ''.join(output).splitlines()
561 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10562
563 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29564 result = profraw_file_pattern.match(line)
565 if result:
566 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10567
568 assert False, ('No profraw data file was generated, did you call '
569 'coverage_util::ConfigureCoverageReportPath() in test setup? '
570 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54571
572
Abhishek Aryac19bc5ef2018-05-04 22:10:02573def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
574 """Returns a relative path to coverage profdata file by merging target
575 profdata files.
Yuke Liao506e8822017-12-04 16:52:54576
577 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02578 profdata_file_paths: A list of relative paths to the profdata data files
579 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54580
581 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02582 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54583
584 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02585 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54586 """
Abhishek Aryafb70b532018-05-06 17:47:40587 logging.info('Creating the coverage profile data file.')
588 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48589 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54590 try:
Abhishek Arya1ec832c2017-12-05 18:06:59591 subprocess_cmd = [
592 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
593 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02594 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01595
596 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18597 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02598 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04599 logging.error(
600 'Failed to merge target profdata files to create coverage profdata. %s',
601 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02602 raise error
603
Abhishek Aryafb70b532018-05-06 17:47:40604 logging.debug('Finished merging target profdata files.')
605 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02606 profdata_file_path)
607 return profdata_file_path
608
609
610def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
611 """Returns a relative path to target profdata file by merging target
612 profraw files.
613
614 Args:
615 profraw_file_paths: A list of relative paths to the profdata data files
616 that are to be merged.
617
618 Returns:
619 A relative path to the merged coverage profdata file.
620
621 Raises:
622 CalledProcessError: An error occurred merging profdata files.
623 """
Abhishek Aryafb70b532018-05-06 17:47:40624 logging.info('Creating target profile data file.')
625 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02626 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
627
628 try:
629 subprocess_cmd = [
630 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
631 ]
Yuke Liao506e8822017-12-04 16:52:54632 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01633 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18634 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54635 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04636 logging.error(
637 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54638 raise error
639
Abhishek Aryafb70b532018-05-06 17:47:40640 logging.debug('Finished merging target profraw files.')
641 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10642 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54643 return profdata_file_path
644
645
Yuke Liao0e4c8682018-04-18 21:06:59646def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
647 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33648 """Generates per file coverage summary using "llvm-cov export" command."""
649 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
650 # [[-object BIN]] [SOURCES].
651 # NOTE: For object files, the first one is specified as a positional argument,
652 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10653 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40654 'export -summary-only" command.')
Sajjad Mirza07f52332020-11-11 01:50:47655 for path in binary_paths:
656 if not os.path.exists(path):
657 logging.error("Binary %s does not exist", path)
Yuke Liaoea228d02018-01-05 19:10:33658 subprocess_cmd = [
659 LLVM_COV_PATH, 'export', '-summary-only',
Choongwoo Han56752522021-06-10 17:38:34660 '-compilation-dir={}'.format(BUILD_DIR),
Yuke Liaoea228d02018-01-05 19:10:33661 '-instr-profile=' + profdata_file_path, binary_paths[0]
662 ]
663 subprocess_cmd.extend(
664 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29665 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33666 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59667 if ignore_filename_regex:
668 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33669
Max Moroz7c5354f2018-05-06 00:03:48670 export_output = subprocess.check_output(subprocess_cmd)
671
672 # Write output on the disk to be used by code coverage bot.
Prakhar65d63832021-06-16 23:01:37673 with open(_GetSummaryFilePath(), 'wb') as f:
Max Moroz7c5354f2018-05-06 00:03:48674 f.write(export_output)
675
Max Moroz1de68d72018-08-21 13:38:18676 return export_output
Yuke Liaoea228d02018-01-05 19:10:33677
678
Yuke Liaob2926832018-03-02 17:34:29679def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
680 """Appends -arch arguments to the command list if it's ios platform.
681
682 iOS binaries are universal binaries, and require specifying the architecture
683 to use, and one architecture needs to be specified for each binary.
684 """
685 if _IsIOS():
686 cmd_list.extend(['-arch=x86_64'] * num_archs)
687
688
Yuke Liao506e8822017-12-04 16:52:54689def _GetBinaryPath(command):
690 """Returns a relative path to the binary to be run by the command.
691
Yuke Liao545db322018-02-15 17:12:01692 Currently, following types of commands are supported (e.g. url_unittests):
693 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
694 2. Use xvfb.
695 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
696 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37697 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
698 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10699 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37700 <iossim_arguments> -c <app_arguments>
701 out/Coverage-iphonesimulator/url_unittests.app"
702
Yuke Liao506e8822017-12-04 16:52:54703 Args:
704 command: A command used to run a target.
705
706 Returns:
707 A relative path to the binary.
708 """
Yuke Liao545db322018-02-15 17:12:01709 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
710
Sajjad Mirza0b96e002020-11-10 19:32:55711 command_parts = _SplitCommand(command)
Yuke Liao545db322018-02-15 17:12:01712 if os.path.basename(command_parts[0]) == 'python':
713 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40714 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01715 return command_parts[2]
716
717 if os.path.basename(command_parts[0]) == xvfb_script_name:
718 return command_parts[1]
719
Yuke Liaob2926832018-03-02 17:34:29720 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10721 # For a given application bundle, the binary resides in the bundle and has
722 # the same name with the application without the .app extension.
Artem Titarenko2b464952018-11-07 17:22:02723 app_path = command_parts[1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10724 app_name = os.path.splitext(os.path.basename(app_path))[0]
725 return os.path.join(app_path, app_name)
726
Sajjad Mirza07f52332020-11-11 01:50:47727 if coverage_utils.GetHostPlatform() == 'win' \
728 and not command_parts[0].endswith('.exe'):
729 return command_parts[0] + '.exe'
730
Yuke Liaob2926832018-03-02 17:34:29731 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54732
733
Yuke Liaob2926832018-03-02 17:34:29734def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10735 """Returns true if command is used to run tests on iOS platform."""
Sajjad Mirza0b96e002020-11-10 19:32:55736 return os.path.basename(_SplitCommand(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10737
738
Yuke Liao95d13d72017-12-07 18:18:50739def _VerifyTargetExecutablesAreInBuildDirectory(commands):
740 """Verifies that the target executables specified in the commands are inside
741 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54742 for command in commands:
743 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18744 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35745 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50746 'Target executable "%s" in command: "%s" is outside of '
747 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54748
749
750def _ValidateBuildingWithClangCoverage():
751 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20752 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54753
754 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
755 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59756 assert False, ('\'{} = true\' is required in args.gn.'
757 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54758
759
Yuke Liaoc60b2d02018-03-02 21:40:43760def _ValidateCurrentPlatformIsSupported():
761 """Asserts that this script suports running on the current platform"""
762 target_os = _GetTargetOS()
763 if target_os:
764 current_platform = target_os
765 else:
Max Moroz1de68d72018-08-21 13:38:18766 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43767
Ben Joyce88282362021-01-29 23:53:31768 supported_platforms = ['android', 'chromeos', 'ios', 'linux', 'mac', 'win']
769 assert current_platform in supported_platforms, ('Coverage is only'
770 'supported on %s' %
771 supported_platforms)
Yuke Liaoc60b2d02018-03-02 21:40:43772
773
Yuke Liao80afff32018-03-07 01:26:20774def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54775 """Parses args.gn file and returns results as a dictionary.
776
777 Returns:
778 A dictionary representing the build args.
779 """
Yuke Liao80afff32018-03-07 01:26:20780 global _BUILD_ARGS
781 if _BUILD_ARGS is not None:
782 return _BUILD_ARGS
783
784 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54785 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
786 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
787 'missing args.gn file.' % BUILD_DIR)
788 with open(build_args_path) as build_args_file:
789 build_args_lines = build_args_file.readlines()
790
Yuke Liao506e8822017-12-04 16:52:54791 for build_arg_line in build_args_lines:
792 build_arg_without_comments = build_arg_line.split('#')[0]
793 key_value_pair = build_arg_without_comments.split('=')
794 if len(key_value_pair) != 2:
795 continue
796
797 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43798
799 # Values are wrapped within a pair of double-quotes, so remove the leading
800 # and trailing double-quotes.
801 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20802 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54803
Yuke Liao80afff32018-03-07 01:26:20804 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54805
806
Abhishek Arya16f059a2017-12-07 17:47:32807def _VerifyPathsAndReturnAbsolutes(paths):
808 """Verifies that the paths specified in |paths| exist and returns absolute
809 versions.
Yuke Liao66da1732017-12-05 22:19:42810
811 Args:
812 paths: A list of files or directories.
813 """
Abhishek Arya16f059a2017-12-07 17:47:32814 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42815 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32816 absolute_path = os.path.join(SRC_ROOT_PATH, path)
817 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
818
819 absolute_paths.append(absolute_path)
820
821 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42822
823
Abhishek Arya64636af2018-05-04 14:42:13824def _GetBinaryPathsFromTargets(targets, build_dir):
825 """Return binary paths from target names."""
Ben Joyce88282362021-01-29 23:53:31826 # TODO(crbug.com/899974): Derive output binary from target build definitions
827 # rather than assuming that it is always the same name.
Abhishek Arya64636af2018-05-04 14:42:13828 binary_paths = []
829 for target in targets:
830 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18831 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13832 binary_path += '.exe'
833
834 if os.path.exists(binary_path):
835 binary_paths.append(binary_path)
836 else:
837 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40838 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13839 os.path.basename(binary_path))
840
841 return binary_paths
842
843
Abhishek Arya03911092018-05-21 16:42:35844def _GetCommandForWebTests(arguments):
845 """Return command to run for blink web tests."""
Dirk Pranke34c093a42021-03-25 19:19:05846 cpu_count = multiprocessing.cpu_count()
847 if sys.platform == 'win32':
848 # TODO(crbug.com/1190269) - we can't use more than 56
849 # cores on Windows or Python3 may hang.
850 cpu_count = min(cpu_count, 56)
851 cpu_count = max(1, cpu_count // 2)
852
Abhishek Arya03911092018-05-21 16:42:35853 command_list = [
854 'python', 'testing/xvfb.py', 'python',
855 'third_party/blink/tools/run_web_tests.py',
856 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24857 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Robert Ma339fc472018-12-14 21:22:42858 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Dirk Pranke34c093a42021-03-25 19:19:05859 '--child-processes=%d' % cpu_count, '--disable-breakpad',
860 '--no-show-results', '--skip-failing-tests',
Weizhong Xia91b53362022-01-05 17:13:35861 '--target=%s' % os.path.basename(BUILD_DIR), '--timeout-ms=30000'
Abhishek Arya03911092018-05-21 16:42:35862 ]
863 if arguments.strip():
864 command_list.append(arguments)
865 return ' '.join(command_list)
866
867
Ben Joyce88282362021-01-29 23:53:31868def _GetBinaryPathsForAndroid(targets):
869 """Return binary paths used when running android tests."""
870 # TODO(crbug.com/899974): Implement approach that doesn't assume .so file is
871 # based on the target's name.
872 android_binaries = set()
873 for target in targets:
874 so_library_path = os.path.join(BUILD_DIR, 'lib.unstripped',
875 'lib%s__library.so' % target)
876 if os.path.exists(so_library_path):
877 android_binaries.add(so_library_path)
878
879 return list(android_binaries)
880
881
Abhishek Arya03911092018-05-21 16:42:35882def _GetBinaryPathForWebTests():
883 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18884 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35885 if host_platform == 'win':
886 return os.path.join(BUILD_DIR, 'content_shell.exe')
887 elif host_platform == 'linux':
888 return os.path.join(BUILD_DIR, 'content_shell')
889 elif host_platform == 'mac':
890 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
891 'Content Shell')
892 else:
893 assert False, 'This platform is not supported for web tests.'
894
895
Abhishek Aryae5811afa2018-05-24 03:56:01896def _SetupOutputDir():
897 """Setup output directory."""
898 if os.path.exists(OUTPUT_DIR):
899 shutil.rmtree(OUTPUT_DIR)
900
901 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18902 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01903
904
Yuke Liaoabfbba42019-06-11 16:03:59905def _SetMacXcodePath():
906 """Set DEVELOPER_DIR to the path to hermetic Xcode.app on Mac OS X."""
907 if sys.platform != 'darwin':
908 return
909
910 xcode_path = os.path.join(SRC_ROOT_PATH, 'build', 'mac_files', 'Xcode.app')
911 if os.path.exists(xcode_path):
912 os.environ['DEVELOPER_DIR'] = xcode_path
913
914
Yuke Liao506e8822017-12-04 16:52:54915def _ParseCommandArguments():
916 """Adds and parses relevant arguments for tool comands.
917
918 Returns:
919 A dictionary representing the arguments.
920 """
921 arg_parser = argparse.ArgumentParser()
922 arg_parser.usage = __doc__
923
Abhishek Arya1ec832c2017-12-05 18:06:59924 arg_parser.add_argument(
925 '-b',
926 '--build-dir',
927 type=str,
928 required=True,
929 help='The build directory, the path needs to be relative to the root of '
930 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54931
Abhishek Arya1ec832c2017-12-05 18:06:59932 arg_parser.add_argument(
933 '-o',
934 '--output-dir',
935 type=str,
936 required=True,
937 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54938
Abhishek Arya1ec832c2017-12-05 18:06:59939 arg_parser.add_argument(
940 '-c',
941 '--command',
942 action='append',
Abhishek Arya64636af2018-05-04 14:42:13943 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59944 help='Commands used to run test targets, one test target needs one and '
945 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13946 'current working directory is the root of the checkout. This option is '
947 'incompatible with -p/--profdata-file option.')
948
949 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35950 '-wt',
951 '--web-tests',
952 nargs='?',
953 type=str,
954 const=' ',
955 required=False,
956 help='Run blink web tests. Support passing arguments to run_web_tests.py')
957
958 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13959 '-p',
960 '--profdata-file',
961 type=str,
962 required=False,
963 help='Path to profdata file to use for generating code coverage reports. '
964 'This can be useful if you generated the profdata file seperately in '
965 'your own test harness. This option is ignored if run command(s) are '
966 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54967
Abhishek Arya1ec832c2017-12-05 18:06:59968 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42969 '-f',
970 '--filters',
971 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32972 required=False,
Yuke Liao66da1732017-12-05 22:19:42973 help='Directories or files to get code coverage for, and all files under '
974 'the directories are included recursively.')
975
976 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59977 '-i',
978 '--ignore-filename-regex',
979 type=str,
980 help='Skip source code files with file paths that match the given '
981 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
982 'to exclude files in third_party/ and out/ folders from the report.')
983
984 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32985 '--no-file-view',
986 action='store_true',
987 help='Don\'t generate the file view in the coverage report. When there '
988 'are large number of html files, the file view becomes heavy and may '
989 'cause the browser to freeze, and this argument comes handy.')
990
991 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18992 '--no-component-view',
993 action='store_true',
994 help='Don\'t generate the component view in the coverage report.')
995
996 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:40997 '--coverage-tools-dir',
998 type=str,
999 help='Path of the directory where LLVM coverage tools (llvm-cov, '
1000 'llvm-profdata) exist. This should be only needed if you are testing '
1001 'against a custom built clang revision. Otherwise, we pick coverage '
1002 'tools automatically from your current source checkout.')
1003
1004 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:591005 '-j',
1006 '--jobs',
1007 type=int,
1008 default=None,
1009 help='Run N jobs to build in parallel. If not specified, a default value '
Max Moroz06576292019-01-03 19:22:521010 'will be derived based on CPUs and goma availability. Please refer to '
1011 '\'autoninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:541012
Abhishek Arya1ec832c2017-12-05 18:06:591013 arg_parser.add_argument(
Sahel Sharify38cabdc2020-01-16 00:40:011014 '--format',
1015 type=str,
1016 default='html',
Akekawit Jitprasertf9cb6622021-08-24 17:48:021017 help='Output format of the "llvm-cov show/export" command. The '
1018 'supported formats are "text", "html" and "lcov".')
Sahel Sharify38cabdc2020-01-16 00:40:011019
1020 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:101021 '-v',
1022 '--verbose',
1023 action='store_true',
1024 help='Prints additional output for diagnostics.')
1025
1026 arg_parser.add_argument(
1027 '-l', '--log_file', type=str, help='Redirects logs to a file.')
1028
1029 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:021030 'targets',
1031 nargs='+',
1032 help='The names of the test targets to run. If multiple run commands are '
1033 'specified using the -c/--command option, then the order of targets and '
1034 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:541035
1036 args = arg_parser.parse_args()
1037 return args
1038
1039
1040def Main():
1041 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:401042
Abhishek Arya64636af2018-05-04 14:42:131043 # Change directory to source root to aid in relative paths calculations.
1044 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:111045
pasthanaa4844112020-05-21 18:03:551046 # Setup coverage binaries even when script is called with empty params. This
1047 # is used by coverage bot for initial setup.
1048 if len(sys.argv) == 1:
Choongwoo Hanbd1aa952021-06-09 22:25:381049 subprocess.check_call([
Akekawit Jitprasert928671e2021-09-20 18:40:581050 sys.executable, 'tools/clang/scripts/update.py', '--package',
1051 'coverage_tools'
Choongwoo Hanbd1aa952021-06-09 22:25:381052 ])
pasthanaa4844112020-05-21 18:03:551053 print(__doc__)
1054 return
1055
Yuke Liao506e8822017-12-04 16:52:541056 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:181057 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:401058 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:131059
Yuke Liao506e8822017-12-04 16:52:541060 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:181061 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:011062
Yuke Liao506e8822017-12-04 16:52:541063 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:181064 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:541065
Abhishek Arya03911092018-05-21 16:42:351066 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:131067 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:351068 'provide prof-data file as input using -p/--profdata-file option OR '
1069 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:431070
Abhishek Arya64636af2018-05-04 14:42:131071 assert not args.command or (len(args.targets) == len(args.command)), (
1072 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:431073
Abhishek Arya1ec832c2017-12-05 18:06:591074 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:401075 'Build directory: "%s" doesn\'t exist. '
1076 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:131077
Yuke Liaoc60b2d02018-03-02 21:40:431078 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:541079 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:321080
1081 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:421082 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:321083 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:421084
Abhishek Aryae5811afa2018-05-24 03:56:011085 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:541086
Abhishek Arya03911092018-05-21 16:42:351087 # Get .profdata file and list of binary paths.
1088 if args.web_tests:
1089 commands = [_GetCommandForWebTests(args.web_tests)]
1090 profdata_file_path = _CreateCoverageProfileDataForTargets(
1091 args.targets, commands, args.jobs)
1092 binary_paths = [_GetBinaryPathForWebTests()]
1093 elif args.command:
1094 for i in range(len(args.command)):
1095 assert not 'run_web_tests.py' in args.command[i], (
1096 'run_web_tests.py is not supported via --command argument. '
1097 'Please use --run-web-tests argument instead.')
1098
Abhishek Arya64636af2018-05-04 14:42:131099 # A list of commands are provided. Run them to generate profdata file, and
1100 # create a list of binary paths from parsing commands.
1101 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1102 profdata_file_path = _CreateCoverageProfileDataForTargets(
1103 args.targets, args.command, args.jobs)
1104 binary_paths = [_GetBinaryPath(command) for command in args.command]
1105 else:
1106 # An input prof-data file is already provided. Just calculate binary paths.
1107 profdata_file_path = args.profdata_file
1108 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331109
Erik Chen283b92c72019-07-22 16:37:391110 # If the checkout uses the hermetic xcode binaries, then otool must be
1111 # directly invoked. The indirection via /usr/bin/otool won't work unless
1112 # there's an actual system install of Xcode.
1113 otool_path = None
1114 if sys.platform == 'darwin':
1115 hermetic_otool_path = os.path.join(
1116 SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents',
1117 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin',
1118 'otool')
1119 if os.path.exists(hermetic_otool_path):
1120 otool_path = hermetic_otool_path
Ben Joyce88282362021-01-29 23:53:311121
1122 if _IsAndroid():
1123 binary_paths = _GetBinaryPathsForAndroid(args.targets)
1124 elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
Brent McBrideb25b177a42020-05-11 18:13:061125 binary_paths.extend(
1126 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path))
Abhishek Arya78120bc2018-05-07 20:53:541127
Akekawit Jitprasertf9cb6622021-08-24 17:48:021128 assert args.format in ['html', 'lcov', 'text'], (
1129 '%s is not a valid output format for "llvm-cov show/export". Only '
1130 '"text", "html" and "lcov" formats are supported.' % (args.format))
Sahel Sharify38cabdc2020-01-16 00:40:011131 logging.info('Generating code coverage report in %s (this can take a while '
1132 'depending on size of target!).' % (args.format))
Max Moroz1de68d72018-08-21 13:38:181133 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591134 binary_paths, profdata_file_path, absolute_filter_paths,
1135 args.ignore_filename_regex)
Akekawit Jitprasertf9cb6622021-08-24 17:48:021136
1137 if args.format == 'lcov':
1138 _GeneratePerFileLineByLineCoverageInLcov(
1139 binary_paths, profdata_file_path, absolute_filter_paths,
1140 args.ignore_filename_regex)
1141 return
1142
Sahel Sharify38cabdc2020-01-16 00:40:011143 _GeneratePerFileLineByLineCoverageInFormat(
1144 binary_paths, profdata_file_path, absolute_filter_paths,
1145 args.ignore_filename_regex, args.format)
Max Moroz1de68d72018-08-21 13:38:181146 component_mappings = None
1147 if not args.no_component_view:
Choongwoo Hanbd1aa952021-06-09 22:25:381148 component_mappings = json.load(urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371149
Max Moroz1de68d72018-08-21 13:38:181150 # Call prepare here.
1151 processor = coverage_utils.CoverageReportPostProcessor(
1152 OUTPUT_DIR,
1153 SRC_ROOT_PATH,
1154 per_file_summary_data,
1155 no_component_view=args.no_component_view,
1156 no_file_view=args.no_file_view,
1157 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371158
Sahel Sharify38cabdc2020-01-16 00:40:011159 if args.format == 'html':
1160 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541161
Abhishek Arya1ec832c2017-12-05 18:06:591162
Yuke Liao506e8822017-12-04 16:52:541163if __name__ == '__main__':
1164 sys.exit(Main())