blob: 688605d60ba1dbda2b0519b30ebe1dc97d33bb1b [file] [log] [blame]
Yuke Liao506e8822017-12-04 16:52:541#!/usr/bin/python
2# Copyright 2017 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
Abhishek Arya1ec832c2017-12-05 18:06:595"""This script helps to generate code coverage report.
Yuke Liao506e8822017-12-04 16:52:546
Abhishek Arya1ec832c2017-12-05 18:06:597 It uses Clang Source-based Code Coverage -
8 https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
Yuke Liao506e8822017-12-04 16:52:549
Abhishek Arya16f059a2017-12-07 17:47:3210 In order to generate code coverage report, you need to first add
Yuke Liaoab9c44e2018-02-21 00:24:4011 "use_clang_coverage=true" and "is_component_build=false" GN flags to args.gn
12 file in your build output directory (e.g. out/coverage).
Yuke Liao506e8822017-12-04 16:52:5413
Abhishek Arya03911092018-05-21 16:42:3514 * Example usage:
Abhishek Arya1ec832c2017-12-05 18:06:5915
Abhishek Arya16f059a2017-12-07 17:47:3216 gn gen out/coverage --args='use_clang_coverage=true is_component_build=false'
17 gclient runhooks
Abhishek Arya1ec832c2017-12-05 18:06:5918 python tools/code_coverage/coverage.py crypto_unittests url_unittests \\
Abhishek Arya16f059a2017-12-07 17:47:3219 -b out/coverage -o out/report -c 'out/coverage/crypto_unittests' \\
20 -c 'out/coverage/url_unittests --gtest_filter=URLParser.PathURL' \\
21 -f url/ -f crypto/
Abhishek Arya1ec832c2017-12-05 18:06:5922
Abhishek Arya16f059a2017-12-07 17:47:3223 The command above builds crypto_unittests and url_unittests targets and then
24 runs them with specified command line arguments. For url_unittests, it only
25 runs the test URLParser.PathURL. The coverage report is filtered to include
26 only files and sub-directories under url/ and crypto/ directories.
Abhishek Arya1ec832c2017-12-05 18:06:5927
Yuke Liao545db322018-02-15 17:12:0128 If you want to run tests that try to draw to the screen but don't have a
29 display connected, you can run tests in headless mode with xvfb.
30
Abhishek Arya03911092018-05-21 16:42:3531 * Sample flow for running a test target with xvfb (e.g. unit_tests):
Yuke Liao545db322018-02-15 17:12:0132
33 python tools/code_coverage/coverage.py unit_tests -b out/coverage \\
34 -o out/report -c 'python testing/xvfb.py out/coverage/unit_tests'
35
Abhishek Arya1ec832c2017-12-05 18:06:5936 If you are building a fuzz target, you need to add "use_libfuzzer=true" GN
37 flag as well.
38
Abhishek Arya03911092018-05-21 16:42:3539 * Sample workflow for a fuzz target (e.g. pdfium_fuzzer):
Abhishek Arya1ec832c2017-12-05 18:06:5940
Abhishek Arya16f059a2017-12-07 17:47:3241 python tools/code_coverage/coverage.py pdfium_fuzzer \\
42 -b out/coverage -o out/report \\
43 -c 'out/coverage/pdfium_fuzzer -runs=<runs> <corpus_dir>' \\
44 -f third_party/pdfium
Abhishek Arya1ec832c2017-12-05 18:06:5945
46 where:
47 <corpus_dir> - directory containing samples files for this format.
48 <runs> - number of times to fuzz target function. Should be 0 when you just
49 want to see the coverage on corpus and don't want to fuzz at all.
50
Abhishek Arya03911092018-05-21 16:42:3551 * Sample workflow for running Blink web tests:
52
53 python tools/code_coverage/coverage.py blink_tests \\
54 -wt -b out/coverage -o out/report -f third_party/blink
55
56 If you need to pass arguments to run_web_tests.py, use
57 -wt='arguments to run_web_tests.py e.g. test directories'
58
Abhishek Arya4a9494f2018-05-22 01:39:4159 Note: Generating coverage over entire suite can take minimum of 3 hours due to
60 --batch-size=1 argument added by default. This is needed since otherwise any
61 crash will cause us to lose coverage from prior successful test runs.
62
Abhishek Arya1ec832c2017-12-05 18:06:5963 For more options, please refer to tools/code_coverage/coverage.py -h.
Yuke Liao8e209fe82018-04-18 20:36:3864
65 For an overview of how code coverage works in Chromium, please refer to
66 https://chromium.googlesource.com/chromium/src/+/master/docs/code_coverage.md
Yuke Liao506e8822017-12-04 16:52:5467"""
68
69from __future__ import print_function
70
71import sys
72
73import argparse
Yuke Liaoea228d02018-01-05 19:10:3374import json
Yuke Liao481d3482018-01-29 19:17:1075import logging
Abhishek Arya03911092018-05-21 16:42:3576import multiprocessing
Yuke Liao506e8822017-12-04 16:52:5477import os
Yuke Liaob2926832018-03-02 17:34:2978import re
79import shlex
Max Moroz025d8952018-05-03 16:33:3480import shutil
Yuke Liao506e8822017-12-04 16:52:5481import subprocess
Yuke Liao506e8822017-12-04 16:52:5482import urllib2
83
Abhishek Arya1ec832c2017-12-05 18:06:5984sys.path.append(
85 os.path.join(
86 os.path.dirname(__file__), os.path.pardir, os.path.pardir, 'tools',
87 'clang', 'scripts'))
Yuke Liao506e8822017-12-04 16:52:5488import update as clang_update
89
Yuke Liaoea228d02018-01-05 19:10:3390sys.path.append(
91 os.path.join(
92 os.path.dirname(__file__), os.path.pardir, os.path.pardir,
93 'third_party'))
Yuke Liaoea228d02018-01-05 19:10:3394from collections import defaultdict
95
Max Moroz1de68d72018-08-21 13:38:1896import coverage_utils
97
Yuke Liao082e99632018-05-18 15:40:4098# Absolute path to the code coverage tools binary. These paths can be
99# overwritten by user specified coverage tool paths.
Yuke Liao506e8822017-12-04 16:52:54100LLVM_BUILD_DIR = clang_update.LLVM_BUILD_DIR
Abhishek Arya1c97ea542018-05-10 03:53:19101LLVM_BIN_DIR = os.path.join(LLVM_BUILD_DIR, 'bin')
102LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
103LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:54104
Abhishek Arya03911092018-05-21 16:42:35105# Absolute path to the root of the checkout.
106SRC_ROOT_PATH = None
107
Yuke Liao506e8822017-12-04 16:52:54108# Build directory, the value is parsed from command line arguments.
109BUILD_DIR = None
110
111# Output directory for generated artifacts, the value is parsed from command
112# line arguemnts.
113OUTPUT_DIR = None
114
115# Default number of jobs used to build when goma is configured and enabled.
116DEFAULT_GOMA_JOBS = 100
117
118# Name of the file extension for profraw data files.
119PROFRAW_FILE_EXTENSION = 'profraw'
120
121# Name of the final profdata file, and this file needs to be passed to
122# "llvm-cov" command in order to call "llvm-cov show" to inspect the
123# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48124PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
125
126# Name of the file with summary information generated by llvm-cov export.
127SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54128
129# Build arg required for generating code coverage data.
130CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
131
Max Moroz7c5354f2018-05-06 00:03:48132LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37133
134# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19135COMPONENT_MAPPING_URL = (
136 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37137
Yuke Liao80afff32018-03-07 01:26:20138# Caches the results returned by _GetBuildArgs, don't use this variable
139# directly, call _GetBuildArgs instead.
140_BUILD_ARGS = None
141
Abhishek Aryac19bc5ef2018-05-04 22:10:02142# Retry failed merges.
143MERGE_RETRIES = 3
144
Abhishek Aryad35de7e2018-05-10 22:23:04145# Message to guide user to file a bug when everything else fails.
146FILE_BUG_MESSAGE = (
147 'If it persists, please file a bug with the command you used, git revision '
148 'and args.gn config here: '
149 'https://bugs.chromium.org/p/chromium/issues/entry?'
150 'components=Tools%3ECodeCoverage')
151
Abhishek Aryabd0655d2018-05-21 19:55:24152# String to replace with actual llvm profile path.
153LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
154
Yuke Liaoea228d02018-01-05 19:10:33155
Yuke Liao082e99632018-05-18 15:40:40156def _ConfigureLLVMCoverageTools(args):
157 """Configures llvm coverage tools."""
158 if args.coverage_tools_dir:
Max Moroz1de68d72018-08-21 13:38:18159 llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40160 global LLVM_COV_PATH
161 global LLVM_PROFDATA_PATH
162 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
163 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
164 else:
165 DownloadCoverageToolsIfNeeded()
166
167 coverage_tools_exist = (
168 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
169 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
170 'both \'%s\' and \'%s\' exist.') % (
171 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
172
173
Abhishek Arya1c97ea542018-05-10 03:53:19174def _GetPathWithLLVMSymbolizerDir():
175 """Add llvm-symbolizer directory to path for symbolized stacks."""
176 path = os.getenv('PATH')
177 dirs = path.split(os.pathsep)
178 if LLVM_BIN_DIR in dirs:
179 return path
180
181 return path + os.pathsep + LLVM_BIN_DIR
182
183
Yuke Liaoc60b2d02018-03-02 21:40:43184def _GetTargetOS():
185 """Returns the target os specified in args.gn file.
186
187 Returns an empty string is target_os is not specified.
188 """
Yuke Liao80afff32018-03-07 01:26:20189 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43190 return build_args['target_os'] if 'target_os' in build_args else ''
191
192
Yuke Liaob2926832018-03-02 17:34:29193def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10194 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43195 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10196
197
Yuke Liao506e8822017-12-04 16:52:54198# TODO(crbug.com/759794): remove this function once tools get included to
199# Clang bundle:
200# https://chromium-review.googlesource.com/c/chromium/src/+/688221
201def DownloadCoverageToolsIfNeeded():
202 """Temporary solution to download llvm-profdata and llvm-cov tools."""
Abhishek Arya1ec832c2017-12-05 18:06:59203
Yuke Liaoc60b2d02018-03-02 21:40:43204 def _GetRevisionFromStampFile(stamp_file_path):
Yuke Liao506e8822017-12-04 16:52:54205 """Returns a pair of revision number by reading the build stamp file.
206
207 Args:
208 stamp_file_path: A path the build stamp file created by
209 tools/clang/scripts/update.py.
210 Returns:
211 A pair of integers represeting the main and sub revision respectively.
212 """
213 if not os.path.exists(stamp_file_path):
214 return 0, 0
215
216 with open(stamp_file_path) as stamp_file:
Yuke Liaoc60b2d02018-03-02 21:40:43217 stamp_file_line = stamp_file.readline()
218 if ',' in stamp_file_line:
219 package_version = stamp_file_line.rstrip().split(',')[0]
220 else:
221 package_version = stamp_file_line.rstrip()
Yuke Liao506e8822017-12-04 16:52:54222
Yuke Liaoc60b2d02018-03-02 21:40:43223 clang_revision_str, clang_sub_revision_str = package_version.split('-')
224 return int(clang_revision_str), int(clang_sub_revision_str)
Abhishek Arya1ec832c2017-12-05 18:06:59225
Max Moroz1de68d72018-08-21 13:38:18226 host_platform = coverage_utils.GetHostPlatform()
Yuke Liao506e8822017-12-04 16:52:54227 clang_revision, clang_sub_revision = _GetRevisionFromStampFile(
Yuke Liaoc60b2d02018-03-02 21:40:43228 clang_update.STAMP_FILE)
Yuke Liao506e8822017-12-04 16:52:54229
230 coverage_revision_stamp_file = os.path.join(
231 os.path.dirname(clang_update.STAMP_FILE), 'cr_coverage_revision')
232 coverage_revision, coverage_sub_revision = _GetRevisionFromStampFile(
Yuke Liaoc60b2d02018-03-02 21:40:43233 coverage_revision_stamp_file)
Yuke Liao506e8822017-12-04 16:52:54234
Yuke Liaoea228d02018-01-05 19:10:33235 has_coverage_tools = (
236 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
Abhishek Arya16f059a2017-12-07 17:47:32237
Yuke Liaoea228d02018-01-05 19:10:33238 if (has_coverage_tools and coverage_revision == clang_revision and
Yuke Liao506e8822017-12-04 16:52:54239 coverage_sub_revision == clang_sub_revision):
240 # LLVM coverage tools are up to date, bail out.
Yuke Liaoc60b2d02018-03-02 21:40:43241 return
Yuke Liao506e8822017-12-04 16:52:54242
243 package_version = '%d-%d' % (clang_revision, clang_sub_revision)
244 coverage_tools_file = 'llvm-code-coverage-%s.tgz' % package_version
245
246 # The code bellow follows the code from tools/clang/scripts/update.py.
Yuke Liaoc60b2d02018-03-02 21:40:43247 if host_platform == 'mac':
Yuke Liao506e8822017-12-04 16:52:54248 coverage_tools_url = clang_update.CDS_URL + '/Mac/' + coverage_tools_file
Yuke Liaoc60b2d02018-03-02 21:40:43249 elif host_platform == 'linux':
Yuke Liao506e8822017-12-04 16:52:54250 coverage_tools_url = (
251 clang_update.CDS_URL + '/Linux_x64/' + coverage_tools_file)
Yuke Liaoc60b2d02018-03-02 21:40:43252 else:
253 assert host_platform == 'win'
254 coverage_tools_url = (clang_update.CDS_URL + '/Win/' + coverage_tools_file)
Yuke Liao506e8822017-12-04 16:52:54255
256 try:
257 clang_update.DownloadAndUnpack(coverage_tools_url,
258 clang_update.LLVM_BUILD_DIR)
Yuke Liao506e8822017-12-04 16:52:54259 with open(coverage_revision_stamp_file, 'w') as file_handle:
Yuke Liaoc60b2d02018-03-02 21:40:43260 file_handle.write('%s,%s' % (package_version, host_platform))
Yuke Liao506e8822017-12-04 16:52:54261 file_handle.write('\n')
262 except urllib2.URLError:
263 raise Exception(
264 'Failed to download coverage tools: %s.' % coverage_tools_url)
265
266
Yuke Liaodd1ec0592018-02-02 01:26:37267def _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:59268 filters, ignore_filename_regex):
Yuke Liao506e8822017-12-04 16:52:54269 """Generates per file line-by-line coverage in html using 'llvm-cov show'.
270
271 For a file with absolute path /a/b/x.cc, a html report is generated as:
272 OUTPUT_DIR/coverage/a/b/x.cc.html. An index html file is also generated as:
273 OUTPUT_DIR/index.html.
274
275 Args:
276 binary_paths: A list of paths to the instrumented binaries.
277 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42278 filters: A list of directories and files to get coverage for.
Yuke Liao506e8822017-12-04 16:52:54279 """
Yuke Liao506e8822017-12-04 16:52:54280 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
281 # [[-object BIN]] [SOURCES]
282 # NOTE: For object files, the first one is specified as a positional argument,
283 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10284 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40285 '"llvm-cov show" command.')
Abhishek Arya1ec832c2017-12-05 18:06:59286 subprocess_cmd = [
287 LLVM_COV_PATH, 'show', '-format=html',
288 '-output-dir={}'.format(OUTPUT_DIR),
289 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
290 ]
291 subprocess_cmd.extend(
292 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29293 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Max Moroz1de68d72018-08-21 13:38:18294 if coverage_utils.GetHostPlatform() in ['linux', 'mac']:
Ryan Sleeviae19b2c32018-05-15 22:36:17295 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42296 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59297 if ignore_filename_regex:
298 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
299
Yuke Liao506e8822017-12-04 16:52:54300 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34301
Abhishek Aryafb70b532018-05-06 17:47:40302 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54303
304
Max Moroz7c5354f2018-05-06 00:03:48305def _GetLogsDirectoryPath():
306 """Path to the logs directory."""
Max Moroz1de68d72018-08-21 13:38:18307 return os.path.join(
308 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR), LOGS_DIR_NAME)
Max Moroz7c5354f2018-05-06 00:03:48309
310
311def _GetProfdataFilePath():
312 """Path to the resulting .profdata file."""
Max Moroz1de68d72018-08-21 13:38:18313 return os.path.join(
314 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
315 PROFDATA_FILE_NAME)
Max Moroz7c5354f2018-05-06 00:03:48316
317
318def _GetSummaryFilePath():
319 """The JSON file that contains coverage summary written by llvm-cov export."""
Max Moroz1de68d72018-08-21 13:38:18320 return os.path.join(
321 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
322 SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33323
324
Yuke Liao506e8822017-12-04 16:52:54325def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
326 """Builds and runs target to generate the coverage profile data.
327
328 Args:
329 targets: A list of targets to build with coverage instrumentation.
330 commands: A list of commands used to run the targets.
331 jobs_count: Number of jobs to run in parallel for building. If None, a
332 default value is derived based on CPUs availability.
333
334 Returns:
335 A relative path to the generated profdata file.
336 """
337 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02338 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59339 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02340 coverage_profdata_file_path = (
341 _CreateCoverageProfileDataFromTargetProfDataFiles(
342 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54343
Abhishek Aryac19bc5ef2018-05-04 22:10:02344 for target_profdata_file_path in target_profdata_file_paths:
345 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52346
Abhishek Aryac19bc5ef2018-05-04 22:10:02347 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54348
349
350def _BuildTargets(targets, jobs_count):
351 """Builds target with Clang coverage instrumentation.
352
353 This function requires current working directory to be the root of checkout.
354
355 Args:
356 targets: A list of targets to build with coverage instrumentation.
357 jobs_count: Number of jobs to run in parallel for compilation. If None, a
358 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54359 """
Abhishek Arya1ec832c2017-12-05 18:06:59360
Yuke Liao506e8822017-12-04 16:52:54361 def _IsGomaConfigured():
362 """Returns True if goma is enabled in the gn build args.
363
364 Returns:
365 A boolean indicates whether goma is configured for building or not.
366 """
Yuke Liao80afff32018-03-07 01:26:20367 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54368 return 'use_goma' in build_args and build_args['use_goma'] == 'true'
369
Abhishek Aryafb70b532018-05-06 17:47:40370 logging.info('Building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54371 if jobs_count is None and _IsGomaConfigured():
372 jobs_count = DEFAULT_GOMA_JOBS
373
374 subprocess_cmd = ['ninja', '-C', BUILD_DIR]
375 if jobs_count is not None:
376 subprocess_cmd.append('-j' + str(jobs_count))
377
378 subprocess_cmd.extend(targets)
379 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40380 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54381
382
Abhishek Aryac19bc5ef2018-05-04 22:10:02383def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54384 """Runs commands and returns the relative paths to the profraw data files.
385
386 Args:
387 targets: A list of targets built with coverage instrumentation.
388 commands: A list of commands used to run the targets.
389
390 Returns:
391 A list of relative paths to the generated profraw data files.
392 """
Abhishek Aryafb70b532018-05-06 17:47:40393 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10394
Yuke Liao506e8822017-12-04 16:52:54395 # Remove existing profraw data files.
Max Moroz1de68d72018-08-21 13:38:18396 report_root_dir = coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR)
397 for file_or_dir in os.listdir(report_root_dir):
Yuke Liao506e8822017-12-04 16:52:54398 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz1de68d72018-08-21 13:38:18399 os.remove(os.path.join(report_root_dir, file_or_dir))
Max Moroz7c5354f2018-05-06 00:03:48400
401 # Ensure that logs directory exists.
402 if not os.path.exists(_GetLogsDirectoryPath()):
403 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54404
Abhishek Aryac19bc5ef2018-05-04 22:10:02405 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10406
Yuke Liaod4a9865202018-01-12 23:17:52407 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54408 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48409 output_file_name = os.extsep.join([target + '_output', 'log'])
410 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10411
Abhishek Aryac19bc5ef2018-05-04 22:10:02412 profdata_file_path = None
413 for _ in xrange(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40414 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02415 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10416
Abhishek Aryac19bc5ef2018-05-04 22:10:02417 if _IsIOSCommand(command):
418 # On iOS platform, due to lack of write permissions, profraw files are
419 # generated outside of the OUTPUT_DIR, and the exact paths are contained
420 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35421 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02422 else:
423 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:35424 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:02425
426 profraw_file_paths = []
427 if _IsIOS():
Yuke Liao9c2c70b2018-05-23 15:37:57428 profraw_file_paths = [_GetProfrawDataFileByParsingOutput(output)]
Abhishek Aryac19bc5ef2018-05-04 22:10:02429 else:
Max Moroz1de68d72018-08-21 13:38:18430 for file_or_dir in os.listdir(report_root_dir):
Abhishek Aryac19bc5ef2018-05-04 22:10:02431 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48432 profraw_file_paths.append(
Max Moroz1de68d72018-08-21 13:38:18433 os.path.join(report_root_dir, file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:02434
435 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:40436 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:04437 'please make sure the binary exists, is properly instrumented and '
438 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02439
Yuke Liao9c2c70b2018-05-23 15:37:57440 assert isinstance(profraw_file_paths, list), (
Max Moroz1de68d72018-08-21 13:38:18441 'Variable \'profraw_file_paths\' is expected to be of type \'list\', '
442 'but it is a %s. %s' % (type(profraw_file_paths), FILE_BUG_MESSAGE))
Yuke Liao9c2c70b2018-05-23 15:37:57443
Abhishek Aryac19bc5ef2018-05-04 22:10:02444 try:
445 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
446 target, profraw_file_paths)
447 break
448 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:04449 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:02450 finally:
451 # Remove profraw files now so that they are not used in next iteration.
452 for profraw_file_path in profraw_file_paths:
453 os.remove(profraw_file_path)
454
455 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:04456 'Failed to merge target "%s" profraw files after %d retries. %s' %
457 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:02458 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54459
Abhishek Aryafb70b532018-05-06 17:47:40460 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10461
Abhishek Aryac19bc5ef2018-05-04 22:10:02462 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:54463
464
Abhishek Arya03911092018-05-21 16:42:35465def _GetEnvironmentVars(profraw_file_path):
466 """Return environment vars for subprocess, given a profraw file path."""
467 env = os.environ.copy()
468 env.update({
469 'LLVM_PROFILE_FILE': profraw_file_path,
470 'PATH': _GetPathWithLLVMSymbolizerDir()
471 })
472 return env
473
474
475def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10476 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:52477 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:01478 #
Max Morozd73e45f2018-04-24 18:32:47479 # "%p" expands out to the process ID. It's not used by this scripts due to:
480 # 1) If a target program spawns too many processess, it may exhaust all disk
481 # space available. For example, unit_tests writes thousands of .profraw
482 # files each of size 1GB+.
483 # 2) If a target binary uses shared libraries, coverage profile data for them
484 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:01485 #
Yuke Liaod4a9865202018-01-12 23:17:52486 # "%Nm" expands out to the instrumented binary's signature. When this pattern
487 # is specified, the runtime creates a pool of N raw profiles which are used
488 # for on-line profile merging. The runtime takes care of selecting a raw
489 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:52490 # N must be between 1 and 9. The merge pool specifier can only occur once per
491 # filename pattern.
492 #
Max Morozd73e45f2018-04-24 18:32:47493 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:01494 #
Max Morozd73e45f2018-04-24 18:32:47495 # For other cases, "%4m" is chosen as it creates some level of parallelism,
496 # but it's not too big to consume too much computing resource or disk space.
497 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:59498 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:01499 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz1de68d72018-08-21 13:38:18500 expected_profraw_file_path = os.path.join(
501 coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR),
502 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:24503 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
504 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:54505
Yuke Liaoa0c8c2f2018-02-28 20:14:10506 try:
Max Moroz7c5354f2018-05-06 00:03:48507 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:35508 with open(output_file_path, 'wb') as output_file_handle:
509 subprocess.check_call(
510 shlex.split(command),
511 stdout=output_file_handle,
512 stderr=subprocess.STDOUT,
513 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10514 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:35515 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:10516
Abhishek Arya03911092018-05-21 16:42:35517 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10518
519
Yuke Liao27349c92018-03-22 21:10:01520def _IsFuzzerTarget(target):
521 """Returns true if the target is a fuzzer target."""
522 build_args = _GetBuildArgs()
523 use_libfuzzer = ('use_libfuzzer' in build_args and
524 build_args['use_libfuzzer'] == 'true')
525 return use_libfuzzer and target.endswith('_fuzzer')
526
527
Abhishek Arya03911092018-05-21 16:42:35528def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:10529 """Runs a single iOS command and generates a profraw data file.
530
531 iOS application doesn't have write access to folders outside of the app, so
532 it's impossible to instruct the app to flush the profraw data file to the
533 desired location. The profraw data file will be generated somewhere within the
534 application's Documents folder, and the full path can be obtained by parsing
535 the output.
536 """
Yuke Liaob2926832018-03-02 17:34:29537 assert _IsIOSCommand(command)
538
539 # After running tests, iossim generates a profraw data file, it won't be
540 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
541 # checkout.
542 iossim_profraw_file_path = os.path.join(
543 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:24544 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
545 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10546
547 try:
Abhishek Arya03911092018-05-21 16:42:35548 with open(output_file_path, 'wb') as output_file_handle:
549 subprocess.check_call(
550 shlex.split(command),
551 stdout=output_file_handle,
552 stderr=subprocess.STDOUT,
553 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:10554 except subprocess.CalledProcessError as e:
555 # iossim emits non-zero return code even if tests run successfully, so
556 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:35557 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:10558
Abhishek Arya03911092018-05-21 16:42:35559 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:10560
561
562def _GetProfrawDataFileByParsingOutput(output):
563 """Returns the path to the profraw data file obtained by parsing the output.
564
565 The output of running the test target has no format, but it is guaranteed to
566 have a single line containing the path to the generated profraw data file.
567 NOTE: This should only be called when target os is iOS.
568 """
Yuke Liaob2926832018-03-02 17:34:29569 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:10570
Yuke Liaob2926832018-03-02 17:34:29571 output_by_lines = ''.join(output).splitlines()
572 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:10573
574 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:29575 result = profraw_file_pattern.match(line)
576 if result:
577 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:10578
579 assert False, ('No profraw data file was generated, did you call '
580 'coverage_util::ConfigureCoverageReportPath() in test setup? '
581 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:54582
583
Abhishek Aryac19bc5ef2018-05-04 22:10:02584def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
585 """Returns a relative path to coverage profdata file by merging target
586 profdata files.
Yuke Liao506e8822017-12-04 16:52:54587
588 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:02589 profdata_file_paths: A list of relative paths to the profdata data files
590 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:54591
592 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:02593 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:54594
595 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:02596 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:54597 """
Abhishek Aryafb70b532018-05-06 17:47:40598 logging.info('Creating the coverage profile data file.')
599 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:48600 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:54601 try:
Abhishek Arya1ec832c2017-12-05 18:06:59602 subprocess_cmd = [
603 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
604 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:02605 subprocess_cmd.extend(profdata_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01606
607 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18608 logging.debug('Merge output: %s', output)
Abhishek Aryac19bc5ef2018-05-04 22:10:02609 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04610 logging.error(
611 'Failed to merge target profdata files to create coverage profdata. %s',
612 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:02613 raise error
614
Abhishek Aryafb70b532018-05-06 17:47:40615 logging.debug('Finished merging target profdata files.')
616 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02617 profdata_file_path)
618 return profdata_file_path
619
620
621def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
622 """Returns a relative path to target profdata file by merging target
623 profraw files.
624
625 Args:
626 profraw_file_paths: A list of relative paths to the profdata data files
627 that are to be merged.
628
629 Returns:
630 A relative path to the merged coverage profdata file.
631
632 Raises:
633 CalledProcessError: An error occurred merging profdata files.
634 """
Abhishek Aryafb70b532018-05-06 17:47:40635 logging.info('Creating target profile data file.')
636 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:02637 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
638
639 try:
640 subprocess_cmd = [
641 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
642 ]
Yuke Liao506e8822017-12-04 16:52:54643 subprocess_cmd.extend(profraw_file_paths)
Abhishek Aryae5811afa2018-05-24 03:56:01644
645 output = subprocess.check_output(subprocess_cmd)
Max Moroz1de68d72018-08-21 13:38:18646 logging.debug('Merge output: %s', output)
Yuke Liao506e8822017-12-04 16:52:54647 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:04648 logging.error(
649 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:54650 raise error
651
Abhishek Aryafb70b532018-05-06 17:47:40652 logging.debug('Finished merging target profraw files.')
653 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:10654 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:54655 return profdata_file_path
656
657
Yuke Liao0e4c8682018-04-18 21:06:59658def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
659 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:33660 """Generates per file coverage summary using "llvm-cov export" command."""
661 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
662 # [[-object BIN]] [SOURCES].
663 # NOTE: For object files, the first one is specified as a positional argument,
664 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10665 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:40666 'export -summary-only" command.')
Yuke Liaoea228d02018-01-05 19:10:33667 subprocess_cmd = [
668 LLVM_COV_PATH, 'export', '-summary-only',
669 '-instr-profile=' + profdata_file_path, binary_paths[0]
670 ]
671 subprocess_cmd.extend(
672 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29673 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:33674 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59675 if ignore_filename_regex:
676 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:33677
Max Moroz7c5354f2018-05-06 00:03:48678 export_output = subprocess.check_output(subprocess_cmd)
679
680 # Write output on the disk to be used by code coverage bot.
681 with open(_GetSummaryFilePath(), 'w') as f:
682 f.write(export_output)
683
Max Moroz1de68d72018-08-21 13:38:18684 return export_output
Yuke Liaoea228d02018-01-05 19:10:33685
686
Yuke Liaob2926832018-03-02 17:34:29687def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
688 """Appends -arch arguments to the command list if it's ios platform.
689
690 iOS binaries are universal binaries, and require specifying the architecture
691 to use, and one architecture needs to be specified for each binary.
692 """
693 if _IsIOS():
694 cmd_list.extend(['-arch=x86_64'] * num_archs)
695
696
Yuke Liao506e8822017-12-04 16:52:54697def _GetBinaryPath(command):
698 """Returns a relative path to the binary to be run by the command.
699
Yuke Liao545db322018-02-15 17:12:01700 Currently, following types of commands are supported (e.g. url_unittests):
701 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
702 2. Use xvfb.
703 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
704 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:37705 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
706 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:10707 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:37708 <iossim_arguments> -c <app_arguments>
709 out/Coverage-iphonesimulator/url_unittests.app"
710
Yuke Liao506e8822017-12-04 16:52:54711 Args:
712 command: A command used to run a target.
713
714 Returns:
715 A relative path to the binary.
716 """
Yuke Liao545db322018-02-15 17:12:01717 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
718
Yuke Liaob2926832018-03-02 17:34:29719 command_parts = shlex.split(command)
Yuke Liao545db322018-02-15 17:12:01720 if os.path.basename(command_parts[0]) == 'python':
721 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:40722 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:01723 return command_parts[2]
724
725 if os.path.basename(command_parts[0]) == xvfb_script_name:
726 return command_parts[1]
727
Yuke Liaob2926832018-03-02 17:34:29728 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10729 # For a given application bundle, the binary resides in the bundle and has
730 # the same name with the application without the .app extension.
Yuke Liao92107f02018-03-07 01:44:37731 app_path = command_parts[-1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:10732 app_name = os.path.splitext(os.path.basename(app_path))[0]
733 return os.path.join(app_path, app_name)
734
Yuke Liaob2926832018-03-02 17:34:29735 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:54736
737
Yuke Liaob2926832018-03-02 17:34:29738def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:10739 """Returns true if command is used to run tests on iOS platform."""
Yuke Liaob2926832018-03-02 17:34:29740 return os.path.basename(shlex.split(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:10741
742
Yuke Liao95d13d72017-12-07 18:18:50743def _VerifyTargetExecutablesAreInBuildDirectory(commands):
744 """Verifies that the target executables specified in the commands are inside
745 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:54746 for command in commands:
747 binary_path = _GetBinaryPath(command)
Max Moroz1de68d72018-08-21 13:38:18748 binary_absolute_path = coverage_utils.GetFullPath(binary_path)
Abhishek Arya03911092018-05-21 16:42:35749 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:50750 'Target executable "%s" in command: "%s" is outside of '
751 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:54752
753
754def _ValidateBuildingWithClangCoverage():
755 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:20756 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54757
758 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
759 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:59760 assert False, ('\'{} = true\' is required in args.gn.'
761 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:54762
763
Yuke Liaoc60b2d02018-03-02 21:40:43764def _ValidateCurrentPlatformIsSupported():
765 """Asserts that this script suports running on the current platform"""
766 target_os = _GetTargetOS()
767 if target_os:
768 current_platform = target_os
769 else:
Max Moroz1de68d72018-08-21 13:38:18770 current_platform = coverage_utils.GetHostPlatform()
Yuke Liaoc60b2d02018-03-02 21:40:43771
772 assert current_platform in [
773 'linux', 'mac', 'chromeos', 'ios'
774 ], ('Coverage is only supported on linux, mac, chromeos and ios.')
775
776
Yuke Liao80afff32018-03-07 01:26:20777def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:54778 """Parses args.gn file and returns results as a dictionary.
779
780 Returns:
781 A dictionary representing the build args.
782 """
Yuke Liao80afff32018-03-07 01:26:20783 global _BUILD_ARGS
784 if _BUILD_ARGS is not None:
785 return _BUILD_ARGS
786
787 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:54788 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
789 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
790 'missing args.gn file.' % BUILD_DIR)
791 with open(build_args_path) as build_args_file:
792 build_args_lines = build_args_file.readlines()
793
Yuke Liao506e8822017-12-04 16:52:54794 for build_arg_line in build_args_lines:
795 build_arg_without_comments = build_arg_line.split('#')[0]
796 key_value_pair = build_arg_without_comments.split('=')
797 if len(key_value_pair) != 2:
798 continue
799
800 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:43801
802 # Values are wrapped within a pair of double-quotes, so remove the leading
803 # and trailing double-quotes.
804 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:20805 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:54806
Yuke Liao80afff32018-03-07 01:26:20807 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:54808
809
Abhishek Arya16f059a2017-12-07 17:47:32810def _VerifyPathsAndReturnAbsolutes(paths):
811 """Verifies that the paths specified in |paths| exist and returns absolute
812 versions.
Yuke Liao66da1732017-12-05 22:19:42813
814 Args:
815 paths: A list of files or directories.
816 """
Abhishek Arya16f059a2017-12-07 17:47:32817 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:42818 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:32819 absolute_path = os.path.join(SRC_ROOT_PATH, path)
820 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
821
822 absolute_paths.append(absolute_path)
823
824 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:42825
826
Abhishek Arya64636af2018-05-04 14:42:13827def _GetBinaryPathsFromTargets(targets, build_dir):
828 """Return binary paths from target names."""
829 # FIXME: Derive output binary from target build definitions rather than
830 # assuming that it is always the same name.
831 binary_paths = []
832 for target in targets:
833 binary_path = os.path.join(build_dir, target)
Max Moroz1de68d72018-08-21 13:38:18834 if coverage_utils.GetHostPlatform() == 'win':
Abhishek Arya64636af2018-05-04 14:42:13835 binary_path += '.exe'
836
837 if os.path.exists(binary_path):
838 binary_paths.append(binary_path)
839 else:
840 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:40841 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:13842 os.path.basename(binary_path))
843
844 return binary_paths
845
846
Abhishek Arya03911092018-05-21 16:42:35847def _GetCommandForWebTests(arguments):
848 """Return command to run for blink web tests."""
849 command_list = [
850 'python', 'testing/xvfb.py', 'python',
851 'third_party/blink/tools/run_web_tests.py',
852 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:24853 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
Max Moroz1de68d72018-08-21 13:38:18854 LLVM_PROFILE_FILE_PATH_SUBSTITUTION, '--batch-size=1',
Abhishek Arya03911092018-05-21 16:42:35855 '--child-processes=%d' % max(1, int(multiprocessing.cpu_count() / 2)),
Abhishek Aryabd0655d2018-05-21 19:55:24856 '--disable-breakpad', '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:35857 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
858 ]
859 if arguments.strip():
860 command_list.append(arguments)
861 return ' '.join(command_list)
862
863
864def _GetBinaryPathForWebTests():
865 """Return binary path used to run blink web tests."""
Max Moroz1de68d72018-08-21 13:38:18866 host_platform = coverage_utils.GetHostPlatform()
Abhishek Arya03911092018-05-21 16:42:35867 if host_platform == 'win':
868 return os.path.join(BUILD_DIR, 'content_shell.exe')
869 elif host_platform == 'linux':
870 return os.path.join(BUILD_DIR, 'content_shell')
871 elif host_platform == 'mac':
872 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
873 'Content Shell')
874 else:
875 assert False, 'This platform is not supported for web tests.'
876
877
Abhishek Aryae5811afa2018-05-24 03:56:01878def _SetupOutputDir():
879 """Setup output directory."""
880 if os.path.exists(OUTPUT_DIR):
881 shutil.rmtree(OUTPUT_DIR)
882
883 # Creates |OUTPUT_DIR| and its platform sub-directory.
Max Moroz1de68d72018-08-21 13:38:18884 os.makedirs(coverage_utils.GetCoverageReportRootDirPath(OUTPUT_DIR))
Abhishek Aryae5811afa2018-05-24 03:56:01885
886
Yuke Liao506e8822017-12-04 16:52:54887def _ParseCommandArguments():
888 """Adds and parses relevant arguments for tool comands.
889
890 Returns:
891 A dictionary representing the arguments.
892 """
893 arg_parser = argparse.ArgumentParser()
894 arg_parser.usage = __doc__
895
Abhishek Arya1ec832c2017-12-05 18:06:59896 arg_parser.add_argument(
897 '-b',
898 '--build-dir',
899 type=str,
900 required=True,
901 help='The build directory, the path needs to be relative to the root of '
902 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:54903
Abhishek Arya1ec832c2017-12-05 18:06:59904 arg_parser.add_argument(
905 '-o',
906 '--output-dir',
907 type=str,
908 required=True,
909 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:54910
Abhishek Arya1ec832c2017-12-05 18:06:59911 arg_parser.add_argument(
912 '-c',
913 '--command',
914 action='append',
Abhishek Arya64636af2018-05-04 14:42:13915 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:59916 help='Commands used to run test targets, one test target needs one and '
917 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:13918 'current working directory is the root of the checkout. This option is '
919 'incompatible with -p/--profdata-file option.')
920
921 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:35922 '-wt',
923 '--web-tests',
924 nargs='?',
925 type=str,
926 const=' ',
927 required=False,
928 help='Run blink web tests. Support passing arguments to run_web_tests.py')
929
930 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:13931 '-p',
932 '--profdata-file',
933 type=str,
934 required=False,
935 help='Path to profdata file to use for generating code coverage reports. '
936 'This can be useful if you generated the profdata file seperately in '
937 'your own test harness. This option is ignored if run command(s) are '
938 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:54939
Abhishek Arya1ec832c2017-12-05 18:06:59940 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:42941 '-f',
942 '--filters',
943 action='append',
Abhishek Arya16f059a2017-12-07 17:47:32944 required=False,
Yuke Liao66da1732017-12-05 22:19:42945 help='Directories or files to get code coverage for, and all files under '
946 'the directories are included recursively.')
947
948 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:59949 '-i',
950 '--ignore-filename-regex',
951 type=str,
952 help='Skip source code files with file paths that match the given '
953 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
954 'to exclude files in third_party/ and out/ folders from the report.')
955
956 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:32957 '--no-file-view',
958 action='store_true',
959 help='Don\'t generate the file view in the coverage report. When there '
960 'are large number of html files, the file view becomes heavy and may '
961 'cause the browser to freeze, and this argument comes handy.')
962
963 arg_parser.add_argument(
Max Moroz1de68d72018-08-21 13:38:18964 '--no-component-view',
965 action='store_true',
966 help='Don\'t generate the component view in the coverage report.')
967
968 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:40969 '--coverage-tools-dir',
970 type=str,
971 help='Path of the directory where LLVM coverage tools (llvm-cov, '
972 'llvm-profdata) exist. This should be only needed if you are testing '
973 'against a custom built clang revision. Otherwise, we pick coverage '
974 'tools automatically from your current source checkout.')
975
976 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:59977 '-j',
978 '--jobs',
979 type=int,
980 default=None,
981 help='Run N jobs to build in parallel. If not specified, a default value '
982 'will be derived based on CPUs availability. Please refer to '
983 '\'ninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:54984
Abhishek Arya1ec832c2017-12-05 18:06:59985 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:10986 '-v',
987 '--verbose',
988 action='store_true',
989 help='Prints additional output for diagnostics.')
990
991 arg_parser.add_argument(
992 '-l', '--log_file', type=str, help='Redirects logs to a file.')
993
994 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:02995 'targets',
996 nargs='+',
997 help='The names of the test targets to run. If multiple run commands are '
998 'specified using the -c/--command option, then the order of targets and '
999 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:541000
1001 args = arg_parser.parse_args()
1002 return args
1003
1004
1005def Main():
1006 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:401007 # Setup coverage binaries even when script is called with empty params. This
1008 # is used by coverage bot for initial setup.
1009 if len(sys.argv) == 1:
1010 DownloadCoverageToolsIfNeeded()
1011 print(__doc__)
1012 return
1013
Abhishek Arya64636af2018-05-04 14:42:131014 # Change directory to source root to aid in relative paths calculations.
Abhishek Arya03911092018-05-21 16:42:351015 global SRC_ROOT_PATH
Max Moroz1de68d72018-08-21 13:38:181016 SRC_ROOT_PATH = coverage_utils.GetFullPath(
Abhishek Arya03911092018-05-21 16:42:351017 os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
Abhishek Arya64636af2018-05-04 14:42:131018 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:111019
Yuke Liao506e8822017-12-04 16:52:541020 args = _ParseCommandArguments()
Max Moroz1de68d72018-08-21 13:38:181021 coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file)
Yuke Liao082e99632018-05-18 15:40:401022 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:131023
Yuke Liao506e8822017-12-04 16:52:541024 global BUILD_DIR
Max Moroz1de68d72018-08-21 13:38:181025 BUILD_DIR = coverage_utils.GetFullPath(args.build_dir)
Abhishek Aryae5811afa2018-05-24 03:56:011026
Yuke Liao506e8822017-12-04 16:52:541027 global OUTPUT_DIR
Max Moroz1de68d72018-08-21 13:38:181028 OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:541029
Abhishek Arya03911092018-05-21 16:42:351030 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:131031 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:351032 'provide prof-data file as input using -p/--profdata-file option OR '
1033 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:431034
Abhishek Arya64636af2018-05-04 14:42:131035 assert not args.command or (len(args.targets) == len(args.command)), (
1036 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:431037
Abhishek Arya1ec832c2017-12-05 18:06:591038 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:401039 'Build directory: "%s" doesn\'t exist. '
1040 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:131041
Yuke Liaoc60b2d02018-03-02 21:40:431042 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:541043 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:321044
1045 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:421046 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:321047 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:421048
Abhishek Aryae5811afa2018-05-24 03:56:011049 _SetupOutputDir()
Yuke Liao506e8822017-12-04 16:52:541050
Abhishek Arya03911092018-05-21 16:42:351051 # Get .profdata file and list of binary paths.
1052 if args.web_tests:
1053 commands = [_GetCommandForWebTests(args.web_tests)]
1054 profdata_file_path = _CreateCoverageProfileDataForTargets(
1055 args.targets, commands, args.jobs)
1056 binary_paths = [_GetBinaryPathForWebTests()]
1057 elif args.command:
1058 for i in range(len(args.command)):
1059 assert not 'run_web_tests.py' in args.command[i], (
1060 'run_web_tests.py is not supported via --command argument. '
1061 'Please use --run-web-tests argument instead.')
1062
Abhishek Arya64636af2018-05-04 14:42:131063 # A list of commands are provided. Run them to generate profdata file, and
1064 # create a list of binary paths from parsing commands.
1065 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1066 profdata_file_path = _CreateCoverageProfileDataForTargets(
1067 args.targets, args.command, args.jobs)
1068 binary_paths = [_GetBinaryPath(command) for command in args.command]
1069 else:
1070 # An input prof-data file is already provided. Just calculate binary paths.
1071 profdata_file_path = args.profdata_file
1072 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331073
Max Moroz1de68d72018-08-21 13:38:181074 binary_paths.extend(
1075 coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR))
Abhishek Arya78120bc2018-05-07 20:53:541076
Yuke Liao481d3482018-01-29 19:17:101077 logging.info('Generating code coverage report in html (this can take a while '
Abhishek Aryafb70b532018-05-06 17:47:401078 'depending on size of target!).')
Max Moroz1de68d72018-08-21 13:38:181079 per_file_summary_data = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591080 binary_paths, profdata_file_path, absolute_filter_paths,
1081 args.ignore_filename_regex)
Yuke Liaodd1ec0592018-02-02 01:26:371082 _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:591083 absolute_filter_paths,
1084 args.ignore_filename_regex)
Max Moroz1de68d72018-08-21 13:38:181085 component_mappings = None
1086 if not args.no_component_view:
1087 component_mappings = json.load(urllib2.urlopen(COMPONENT_MAPPING_URL))
Yuke Liaodd1ec0592018-02-02 01:26:371088
Max Moroz1de68d72018-08-21 13:38:181089 # Call prepare here.
1090 processor = coverage_utils.CoverageReportPostProcessor(
1091 OUTPUT_DIR,
1092 SRC_ROOT_PATH,
1093 per_file_summary_data,
1094 no_component_view=args.no_component_view,
1095 no_file_view=args.no_file_view,
1096 component_mappings=component_mappings)
Yuke Liaodd1ec0592018-02-02 01:26:371097
Max Moroz1de68d72018-08-21 13:38:181098 processor.PrepareHtmlReport()
Yuke Liao506e8822017-12-04 16:52:541099
Abhishek Arya1ec832c2017-12-05 18:06:591100
Yuke Liao506e8822017-12-04 16:52:541101if __name__ == '__main__':
1102 sys.exit(Main())