blob: d209998afee7d9c6318dac699becc19b87f91d7a [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'))
94import jinja2
95from collections import defaultdict
96
Yuke Liao082e99632018-05-18 15:40:4097# Absolute path to the code coverage tools binary. These paths can be
98# overwritten by user specified coverage tool paths.
Yuke Liao506e8822017-12-04 16:52:5499LLVM_BUILD_DIR = clang_update.LLVM_BUILD_DIR
Abhishek Arya1c97ea542018-05-10 03:53:19100LLVM_BIN_DIR = os.path.join(LLVM_BUILD_DIR, 'bin')
101LLVM_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# Absolute path to the root of the checkout.
105SRC_ROOT_PATH = None
106
Yuke Liao506e8822017-12-04 16:52:54107# Build directory, the value is parsed from command line arguments.
108BUILD_DIR = None
109
110# Output directory for generated artifacts, the value is parsed from command
111# line arguemnts.
112OUTPUT_DIR = None
113
114# Default number of jobs used to build when goma is configured and enabled.
115DEFAULT_GOMA_JOBS = 100
116
117# Name of the file extension for profraw data files.
118PROFRAW_FILE_EXTENSION = 'profraw'
119
120# Name of the final profdata file, and this file needs to be passed to
121# "llvm-cov" command in order to call "llvm-cov show" to inspect the
122# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48123PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
124
125# Name of the file with summary information generated by llvm-cov export.
126SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54127
128# Build arg required for generating code coverage data.
129CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
130
Yuke Liaoea228d02018-01-05 19:10:33131# The default name of the html coverage report for a directory.
132DIRECTORY_COVERAGE_HTML_REPORT_NAME = os.extsep.join(['report', 'html'])
133
Yuke Liaodd1ec0592018-02-02 01:26:37134# Name of the html index files for different views.
Yuke Liaodd1ec0592018-02-02 01:26:37135COMPONENT_VIEW_INDEX_FILE = os.extsep.join(['component_view_index', 'html'])
Max Moroz7c5354f2018-05-06 00:03:48136DIRECTORY_VIEW_INDEX_FILE = os.extsep.join(['directory_view_index', 'html'])
Yuke Liaodd1ec0592018-02-02 01:26:37137FILE_VIEW_INDEX_FILE = os.extsep.join(['file_view_index', 'html'])
Max Moroz7c5354f2018-05-06 00:03:48138INDEX_HTML_FILE = os.extsep.join(['index', 'html'])
139
140LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37141
142# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19143COMPONENT_MAPPING_URL = (
144 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37145
Yuke Liao80afff32018-03-07 01:26:20146# Caches the results returned by _GetBuildArgs, don't use this variable
147# directly, call _GetBuildArgs instead.
148_BUILD_ARGS = None
149
Abhishek Aryac19bc5ef2018-05-04 22:10:02150# Retry failed merges.
151MERGE_RETRIES = 3
152
Abhishek Aryad35de7e2018-05-10 22:23:04153# Message to guide user to file a bug when everything else fails.
154FILE_BUG_MESSAGE = (
155 'If it persists, please file a bug with the command you used, git revision '
156 'and args.gn config here: '
157 'https://bugs.chromium.org/p/chromium/issues/entry?'
158 'components=Tools%3ECodeCoverage')
159
Abhishek Aryabd0655d2018-05-21 19:55:24160# String to replace with actual llvm profile path.
161LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
162
Yuke Liaoea228d02018-01-05 19:10:33163
164class _CoverageSummary(object):
165 """Encapsulates coverage summary representation."""
166
Yuke Liaodd1ec0592018-02-02 01:26:37167 def __init__(self,
168 regions_total=0,
169 regions_covered=0,
170 functions_total=0,
171 functions_covered=0,
172 lines_total=0,
173 lines_covered=0):
Yuke Liaoea228d02018-01-05 19:10:33174 """Initializes _CoverageSummary object."""
175 self._summary = {
176 'regions': {
177 'total': regions_total,
178 'covered': regions_covered
179 },
180 'functions': {
181 'total': functions_total,
182 'covered': functions_covered
183 },
184 'lines': {
185 'total': lines_total,
186 'covered': lines_covered
187 }
188 }
189
190 def Get(self):
191 """Returns summary as a dictionary."""
192 return self._summary
193
194 def AddSummary(self, other_summary):
195 """Adds another summary to this one element-wise."""
196 for feature in self._summary:
197 self._summary[feature]['total'] += other_summary.Get()[feature]['total']
198 self._summary[feature]['covered'] += other_summary.Get()[feature][
199 'covered']
200
201
Yuke Liaodd1ec0592018-02-02 01:26:37202class _CoverageReportHtmlGenerator(object):
203 """Encapsulates coverage html report generation.
Yuke Liaoea228d02018-01-05 19:10:33204
Yuke Liaodd1ec0592018-02-02 01:26:37205 The generated html has a table that contains links to other coverage reports.
Yuke Liaoea228d02018-01-05 19:10:33206 """
207
Yuke Liaodd1ec0592018-02-02 01:26:37208 def __init__(self, output_path, table_entry_type):
209 """Initializes _CoverageReportHtmlGenerator object.
210
211 Args:
212 output_path: Path to the html report that will be generated.
213 table_entry_type: Type of the table entries to be displayed in the table
214 header. For example: 'Path', 'Component'.
215 """
Yuke Liaoea228d02018-01-05 19:10:33216 css_file_name = os.extsep.join(['style', 'css'])
Max Moroz7c5354f2018-05-06 00:03:48217 css_absolute_path = os.path.join(OUTPUT_DIR, css_file_name)
Yuke Liaoea228d02018-01-05 19:10:33218 assert os.path.exists(css_absolute_path), (
219 'css file doesn\'t exit. Please make sure "llvm-cov show -format=html" '
Abhishek Aryafb70b532018-05-06 17:47:40220 'is called first, and the css file is generated at: "%s".' %
Yuke Liaoea228d02018-01-05 19:10:33221 css_absolute_path)
222
223 self._css_absolute_path = css_absolute_path
Yuke Liaodd1ec0592018-02-02 01:26:37224 self._output_path = output_path
225 self._table_entry_type = table_entry_type
226
Yuke Liaoea228d02018-01-05 19:10:33227 self._table_entries = []
Yuke Liaod54030e2018-01-08 17:34:12228 self._total_entry = {}
Abhishek Arya302b67a2018-05-10 19:43:23229
230 source_dir = os.path.dirname(os.path.realpath(__file__))
231 template_dir = os.path.join(source_dir, 'html_templates')
Yuke Liaoea228d02018-01-05 19:10:33232
233 jinja_env = jinja2.Environment(
234 loader=jinja2.FileSystemLoader(template_dir), trim_blocks=True)
235 self._header_template = jinja_env.get_template('header.html')
236 self._table_template = jinja_env.get_template('table.html')
237 self._footer_template = jinja_env.get_template('footer.html')
Abhishek Arya302b67a2018-05-10 19:43:23238
Abhishek Arya865fffd2018-05-08 22:16:01239 self._style_overrides = open(
Abhishek Arya302b67a2018-05-10 19:43:23240 os.path.join(source_dir, 'static', 'css', 'style.css')).read()
Yuke Liaoea228d02018-01-05 19:10:33241
242 def AddLinkToAnotherReport(self, html_report_path, name, summary):
243 """Adds a link to another html report in this report.
244
245 The link to be added is assumed to be an entry in this directory.
246 """
Yuke Liaodd1ec0592018-02-02 01:26:37247 # Use relative paths instead of absolute paths to make the generated reports
248 # portable.
249 html_report_relative_path = _GetRelativePathToDirectoryOfFile(
250 html_report_path, self._output_path)
251
Yuke Liaod54030e2018-01-08 17:34:12252 table_entry = self._CreateTableEntryFromCoverageSummary(
Yuke Liaodd1ec0592018-02-02 01:26:37253 summary, html_report_relative_path, name,
Yuke Liaod54030e2018-01-08 17:34:12254 os.path.basename(html_report_path) ==
255 DIRECTORY_COVERAGE_HTML_REPORT_NAME)
256 self._table_entries.append(table_entry)
257
258 def CreateTotalsEntry(self, summary):
Yuke Liaoa785f4d32018-02-13 21:41:35259 """Creates an entry corresponds to the 'Totals' row in the html report."""
Yuke Liaod54030e2018-01-08 17:34:12260 self._total_entry = self._CreateTableEntryFromCoverageSummary(summary)
261
262 def _CreateTableEntryFromCoverageSummary(self,
263 summary,
264 href=None,
265 name=None,
266 is_dir=None):
267 """Creates an entry to display in the html report."""
Yuke Liaodd1ec0592018-02-02 01:26:37268 assert (href is None and name is None and is_dir is None) or (
269 href is not None and name is not None and is_dir is not None), (
270 'The only scenario when href or name or is_dir can be None is when '
Yuke Liaoa785f4d32018-02-13 21:41:35271 'creating an entry for the Totals row, and in that case, all three '
Yuke Liaodd1ec0592018-02-02 01:26:37272 'attributes must be None.')
273
Yuke Liaod54030e2018-01-08 17:34:12274 entry = {}
Yuke Liaodd1ec0592018-02-02 01:26:37275 if href is not None:
276 entry['href'] = href
277 if name is not None:
278 entry['name'] = name
279 if is_dir is not None:
280 entry['is_dir'] = is_dir
281
Yuke Liaoea228d02018-01-05 19:10:33282 summary_dict = summary.Get()
Yuke Liaod54030e2018-01-08 17:34:12283 for feature in summary_dict:
Yuke Liaodd1ec0592018-02-02 01:26:37284 if summary_dict[feature]['total'] == 0:
285 percentage = 0.0
286 else:
Yuke Liao0e4c8682018-04-18 21:06:59287 percentage = float(summary_dict[feature]
288 ['covered']) / summary_dict[feature]['total'] * 100
Yuke Liaoa785f4d32018-02-13 21:41:35289
Yuke Liaoea228d02018-01-05 19:10:33290 color_class = self._GetColorClass(percentage)
Yuke Liaod54030e2018-01-08 17:34:12291 entry[feature] = {
Yuke Liaoea228d02018-01-05 19:10:33292 'total': summary_dict[feature]['total'],
293 'covered': summary_dict[feature]['covered'],
Yuke Liaoa785f4d32018-02-13 21:41:35294 'percentage': '{:6.2f}'.format(percentage),
Yuke Liaoea228d02018-01-05 19:10:33295 'color_class': color_class
296 }
Yuke Liaod54030e2018-01-08 17:34:12297
Yuke Liaod54030e2018-01-08 17:34:12298 return entry
Yuke Liaoea228d02018-01-05 19:10:33299
300 def _GetColorClass(self, percentage):
301 """Returns the css color class based on coverage percentage."""
302 if percentage >= 0 and percentage < 80:
303 return 'red'
304 if percentage >= 80 and percentage < 100:
305 return 'yellow'
306 if percentage == 100:
307 return 'green'
308
Abhishek Aryafb70b532018-05-06 17:47:40309 assert False, 'Invalid coverage percentage: "%d".' % percentage
Yuke Liaoea228d02018-01-05 19:10:33310
Yuke Liao1b852fd2018-05-11 17:07:32311 def WriteHtmlCoverageReport(self, no_file_view):
Yuke Liaodd1ec0592018-02-02 01:26:37312 """Writes html coverage report.
Yuke Liaoea228d02018-01-05 19:10:33313
314 In the report, sub-directories are displayed before files and within each
315 category, entries are sorted alphabetically.
Yuke Liaoea228d02018-01-05 19:10:33316 """
317
318 def EntryCmp(left, right):
319 """Compare function for table entries."""
320 if left['is_dir'] != right['is_dir']:
321 return -1 if left['is_dir'] == True else 1
322
Yuke Liaodd1ec0592018-02-02 01:26:37323 return -1 if left['name'] < right['name'] else 1
Yuke Liaoea228d02018-01-05 19:10:33324
325 self._table_entries = sorted(self._table_entries, cmp=EntryCmp)
326
327 css_path = os.path.join(OUTPUT_DIR, os.extsep.join(['style', 'css']))
Max Moroz7c5354f2018-05-06 00:03:48328
329 directory_view_path = _GetDirectoryViewPath()
Yuke Liao1b852fd2018-05-11 17:07:32330 directory_view_href = _GetRelativePathToDirectoryOfFile(
331 directory_view_path, self._output_path)
Max Moroz7c5354f2018-05-06 00:03:48332 component_view_path = _GetComponentViewPath()
Yuke Liao1b852fd2018-05-11 17:07:32333 component_view_href = _GetRelativePathToDirectoryOfFile(
334 component_view_path, self._output_path)
335
336 # File view is optional in the report.
337 file_view_href = None
338 if not no_file_view:
339 file_view_path = _GetFileViewPath()
340 file_view_href = _GetRelativePathToDirectoryOfFile(
341 file_view_path, self._output_path)
Yuke Liaodd1ec0592018-02-02 01:26:37342
Yuke Liaoea228d02018-01-05 19:10:33343 html_header = self._header_template.render(
Yuke Liaodd1ec0592018-02-02 01:26:37344 css_path=_GetRelativePathToDirectoryOfFile(css_path, self._output_path),
Yuke Liao1b852fd2018-05-11 17:07:32345 directory_view_href=directory_view_href,
346 component_view_href=component_view_href,
347 file_view_href=file_view_href,
Abhishek Arya865fffd2018-05-08 22:16:01348 style_overrides=self._style_overrides)
Yuke Liaodd1ec0592018-02-02 01:26:37349
Yuke Liaod54030e2018-01-08 17:34:12350 html_table = self._table_template.render(
Yuke Liaodd1ec0592018-02-02 01:26:37351 entries=self._table_entries,
352 total_entry=self._total_entry,
353 table_entry_type=self._table_entry_type)
Yuke Liaoea228d02018-01-05 19:10:33354 html_footer = self._footer_template.render()
355
Yuke Liaodd1ec0592018-02-02 01:26:37356 with open(self._output_path, 'w') as html_file:
Yuke Liaoea228d02018-01-05 19:10:33357 html_file.write(html_header + html_table + html_footer)
358
Yuke Liao506e8822017-12-04 16:52:54359
Abhishek Arya64636af2018-05-04 14:42:13360def _ConfigureLogging(args):
361 """Configures logging settings for later use."""
362 log_level = logging.DEBUG if args.verbose else logging.INFO
363 log_format = '[%(asctime)s %(levelname)s] %(message)s'
364 log_file = args.log_file if args.log_file else None
365 logging.basicConfig(filename=log_file, level=log_level, format=log_format)
366
367
Yuke Liao082e99632018-05-18 15:40:40368def _ConfigureLLVMCoverageTools(args):
369 """Configures llvm coverage tools."""
370 if args.coverage_tools_dir:
Abhishek Arya03911092018-05-21 16:42:35371 llvm_bin_dir = _GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40372 global LLVM_COV_PATH
373 global LLVM_PROFDATA_PATH
374 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
375 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
376 else:
377 DownloadCoverageToolsIfNeeded()
378
379 coverage_tools_exist = (
380 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
381 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
382 'both \'%s\' and \'%s\' exist.') % (
383 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
384
385
Max Morozd73e45f2018-04-24 18:32:47386def _GetSharedLibraries(binary_paths):
Abhishek Arya78120bc2018-05-07 20:53:54387 """Returns list of shared libraries used by specified binaries."""
388 logging.info('Finding shared libraries for targets (if any).')
389 shared_libraries = []
Max Morozd73e45f2018-04-24 18:32:47390 cmd = []
391 shared_library_re = None
392
393 if sys.platform.startswith('linux'):
394 cmd.extend(['ldd'])
Abhishek Arya64636af2018-05-04 14:42:13395 shared_library_re = re.compile(r'.*\.so\s=>\s(.*' + BUILD_DIR +
396 r'.*\.so)\s.*')
Max Morozd73e45f2018-04-24 18:32:47397 elif sys.platform.startswith('darwin'):
398 cmd.extend(['otool', '-L'])
399 shared_library_re = re.compile(r'\s+(@rpath/.*\.dylib)\s.*')
400 else:
Abhishek Aryafb70b532018-05-06 17:47:40401 assert False, 'Cannot detect shared libraries used by the given targets.'
Max Morozd73e45f2018-04-24 18:32:47402
403 assert shared_library_re is not None
404
405 cmd.extend(binary_paths)
406 output = subprocess.check_output(cmd)
407
408 for line in output.splitlines():
409 m = shared_library_re.match(line)
410 if not m:
411 continue
412
413 shared_library_path = m.group(1)
414 if sys.platform.startswith('darwin'):
415 # otool outputs "@rpath" macro instead of the dirname of the given binary.
416 shared_library_path = shared_library_path.replace('@rpath', BUILD_DIR)
417
Abhishek Arya78120bc2018-05-07 20:53:54418 if shared_library_path in shared_libraries:
419 continue
420
Max Morozd73e45f2018-04-24 18:32:47421 assert os.path.exists(shared_library_path), ('Shared library "%s" used by '
422 'the given target(s) does not '
423 'exist.' % shared_library_path)
424 with open(shared_library_path) as f:
425 data = f.read()
426
427 # Do not add non-instrumented libraries. Otherwise, llvm-cov errors outs.
428 if '__llvm_cov' in data:
Abhishek Arya78120bc2018-05-07 20:53:54429 shared_libraries.append(shared_library_path)
Max Morozd73e45f2018-04-24 18:32:47430
Abhishek Arya78120bc2018-05-07 20:53:54431 logging.debug('Found shared libraries (%d): %s.', len(shared_libraries),
432 shared_libraries)
433 logging.info('Finished finding shared libraries for targets.')
434 return shared_libraries
Max Morozd73e45f2018-04-24 18:32:47435
436
Yuke Liaoc60b2d02018-03-02 21:40:43437def _GetHostPlatform():
438 """Returns the host platform.
439
440 This is separate from the target platform/os that coverage is running for.
441 """
Abhishek Arya1ec832c2017-12-05 18:06:59442 if sys.platform == 'win32' or sys.platform == 'cygwin':
443 return 'win'
444 if sys.platform.startswith('linux'):
445 return 'linux'
446 else:
447 assert sys.platform == 'darwin'
448 return 'mac'
449
450
Abhishek Arya1c97ea542018-05-10 03:53:19451def _GetPathWithLLVMSymbolizerDir():
452 """Add llvm-symbolizer directory to path for symbolized stacks."""
453 path = os.getenv('PATH')
454 dirs = path.split(os.pathsep)
455 if LLVM_BIN_DIR in dirs:
456 return path
457
458 return path + os.pathsep + LLVM_BIN_DIR
459
460
Yuke Liaoc60b2d02018-03-02 21:40:43461def _GetTargetOS():
462 """Returns the target os specified in args.gn file.
463
464 Returns an empty string is target_os is not specified.
465 """
Yuke Liao80afff32018-03-07 01:26:20466 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43467 return build_args['target_os'] if 'target_os' in build_args else ''
468
469
Yuke Liaob2926832018-03-02 17:34:29470def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10471 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43472 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10473
474
Yuke Liao506e8822017-12-04 16:52:54475# TODO(crbug.com/759794): remove this function once tools get included to
476# Clang bundle:
477# https://chromium-review.googlesource.com/c/chromium/src/+/688221
478def DownloadCoverageToolsIfNeeded():
479 """Temporary solution to download llvm-profdata and llvm-cov tools."""
Abhishek Arya1ec832c2017-12-05 18:06:59480
Yuke Liaoc60b2d02018-03-02 21:40:43481 def _GetRevisionFromStampFile(stamp_file_path):
Yuke Liao506e8822017-12-04 16:52:54482 """Returns a pair of revision number by reading the build stamp file.
483
484 Args:
485 stamp_file_path: A path the build stamp file created by
486 tools/clang/scripts/update.py.
487 Returns:
488 A pair of integers represeting the main and sub revision respectively.
489 """
490 if not os.path.exists(stamp_file_path):
491 return 0, 0
492
493 with open(stamp_file_path) as stamp_file:
Yuke Liaoc60b2d02018-03-02 21:40:43494 stamp_file_line = stamp_file.readline()
495 if ',' in stamp_file_line:
496 package_version = stamp_file_line.rstrip().split(',')[0]
497 else:
498 package_version = stamp_file_line.rstrip()
Yuke Liao506e8822017-12-04 16:52:54499
Yuke Liaoc60b2d02018-03-02 21:40:43500 clang_revision_str, clang_sub_revision_str = package_version.split('-')
501 return int(clang_revision_str), int(clang_sub_revision_str)
Abhishek Arya1ec832c2017-12-05 18:06:59502
Yuke Liaoc60b2d02018-03-02 21:40:43503 host_platform = _GetHostPlatform()
Yuke Liao506e8822017-12-04 16:52:54504 clang_revision, clang_sub_revision = _GetRevisionFromStampFile(
Yuke Liaoc60b2d02018-03-02 21:40:43505 clang_update.STAMP_FILE)
Yuke Liao506e8822017-12-04 16:52:54506
507 coverage_revision_stamp_file = os.path.join(
508 os.path.dirname(clang_update.STAMP_FILE), 'cr_coverage_revision')
509 coverage_revision, coverage_sub_revision = _GetRevisionFromStampFile(
Yuke Liaoc60b2d02018-03-02 21:40:43510 coverage_revision_stamp_file)
Yuke Liao506e8822017-12-04 16:52:54511
Yuke Liaoea228d02018-01-05 19:10:33512 has_coverage_tools = (
513 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
Abhishek Arya16f059a2017-12-07 17:47:32514
Yuke Liaoea228d02018-01-05 19:10:33515 if (has_coverage_tools and coverage_revision == clang_revision and
Yuke Liao506e8822017-12-04 16:52:54516 coverage_sub_revision == clang_sub_revision):
517 # LLVM coverage tools are up to date, bail out.
Yuke Liaoc60b2d02018-03-02 21:40:43518 return
Yuke Liao506e8822017-12-04 16:52:54519
520 package_version = '%d-%d' % (clang_revision, clang_sub_revision)
521 coverage_tools_file = 'llvm-code-coverage-%s.tgz' % package_version
522
523 # The code bellow follows the code from tools/clang/scripts/update.py.
Yuke Liaoc60b2d02018-03-02 21:40:43524 if host_platform == 'mac':
Yuke Liao506e8822017-12-04 16:52:54525 coverage_tools_url = clang_update.CDS_URL + '/Mac/' + coverage_tools_file
Yuke Liaoc60b2d02018-03-02 21:40:43526 elif host_platform == 'linux':
Yuke Liao506e8822017-12-04 16:52:54527 coverage_tools_url = (
528 clang_update.CDS_URL + '/Linux_x64/' + coverage_tools_file)
Yuke Liaoc60b2d02018-03-02 21:40:43529 else:
530 assert host_platform == 'win'
531 coverage_tools_url = (clang_update.CDS_URL + '/Win/' + coverage_tools_file)
Yuke Liao506e8822017-12-04 16:52:54532
533 try:
534 clang_update.DownloadAndUnpack(coverage_tools_url,
535 clang_update.LLVM_BUILD_DIR)
Yuke Liao506e8822017-12-04 16:52:54536 with open(coverage_revision_stamp_file, 'w') as file_handle:
Yuke Liaoc60b2d02018-03-02 21:40:43537 file_handle.write('%s,%s' % (package_version, host_platform))
Yuke Liao506e8822017-12-04 16:52:54538 file_handle.write('\n')
539 except urllib2.URLError:
540 raise Exception(
541 'Failed to download coverage tools: %s.' % coverage_tools_url)
542
543
Yuke Liaodd1ec0592018-02-02 01:26:37544def _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:59545 filters, ignore_filename_regex):
Yuke Liao506e8822017-12-04 16:52:54546 """Generates per file line-by-line coverage in html using 'llvm-cov show'.
547
548 For a file with absolute path /a/b/x.cc, a html report is generated as:
549 OUTPUT_DIR/coverage/a/b/x.cc.html. An index html file is also generated as:
550 OUTPUT_DIR/index.html.
551
552 Args:
553 binary_paths: A list of paths to the instrumented binaries.
554 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42555 filters: A list of directories and files to get coverage for.
Yuke Liao506e8822017-12-04 16:52:54556 """
Yuke Liao506e8822017-12-04 16:52:54557 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
558 # [[-object BIN]] [SOURCES]
559 # NOTE: For object files, the first one is specified as a positional argument,
560 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10561 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40562 '"llvm-cov show" command.')
Abhishek Arya1ec832c2017-12-05 18:06:59563 subprocess_cmd = [
564 LLVM_COV_PATH, 'show', '-format=html',
565 '-output-dir={}'.format(OUTPUT_DIR),
566 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
567 ]
568 subprocess_cmd.extend(
569 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29570 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Ryan Sleeviae19b2c32018-05-15 22:36:17571 if _GetHostPlatform() in ['linux', 'mac']:
572 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42573 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59574 if ignore_filename_regex:
575 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
576
Yuke Liao506e8822017-12-04 16:52:54577 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34578
579 # llvm-cov creates "coverage" subdir in the output dir. We would like to use
580 # the platform name instead, as it simplifies the report dir structure when
581 # the same report is generated for different platforms.
582 default_report_subdir_path = os.path.join(OUTPUT_DIR, 'coverage')
Max Moroz7c5354f2018-05-06 00:03:48583 platform_report_subdir_path = _GetCoverageReportRootDirPath()
584 _MergeTwoDirectories(default_report_subdir_path, platform_report_subdir_path)
Max Moroz025d8952018-05-03 16:33:34585
Abhishek Aryafb70b532018-05-06 17:47:40586 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54587
588
Yuke Liaodd1ec0592018-02-02 01:26:37589def _GenerateFileViewHtmlIndexFile(per_file_coverage_summary):
590 """Generates html index file for file view."""
Max Moroz7c5354f2018-05-06 00:03:48591 file_view_index_file_path = _GetFileViewPath()
Yuke Liaodd1ec0592018-02-02 01:26:37592 logging.debug('Generating file view html index file as: "%s".',
593 file_view_index_file_path)
594 html_generator = _CoverageReportHtmlGenerator(file_view_index_file_path,
595 'Path')
596 totals_coverage_summary = _CoverageSummary()
Yuke Liaoea228d02018-01-05 19:10:33597
Yuke Liaodd1ec0592018-02-02 01:26:37598 for file_path in per_file_coverage_summary:
599 totals_coverage_summary.AddSummary(per_file_coverage_summary[file_path])
600
601 html_generator.AddLinkToAnotherReport(
602 _GetCoverageHtmlReportPathForFile(file_path),
603 os.path.relpath(file_path, SRC_ROOT_PATH),
604 per_file_coverage_summary[file_path])
605
606 html_generator.CreateTotalsEntry(totals_coverage_summary)
Yuke Liao1b852fd2018-05-11 17:07:32607 html_generator.WriteHtmlCoverageReport(no_file_view=False)
Yuke Liaodd1ec0592018-02-02 01:26:37608 logging.debug('Finished generating file view html index file.')
609
610
611def _CalculatePerDirectoryCoverageSummary(per_file_coverage_summary):
612 """Calculates per directory coverage summary."""
Abhishek Aryafb70b532018-05-06 17:47:40613 logging.debug('Calculating per-directory coverage summary.')
Yuke Liaodd1ec0592018-02-02 01:26:37614 per_directory_coverage_summary = defaultdict(lambda: _CoverageSummary())
615
Yuke Liaoea228d02018-01-05 19:10:33616 for file_path in per_file_coverage_summary:
617 summary = per_file_coverage_summary[file_path]
618 parent_dir = os.path.dirname(file_path)
Abhishek Aryafb70b532018-05-06 17:47:40619
Yuke Liaoea228d02018-01-05 19:10:33620 while True:
621 per_directory_coverage_summary[parent_dir].AddSummary(summary)
622
623 if parent_dir == SRC_ROOT_PATH:
624 break
625 parent_dir = os.path.dirname(parent_dir)
626
Abhishek Aryafb70b532018-05-06 17:47:40627 logging.debug('Finished calculating per-directory coverage summary.')
Yuke Liaodd1ec0592018-02-02 01:26:37628 return per_directory_coverage_summary
629
630
Yuke Liao1b852fd2018-05-11 17:07:32631def _GeneratePerDirectoryCoverageInHtml(
632 per_directory_coverage_summary, per_file_coverage_summary, no_file_view):
Yuke Liaodd1ec0592018-02-02 01:26:37633 """Generates per directory coverage breakdown in html."""
Abhishek Aryafb70b532018-05-06 17:47:40634 logging.debug('Writing per-directory coverage html reports.')
Yuke Liaoea228d02018-01-05 19:10:33635 for dir_path in per_directory_coverage_summary:
Yuke Liao1b852fd2018-05-11 17:07:32636 _GenerateCoverageInHtmlForDirectory(dir_path,
637 per_directory_coverage_summary,
638 per_file_coverage_summary, no_file_view)
Yuke Liaoea228d02018-01-05 19:10:33639
Abhishek Aryafb70b532018-05-06 17:47:40640 logging.debug('Finished writing per-directory coverage html reports.')
Yuke Liao481d3482018-01-29 19:17:10641
Yuke Liaoea228d02018-01-05 19:10:33642
643def _GenerateCoverageInHtmlForDirectory(
Yuke Liao1b852fd2018-05-11 17:07:32644 dir_path, per_directory_coverage_summary, per_file_coverage_summary,
645 no_file_view):
Yuke Liaoea228d02018-01-05 19:10:33646 """Generates coverage html report for a single directory."""
Yuke Liaodd1ec0592018-02-02 01:26:37647 html_generator = _CoverageReportHtmlGenerator(
648 _GetCoverageHtmlReportPathForDirectory(dir_path), 'Path')
Yuke Liaoea228d02018-01-05 19:10:33649
650 for entry_name in os.listdir(dir_path):
651 entry_path = os.path.normpath(os.path.join(dir_path, entry_name))
Yuke Liaoea228d02018-01-05 19:10:33652
Yuke Liaodd1ec0592018-02-02 01:26:37653 if entry_path in per_file_coverage_summary:
654 entry_html_report_path = _GetCoverageHtmlReportPathForFile(entry_path)
655 entry_coverage_summary = per_file_coverage_summary[entry_path]
656 elif entry_path in per_directory_coverage_summary:
657 entry_html_report_path = _GetCoverageHtmlReportPathForDirectory(
658 entry_path)
659 entry_coverage_summary = per_directory_coverage_summary[entry_path]
660 else:
Yuke Liaoc7e607142018-02-05 20:26:14661 # Any file without executable lines shouldn't be included into the report.
662 # For example, OWNER and README.md files.
Yuke Liaodd1ec0592018-02-02 01:26:37663 continue
Yuke Liaoea228d02018-01-05 19:10:33664
Yuke Liaodd1ec0592018-02-02 01:26:37665 html_generator.AddLinkToAnotherReport(entry_html_report_path,
666 os.path.basename(entry_path),
667 entry_coverage_summary)
Yuke Liaoea228d02018-01-05 19:10:33668
Yuke Liaod54030e2018-01-08 17:34:12669 html_generator.CreateTotalsEntry(per_directory_coverage_summary[dir_path])
Yuke Liao1b852fd2018-05-11 17:07:32670 html_generator.WriteHtmlCoverageReport(no_file_view)
Yuke Liaodd1ec0592018-02-02 01:26:37671
672
673def _GenerateDirectoryViewHtmlIndexFile():
674 """Generates the html index file for directory view.
675
676 Note that the index file is already generated under SRC_ROOT_PATH, so this
677 file simply redirects to it, and the reason of this extra layer is for
678 structural consistency with other views.
679 """
Max Moroz7c5354f2018-05-06 00:03:48680 directory_view_index_file_path = _GetDirectoryViewPath()
Yuke Liaodd1ec0592018-02-02 01:26:37681 logging.debug('Generating directory view html index file as: "%s".',
682 directory_view_index_file_path)
683 src_root_html_report_path = _GetCoverageHtmlReportPathForDirectory(
684 SRC_ROOT_PATH)
685 _WriteRedirectHtmlFile(directory_view_index_file_path,
686 src_root_html_report_path)
687 logging.debug('Finished generating directory view html index file.')
688
689
690def _CalculatePerComponentCoverageSummary(component_to_directories,
691 per_directory_coverage_summary):
692 """Calculates per component coverage summary."""
Abhishek Aryafb70b532018-05-06 17:47:40693 logging.debug('Calculating per-component coverage summary.')
Yuke Liaodd1ec0592018-02-02 01:26:37694 per_component_coverage_summary = defaultdict(lambda: _CoverageSummary())
695
696 for component in component_to_directories:
697 for directory in component_to_directories[component]:
Abhishek Arya03911092018-05-21 16:42:35698 absolute_directory_path = _GetFullPath(directory)
Yuke Liaodd1ec0592018-02-02 01:26:37699 if absolute_directory_path in per_directory_coverage_summary:
700 per_component_coverage_summary[component].AddSummary(
701 per_directory_coverage_summary[absolute_directory_path])
702
Abhishek Aryafb70b532018-05-06 17:47:40703 logging.debug('Finished calculating per-component coverage summary.')
Yuke Liaodd1ec0592018-02-02 01:26:37704 return per_component_coverage_summary
705
706
707def _ExtractComponentToDirectoriesMapping():
708 """Returns a mapping from components to directories."""
709 component_mappings = json.load(urllib2.urlopen(COMPONENT_MAPPING_URL))
710 directory_to_component = component_mappings['dir-to-component']
711
712 component_to_directories = defaultdict(list)
Abhishek Arya8c3a1ce322018-05-13 04:14:01713 for directory in sorted(directory_to_component):
Yuke Liaodd1ec0592018-02-02 01:26:37714 component = directory_to_component[directory]
Abhishek Arya8c3a1ce322018-05-13 04:14:01715
716 # Check if we already added the parent directory of this directory. If yes,
717 # skip this sub-directory to avoid double-counting.
718 found_parent_directory = False
719 for component_directory in component_to_directories[component]:
720 if directory.startswith(component_directory + '/'):
721 found_parent_directory = True
722 break
723
724 if not found_parent_directory:
725 component_to_directories[component].append(directory)
Yuke Liaodd1ec0592018-02-02 01:26:37726
727 return component_to_directories
728
729
Yuke Liao1b852fd2018-05-11 17:07:32730def _GeneratePerComponentCoverageInHtml(
731 per_component_coverage_summary, component_to_directories,
732 per_directory_coverage_summary, no_file_view):
Yuke Liaodd1ec0592018-02-02 01:26:37733 """Generates per-component coverage reports in html."""
734 logging.debug('Writing per-component coverage html reports.')
735 for component in per_component_coverage_summary:
736 _GenerateCoverageInHtmlForComponent(
737 component, per_component_coverage_summary, component_to_directories,
Yuke Liao1b852fd2018-05-11 17:07:32738 per_directory_coverage_summary, no_file_view)
Yuke Liaodd1ec0592018-02-02 01:26:37739
740 logging.debug('Finished writing per-component coverage html reports.')
741
742
743def _GenerateCoverageInHtmlForComponent(
744 component_name, per_component_coverage_summary, component_to_directories,
Yuke Liao1b852fd2018-05-11 17:07:32745 per_directory_coverage_summary, no_file_view):
Yuke Liaodd1ec0592018-02-02 01:26:37746 """Generates coverage html report for a component."""
747 component_html_report_path = _GetCoverageHtmlReportPathForComponent(
748 component_name)
Yuke Liaoc7e607142018-02-05 20:26:14749 component_html_report_dir = os.path.dirname(component_html_report_path)
750 if not os.path.exists(component_html_report_dir):
751 os.makedirs(component_html_report_dir)
Yuke Liaodd1ec0592018-02-02 01:26:37752
753 html_generator = _CoverageReportHtmlGenerator(component_html_report_path,
754 'Path')
755
756 for dir_path in component_to_directories[component_name]:
Abhishek Arya03911092018-05-21 16:42:35757 dir_absolute_path = _GetFullPath(dir_path)
Yuke Liaodd1ec0592018-02-02 01:26:37758 if dir_absolute_path not in per_directory_coverage_summary:
Yuke Liaoc7e607142018-02-05 20:26:14759 # Any directory without an excercised file shouldn't be included into the
760 # report.
Yuke Liaodd1ec0592018-02-02 01:26:37761 continue
762
763 html_generator.AddLinkToAnotherReport(
764 _GetCoverageHtmlReportPathForDirectory(dir_path),
765 os.path.relpath(dir_path, SRC_ROOT_PATH),
766 per_directory_coverage_summary[dir_absolute_path])
767
768 html_generator.CreateTotalsEntry(
769 per_component_coverage_summary[component_name])
Yuke Liao1b852fd2018-05-11 17:07:32770 html_generator.WriteHtmlCoverageReport(no_file_view)
Yuke Liaodd1ec0592018-02-02 01:26:37771
772
Yuke Liao1b852fd2018-05-11 17:07:32773def _GenerateComponentViewHtmlIndexFile(per_component_coverage_summary,
774 no_file_view):
Yuke Liaodd1ec0592018-02-02 01:26:37775 """Generates the html index file for component view."""
Max Moroz7c5354f2018-05-06 00:03:48776 component_view_index_file_path = _GetComponentViewPath()
Yuke Liaodd1ec0592018-02-02 01:26:37777 logging.debug('Generating component view html index file as: "%s".',
778 component_view_index_file_path)
779 html_generator = _CoverageReportHtmlGenerator(component_view_index_file_path,
780 'Component')
Yuke Liaodd1ec0592018-02-02 01:26:37781 for component in per_component_coverage_summary:
Yuke Liaodd1ec0592018-02-02 01:26:37782 html_generator.AddLinkToAnotherReport(
783 _GetCoverageHtmlReportPathForComponent(component), component,
784 per_component_coverage_summary[component])
785
Abhishek Aryaefbe1df2018-05-14 20:19:48786 # Do not create a totals row for the component view as the value is incorrect
787 # due to failure to account for UNKNOWN component and some paths belonging to
788 # multiple components.
789
Yuke Liao1b852fd2018-05-11 17:07:32790 html_generator.WriteHtmlCoverageReport(no_file_view)
Yuke Liaoc7e607142018-02-05 20:26:14791 logging.debug('Finished generating component view html index file.')
Yuke Liaoea228d02018-01-05 19:10:33792
793
Max Moroz7c5354f2018-05-06 00:03:48794def _MergeTwoDirectories(src_path, dst_path):
795 """Merge src_path directory into dst_path directory."""
796 for filename in os.listdir(src_path):
797 dst_path = os.path.join(dst_path, filename)
798 if os.path.exists(dst_path):
799 shutil.rmtree(dst_path)
800 os.rename(os.path.join(src_path, filename), dst_path)
801 shutil.rmtree(src_path)
802
803
Yuke Liaoea228d02018-01-05 19:10:33804def _OverwriteHtmlReportsIndexFile():
Yuke Liaodd1ec0592018-02-02 01:26:37805 """Overwrites the root index file to redirect to the default view."""
Max Moroz7c5354f2018-05-06 00:03:48806 html_index_file_path = _GetHtmlIndexPath()
807 directory_view_index_file_path = _GetDirectoryViewPath()
Yuke Liaodd1ec0592018-02-02 01:26:37808 _WriteRedirectHtmlFile(html_index_file_path, directory_view_index_file_path)
809
810
811def _WriteRedirectHtmlFile(from_html_path, to_html_path):
812 """Writes a html file that redirects to another html file."""
813 to_html_relative_path = _GetRelativePathToDirectoryOfFile(
814 to_html_path, from_html_path)
Yuke Liaoea228d02018-01-05 19:10:33815 content = ("""
816 <!DOCTYPE html>
817 <html>
818 <head>
819 <!-- HTML meta refresh URL redirection -->
820 <meta http-equiv="refresh" content="0; url=%s">
821 </head>
Yuke Liaodd1ec0592018-02-02 01:26:37822 </html>""" % to_html_relative_path)
823 with open(from_html_path, 'w') as f:
Yuke Liaoea228d02018-01-05 19:10:33824 f.write(content)
825
826
Max Moroz7c5354f2018-05-06 00:03:48827def _CleanUpOutputDir():
828 """Perform a cleanup of the output dir."""
829 # Remove the default index.html file produced by llvm-cov.
830 index_path = os.path.join(OUTPUT_DIR, INDEX_HTML_FILE)
831 if os.path.exists(index_path):
832 os.remove(index_path)
833
834
Yuke Liaodd1ec0592018-02-02 01:26:37835def _GetCoverageHtmlReportPathForFile(file_path):
836 """Given a file path, returns the corresponding html report path."""
Abhishek Aryafb70b532018-05-06 17:47:40837 assert os.path.isfile(file_path), '"%s" is not a file.' % file_path
Abhishek Arya03911092018-05-21 16:42:35838 html_report_path = os.extsep.join([_GetFullPath(file_path), 'html'])
Yuke Liaodd1ec0592018-02-02 01:26:37839
840 # '+' is used instead of os.path.join because both of them are absolute paths
841 # and os.path.join ignores the first path.
Yuke Liaoc7e607142018-02-05 20:26:14842 # TODO(crbug.com/809150): Think of a generic cross platform fix (Windows).
Yuke Liaodd1ec0592018-02-02 01:26:37843 return _GetCoverageReportRootDirPath() + html_report_path
844
845
846def _GetCoverageHtmlReportPathForDirectory(dir_path):
847 """Given a directory path, returns the corresponding html report path."""
Abhishek Aryafb70b532018-05-06 17:47:40848 assert os.path.isdir(dir_path), '"%s" is not a directory.' % dir_path
Yuke Liaodd1ec0592018-02-02 01:26:37849 html_report_path = os.path.join(
Abhishek Arya03911092018-05-21 16:42:35850 _GetFullPath(dir_path), DIRECTORY_COVERAGE_HTML_REPORT_NAME)
Yuke Liaodd1ec0592018-02-02 01:26:37851
852 # '+' is used instead of os.path.join because both of them are absolute paths
853 # and os.path.join ignores the first path.
Yuke Liaoc7e607142018-02-05 20:26:14854 # TODO(crbug.com/809150): Think of a generic cross platform fix (Windows).
Yuke Liaodd1ec0592018-02-02 01:26:37855 return _GetCoverageReportRootDirPath() + html_report_path
856
857
858def _GetCoverageHtmlReportPathForComponent(component_name):
859 """Given a component, returns the corresponding html report path."""
860 component_file_name = component_name.lower().replace('>', '-')
861 html_report_name = os.extsep.join([component_file_name, 'html'])
862 return os.path.join(_GetCoverageReportRootDirPath(), 'components',
863 html_report_name)
864
865
866def _GetCoverageReportRootDirPath():
867 """The root directory that contains all generated coverage html reports."""
Max Moroz7c5354f2018-05-06 00:03:48868 return os.path.join(OUTPUT_DIR, _GetHostPlatform())
869
870
871def _GetComponentViewPath():
872 """Path to the HTML file for the component view."""
873 return os.path.join(_GetCoverageReportRootDirPath(),
874 COMPONENT_VIEW_INDEX_FILE)
875
876
877def _GetDirectoryViewPath():
878 """Path to the HTML file for the directory view."""
879 return os.path.join(_GetCoverageReportRootDirPath(),
880 DIRECTORY_VIEW_INDEX_FILE)
881
882
883def _GetFileViewPath():
884 """Path to the HTML file for the file view."""
885 return os.path.join(_GetCoverageReportRootDirPath(), FILE_VIEW_INDEX_FILE)
886
887
888def _GetLogsDirectoryPath():
889 """Path to the logs directory."""
890 return os.path.join(_GetCoverageReportRootDirPath(), LOGS_DIR_NAME)
891
892
893def _GetHtmlIndexPath():
894 """Path to the main HTML index file."""
895 return os.path.join(_GetCoverageReportRootDirPath(), INDEX_HTML_FILE)
896
897
898def _GetProfdataFilePath():
899 """Path to the resulting .profdata file."""
900 return os.path.join(_GetCoverageReportRootDirPath(), PROFDATA_FILE_NAME)
901
902
903def _GetSummaryFilePath():
904 """The JSON file that contains coverage summary written by llvm-cov export."""
905 return os.path.join(_GetCoverageReportRootDirPath(), SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33906
907
Yuke Liao506e8822017-12-04 16:52:54908def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
909 """Builds and runs target to generate the coverage profile data.
910
911 Args:
912 targets: A list of targets to build with coverage instrumentation.
913 commands: A list of commands used to run the targets.
914 jobs_count: Number of jobs to run in parallel for building. If None, a
915 default value is derived based on CPUs availability.
916
917 Returns:
918 A relative path to the generated profdata file.
919 """
920 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02921 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59922 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02923 coverage_profdata_file_path = (
924 _CreateCoverageProfileDataFromTargetProfDataFiles(
925 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54926
Abhishek Aryac19bc5ef2018-05-04 22:10:02927 for target_profdata_file_path in target_profdata_file_paths:
928 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52929
Abhishek Aryac19bc5ef2018-05-04 22:10:02930 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54931
932
933def _BuildTargets(targets, jobs_count):
934 """Builds target with Clang coverage instrumentation.
935
936 This function requires current working directory to be the root of checkout.
937
938 Args:
939 targets: A list of targets to build with coverage instrumentation.
940 jobs_count: Number of jobs to run in parallel for compilation. If None, a
941 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54942 """
Abhishek Arya1ec832c2017-12-05 18:06:59943
Yuke Liao506e8822017-12-04 16:52:54944 def _IsGomaConfigured():
945 """Returns True if goma is enabled in the gn build args.
946
947 Returns:
948 A boolean indicates whether goma is configured for building or not.
949 """
Yuke Liao80afff32018-03-07 01:26:20950 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54951 return 'use_goma' in build_args and build_args['use_goma'] == 'true'
952
Abhishek Aryafb70b532018-05-06 17:47:40953 logging.info('Building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54954 if jobs_count is None and _IsGomaConfigured():
955 jobs_count = DEFAULT_GOMA_JOBS
956
957 subprocess_cmd = ['ninja', '-C', BUILD_DIR]
958 if jobs_count is not None:
959 subprocess_cmd.append('-j' + str(jobs_count))
960
961 subprocess_cmd.extend(targets)
962 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40963 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54964
965
Abhishek Aryac19bc5ef2018-05-04 22:10:02966def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54967 """Runs commands and returns the relative paths to the profraw data files.
968
969 Args:
970 targets: A list of targets built with coverage instrumentation.
971 commands: A list of commands used to run the targets.
972
973 Returns:
974 A list of relative paths to the generated profraw data files.
975 """
Abhishek Aryafb70b532018-05-06 17:47:40976 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10977
Yuke Liao506e8822017-12-04 16:52:54978 # Remove existing profraw data files.
Max Moroz7c5354f2018-05-06 00:03:48979 for file_or_dir in os.listdir(_GetCoverageReportRootDirPath()):
Yuke Liao506e8822017-12-04 16:52:54980 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48981 os.remove(os.path.join(_GetCoverageReportRootDirPath(), file_or_dir))
982
983 # Ensure that logs directory exists.
984 if not os.path.exists(_GetLogsDirectoryPath()):
985 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54986
Abhishek Aryac19bc5ef2018-05-04 22:10:02987 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10988
Yuke Liaod4a9865202018-01-12 23:17:52989 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54990 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48991 output_file_name = os.extsep.join([target + '_output', 'log'])
992 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10993
Abhishek Aryac19bc5ef2018-05-04 22:10:02994 profdata_file_path = None
995 for _ in xrange(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40996 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02997 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10998
Abhishek Aryac19bc5ef2018-05-04 22:10:02999 if _IsIOSCommand(command):
1000 # On iOS platform, due to lack of write permissions, profraw files are
1001 # generated outside of the OUTPUT_DIR, and the exact paths are contained
1002 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:351003 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:021004 else:
1005 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:351006 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:021007
1008 profraw_file_paths = []
1009 if _IsIOS():
1010 profraw_file_paths = _GetProfrawDataFileByParsingOutput(output)
1011 else:
Max Moroz7c5354f2018-05-06 00:03:481012 for file_or_dir in os.listdir(_GetCoverageReportRootDirPath()):
Abhishek Aryac19bc5ef2018-05-04 22:10:021013 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:481014 profraw_file_paths.append(
1015 os.path.join(_GetCoverageReportRootDirPath(), file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:021016
1017 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:401018 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:041019 'please make sure the binary exists, is properly instrumented and '
1020 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:021021
1022 try:
1023 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
1024 target, profraw_file_paths)
1025 break
1026 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:041027 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:021028 finally:
1029 # Remove profraw files now so that they are not used in next iteration.
1030 for profraw_file_path in profraw_file_paths:
1031 os.remove(profraw_file_path)
1032
1033 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:041034 'Failed to merge target "%s" profraw files after %d retries. %s' %
1035 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:021036 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:541037
Abhishek Aryafb70b532018-05-06 17:47:401038 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:101039
Abhishek Aryac19bc5ef2018-05-04 22:10:021040 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:541041
1042
Abhishek Arya03911092018-05-21 16:42:351043def _GetEnvironmentVars(profraw_file_path):
1044 """Return environment vars for subprocess, given a profraw file path."""
1045 env = os.environ.copy()
1046 env.update({
1047 'LLVM_PROFILE_FILE': profraw_file_path,
1048 'PATH': _GetPathWithLLVMSymbolizerDir()
1049 })
1050 return env
1051
1052
1053def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:101054 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:521055 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:011056 #
Max Morozd73e45f2018-04-24 18:32:471057 # "%p" expands out to the process ID. It's not used by this scripts due to:
1058 # 1) If a target program spawns too many processess, it may exhaust all disk
1059 # space available. For example, unit_tests writes thousands of .profraw
1060 # files each of size 1GB+.
1061 # 2) If a target binary uses shared libraries, coverage profile data for them
1062 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:011063 #
Yuke Liaod4a9865202018-01-12 23:17:521064 # "%Nm" expands out to the instrumented binary's signature. When this pattern
1065 # is specified, the runtime creates a pool of N raw profiles which are used
1066 # for on-line profile merging. The runtime takes care of selecting a raw
1067 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:521068 # N must be between 1 and 9. The merge pool specifier can only occur once per
1069 # filename pattern.
1070 #
Max Morozd73e45f2018-04-24 18:32:471071 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:011072 #
Max Morozd73e45f2018-04-24 18:32:471073 # For other cases, "%4m" is chosen as it creates some level of parallelism,
1074 # but it's not too big to consume too much computing resource or disk space.
1075 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:591076 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:011077 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz7c5354f2018-05-06 00:03:481078 expected_profraw_file_path = os.path.join(_GetCoverageReportRootDirPath(),
Yuke Liao506e8822017-12-04 16:52:541079 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:241080 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
1081 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:541082
Yuke Liaoa0c8c2f2018-02-28 20:14:101083 try:
Max Moroz7c5354f2018-05-06 00:03:481084 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:351085 with open(output_file_path, 'wb') as output_file_handle:
1086 subprocess.check_call(
1087 shlex.split(command),
1088 stdout=output_file_handle,
1089 stderr=subprocess.STDOUT,
1090 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:101091 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:351092 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:101093
Abhishek Arya03911092018-05-21 16:42:351094 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:101095
1096
Yuke Liao27349c92018-03-22 21:10:011097def _IsFuzzerTarget(target):
1098 """Returns true if the target is a fuzzer target."""
1099 build_args = _GetBuildArgs()
1100 use_libfuzzer = ('use_libfuzzer' in build_args and
1101 build_args['use_libfuzzer'] == 'true')
1102 return use_libfuzzer and target.endswith('_fuzzer')
1103
1104
Abhishek Arya03911092018-05-21 16:42:351105def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:101106 """Runs a single iOS command and generates a profraw data file.
1107
1108 iOS application doesn't have write access to folders outside of the app, so
1109 it's impossible to instruct the app to flush the profraw data file to the
1110 desired location. The profraw data file will be generated somewhere within the
1111 application's Documents folder, and the full path can be obtained by parsing
1112 the output.
1113 """
Yuke Liaob2926832018-03-02 17:34:291114 assert _IsIOSCommand(command)
1115
1116 # After running tests, iossim generates a profraw data file, it won't be
1117 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
1118 # checkout.
1119 iossim_profraw_file_path = os.path.join(
1120 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:241121 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
1122 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:101123
1124 try:
Abhishek Arya03911092018-05-21 16:42:351125 with open(output_file_path, 'wb') as output_file_handle:
1126 subprocess.check_call(
1127 shlex.split(command),
1128 stdout=output_file_handle,
1129 stderr=subprocess.STDOUT,
1130 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:101131 except subprocess.CalledProcessError as e:
1132 # iossim emits non-zero return code even if tests run successfully, so
1133 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:351134 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:101135
Abhishek Arya03911092018-05-21 16:42:351136 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:101137
1138
1139def _GetProfrawDataFileByParsingOutput(output):
1140 """Returns the path to the profraw data file obtained by parsing the output.
1141
1142 The output of running the test target has no format, but it is guaranteed to
1143 have a single line containing the path to the generated profraw data file.
1144 NOTE: This should only be called when target os is iOS.
1145 """
Yuke Liaob2926832018-03-02 17:34:291146 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:101147
Yuke Liaob2926832018-03-02 17:34:291148 output_by_lines = ''.join(output).splitlines()
1149 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:101150
1151 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:291152 result = profraw_file_pattern.match(line)
1153 if result:
1154 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:101155
1156 assert False, ('No profraw data file was generated, did you call '
1157 'coverage_util::ConfigureCoverageReportPath() in test setup? '
1158 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:541159
1160
Abhishek Aryac19bc5ef2018-05-04 22:10:021161def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
1162 """Returns a relative path to coverage profdata file by merging target
1163 profdata files.
Yuke Liao506e8822017-12-04 16:52:541164
1165 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:021166 profdata_file_paths: A list of relative paths to the profdata data files
1167 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:541168
1169 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:021170 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:541171
1172 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:021173 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:541174 """
Abhishek Aryafb70b532018-05-06 17:47:401175 logging.info('Creating the coverage profile data file.')
1176 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:481177 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:541178 try:
Abhishek Arya1ec832c2017-12-05 18:06:591179 subprocess_cmd = [
1180 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
1181 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:021182 subprocess_cmd.extend(profdata_file_paths)
1183 subprocess.check_call(subprocess_cmd)
1184 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:041185 logging.error(
1186 'Failed to merge target profdata files to create coverage profdata. %s',
1187 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:021188 raise error
1189
Abhishek Aryafb70b532018-05-06 17:47:401190 logging.debug('Finished merging target profdata files.')
1191 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:021192 profdata_file_path)
1193 return profdata_file_path
1194
1195
1196def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
1197 """Returns a relative path to target profdata file by merging target
1198 profraw files.
1199
1200 Args:
1201 profraw_file_paths: A list of relative paths to the profdata data files
1202 that are to be merged.
1203
1204 Returns:
1205 A relative path to the merged coverage profdata file.
1206
1207 Raises:
1208 CalledProcessError: An error occurred merging profdata files.
1209 """
Abhishek Aryafb70b532018-05-06 17:47:401210 logging.info('Creating target profile data file.')
1211 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:021212 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
1213
1214 try:
1215 subprocess_cmd = [
1216 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
1217 ]
Yuke Liao506e8822017-12-04 16:52:541218 subprocess_cmd.extend(profraw_file_paths)
1219 subprocess.check_call(subprocess_cmd)
1220 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:041221 logging.error(
1222 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:541223 raise error
1224
Abhishek Aryafb70b532018-05-06 17:47:401225 logging.debug('Finished merging target profraw files.')
1226 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:101227 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:541228 return profdata_file_path
1229
1230
Yuke Liao0e4c8682018-04-18 21:06:591231def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
1232 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:331233 """Generates per file coverage summary using "llvm-cov export" command."""
1234 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
1235 # [[-object BIN]] [SOURCES].
1236 # NOTE: For object files, the first one is specified as a positional argument,
1237 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:101238 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:401239 'export -summary-only" command.')
Yuke Liaoea228d02018-01-05 19:10:331240 subprocess_cmd = [
1241 LLVM_COV_PATH, 'export', '-summary-only',
1242 '-instr-profile=' + profdata_file_path, binary_paths[0]
1243 ]
1244 subprocess_cmd.extend(
1245 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:291246 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:331247 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:591248 if ignore_filename_regex:
1249 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:331250
Max Moroz7c5354f2018-05-06 00:03:481251 export_output = subprocess.check_output(subprocess_cmd)
1252
1253 # Write output on the disk to be used by code coverage bot.
1254 with open(_GetSummaryFilePath(), 'w') as f:
1255 f.write(export_output)
1256
1257 json_output = json.loads(export_output)
Yuke Liaoea228d02018-01-05 19:10:331258 assert len(json_output['data']) == 1
1259 files_coverage_data = json_output['data'][0]['files']
1260
1261 per_file_coverage_summary = {}
1262 for file_coverage_data in files_coverage_data:
1263 file_path = file_coverage_data['filename']
Abhishek Aryafb70b532018-05-06 17:47:401264 assert file_path.startswith(SRC_ROOT_PATH + os.sep), (
1265 'File path "%s" in coverage summary is outside source checkout.' %
1266 file_path)
Yuke Liaoea228d02018-01-05 19:10:331267
Abhishek Aryafb70b532018-05-06 17:47:401268 summary = file_coverage_data['summary']
Yuke Liaoea228d02018-01-05 19:10:331269 if summary['lines']['count'] == 0:
1270 continue
1271
1272 per_file_coverage_summary[file_path] = _CoverageSummary(
1273 regions_total=summary['regions']['count'],
1274 regions_covered=summary['regions']['covered'],
1275 functions_total=summary['functions']['count'],
1276 functions_covered=summary['functions']['covered'],
1277 lines_total=summary['lines']['count'],
1278 lines_covered=summary['lines']['covered'])
1279
Abhishek Aryafb70b532018-05-06 17:47:401280 logging.debug('Finished generating per-file code coverage summary.')
Yuke Liaoea228d02018-01-05 19:10:331281 return per_file_coverage_summary
1282
1283
Yuke Liaob2926832018-03-02 17:34:291284def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
1285 """Appends -arch arguments to the command list if it's ios platform.
1286
1287 iOS binaries are universal binaries, and require specifying the architecture
1288 to use, and one architecture needs to be specified for each binary.
1289 """
1290 if _IsIOS():
1291 cmd_list.extend(['-arch=x86_64'] * num_archs)
1292
1293
Yuke Liao506e8822017-12-04 16:52:541294def _GetBinaryPath(command):
1295 """Returns a relative path to the binary to be run by the command.
1296
Yuke Liao545db322018-02-15 17:12:011297 Currently, following types of commands are supported (e.g. url_unittests):
1298 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
1299 2. Use xvfb.
1300 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
1301 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:371302 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
1303 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:101304 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:371305 <iossim_arguments> -c <app_arguments>
1306 out/Coverage-iphonesimulator/url_unittests.app"
1307
Yuke Liao506e8822017-12-04 16:52:541308 Args:
1309 command: A command used to run a target.
1310
1311 Returns:
1312 A relative path to the binary.
1313 """
Yuke Liao545db322018-02-15 17:12:011314 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
1315
Yuke Liaob2926832018-03-02 17:34:291316 command_parts = shlex.split(command)
Yuke Liao545db322018-02-15 17:12:011317 if os.path.basename(command_parts[0]) == 'python':
1318 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:401319 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:011320 return command_parts[2]
1321
1322 if os.path.basename(command_parts[0]) == xvfb_script_name:
1323 return command_parts[1]
1324
Yuke Liaob2926832018-03-02 17:34:291325 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:101326 # For a given application bundle, the binary resides in the bundle and has
1327 # the same name with the application without the .app extension.
Yuke Liao92107f02018-03-07 01:44:371328 app_path = command_parts[-1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:101329 app_name = os.path.splitext(os.path.basename(app_path))[0]
1330 return os.path.join(app_path, app_name)
1331
Yuke Liaob2926832018-03-02 17:34:291332 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:541333
1334
Yuke Liaob2926832018-03-02 17:34:291335def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:101336 """Returns true if command is used to run tests on iOS platform."""
Yuke Liaob2926832018-03-02 17:34:291337 return os.path.basename(shlex.split(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:101338
1339
Yuke Liao95d13d72017-12-07 18:18:501340def _VerifyTargetExecutablesAreInBuildDirectory(commands):
1341 """Verifies that the target executables specified in the commands are inside
1342 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:541343 for command in commands:
1344 binary_path = _GetBinaryPath(command)
Abhishek Arya03911092018-05-21 16:42:351345 binary_absolute_path = _GetFullPath(binary_path)
1346 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:501347 'Target executable "%s" in command: "%s" is outside of '
1348 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:541349
1350
1351def _ValidateBuildingWithClangCoverage():
1352 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:201353 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:541354
1355 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
1356 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:591357 assert False, ('\'{} = true\' is required in args.gn.'
1358 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:541359
1360
Yuke Liaoc60b2d02018-03-02 21:40:431361def _ValidateCurrentPlatformIsSupported():
1362 """Asserts that this script suports running on the current platform"""
1363 target_os = _GetTargetOS()
1364 if target_os:
1365 current_platform = target_os
1366 else:
1367 current_platform = _GetHostPlatform()
1368
1369 assert current_platform in [
1370 'linux', 'mac', 'chromeos', 'ios'
1371 ], ('Coverage is only supported on linux, mac, chromeos and ios.')
1372
1373
Yuke Liao80afff32018-03-07 01:26:201374def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:541375 """Parses args.gn file and returns results as a dictionary.
1376
1377 Returns:
1378 A dictionary representing the build args.
1379 """
Yuke Liao80afff32018-03-07 01:26:201380 global _BUILD_ARGS
1381 if _BUILD_ARGS is not None:
1382 return _BUILD_ARGS
1383
1384 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:541385 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
1386 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
1387 'missing args.gn file.' % BUILD_DIR)
1388 with open(build_args_path) as build_args_file:
1389 build_args_lines = build_args_file.readlines()
1390
Yuke Liao506e8822017-12-04 16:52:541391 for build_arg_line in build_args_lines:
1392 build_arg_without_comments = build_arg_line.split('#')[0]
1393 key_value_pair = build_arg_without_comments.split('=')
1394 if len(key_value_pair) != 2:
1395 continue
1396
1397 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:431398
1399 # Values are wrapped within a pair of double-quotes, so remove the leading
1400 # and trailing double-quotes.
1401 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:201402 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:541403
Yuke Liao80afff32018-03-07 01:26:201404 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:541405
1406
Abhishek Arya16f059a2017-12-07 17:47:321407def _VerifyPathsAndReturnAbsolutes(paths):
1408 """Verifies that the paths specified in |paths| exist and returns absolute
1409 versions.
Yuke Liao66da1732017-12-05 22:19:421410
1411 Args:
1412 paths: A list of files or directories.
1413 """
Abhishek Arya16f059a2017-12-07 17:47:321414 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:421415 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:321416 absolute_path = os.path.join(SRC_ROOT_PATH, path)
1417 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
1418
1419 absolute_paths.append(absolute_path)
1420
1421 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:421422
1423
Yuke Liaodd1ec0592018-02-02 01:26:371424def _GetRelativePathToDirectoryOfFile(target_path, base_path):
1425 """Returns a target path relative to the directory of base_path.
1426
1427 This method requires base_path to be a file, otherwise, one should call
1428 os.path.relpath directly.
1429 """
1430 assert os.path.dirname(base_path) != base_path, (
Yuke Liaoc7e607142018-02-05 20:26:141431 'Base path: "%s" is a directory, please call os.path.relpath directly.' %
Yuke Liaodd1ec0592018-02-02 01:26:371432 base_path)
Yuke Liaoc7e607142018-02-05 20:26:141433 base_dir = os.path.dirname(base_path)
1434 return os.path.relpath(target_path, base_dir)
Yuke Liaodd1ec0592018-02-02 01:26:371435
1436
Abhishek Arya64636af2018-05-04 14:42:131437def _GetBinaryPathsFromTargets(targets, build_dir):
1438 """Return binary paths from target names."""
1439 # FIXME: Derive output binary from target build definitions rather than
1440 # assuming that it is always the same name.
1441 binary_paths = []
1442 for target in targets:
1443 binary_path = os.path.join(build_dir, target)
1444 if _GetHostPlatform() == 'win':
1445 binary_path += '.exe'
1446
1447 if os.path.exists(binary_path):
1448 binary_paths.append(binary_path)
1449 else:
1450 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:401451 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:131452 os.path.basename(binary_path))
1453
1454 return binary_paths
1455
1456
Abhishek Arya03911092018-05-21 16:42:351457def _GetFullPath(path):
1458 """Return full absolute path."""
1459 return (os.path.abspath(
1460 os.path.realpath(os.path.expandvars(os.path.expanduser(path)))))
1461
1462
1463def _GetCommandForWebTests(arguments):
1464 """Return command to run for blink web tests."""
1465 command_list = [
1466 'python', 'testing/xvfb.py', 'python',
1467 'third_party/blink/tools/run_web_tests.py',
1468 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:241469 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
1470 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Abhishek Arya4a9494f2018-05-22 01:39:411471 '--batch-size=1',
Abhishek Arya03911092018-05-21 16:42:351472 '--child-processes=%d' % max(1, int(multiprocessing.cpu_count() / 2)),
Abhishek Aryabd0655d2018-05-21 19:55:241473 '--disable-breakpad', '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:351474 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
1475 ]
1476 if arguments.strip():
1477 command_list.append(arguments)
1478 return ' '.join(command_list)
1479
1480
1481def _GetBinaryPathForWebTests():
1482 """Return binary path used to run blink web tests."""
1483 host_platform = _GetHostPlatform()
1484 if host_platform == 'win':
1485 return os.path.join(BUILD_DIR, 'content_shell.exe')
1486 elif host_platform == 'linux':
1487 return os.path.join(BUILD_DIR, 'content_shell')
1488 elif host_platform == 'mac':
1489 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
1490 'Content Shell')
1491 else:
1492 assert False, 'This platform is not supported for web tests.'
1493
1494
Yuke Liao506e8822017-12-04 16:52:541495def _ParseCommandArguments():
1496 """Adds and parses relevant arguments for tool comands.
1497
1498 Returns:
1499 A dictionary representing the arguments.
1500 """
1501 arg_parser = argparse.ArgumentParser()
1502 arg_parser.usage = __doc__
1503
Abhishek Arya1ec832c2017-12-05 18:06:591504 arg_parser.add_argument(
1505 '-b',
1506 '--build-dir',
1507 type=str,
1508 required=True,
1509 help='The build directory, the path needs to be relative to the root of '
1510 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:541511
Abhishek Arya1ec832c2017-12-05 18:06:591512 arg_parser.add_argument(
1513 '-o',
1514 '--output-dir',
1515 type=str,
1516 required=True,
1517 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:541518
Abhishek Arya1ec832c2017-12-05 18:06:591519 arg_parser.add_argument(
1520 '-c',
1521 '--command',
1522 action='append',
Abhishek Arya64636af2018-05-04 14:42:131523 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:591524 help='Commands used to run test targets, one test target needs one and '
1525 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:131526 'current working directory is the root of the checkout. This option is '
1527 'incompatible with -p/--profdata-file option.')
1528
1529 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:351530 '-wt',
1531 '--web-tests',
1532 nargs='?',
1533 type=str,
1534 const=' ',
1535 required=False,
1536 help='Run blink web tests. Support passing arguments to run_web_tests.py')
1537
1538 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:131539 '-p',
1540 '--profdata-file',
1541 type=str,
1542 required=False,
1543 help='Path to profdata file to use for generating code coverage reports. '
1544 'This can be useful if you generated the profdata file seperately in '
1545 'your own test harness. This option is ignored if run command(s) are '
1546 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:541547
Abhishek Arya1ec832c2017-12-05 18:06:591548 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:421549 '-f',
1550 '--filters',
1551 action='append',
Abhishek Arya16f059a2017-12-07 17:47:321552 required=False,
Yuke Liao66da1732017-12-05 22:19:421553 help='Directories or files to get code coverage for, and all files under '
1554 'the directories are included recursively.')
1555
1556 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:591557 '-i',
1558 '--ignore-filename-regex',
1559 type=str,
1560 help='Skip source code files with file paths that match the given '
1561 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
1562 'to exclude files in third_party/ and out/ folders from the report.')
1563
1564 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:321565 '--no-file-view',
1566 action='store_true',
1567 help='Don\'t generate the file view in the coverage report. When there '
1568 'are large number of html files, the file view becomes heavy and may '
1569 'cause the browser to freeze, and this argument comes handy.')
1570
1571 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:401572 '--coverage-tools-dir',
1573 type=str,
1574 help='Path of the directory where LLVM coverage tools (llvm-cov, '
1575 'llvm-profdata) exist. This should be only needed if you are testing '
1576 'against a custom built clang revision. Otherwise, we pick coverage '
1577 'tools automatically from your current source checkout.')
1578
1579 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:591580 '-j',
1581 '--jobs',
1582 type=int,
1583 default=None,
1584 help='Run N jobs to build in parallel. If not specified, a default value '
1585 'will be derived based on CPUs availability. Please refer to '
1586 '\'ninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:541587
Abhishek Arya1ec832c2017-12-05 18:06:591588 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:101589 '-v',
1590 '--verbose',
1591 action='store_true',
1592 help='Prints additional output for diagnostics.')
1593
1594 arg_parser.add_argument(
1595 '-l', '--log_file', type=str, help='Redirects logs to a file.')
1596
1597 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:021598 'targets',
1599 nargs='+',
1600 help='The names of the test targets to run. If multiple run commands are '
1601 'specified using the -c/--command option, then the order of targets and '
1602 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:541603
1604 args = arg_parser.parse_args()
1605 return args
1606
1607
1608def Main():
1609 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:401610 # Setup coverage binaries even when script is called with empty params. This
1611 # is used by coverage bot for initial setup.
1612 if len(sys.argv) == 1:
1613 DownloadCoverageToolsIfNeeded()
1614 print(__doc__)
1615 return
1616
Abhishek Arya64636af2018-05-04 14:42:131617 # Change directory to source root to aid in relative paths calculations.
Abhishek Arya03911092018-05-21 16:42:351618 global SRC_ROOT_PATH
1619 SRC_ROOT_PATH = _GetFullPath(
1620 os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
Abhishek Arya64636af2018-05-04 14:42:131621 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:111622
Yuke Liao506e8822017-12-04 16:52:541623 args = _ParseCommandArguments()
Abhishek Arya64636af2018-05-04 14:42:131624 _ConfigureLogging(args)
Yuke Liao082e99632018-05-18 15:40:401625 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:131626
Yuke Liao506e8822017-12-04 16:52:541627 global BUILD_DIR
Abhishek Arya03911092018-05-21 16:42:351628 BUILD_DIR = _GetFullPath(args.build_dir)
Yuke Liao506e8822017-12-04 16:52:541629 global OUTPUT_DIR
Abhishek Arya03911092018-05-21 16:42:351630 OUTPUT_DIR = _GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:541631
Abhishek Arya03911092018-05-21 16:42:351632 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:131633 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:351634 'provide prof-data file as input using -p/--profdata-file option OR '
1635 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:431636
Abhishek Arya64636af2018-05-04 14:42:131637 assert not args.command or (len(args.targets) == len(args.command)), (
1638 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:431639
Abhishek Arya1ec832c2017-12-05 18:06:591640 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:401641 'Build directory: "%s" doesn\'t exist. '
1642 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:131643
Yuke Liaoc60b2d02018-03-02 21:40:431644 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:541645 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:321646
1647 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:421648 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:321649 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:421650
Max Moroz7c5354f2018-05-06 00:03:481651 if not os.path.exists(_GetCoverageReportRootDirPath()):
1652 os.makedirs(_GetCoverageReportRootDirPath())
Yuke Liao506e8822017-12-04 16:52:541653
Abhishek Arya03911092018-05-21 16:42:351654 # Get .profdata file and list of binary paths.
1655 if args.web_tests:
1656 commands = [_GetCommandForWebTests(args.web_tests)]
1657 profdata_file_path = _CreateCoverageProfileDataForTargets(
1658 args.targets, commands, args.jobs)
1659 binary_paths = [_GetBinaryPathForWebTests()]
1660 elif args.command:
1661 for i in range(len(args.command)):
1662 assert not 'run_web_tests.py' in args.command[i], (
1663 'run_web_tests.py is not supported via --command argument. '
1664 'Please use --run-web-tests argument instead.')
1665
Abhishek Arya64636af2018-05-04 14:42:131666 # A list of commands are provided. Run them to generate profdata file, and
1667 # create a list of binary paths from parsing commands.
1668 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1669 profdata_file_path = _CreateCoverageProfileDataForTargets(
1670 args.targets, args.command, args.jobs)
1671 binary_paths = [_GetBinaryPath(command) for command in args.command]
1672 else:
1673 # An input prof-data file is already provided. Just calculate binary paths.
1674 profdata_file_path = args.profdata_file
1675 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331676
Abhishek Arya78120bc2018-05-07 20:53:541677 binary_paths.extend(_GetSharedLibraries(binary_paths))
1678
Yuke Liao481d3482018-01-29 19:17:101679 logging.info('Generating code coverage report in html (this can take a while '
Abhishek Aryafb70b532018-05-06 17:47:401680 'depending on size of target!).')
Yuke Liaodd1ec0592018-02-02 01:26:371681 per_file_coverage_summary = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591682 binary_paths, profdata_file_path, absolute_filter_paths,
1683 args.ignore_filename_regex)
Yuke Liaodd1ec0592018-02-02 01:26:371684 _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:591685 absolute_filter_paths,
1686 args.ignore_filename_regex)
Yuke Liao1b852fd2018-05-11 17:07:321687 if not args.no_file_view:
1688 _GenerateFileViewHtmlIndexFile(per_file_coverage_summary)
Yuke Liaodd1ec0592018-02-02 01:26:371689
1690 per_directory_coverage_summary = _CalculatePerDirectoryCoverageSummary(
1691 per_file_coverage_summary)
1692 _GeneratePerDirectoryCoverageInHtml(per_directory_coverage_summary,
Yuke Liao1b852fd2018-05-11 17:07:321693 per_file_coverage_summary,
1694 args.no_file_view)
Yuke Liaodd1ec0592018-02-02 01:26:371695 _GenerateDirectoryViewHtmlIndexFile()
1696
1697 component_to_directories = _ExtractComponentToDirectoriesMapping()
1698 per_component_coverage_summary = _CalculatePerComponentCoverageSummary(
1699 component_to_directories, per_directory_coverage_summary)
Yuke Liao1b852fd2018-05-11 17:07:321700 _GeneratePerComponentCoverageInHtml(
1701 per_component_coverage_summary, component_to_directories,
1702 per_directory_coverage_summary, args.no_file_view)
1703 _GenerateComponentViewHtmlIndexFile(per_component_coverage_summary,
1704 args.no_file_view)
Yuke Liaoea228d02018-01-05 19:10:331705
1706 # The default index file is generated only for the list of source files, needs
Yuke Liaodd1ec0592018-02-02 01:26:371707 # to overwrite it to display per directory coverage view by default.
Yuke Liaoea228d02018-01-05 19:10:331708 _OverwriteHtmlReportsIndexFile()
Max Moroz7c5354f2018-05-06 00:03:481709 _CleanUpOutputDir()
Yuke Liaoea228d02018-01-05 19:10:331710
Abhishek Arya03911092018-05-21 16:42:351711 html_index_file_path = 'file://' + _GetFullPath(_GetHtmlIndexPath())
Abhishek Aryafb70b532018-05-06 17:47:401712 logging.info('Index file for html report is generated as: "%s".',
Yuke Liao481d3482018-01-29 19:17:101713 html_index_file_path)
Yuke Liao506e8822017-12-04 16:52:541714
Abhishek Arya1ec832c2017-12-05 18:06:591715
Yuke Liao506e8822017-12-04 16:52:541716if __name__ == '__main__':
1717 sys.exit(Main())