blob: 3dab1224a7f7d699524a5f030e05b3edf7d91e5e [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 Arya1ec832c2017-12-05 18:06:5959 For more options, please refer to tools/code_coverage/coverage.py -h.
Yuke Liao8e209fe82018-04-18 20:36:3860
61 For an overview of how code coverage works in Chromium, please refer to
62 https://chromium.googlesource.com/chromium/src/+/master/docs/code_coverage.md
Yuke Liao506e8822017-12-04 16:52:5463"""
64
65from __future__ import print_function
66
67import sys
68
69import argparse
Yuke Liaoea228d02018-01-05 19:10:3370import json
Yuke Liao481d3482018-01-29 19:17:1071import logging
Abhishek Arya03911092018-05-21 16:42:3572import multiprocessing
Yuke Liao506e8822017-12-04 16:52:5473import os
Yuke Liaob2926832018-03-02 17:34:2974import re
75import shlex
Max Moroz025d8952018-05-03 16:33:3476import shutil
Yuke Liao506e8822017-12-04 16:52:5477import subprocess
Yuke Liao506e8822017-12-04 16:52:5478import urllib2
79
Abhishek Arya1ec832c2017-12-05 18:06:5980sys.path.append(
81 os.path.join(
82 os.path.dirname(__file__), os.path.pardir, os.path.pardir, 'tools',
83 'clang', 'scripts'))
Yuke Liao506e8822017-12-04 16:52:5484import update as clang_update
85
Yuke Liaoea228d02018-01-05 19:10:3386sys.path.append(
87 os.path.join(
88 os.path.dirname(__file__), os.path.pardir, os.path.pardir,
89 'third_party'))
90import jinja2
91from collections import defaultdict
92
Yuke Liao082e99632018-05-18 15:40:4093# Absolute path to the code coverage tools binary. These paths can be
94# overwritten by user specified coverage tool paths.
Yuke Liao506e8822017-12-04 16:52:5495LLVM_BUILD_DIR = clang_update.LLVM_BUILD_DIR
Abhishek Arya1c97ea542018-05-10 03:53:1996LLVM_BIN_DIR = os.path.join(LLVM_BUILD_DIR, 'bin')
97LLVM_COV_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-cov')
98LLVM_PROFDATA_PATH = os.path.join(LLVM_BIN_DIR, 'llvm-profdata')
Yuke Liao506e8822017-12-04 16:52:5499
Abhishek Arya03911092018-05-21 16:42:35100# Absolute path to the root of the checkout.
101SRC_ROOT_PATH = None
102
Yuke Liao506e8822017-12-04 16:52:54103# Build directory, the value is parsed from command line arguments.
104BUILD_DIR = None
105
106# Output directory for generated artifacts, the value is parsed from command
107# line arguemnts.
108OUTPUT_DIR = None
109
110# Default number of jobs used to build when goma is configured and enabled.
111DEFAULT_GOMA_JOBS = 100
112
113# Name of the file extension for profraw data files.
114PROFRAW_FILE_EXTENSION = 'profraw'
115
116# Name of the final profdata file, and this file needs to be passed to
117# "llvm-cov" command in order to call "llvm-cov show" to inspect the
118# line-by-line coverage of specific files.
Max Moroz7c5354f2018-05-06 00:03:48119PROFDATA_FILE_NAME = os.extsep.join(['coverage', 'profdata'])
120
121# Name of the file with summary information generated by llvm-cov export.
122SUMMARY_FILE_NAME = os.extsep.join(['summary', 'json'])
Yuke Liao506e8822017-12-04 16:52:54123
124# Build arg required for generating code coverage data.
125CLANG_COVERAGE_BUILD_ARG = 'use_clang_coverage'
126
Yuke Liaoea228d02018-01-05 19:10:33127# The default name of the html coverage report for a directory.
128DIRECTORY_COVERAGE_HTML_REPORT_NAME = os.extsep.join(['report', 'html'])
129
Yuke Liaodd1ec0592018-02-02 01:26:37130# Name of the html index files for different views.
Yuke Liaodd1ec0592018-02-02 01:26:37131COMPONENT_VIEW_INDEX_FILE = os.extsep.join(['component_view_index', 'html'])
Max Moroz7c5354f2018-05-06 00:03:48132DIRECTORY_VIEW_INDEX_FILE = os.extsep.join(['directory_view_index', 'html'])
Yuke Liaodd1ec0592018-02-02 01:26:37133FILE_VIEW_INDEX_FILE = os.extsep.join(['file_view_index', 'html'])
Max Moroz7c5354f2018-05-06 00:03:48134INDEX_HTML_FILE = os.extsep.join(['index', 'html'])
135
136LOGS_DIR_NAME = 'logs'
Yuke Liaodd1ec0592018-02-02 01:26:37137
138# Used to extract a mapping between directories and components.
Abhishek Arya1c97ea542018-05-10 03:53:19139COMPONENT_MAPPING_URL = (
140 'https://storage.googleapis.com/chromium-owners/component_map.json')
Yuke Liaodd1ec0592018-02-02 01:26:37141
Yuke Liao80afff32018-03-07 01:26:20142# Caches the results returned by _GetBuildArgs, don't use this variable
143# directly, call _GetBuildArgs instead.
144_BUILD_ARGS = None
145
Abhishek Aryac19bc5ef2018-05-04 22:10:02146# Retry failed merges.
147MERGE_RETRIES = 3
148
Abhishek Aryad35de7e2018-05-10 22:23:04149# Message to guide user to file a bug when everything else fails.
150FILE_BUG_MESSAGE = (
151 'If it persists, please file a bug with the command you used, git revision '
152 'and args.gn config here: '
153 'https://bugs.chromium.org/p/chromium/issues/entry?'
154 'components=Tools%3ECodeCoverage')
155
Abhishek Aryabd0655d2018-05-21 19:55:24156# String to replace with actual llvm profile path.
157LLVM_PROFILE_FILE_PATH_SUBSTITUTION = '<llvm_profile_file_path>'
158
Yuke Liaoea228d02018-01-05 19:10:33159
160class _CoverageSummary(object):
161 """Encapsulates coverage summary representation."""
162
Yuke Liaodd1ec0592018-02-02 01:26:37163 def __init__(self,
164 regions_total=0,
165 regions_covered=0,
166 functions_total=0,
167 functions_covered=0,
168 lines_total=0,
169 lines_covered=0):
Yuke Liaoea228d02018-01-05 19:10:33170 """Initializes _CoverageSummary object."""
171 self._summary = {
172 'regions': {
173 'total': regions_total,
174 'covered': regions_covered
175 },
176 'functions': {
177 'total': functions_total,
178 'covered': functions_covered
179 },
180 'lines': {
181 'total': lines_total,
182 'covered': lines_covered
183 }
184 }
185
186 def Get(self):
187 """Returns summary as a dictionary."""
188 return self._summary
189
190 def AddSummary(self, other_summary):
191 """Adds another summary to this one element-wise."""
192 for feature in self._summary:
193 self._summary[feature]['total'] += other_summary.Get()[feature]['total']
194 self._summary[feature]['covered'] += other_summary.Get()[feature][
195 'covered']
196
197
Yuke Liaodd1ec0592018-02-02 01:26:37198class _CoverageReportHtmlGenerator(object):
199 """Encapsulates coverage html report generation.
Yuke Liaoea228d02018-01-05 19:10:33200
Yuke Liaodd1ec0592018-02-02 01:26:37201 The generated html has a table that contains links to other coverage reports.
Yuke Liaoea228d02018-01-05 19:10:33202 """
203
Yuke Liaodd1ec0592018-02-02 01:26:37204 def __init__(self, output_path, table_entry_type):
205 """Initializes _CoverageReportHtmlGenerator object.
206
207 Args:
208 output_path: Path to the html report that will be generated.
209 table_entry_type: Type of the table entries to be displayed in the table
210 header. For example: 'Path', 'Component'.
211 """
Yuke Liaoea228d02018-01-05 19:10:33212 css_file_name = os.extsep.join(['style', 'css'])
Max Moroz7c5354f2018-05-06 00:03:48213 css_absolute_path = os.path.join(OUTPUT_DIR, css_file_name)
Yuke Liaoea228d02018-01-05 19:10:33214 assert os.path.exists(css_absolute_path), (
215 'css file doesn\'t exit. Please make sure "llvm-cov show -format=html" '
Abhishek Aryafb70b532018-05-06 17:47:40216 'is called first, and the css file is generated at: "%s".' %
Yuke Liaoea228d02018-01-05 19:10:33217 css_absolute_path)
218
219 self._css_absolute_path = css_absolute_path
Yuke Liaodd1ec0592018-02-02 01:26:37220 self._output_path = output_path
221 self._table_entry_type = table_entry_type
222
Yuke Liaoea228d02018-01-05 19:10:33223 self._table_entries = []
Yuke Liaod54030e2018-01-08 17:34:12224 self._total_entry = {}
Abhishek Arya302b67a2018-05-10 19:43:23225
226 source_dir = os.path.dirname(os.path.realpath(__file__))
227 template_dir = os.path.join(source_dir, 'html_templates')
Yuke Liaoea228d02018-01-05 19:10:33228
229 jinja_env = jinja2.Environment(
230 loader=jinja2.FileSystemLoader(template_dir), trim_blocks=True)
231 self._header_template = jinja_env.get_template('header.html')
232 self._table_template = jinja_env.get_template('table.html')
233 self._footer_template = jinja_env.get_template('footer.html')
Abhishek Arya302b67a2018-05-10 19:43:23234
Abhishek Arya865fffd2018-05-08 22:16:01235 self._style_overrides = open(
Abhishek Arya302b67a2018-05-10 19:43:23236 os.path.join(source_dir, 'static', 'css', 'style.css')).read()
Yuke Liaoea228d02018-01-05 19:10:33237
238 def AddLinkToAnotherReport(self, html_report_path, name, summary):
239 """Adds a link to another html report in this report.
240
241 The link to be added is assumed to be an entry in this directory.
242 """
Yuke Liaodd1ec0592018-02-02 01:26:37243 # Use relative paths instead of absolute paths to make the generated reports
244 # portable.
245 html_report_relative_path = _GetRelativePathToDirectoryOfFile(
246 html_report_path, self._output_path)
247
Yuke Liaod54030e2018-01-08 17:34:12248 table_entry = self._CreateTableEntryFromCoverageSummary(
Yuke Liaodd1ec0592018-02-02 01:26:37249 summary, html_report_relative_path, name,
Yuke Liaod54030e2018-01-08 17:34:12250 os.path.basename(html_report_path) ==
251 DIRECTORY_COVERAGE_HTML_REPORT_NAME)
252 self._table_entries.append(table_entry)
253
254 def CreateTotalsEntry(self, summary):
Yuke Liaoa785f4d32018-02-13 21:41:35255 """Creates an entry corresponds to the 'Totals' row in the html report."""
Yuke Liaod54030e2018-01-08 17:34:12256 self._total_entry = self._CreateTableEntryFromCoverageSummary(summary)
257
258 def _CreateTableEntryFromCoverageSummary(self,
259 summary,
260 href=None,
261 name=None,
262 is_dir=None):
263 """Creates an entry to display in the html report."""
Yuke Liaodd1ec0592018-02-02 01:26:37264 assert (href is None and name is None and is_dir is None) or (
265 href is not None and name is not None and is_dir is not None), (
266 'The only scenario when href or name or is_dir can be None is when '
Yuke Liaoa785f4d32018-02-13 21:41:35267 'creating an entry for the Totals row, and in that case, all three '
Yuke Liaodd1ec0592018-02-02 01:26:37268 'attributes must be None.')
269
Yuke Liaod54030e2018-01-08 17:34:12270 entry = {}
Yuke Liaodd1ec0592018-02-02 01:26:37271 if href is not None:
272 entry['href'] = href
273 if name is not None:
274 entry['name'] = name
275 if is_dir is not None:
276 entry['is_dir'] = is_dir
277
Yuke Liaoea228d02018-01-05 19:10:33278 summary_dict = summary.Get()
Yuke Liaod54030e2018-01-08 17:34:12279 for feature in summary_dict:
Yuke Liaodd1ec0592018-02-02 01:26:37280 if summary_dict[feature]['total'] == 0:
281 percentage = 0.0
282 else:
Yuke Liao0e4c8682018-04-18 21:06:59283 percentage = float(summary_dict[feature]
284 ['covered']) / summary_dict[feature]['total'] * 100
Yuke Liaoa785f4d32018-02-13 21:41:35285
Yuke Liaoea228d02018-01-05 19:10:33286 color_class = self._GetColorClass(percentage)
Yuke Liaod54030e2018-01-08 17:34:12287 entry[feature] = {
Yuke Liaoea228d02018-01-05 19:10:33288 'total': summary_dict[feature]['total'],
289 'covered': summary_dict[feature]['covered'],
Yuke Liaoa785f4d32018-02-13 21:41:35290 'percentage': '{:6.2f}'.format(percentage),
Yuke Liaoea228d02018-01-05 19:10:33291 'color_class': color_class
292 }
Yuke Liaod54030e2018-01-08 17:34:12293
Yuke Liaod54030e2018-01-08 17:34:12294 return entry
Yuke Liaoea228d02018-01-05 19:10:33295
296 def _GetColorClass(self, percentage):
297 """Returns the css color class based on coverage percentage."""
298 if percentage >= 0 and percentage < 80:
299 return 'red'
300 if percentage >= 80 and percentage < 100:
301 return 'yellow'
302 if percentage == 100:
303 return 'green'
304
Abhishek Aryafb70b532018-05-06 17:47:40305 assert False, 'Invalid coverage percentage: "%d".' % percentage
Yuke Liaoea228d02018-01-05 19:10:33306
Yuke Liao1b852fd2018-05-11 17:07:32307 def WriteHtmlCoverageReport(self, no_file_view):
Yuke Liaodd1ec0592018-02-02 01:26:37308 """Writes html coverage report.
Yuke Liaoea228d02018-01-05 19:10:33309
310 In the report, sub-directories are displayed before files and within each
311 category, entries are sorted alphabetically.
Yuke Liaoea228d02018-01-05 19:10:33312 """
313
314 def EntryCmp(left, right):
315 """Compare function for table entries."""
316 if left['is_dir'] != right['is_dir']:
317 return -1 if left['is_dir'] == True else 1
318
Yuke Liaodd1ec0592018-02-02 01:26:37319 return -1 if left['name'] < right['name'] else 1
Yuke Liaoea228d02018-01-05 19:10:33320
321 self._table_entries = sorted(self._table_entries, cmp=EntryCmp)
322
323 css_path = os.path.join(OUTPUT_DIR, os.extsep.join(['style', 'css']))
Max Moroz7c5354f2018-05-06 00:03:48324
325 directory_view_path = _GetDirectoryViewPath()
Yuke Liao1b852fd2018-05-11 17:07:32326 directory_view_href = _GetRelativePathToDirectoryOfFile(
327 directory_view_path, self._output_path)
Max Moroz7c5354f2018-05-06 00:03:48328 component_view_path = _GetComponentViewPath()
Yuke Liao1b852fd2018-05-11 17:07:32329 component_view_href = _GetRelativePathToDirectoryOfFile(
330 component_view_path, self._output_path)
331
332 # File view is optional in the report.
333 file_view_href = None
334 if not no_file_view:
335 file_view_path = _GetFileViewPath()
336 file_view_href = _GetRelativePathToDirectoryOfFile(
337 file_view_path, self._output_path)
Yuke Liaodd1ec0592018-02-02 01:26:37338
Yuke Liaoea228d02018-01-05 19:10:33339 html_header = self._header_template.render(
Yuke Liaodd1ec0592018-02-02 01:26:37340 css_path=_GetRelativePathToDirectoryOfFile(css_path, self._output_path),
Yuke Liao1b852fd2018-05-11 17:07:32341 directory_view_href=directory_view_href,
342 component_view_href=component_view_href,
343 file_view_href=file_view_href,
Abhishek Arya865fffd2018-05-08 22:16:01344 style_overrides=self._style_overrides)
Yuke Liaodd1ec0592018-02-02 01:26:37345
Yuke Liaod54030e2018-01-08 17:34:12346 html_table = self._table_template.render(
Yuke Liaodd1ec0592018-02-02 01:26:37347 entries=self._table_entries,
348 total_entry=self._total_entry,
349 table_entry_type=self._table_entry_type)
Yuke Liaoea228d02018-01-05 19:10:33350 html_footer = self._footer_template.render()
351
Yuke Liaodd1ec0592018-02-02 01:26:37352 with open(self._output_path, 'w') as html_file:
Yuke Liaoea228d02018-01-05 19:10:33353 html_file.write(html_header + html_table + html_footer)
354
Yuke Liao506e8822017-12-04 16:52:54355
Abhishek Arya64636af2018-05-04 14:42:13356def _ConfigureLogging(args):
357 """Configures logging settings for later use."""
358 log_level = logging.DEBUG if args.verbose else logging.INFO
359 log_format = '[%(asctime)s %(levelname)s] %(message)s'
360 log_file = args.log_file if args.log_file else None
361 logging.basicConfig(filename=log_file, level=log_level, format=log_format)
362
363
Yuke Liao082e99632018-05-18 15:40:40364def _ConfigureLLVMCoverageTools(args):
365 """Configures llvm coverage tools."""
366 if args.coverage_tools_dir:
Abhishek Arya03911092018-05-21 16:42:35367 llvm_bin_dir = _GetFullPath(args.coverage_tools_dir)
Yuke Liao082e99632018-05-18 15:40:40368 global LLVM_COV_PATH
369 global LLVM_PROFDATA_PATH
370 LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov')
371 LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata')
372 else:
373 DownloadCoverageToolsIfNeeded()
374
375 coverage_tools_exist = (
376 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
377 assert coverage_tools_exist, ('Cannot find coverage tools, please make sure '
378 'both \'%s\' and \'%s\' exist.') % (
379 LLVM_COV_PATH, LLVM_PROFDATA_PATH)
380
381
Max Morozd73e45f2018-04-24 18:32:47382def _GetSharedLibraries(binary_paths):
Abhishek Arya78120bc2018-05-07 20:53:54383 """Returns list of shared libraries used by specified binaries."""
384 logging.info('Finding shared libraries for targets (if any).')
385 shared_libraries = []
Max Morozd73e45f2018-04-24 18:32:47386 cmd = []
387 shared_library_re = None
388
389 if sys.platform.startswith('linux'):
390 cmd.extend(['ldd'])
Abhishek Arya64636af2018-05-04 14:42:13391 shared_library_re = re.compile(r'.*\.so\s=>\s(.*' + BUILD_DIR +
392 r'.*\.so)\s.*')
Max Morozd73e45f2018-04-24 18:32:47393 elif sys.platform.startswith('darwin'):
394 cmd.extend(['otool', '-L'])
395 shared_library_re = re.compile(r'\s+(@rpath/.*\.dylib)\s.*')
396 else:
Abhishek Aryafb70b532018-05-06 17:47:40397 assert False, 'Cannot detect shared libraries used by the given targets.'
Max Morozd73e45f2018-04-24 18:32:47398
399 assert shared_library_re is not None
400
401 cmd.extend(binary_paths)
402 output = subprocess.check_output(cmd)
403
404 for line in output.splitlines():
405 m = shared_library_re.match(line)
406 if not m:
407 continue
408
409 shared_library_path = m.group(1)
410 if sys.platform.startswith('darwin'):
411 # otool outputs "@rpath" macro instead of the dirname of the given binary.
412 shared_library_path = shared_library_path.replace('@rpath', BUILD_DIR)
413
Abhishek Arya78120bc2018-05-07 20:53:54414 if shared_library_path in shared_libraries:
415 continue
416
Max Morozd73e45f2018-04-24 18:32:47417 assert os.path.exists(shared_library_path), ('Shared library "%s" used by '
418 'the given target(s) does not '
419 'exist.' % shared_library_path)
420 with open(shared_library_path) as f:
421 data = f.read()
422
423 # Do not add non-instrumented libraries. Otherwise, llvm-cov errors outs.
424 if '__llvm_cov' in data:
Abhishek Arya78120bc2018-05-07 20:53:54425 shared_libraries.append(shared_library_path)
Max Morozd73e45f2018-04-24 18:32:47426
Abhishek Arya78120bc2018-05-07 20:53:54427 logging.debug('Found shared libraries (%d): %s.', len(shared_libraries),
428 shared_libraries)
429 logging.info('Finished finding shared libraries for targets.')
430 return shared_libraries
Max Morozd73e45f2018-04-24 18:32:47431
432
Yuke Liaoc60b2d02018-03-02 21:40:43433def _GetHostPlatform():
434 """Returns the host platform.
435
436 This is separate from the target platform/os that coverage is running for.
437 """
Abhishek Arya1ec832c2017-12-05 18:06:59438 if sys.platform == 'win32' or sys.platform == 'cygwin':
439 return 'win'
440 if sys.platform.startswith('linux'):
441 return 'linux'
442 else:
443 assert sys.platform == 'darwin'
444 return 'mac'
445
446
Abhishek Arya1c97ea542018-05-10 03:53:19447def _GetPathWithLLVMSymbolizerDir():
448 """Add llvm-symbolizer directory to path for symbolized stacks."""
449 path = os.getenv('PATH')
450 dirs = path.split(os.pathsep)
451 if LLVM_BIN_DIR in dirs:
452 return path
453
454 return path + os.pathsep + LLVM_BIN_DIR
455
456
Yuke Liaoc60b2d02018-03-02 21:40:43457def _GetTargetOS():
458 """Returns the target os specified in args.gn file.
459
460 Returns an empty string is target_os is not specified.
461 """
Yuke Liao80afff32018-03-07 01:26:20462 build_args = _GetBuildArgs()
Yuke Liaoc60b2d02018-03-02 21:40:43463 return build_args['target_os'] if 'target_os' in build_args else ''
464
465
Yuke Liaob2926832018-03-02 17:34:29466def _IsIOS():
Yuke Liaoa0c8c2f2018-02-28 20:14:10467 """Returns true if the target_os specified in args.gn file is ios"""
Yuke Liaoc60b2d02018-03-02 21:40:43468 return _GetTargetOS() == 'ios'
Yuke Liaoa0c8c2f2018-02-28 20:14:10469
470
Yuke Liao506e8822017-12-04 16:52:54471# TODO(crbug.com/759794): remove this function once tools get included to
472# Clang bundle:
473# https://chromium-review.googlesource.com/c/chromium/src/+/688221
474def DownloadCoverageToolsIfNeeded():
475 """Temporary solution to download llvm-profdata and llvm-cov tools."""
Abhishek Arya1ec832c2017-12-05 18:06:59476
Yuke Liaoc60b2d02018-03-02 21:40:43477 def _GetRevisionFromStampFile(stamp_file_path):
Yuke Liao506e8822017-12-04 16:52:54478 """Returns a pair of revision number by reading the build stamp file.
479
480 Args:
481 stamp_file_path: A path the build stamp file created by
482 tools/clang/scripts/update.py.
483 Returns:
484 A pair of integers represeting the main and sub revision respectively.
485 """
486 if not os.path.exists(stamp_file_path):
487 return 0, 0
488
489 with open(stamp_file_path) as stamp_file:
Yuke Liaoc60b2d02018-03-02 21:40:43490 stamp_file_line = stamp_file.readline()
491 if ',' in stamp_file_line:
492 package_version = stamp_file_line.rstrip().split(',')[0]
493 else:
494 package_version = stamp_file_line.rstrip()
Yuke Liao506e8822017-12-04 16:52:54495
Yuke Liaoc60b2d02018-03-02 21:40:43496 clang_revision_str, clang_sub_revision_str = package_version.split('-')
497 return int(clang_revision_str), int(clang_sub_revision_str)
Abhishek Arya1ec832c2017-12-05 18:06:59498
Yuke Liaoc60b2d02018-03-02 21:40:43499 host_platform = _GetHostPlatform()
Yuke Liao506e8822017-12-04 16:52:54500 clang_revision, clang_sub_revision = _GetRevisionFromStampFile(
Yuke Liaoc60b2d02018-03-02 21:40:43501 clang_update.STAMP_FILE)
Yuke Liao506e8822017-12-04 16:52:54502
503 coverage_revision_stamp_file = os.path.join(
504 os.path.dirname(clang_update.STAMP_FILE), 'cr_coverage_revision')
505 coverage_revision, coverage_sub_revision = _GetRevisionFromStampFile(
Yuke Liaoc60b2d02018-03-02 21:40:43506 coverage_revision_stamp_file)
Yuke Liao506e8822017-12-04 16:52:54507
Yuke Liaoea228d02018-01-05 19:10:33508 has_coverage_tools = (
509 os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
Abhishek Arya16f059a2017-12-07 17:47:32510
Yuke Liaoea228d02018-01-05 19:10:33511 if (has_coverage_tools and coverage_revision == clang_revision and
Yuke Liao506e8822017-12-04 16:52:54512 coverage_sub_revision == clang_sub_revision):
513 # LLVM coverage tools are up to date, bail out.
Yuke Liaoc60b2d02018-03-02 21:40:43514 return
Yuke Liao506e8822017-12-04 16:52:54515
516 package_version = '%d-%d' % (clang_revision, clang_sub_revision)
517 coverage_tools_file = 'llvm-code-coverage-%s.tgz' % package_version
518
519 # The code bellow follows the code from tools/clang/scripts/update.py.
Yuke Liaoc60b2d02018-03-02 21:40:43520 if host_platform == 'mac':
Yuke Liao506e8822017-12-04 16:52:54521 coverage_tools_url = clang_update.CDS_URL + '/Mac/' + coverage_tools_file
Yuke Liaoc60b2d02018-03-02 21:40:43522 elif host_platform == 'linux':
Yuke Liao506e8822017-12-04 16:52:54523 coverage_tools_url = (
524 clang_update.CDS_URL + '/Linux_x64/' + coverage_tools_file)
Yuke Liaoc60b2d02018-03-02 21:40:43525 else:
526 assert host_platform == 'win'
527 coverage_tools_url = (clang_update.CDS_URL + '/Win/' + coverage_tools_file)
Yuke Liao506e8822017-12-04 16:52:54528
529 try:
530 clang_update.DownloadAndUnpack(coverage_tools_url,
531 clang_update.LLVM_BUILD_DIR)
Yuke Liao506e8822017-12-04 16:52:54532 with open(coverage_revision_stamp_file, 'w') as file_handle:
Yuke Liaoc60b2d02018-03-02 21:40:43533 file_handle.write('%s,%s' % (package_version, host_platform))
Yuke Liao506e8822017-12-04 16:52:54534 file_handle.write('\n')
535 except urllib2.URLError:
536 raise Exception(
537 'Failed to download coverage tools: %s.' % coverage_tools_url)
538
539
Yuke Liaodd1ec0592018-02-02 01:26:37540def _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:59541 filters, ignore_filename_regex):
Yuke Liao506e8822017-12-04 16:52:54542 """Generates per file line-by-line coverage in html using 'llvm-cov show'.
543
544 For a file with absolute path /a/b/x.cc, a html report is generated as:
545 OUTPUT_DIR/coverage/a/b/x.cc.html. An index html file is also generated as:
546 OUTPUT_DIR/index.html.
547
548 Args:
549 binary_paths: A list of paths to the instrumented binaries.
550 profdata_file_path: A path to the profdata file.
Yuke Liao66da1732017-12-05 22:19:42551 filters: A list of directories and files to get coverage for.
Yuke Liao506e8822017-12-04 16:52:54552 """
Yuke Liao506e8822017-12-04 16:52:54553 # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...]
554 # [[-object BIN]] [SOURCES]
555 # NOTE: For object files, the first one is specified as a positional argument,
556 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:10557 logging.debug('Generating per file line by line coverage reports using '
Abhishek Aryafb70b532018-05-06 17:47:40558 '"llvm-cov show" command.')
Abhishek Arya1ec832c2017-12-05 18:06:59559 subprocess_cmd = [
560 LLVM_COV_PATH, 'show', '-format=html',
561 '-output-dir={}'.format(OUTPUT_DIR),
562 '-instr-profile={}'.format(profdata_file_path), binary_paths[0]
563 ]
564 subprocess_cmd.extend(
565 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:29566 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Ryan Sleeviae19b2c32018-05-15 22:36:17567 if _GetHostPlatform() in ['linux', 'mac']:
568 subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n'])
Yuke Liao66da1732017-12-05 22:19:42569 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:59570 if ignore_filename_regex:
571 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
572
Yuke Liao506e8822017-12-04 16:52:54573 subprocess.check_call(subprocess_cmd)
Max Moroz025d8952018-05-03 16:33:34574
575 # llvm-cov creates "coverage" subdir in the output dir. We would like to use
576 # the platform name instead, as it simplifies the report dir structure when
577 # the same report is generated for different platforms.
578 default_report_subdir_path = os.path.join(OUTPUT_DIR, 'coverage')
Max Moroz7c5354f2018-05-06 00:03:48579 platform_report_subdir_path = _GetCoverageReportRootDirPath()
580 _MergeTwoDirectories(default_report_subdir_path, platform_report_subdir_path)
Max Moroz025d8952018-05-03 16:33:34581
Abhishek Aryafb70b532018-05-06 17:47:40582 logging.debug('Finished running "llvm-cov show" command.')
Yuke Liao506e8822017-12-04 16:52:54583
584
Yuke Liaodd1ec0592018-02-02 01:26:37585def _GenerateFileViewHtmlIndexFile(per_file_coverage_summary):
586 """Generates html index file for file view."""
Max Moroz7c5354f2018-05-06 00:03:48587 file_view_index_file_path = _GetFileViewPath()
Yuke Liaodd1ec0592018-02-02 01:26:37588 logging.debug('Generating file view html index file as: "%s".',
589 file_view_index_file_path)
590 html_generator = _CoverageReportHtmlGenerator(file_view_index_file_path,
591 'Path')
592 totals_coverage_summary = _CoverageSummary()
Yuke Liaoea228d02018-01-05 19:10:33593
Yuke Liaodd1ec0592018-02-02 01:26:37594 for file_path in per_file_coverage_summary:
595 totals_coverage_summary.AddSummary(per_file_coverage_summary[file_path])
596
597 html_generator.AddLinkToAnotherReport(
598 _GetCoverageHtmlReportPathForFile(file_path),
599 os.path.relpath(file_path, SRC_ROOT_PATH),
600 per_file_coverage_summary[file_path])
601
602 html_generator.CreateTotalsEntry(totals_coverage_summary)
Yuke Liao1b852fd2018-05-11 17:07:32603 html_generator.WriteHtmlCoverageReport(no_file_view=False)
Yuke Liaodd1ec0592018-02-02 01:26:37604 logging.debug('Finished generating file view html index file.')
605
606
607def _CalculatePerDirectoryCoverageSummary(per_file_coverage_summary):
608 """Calculates per directory coverage summary."""
Abhishek Aryafb70b532018-05-06 17:47:40609 logging.debug('Calculating per-directory coverage summary.')
Yuke Liaodd1ec0592018-02-02 01:26:37610 per_directory_coverage_summary = defaultdict(lambda: _CoverageSummary())
611
Yuke Liaoea228d02018-01-05 19:10:33612 for file_path in per_file_coverage_summary:
613 summary = per_file_coverage_summary[file_path]
614 parent_dir = os.path.dirname(file_path)
Abhishek Aryafb70b532018-05-06 17:47:40615
Yuke Liaoea228d02018-01-05 19:10:33616 while True:
617 per_directory_coverage_summary[parent_dir].AddSummary(summary)
618
619 if parent_dir == SRC_ROOT_PATH:
620 break
621 parent_dir = os.path.dirname(parent_dir)
622
Abhishek Aryafb70b532018-05-06 17:47:40623 logging.debug('Finished calculating per-directory coverage summary.')
Yuke Liaodd1ec0592018-02-02 01:26:37624 return per_directory_coverage_summary
625
626
Yuke Liao1b852fd2018-05-11 17:07:32627def _GeneratePerDirectoryCoverageInHtml(
628 per_directory_coverage_summary, per_file_coverage_summary, no_file_view):
Yuke Liaodd1ec0592018-02-02 01:26:37629 """Generates per directory coverage breakdown in html."""
Abhishek Aryafb70b532018-05-06 17:47:40630 logging.debug('Writing per-directory coverage html reports.')
Yuke Liaoea228d02018-01-05 19:10:33631 for dir_path in per_directory_coverage_summary:
Yuke Liao1b852fd2018-05-11 17:07:32632 _GenerateCoverageInHtmlForDirectory(dir_path,
633 per_directory_coverage_summary,
634 per_file_coverage_summary, no_file_view)
Yuke Liaoea228d02018-01-05 19:10:33635
Abhishek Aryafb70b532018-05-06 17:47:40636 logging.debug('Finished writing per-directory coverage html reports.')
Yuke Liao481d3482018-01-29 19:17:10637
Yuke Liaoea228d02018-01-05 19:10:33638
639def _GenerateCoverageInHtmlForDirectory(
Yuke Liao1b852fd2018-05-11 17:07:32640 dir_path, per_directory_coverage_summary, per_file_coverage_summary,
641 no_file_view):
Yuke Liaoea228d02018-01-05 19:10:33642 """Generates coverage html report for a single directory."""
Yuke Liaodd1ec0592018-02-02 01:26:37643 html_generator = _CoverageReportHtmlGenerator(
644 _GetCoverageHtmlReportPathForDirectory(dir_path), 'Path')
Yuke Liaoea228d02018-01-05 19:10:33645
646 for entry_name in os.listdir(dir_path):
647 entry_path = os.path.normpath(os.path.join(dir_path, entry_name))
Yuke Liaoea228d02018-01-05 19:10:33648
Yuke Liaodd1ec0592018-02-02 01:26:37649 if entry_path in per_file_coverage_summary:
650 entry_html_report_path = _GetCoverageHtmlReportPathForFile(entry_path)
651 entry_coverage_summary = per_file_coverage_summary[entry_path]
652 elif entry_path in per_directory_coverage_summary:
653 entry_html_report_path = _GetCoverageHtmlReportPathForDirectory(
654 entry_path)
655 entry_coverage_summary = per_directory_coverage_summary[entry_path]
656 else:
Yuke Liaoc7e607142018-02-05 20:26:14657 # Any file without executable lines shouldn't be included into the report.
658 # For example, OWNER and README.md files.
Yuke Liaodd1ec0592018-02-02 01:26:37659 continue
Yuke Liaoea228d02018-01-05 19:10:33660
Yuke Liaodd1ec0592018-02-02 01:26:37661 html_generator.AddLinkToAnotherReport(entry_html_report_path,
662 os.path.basename(entry_path),
663 entry_coverage_summary)
Yuke Liaoea228d02018-01-05 19:10:33664
Yuke Liaod54030e2018-01-08 17:34:12665 html_generator.CreateTotalsEntry(per_directory_coverage_summary[dir_path])
Yuke Liao1b852fd2018-05-11 17:07:32666 html_generator.WriteHtmlCoverageReport(no_file_view)
Yuke Liaodd1ec0592018-02-02 01:26:37667
668
669def _GenerateDirectoryViewHtmlIndexFile():
670 """Generates the html index file for directory view.
671
672 Note that the index file is already generated under SRC_ROOT_PATH, so this
673 file simply redirects to it, and the reason of this extra layer is for
674 structural consistency with other views.
675 """
Max Moroz7c5354f2018-05-06 00:03:48676 directory_view_index_file_path = _GetDirectoryViewPath()
Yuke Liaodd1ec0592018-02-02 01:26:37677 logging.debug('Generating directory view html index file as: "%s".',
678 directory_view_index_file_path)
679 src_root_html_report_path = _GetCoverageHtmlReportPathForDirectory(
680 SRC_ROOT_PATH)
681 _WriteRedirectHtmlFile(directory_view_index_file_path,
682 src_root_html_report_path)
683 logging.debug('Finished generating directory view html index file.')
684
685
686def _CalculatePerComponentCoverageSummary(component_to_directories,
687 per_directory_coverage_summary):
688 """Calculates per component coverage summary."""
Abhishek Aryafb70b532018-05-06 17:47:40689 logging.debug('Calculating per-component coverage summary.')
Yuke Liaodd1ec0592018-02-02 01:26:37690 per_component_coverage_summary = defaultdict(lambda: _CoverageSummary())
691
692 for component in component_to_directories:
693 for directory in component_to_directories[component]:
Abhishek Arya03911092018-05-21 16:42:35694 absolute_directory_path = _GetFullPath(directory)
Yuke Liaodd1ec0592018-02-02 01:26:37695 if absolute_directory_path in per_directory_coverage_summary:
696 per_component_coverage_summary[component].AddSummary(
697 per_directory_coverage_summary[absolute_directory_path])
698
Abhishek Aryafb70b532018-05-06 17:47:40699 logging.debug('Finished calculating per-component coverage summary.')
Yuke Liaodd1ec0592018-02-02 01:26:37700 return per_component_coverage_summary
701
702
703def _ExtractComponentToDirectoriesMapping():
704 """Returns a mapping from components to directories."""
705 component_mappings = json.load(urllib2.urlopen(COMPONENT_MAPPING_URL))
706 directory_to_component = component_mappings['dir-to-component']
707
708 component_to_directories = defaultdict(list)
Abhishek Arya8c3a1ce322018-05-13 04:14:01709 for directory in sorted(directory_to_component):
Yuke Liaodd1ec0592018-02-02 01:26:37710 component = directory_to_component[directory]
Abhishek Arya8c3a1ce322018-05-13 04:14:01711
712 # Check if we already added the parent directory of this directory. If yes,
713 # skip this sub-directory to avoid double-counting.
714 found_parent_directory = False
715 for component_directory in component_to_directories[component]:
716 if directory.startswith(component_directory + '/'):
717 found_parent_directory = True
718 break
719
720 if not found_parent_directory:
721 component_to_directories[component].append(directory)
Yuke Liaodd1ec0592018-02-02 01:26:37722
723 return component_to_directories
724
725
Yuke Liao1b852fd2018-05-11 17:07:32726def _GeneratePerComponentCoverageInHtml(
727 per_component_coverage_summary, component_to_directories,
728 per_directory_coverage_summary, no_file_view):
Yuke Liaodd1ec0592018-02-02 01:26:37729 """Generates per-component coverage reports in html."""
730 logging.debug('Writing per-component coverage html reports.')
731 for component in per_component_coverage_summary:
732 _GenerateCoverageInHtmlForComponent(
733 component, per_component_coverage_summary, component_to_directories,
Yuke Liao1b852fd2018-05-11 17:07:32734 per_directory_coverage_summary, no_file_view)
Yuke Liaodd1ec0592018-02-02 01:26:37735
736 logging.debug('Finished writing per-component coverage html reports.')
737
738
739def _GenerateCoverageInHtmlForComponent(
740 component_name, per_component_coverage_summary, component_to_directories,
Yuke Liao1b852fd2018-05-11 17:07:32741 per_directory_coverage_summary, no_file_view):
Yuke Liaodd1ec0592018-02-02 01:26:37742 """Generates coverage html report for a component."""
743 component_html_report_path = _GetCoverageHtmlReportPathForComponent(
744 component_name)
Yuke Liaoc7e607142018-02-05 20:26:14745 component_html_report_dir = os.path.dirname(component_html_report_path)
746 if not os.path.exists(component_html_report_dir):
747 os.makedirs(component_html_report_dir)
Yuke Liaodd1ec0592018-02-02 01:26:37748
749 html_generator = _CoverageReportHtmlGenerator(component_html_report_path,
750 'Path')
751
752 for dir_path in component_to_directories[component_name]:
Abhishek Arya03911092018-05-21 16:42:35753 dir_absolute_path = _GetFullPath(dir_path)
Yuke Liaodd1ec0592018-02-02 01:26:37754 if dir_absolute_path not in per_directory_coverage_summary:
Yuke Liaoc7e607142018-02-05 20:26:14755 # Any directory without an excercised file shouldn't be included into the
756 # report.
Yuke Liaodd1ec0592018-02-02 01:26:37757 continue
758
759 html_generator.AddLinkToAnotherReport(
760 _GetCoverageHtmlReportPathForDirectory(dir_path),
761 os.path.relpath(dir_path, SRC_ROOT_PATH),
762 per_directory_coverage_summary[dir_absolute_path])
763
764 html_generator.CreateTotalsEntry(
765 per_component_coverage_summary[component_name])
Yuke Liao1b852fd2018-05-11 17:07:32766 html_generator.WriteHtmlCoverageReport(no_file_view)
Yuke Liaodd1ec0592018-02-02 01:26:37767
768
Yuke Liao1b852fd2018-05-11 17:07:32769def _GenerateComponentViewHtmlIndexFile(per_component_coverage_summary,
770 no_file_view):
Yuke Liaodd1ec0592018-02-02 01:26:37771 """Generates the html index file for component view."""
Max Moroz7c5354f2018-05-06 00:03:48772 component_view_index_file_path = _GetComponentViewPath()
Yuke Liaodd1ec0592018-02-02 01:26:37773 logging.debug('Generating component view html index file as: "%s".',
774 component_view_index_file_path)
775 html_generator = _CoverageReportHtmlGenerator(component_view_index_file_path,
776 'Component')
Yuke Liaodd1ec0592018-02-02 01:26:37777 for component in per_component_coverage_summary:
Yuke Liaodd1ec0592018-02-02 01:26:37778 html_generator.AddLinkToAnotherReport(
779 _GetCoverageHtmlReportPathForComponent(component), component,
780 per_component_coverage_summary[component])
781
Abhishek Aryaefbe1df2018-05-14 20:19:48782 # Do not create a totals row for the component view as the value is incorrect
783 # due to failure to account for UNKNOWN component and some paths belonging to
784 # multiple components.
785
Yuke Liao1b852fd2018-05-11 17:07:32786 html_generator.WriteHtmlCoverageReport(no_file_view)
Yuke Liaoc7e607142018-02-05 20:26:14787 logging.debug('Finished generating component view html index file.')
Yuke Liaoea228d02018-01-05 19:10:33788
789
Max Moroz7c5354f2018-05-06 00:03:48790def _MergeTwoDirectories(src_path, dst_path):
791 """Merge src_path directory into dst_path directory."""
792 for filename in os.listdir(src_path):
793 dst_path = os.path.join(dst_path, filename)
794 if os.path.exists(dst_path):
795 shutil.rmtree(dst_path)
796 os.rename(os.path.join(src_path, filename), dst_path)
797 shutil.rmtree(src_path)
798
799
Yuke Liaoea228d02018-01-05 19:10:33800def _OverwriteHtmlReportsIndexFile():
Yuke Liaodd1ec0592018-02-02 01:26:37801 """Overwrites the root index file to redirect to the default view."""
Max Moroz7c5354f2018-05-06 00:03:48802 html_index_file_path = _GetHtmlIndexPath()
803 directory_view_index_file_path = _GetDirectoryViewPath()
Yuke Liaodd1ec0592018-02-02 01:26:37804 _WriteRedirectHtmlFile(html_index_file_path, directory_view_index_file_path)
805
806
807def _WriteRedirectHtmlFile(from_html_path, to_html_path):
808 """Writes a html file that redirects to another html file."""
809 to_html_relative_path = _GetRelativePathToDirectoryOfFile(
810 to_html_path, from_html_path)
Yuke Liaoea228d02018-01-05 19:10:33811 content = ("""
812 <!DOCTYPE html>
813 <html>
814 <head>
815 <!-- HTML meta refresh URL redirection -->
816 <meta http-equiv="refresh" content="0; url=%s">
817 </head>
Yuke Liaodd1ec0592018-02-02 01:26:37818 </html>""" % to_html_relative_path)
819 with open(from_html_path, 'w') as f:
Yuke Liaoea228d02018-01-05 19:10:33820 f.write(content)
821
822
Max Moroz7c5354f2018-05-06 00:03:48823def _CleanUpOutputDir():
824 """Perform a cleanup of the output dir."""
825 # Remove the default index.html file produced by llvm-cov.
826 index_path = os.path.join(OUTPUT_DIR, INDEX_HTML_FILE)
827 if os.path.exists(index_path):
828 os.remove(index_path)
829
830
Yuke Liaodd1ec0592018-02-02 01:26:37831def _GetCoverageHtmlReportPathForFile(file_path):
832 """Given a file path, returns the corresponding html report path."""
Abhishek Aryafb70b532018-05-06 17:47:40833 assert os.path.isfile(file_path), '"%s" is not a file.' % file_path
Abhishek Arya03911092018-05-21 16:42:35834 html_report_path = os.extsep.join([_GetFullPath(file_path), 'html'])
Yuke Liaodd1ec0592018-02-02 01:26:37835
836 # '+' is used instead of os.path.join because both of them are absolute paths
837 # and os.path.join ignores the first path.
Yuke Liaoc7e607142018-02-05 20:26:14838 # TODO(crbug.com/809150): Think of a generic cross platform fix (Windows).
Yuke Liaodd1ec0592018-02-02 01:26:37839 return _GetCoverageReportRootDirPath() + html_report_path
840
841
842def _GetCoverageHtmlReportPathForDirectory(dir_path):
843 """Given a directory path, returns the corresponding html report path."""
Abhishek Aryafb70b532018-05-06 17:47:40844 assert os.path.isdir(dir_path), '"%s" is not a directory.' % dir_path
Yuke Liaodd1ec0592018-02-02 01:26:37845 html_report_path = os.path.join(
Abhishek Arya03911092018-05-21 16:42:35846 _GetFullPath(dir_path), DIRECTORY_COVERAGE_HTML_REPORT_NAME)
Yuke Liaodd1ec0592018-02-02 01:26:37847
848 # '+' is used instead of os.path.join because both of them are absolute paths
849 # and os.path.join ignores the first path.
Yuke Liaoc7e607142018-02-05 20:26:14850 # TODO(crbug.com/809150): Think of a generic cross platform fix (Windows).
Yuke Liaodd1ec0592018-02-02 01:26:37851 return _GetCoverageReportRootDirPath() + html_report_path
852
853
854def _GetCoverageHtmlReportPathForComponent(component_name):
855 """Given a component, returns the corresponding html report path."""
856 component_file_name = component_name.lower().replace('>', '-')
857 html_report_name = os.extsep.join([component_file_name, 'html'])
858 return os.path.join(_GetCoverageReportRootDirPath(), 'components',
859 html_report_name)
860
861
862def _GetCoverageReportRootDirPath():
863 """The root directory that contains all generated coverage html reports."""
Max Moroz7c5354f2018-05-06 00:03:48864 return os.path.join(OUTPUT_DIR, _GetHostPlatform())
865
866
867def _GetComponentViewPath():
868 """Path to the HTML file for the component view."""
869 return os.path.join(_GetCoverageReportRootDirPath(),
870 COMPONENT_VIEW_INDEX_FILE)
871
872
873def _GetDirectoryViewPath():
874 """Path to the HTML file for the directory view."""
875 return os.path.join(_GetCoverageReportRootDirPath(),
876 DIRECTORY_VIEW_INDEX_FILE)
877
878
879def _GetFileViewPath():
880 """Path to the HTML file for the file view."""
881 return os.path.join(_GetCoverageReportRootDirPath(), FILE_VIEW_INDEX_FILE)
882
883
884def _GetLogsDirectoryPath():
885 """Path to the logs directory."""
886 return os.path.join(_GetCoverageReportRootDirPath(), LOGS_DIR_NAME)
887
888
889def _GetHtmlIndexPath():
890 """Path to the main HTML index file."""
891 return os.path.join(_GetCoverageReportRootDirPath(), INDEX_HTML_FILE)
892
893
894def _GetProfdataFilePath():
895 """Path to the resulting .profdata file."""
896 return os.path.join(_GetCoverageReportRootDirPath(), PROFDATA_FILE_NAME)
897
898
899def _GetSummaryFilePath():
900 """The JSON file that contains coverage summary written by llvm-cov export."""
901 return os.path.join(_GetCoverageReportRootDirPath(), SUMMARY_FILE_NAME)
Yuke Liaoea228d02018-01-05 19:10:33902
903
Yuke Liao506e8822017-12-04 16:52:54904def _CreateCoverageProfileDataForTargets(targets, commands, jobs_count=None):
905 """Builds and runs target to generate the coverage profile data.
906
907 Args:
908 targets: A list of targets to build with coverage instrumentation.
909 commands: A list of commands used to run the targets.
910 jobs_count: Number of jobs to run in parallel for building. If None, a
911 default value is derived based on CPUs availability.
912
913 Returns:
914 A relative path to the generated profdata file.
915 """
916 _BuildTargets(targets, jobs_count)
Abhishek Aryac19bc5ef2018-05-04 22:10:02917 target_profdata_file_paths = _GetTargetProfDataPathsByExecutingCommands(
Abhishek Arya1ec832c2017-12-05 18:06:59918 targets, commands)
Abhishek Aryac19bc5ef2018-05-04 22:10:02919 coverage_profdata_file_path = (
920 _CreateCoverageProfileDataFromTargetProfDataFiles(
921 target_profdata_file_paths))
Yuke Liao506e8822017-12-04 16:52:54922
Abhishek Aryac19bc5ef2018-05-04 22:10:02923 for target_profdata_file_path in target_profdata_file_paths:
924 os.remove(target_profdata_file_path)
Yuke Liaod4a9865202018-01-12 23:17:52925
Abhishek Aryac19bc5ef2018-05-04 22:10:02926 return coverage_profdata_file_path
Yuke Liao506e8822017-12-04 16:52:54927
928
929def _BuildTargets(targets, jobs_count):
930 """Builds target with Clang coverage instrumentation.
931
932 This function requires current working directory to be the root of checkout.
933
934 Args:
935 targets: A list of targets to build with coverage instrumentation.
936 jobs_count: Number of jobs to run in parallel for compilation. If None, a
937 default value is derived based on CPUs availability.
Yuke Liao506e8822017-12-04 16:52:54938 """
Abhishek Arya1ec832c2017-12-05 18:06:59939
Yuke Liao506e8822017-12-04 16:52:54940 def _IsGomaConfigured():
941 """Returns True if goma is enabled in the gn build args.
942
943 Returns:
944 A boolean indicates whether goma is configured for building or not.
945 """
Yuke Liao80afff32018-03-07 01:26:20946 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:54947 return 'use_goma' in build_args and build_args['use_goma'] == 'true'
948
Abhishek Aryafb70b532018-05-06 17:47:40949 logging.info('Building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54950 if jobs_count is None and _IsGomaConfigured():
951 jobs_count = DEFAULT_GOMA_JOBS
952
953 subprocess_cmd = ['ninja', '-C', BUILD_DIR]
954 if jobs_count is not None:
955 subprocess_cmd.append('-j' + str(jobs_count))
956
957 subprocess_cmd.extend(targets)
958 subprocess.check_call(subprocess_cmd)
Abhishek Aryafb70b532018-05-06 17:47:40959 logging.debug('Finished building %s.', str(targets))
Yuke Liao506e8822017-12-04 16:52:54960
961
Abhishek Aryac19bc5ef2018-05-04 22:10:02962def _GetTargetProfDataPathsByExecutingCommands(targets, commands):
Yuke Liao506e8822017-12-04 16:52:54963 """Runs commands and returns the relative paths to the profraw data files.
964
965 Args:
966 targets: A list of targets built with coverage instrumentation.
967 commands: A list of commands used to run the targets.
968
969 Returns:
970 A list of relative paths to the generated profraw data files.
971 """
Abhishek Aryafb70b532018-05-06 17:47:40972 logging.debug('Executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:10973
Yuke Liao506e8822017-12-04 16:52:54974 # Remove existing profraw data files.
Max Moroz7c5354f2018-05-06 00:03:48975 for file_or_dir in os.listdir(_GetCoverageReportRootDirPath()):
Yuke Liao506e8822017-12-04 16:52:54976 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:48977 os.remove(os.path.join(_GetCoverageReportRootDirPath(), file_or_dir))
978
979 # Ensure that logs directory exists.
980 if not os.path.exists(_GetLogsDirectoryPath()):
981 os.makedirs(_GetLogsDirectoryPath())
Yuke Liao506e8822017-12-04 16:52:54982
Abhishek Aryac19bc5ef2018-05-04 22:10:02983 profdata_file_paths = []
Yuke Liaoa0c8c2f2018-02-28 20:14:10984
Yuke Liaod4a9865202018-01-12 23:17:52985 # Run all test targets to generate profraw data files.
Yuke Liao506e8822017-12-04 16:52:54986 for target, command in zip(targets, commands):
Max Moroz7c5354f2018-05-06 00:03:48987 output_file_name = os.extsep.join([target + '_output', 'log'])
988 output_file_path = os.path.join(_GetLogsDirectoryPath(), output_file_name)
Yuke Liaoa0c8c2f2018-02-28 20:14:10989
Abhishek Aryac19bc5ef2018-05-04 22:10:02990 profdata_file_path = None
991 for _ in xrange(MERGE_RETRIES):
Abhishek Aryafb70b532018-05-06 17:47:40992 logging.info('Running command: "%s", the output is redirected to "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:02993 command, output_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:10994
Abhishek Aryac19bc5ef2018-05-04 22:10:02995 if _IsIOSCommand(command):
996 # On iOS platform, due to lack of write permissions, profraw files are
997 # generated outside of the OUTPUT_DIR, and the exact paths are contained
998 # in the output of the command execution.
Abhishek Arya03911092018-05-21 16:42:35999 output = _ExecuteIOSCommand(command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:021000 else:
1001 # On other platforms, profraw files are generated inside the OUTPUT_DIR.
Abhishek Arya03911092018-05-21 16:42:351002 output = _ExecuteCommand(target, command, output_file_path)
Abhishek Aryac19bc5ef2018-05-04 22:10:021003
1004 profraw_file_paths = []
1005 if _IsIOS():
1006 profraw_file_paths = _GetProfrawDataFileByParsingOutput(output)
1007 else:
Max Moroz7c5354f2018-05-06 00:03:481008 for file_or_dir in os.listdir(_GetCoverageReportRootDirPath()):
Abhishek Aryac19bc5ef2018-05-04 22:10:021009 if file_or_dir.endswith(PROFRAW_FILE_EXTENSION):
Max Moroz7c5354f2018-05-06 00:03:481010 profraw_file_paths.append(
1011 os.path.join(_GetCoverageReportRootDirPath(), file_or_dir))
Abhishek Aryac19bc5ef2018-05-04 22:10:021012
1013 assert profraw_file_paths, (
Abhishek Aryafb70b532018-05-06 17:47:401014 'Running target "%s" failed to generate any profraw data file, '
Abhishek Aryad35de7e2018-05-10 22:23:041015 'please make sure the binary exists, is properly instrumented and '
1016 'does not crash. %s' % (target, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:021017
1018 try:
1019 profdata_file_path = _CreateTargetProfDataFileFromProfRawFiles(
1020 target, profraw_file_paths)
1021 break
1022 except Exception:
Abhishek Aryad35de7e2018-05-10 22:23:041023 logging.info('Retrying...')
Abhishek Aryac19bc5ef2018-05-04 22:10:021024 finally:
1025 # Remove profraw files now so that they are not used in next iteration.
1026 for profraw_file_path in profraw_file_paths:
1027 os.remove(profraw_file_path)
1028
1029 assert profdata_file_path, (
Abhishek Aryad35de7e2018-05-10 22:23:041030 'Failed to merge target "%s" profraw files after %d retries. %s' %
1031 (target, MERGE_RETRIES, FILE_BUG_MESSAGE))
Abhishek Aryac19bc5ef2018-05-04 22:10:021032 profdata_file_paths.append(profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:541033
Abhishek Aryafb70b532018-05-06 17:47:401034 logging.debug('Finished executing the test commands.')
Yuke Liao481d3482018-01-29 19:17:101035
Abhishek Aryac19bc5ef2018-05-04 22:10:021036 return profdata_file_paths
Yuke Liao506e8822017-12-04 16:52:541037
1038
Abhishek Arya03911092018-05-21 16:42:351039def _GetEnvironmentVars(profraw_file_path):
1040 """Return environment vars for subprocess, given a profraw file path."""
1041 env = os.environ.copy()
1042 env.update({
1043 'LLVM_PROFILE_FILE': profraw_file_path,
1044 'PATH': _GetPathWithLLVMSymbolizerDir()
1045 })
1046 return env
1047
1048
1049def _ExecuteCommand(target, command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:101050 """Runs a single command and generates a profraw data file."""
Yuke Liaod4a9865202018-01-12 23:17:521051 # Per Clang "Source-based Code Coverage" doc:
Yuke Liao27349c92018-03-22 21:10:011052 #
Max Morozd73e45f2018-04-24 18:32:471053 # "%p" expands out to the process ID. It's not used by this scripts due to:
1054 # 1) If a target program spawns too many processess, it may exhaust all disk
1055 # space available. For example, unit_tests writes thousands of .profraw
1056 # files each of size 1GB+.
1057 # 2) If a target binary uses shared libraries, coverage profile data for them
1058 # will be missing, resulting in incomplete coverage reports.
Yuke Liao27349c92018-03-22 21:10:011059 #
Yuke Liaod4a9865202018-01-12 23:17:521060 # "%Nm" expands out to the instrumented binary's signature. When this pattern
1061 # is specified, the runtime creates a pool of N raw profiles which are used
1062 # for on-line profile merging. The runtime takes care of selecting a raw
1063 # profile from the pool, locking it, and updating it before the program exits.
Yuke Liaod4a9865202018-01-12 23:17:521064 # N must be between 1 and 9. The merge pool specifier can only occur once per
1065 # filename pattern.
1066 #
Max Morozd73e45f2018-04-24 18:32:471067 # "%1m" is used when tests run in single process, such as fuzz targets.
Yuke Liao27349c92018-03-22 21:10:011068 #
Max Morozd73e45f2018-04-24 18:32:471069 # For other cases, "%4m" is chosen as it creates some level of parallelism,
1070 # but it's not too big to consume too much computing resource or disk space.
1071 profile_pattern_string = '%1m' if _IsFuzzerTarget(target) else '%4m'
Abhishek Arya1ec832c2017-12-05 18:06:591072 expected_profraw_file_name = os.extsep.join(
Yuke Liao27349c92018-03-22 21:10:011073 [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
Max Moroz7c5354f2018-05-06 00:03:481074 expected_profraw_file_path = os.path.join(_GetCoverageReportRootDirPath(),
Yuke Liao506e8822017-12-04 16:52:541075 expected_profraw_file_name)
Abhishek Aryabd0655d2018-05-21 19:55:241076 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
1077 expected_profraw_file_path)
Yuke Liao506e8822017-12-04 16:52:541078
Yuke Liaoa0c8c2f2018-02-28 20:14:101079 try:
Max Moroz7c5354f2018-05-06 00:03:481080 # Some fuzz targets or tests may write into stderr, redirect it as well.
Abhishek Arya03911092018-05-21 16:42:351081 with open(output_file_path, 'wb') as output_file_handle:
1082 subprocess.check_call(
1083 shlex.split(command),
1084 stdout=output_file_handle,
1085 stderr=subprocess.STDOUT,
1086 env=_GetEnvironmentVars(expected_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:101087 except subprocess.CalledProcessError as e:
Abhishek Arya03911092018-05-21 16:42:351088 logging.warning('Command: "%s" exited with non-zero return code.', command)
Yuke Liaoa0c8c2f2018-02-28 20:14:101089
Abhishek Arya03911092018-05-21 16:42:351090 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:101091
1092
Yuke Liao27349c92018-03-22 21:10:011093def _IsFuzzerTarget(target):
1094 """Returns true if the target is a fuzzer target."""
1095 build_args = _GetBuildArgs()
1096 use_libfuzzer = ('use_libfuzzer' in build_args and
1097 build_args['use_libfuzzer'] == 'true')
1098 return use_libfuzzer and target.endswith('_fuzzer')
1099
1100
Abhishek Arya03911092018-05-21 16:42:351101def _ExecuteIOSCommand(command, output_file_path):
Yuke Liaoa0c8c2f2018-02-28 20:14:101102 """Runs a single iOS command and generates a profraw data file.
1103
1104 iOS application doesn't have write access to folders outside of the app, so
1105 it's impossible to instruct the app to flush the profraw data file to the
1106 desired location. The profraw data file will be generated somewhere within the
1107 application's Documents folder, and the full path can be obtained by parsing
1108 the output.
1109 """
Yuke Liaob2926832018-03-02 17:34:291110 assert _IsIOSCommand(command)
1111
1112 # After running tests, iossim generates a profraw data file, it won't be
1113 # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
1114 # checkout.
1115 iossim_profraw_file_path = os.path.join(
1116 OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
Abhishek Aryabd0655d2018-05-21 19:55:241117 command = command.replace(LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
1118 iossim_profraw_file_path)
Yuke Liaoa0c8c2f2018-02-28 20:14:101119
1120 try:
Abhishek Arya03911092018-05-21 16:42:351121 with open(output_file_path, 'wb') as output_file_handle:
1122 subprocess.check_call(
1123 shlex.split(command),
1124 stdout=output_file_handle,
1125 stderr=subprocess.STDOUT,
1126 env=_GetEnvironmentVars(iossim_profraw_file_path))
Yuke Liaoa0c8c2f2018-02-28 20:14:101127 except subprocess.CalledProcessError as e:
1128 # iossim emits non-zero return code even if tests run successfully, so
1129 # ignore the return code.
Abhishek Arya03911092018-05-21 16:42:351130 pass
Yuke Liaoa0c8c2f2018-02-28 20:14:101131
Abhishek Arya03911092018-05-21 16:42:351132 return open(output_file_path, 'rb').read()
Yuke Liaoa0c8c2f2018-02-28 20:14:101133
1134
1135def _GetProfrawDataFileByParsingOutput(output):
1136 """Returns the path to the profraw data file obtained by parsing the output.
1137
1138 The output of running the test target has no format, but it is guaranteed to
1139 have a single line containing the path to the generated profraw data file.
1140 NOTE: This should only be called when target os is iOS.
1141 """
Yuke Liaob2926832018-03-02 17:34:291142 assert _IsIOS()
Yuke Liaoa0c8c2f2018-02-28 20:14:101143
Yuke Liaob2926832018-03-02 17:34:291144 output_by_lines = ''.join(output).splitlines()
1145 profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
Yuke Liaoa0c8c2f2018-02-28 20:14:101146
1147 for line in output_by_lines:
Yuke Liaob2926832018-03-02 17:34:291148 result = profraw_file_pattern.match(line)
1149 if result:
1150 return result.group(1)
Yuke Liaoa0c8c2f2018-02-28 20:14:101151
1152 assert False, ('No profraw data file was generated, did you call '
1153 'coverage_util::ConfigureCoverageReportPath() in test setup? '
1154 'Please refer to base/test/test_support_ios.mm for example.')
Yuke Liao506e8822017-12-04 16:52:541155
1156
Abhishek Aryac19bc5ef2018-05-04 22:10:021157def _CreateCoverageProfileDataFromTargetProfDataFiles(profdata_file_paths):
1158 """Returns a relative path to coverage profdata file by merging target
1159 profdata files.
Yuke Liao506e8822017-12-04 16:52:541160
1161 Args:
Abhishek Aryac19bc5ef2018-05-04 22:10:021162 profdata_file_paths: A list of relative paths to the profdata data files
1163 that are to be merged.
Yuke Liao506e8822017-12-04 16:52:541164
1165 Returns:
Abhishek Aryac19bc5ef2018-05-04 22:10:021166 A relative path to the merged coverage profdata file.
Yuke Liao506e8822017-12-04 16:52:541167
1168 Raises:
Abhishek Aryac19bc5ef2018-05-04 22:10:021169 CalledProcessError: An error occurred merging profdata files.
Yuke Liao506e8822017-12-04 16:52:541170 """
Abhishek Aryafb70b532018-05-06 17:47:401171 logging.info('Creating the coverage profile data file.')
1172 logging.debug('Merging target profraw files to create target profdata file.')
Max Moroz7c5354f2018-05-06 00:03:481173 profdata_file_path = _GetProfdataFilePath()
Yuke Liao506e8822017-12-04 16:52:541174 try:
Abhishek Arya1ec832c2017-12-05 18:06:591175 subprocess_cmd = [
1176 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
1177 ]
Abhishek Aryac19bc5ef2018-05-04 22:10:021178 subprocess_cmd.extend(profdata_file_paths)
1179 subprocess.check_call(subprocess_cmd)
1180 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:041181 logging.error(
1182 'Failed to merge target profdata files to create coverage profdata. %s',
1183 FILE_BUG_MESSAGE)
Abhishek Aryac19bc5ef2018-05-04 22:10:021184 raise error
1185
Abhishek Aryafb70b532018-05-06 17:47:401186 logging.debug('Finished merging target profdata files.')
1187 logging.info('Code coverage profile data is created as: "%s".',
Abhishek Aryac19bc5ef2018-05-04 22:10:021188 profdata_file_path)
1189 return profdata_file_path
1190
1191
1192def _CreateTargetProfDataFileFromProfRawFiles(target, profraw_file_paths):
1193 """Returns a relative path to target profdata file by merging target
1194 profraw files.
1195
1196 Args:
1197 profraw_file_paths: A list of relative paths to the profdata data files
1198 that are to be merged.
1199
1200 Returns:
1201 A relative path to the merged coverage profdata file.
1202
1203 Raises:
1204 CalledProcessError: An error occurred merging profdata files.
1205 """
Abhishek Aryafb70b532018-05-06 17:47:401206 logging.info('Creating target profile data file.')
1207 logging.debug('Merging target profraw files to create target profdata file.')
Abhishek Aryac19bc5ef2018-05-04 22:10:021208 profdata_file_path = os.path.join(OUTPUT_DIR, '%s.profdata' % target)
1209
1210 try:
1211 subprocess_cmd = [
1212 LLVM_PROFDATA_PATH, 'merge', '-o', profdata_file_path, '-sparse=true'
1213 ]
Yuke Liao506e8822017-12-04 16:52:541214 subprocess_cmd.extend(profraw_file_paths)
1215 subprocess.check_call(subprocess_cmd)
1216 except subprocess.CalledProcessError as error:
Abhishek Aryad35de7e2018-05-10 22:23:041217 logging.error(
1218 'Failed to merge target profraw files to create target profdata.')
Yuke Liao506e8822017-12-04 16:52:541219 raise error
1220
Abhishek Aryafb70b532018-05-06 17:47:401221 logging.debug('Finished merging target profraw files.')
1222 logging.info('Target "%s" profile data is created as: "%s".', target,
Yuke Liao481d3482018-01-29 19:17:101223 profdata_file_path)
Yuke Liao506e8822017-12-04 16:52:541224 return profdata_file_path
1225
1226
Yuke Liao0e4c8682018-04-18 21:06:591227def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters,
1228 ignore_filename_regex):
Yuke Liaoea228d02018-01-05 19:10:331229 """Generates per file coverage summary using "llvm-cov export" command."""
1230 # llvm-cov export [options] -instr-profile PROFILE BIN [-object BIN,...]
1231 # [[-object BIN]] [SOURCES].
1232 # NOTE: For object files, the first one is specified as a positional argument,
1233 # and the rest are specified as keyword argument.
Yuke Liao481d3482018-01-29 19:17:101234 logging.debug('Generating per-file code coverage summary using "llvm-cov '
Abhishek Aryafb70b532018-05-06 17:47:401235 'export -summary-only" command.')
Yuke Liaoea228d02018-01-05 19:10:331236 subprocess_cmd = [
1237 LLVM_COV_PATH, 'export', '-summary-only',
1238 '-instr-profile=' + profdata_file_path, binary_paths[0]
1239 ]
1240 subprocess_cmd.extend(
1241 ['-object=' + binary_path for binary_path in binary_paths[1:]])
Yuke Liaob2926832018-03-02 17:34:291242 _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
Yuke Liaoea228d02018-01-05 19:10:331243 subprocess_cmd.extend(filters)
Yuke Liao0e4c8682018-04-18 21:06:591244 if ignore_filename_regex:
1245 subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex)
Yuke Liaoea228d02018-01-05 19:10:331246
Max Moroz7c5354f2018-05-06 00:03:481247 export_output = subprocess.check_output(subprocess_cmd)
1248
1249 # Write output on the disk to be used by code coverage bot.
1250 with open(_GetSummaryFilePath(), 'w') as f:
1251 f.write(export_output)
1252
1253 json_output = json.loads(export_output)
Yuke Liaoea228d02018-01-05 19:10:331254 assert len(json_output['data']) == 1
1255 files_coverage_data = json_output['data'][0]['files']
1256
1257 per_file_coverage_summary = {}
1258 for file_coverage_data in files_coverage_data:
1259 file_path = file_coverage_data['filename']
Abhishek Aryafb70b532018-05-06 17:47:401260 assert file_path.startswith(SRC_ROOT_PATH + os.sep), (
1261 'File path "%s" in coverage summary is outside source checkout.' %
1262 file_path)
Yuke Liaoea228d02018-01-05 19:10:331263
Abhishek Aryafb70b532018-05-06 17:47:401264 summary = file_coverage_data['summary']
Yuke Liaoea228d02018-01-05 19:10:331265 if summary['lines']['count'] == 0:
1266 continue
1267
1268 per_file_coverage_summary[file_path] = _CoverageSummary(
1269 regions_total=summary['regions']['count'],
1270 regions_covered=summary['regions']['covered'],
1271 functions_total=summary['functions']['count'],
1272 functions_covered=summary['functions']['covered'],
1273 lines_total=summary['lines']['count'],
1274 lines_covered=summary['lines']['covered'])
1275
Abhishek Aryafb70b532018-05-06 17:47:401276 logging.debug('Finished generating per-file code coverage summary.')
Yuke Liaoea228d02018-01-05 19:10:331277 return per_file_coverage_summary
1278
1279
Yuke Liaob2926832018-03-02 17:34:291280def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
1281 """Appends -arch arguments to the command list if it's ios platform.
1282
1283 iOS binaries are universal binaries, and require specifying the architecture
1284 to use, and one architecture needs to be specified for each binary.
1285 """
1286 if _IsIOS():
1287 cmd_list.extend(['-arch=x86_64'] * num_archs)
1288
1289
Yuke Liao506e8822017-12-04 16:52:541290def _GetBinaryPath(command):
1291 """Returns a relative path to the binary to be run by the command.
1292
Yuke Liao545db322018-02-15 17:12:011293 Currently, following types of commands are supported (e.g. url_unittests):
1294 1. Run test binary direcly: "out/coverage/url_unittests <arguments>"
1295 2. Use xvfb.
1296 2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
1297 2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
Yuke Liao92107f02018-03-07 01:44:371298 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
1299 for its usage.
Yuke Liaoa0c8c2f2018-02-28 20:14:101300 3.1. "out/Coverage-iphonesimulator/iossim
Yuke Liao92107f02018-03-07 01:44:371301 <iossim_arguments> -c <app_arguments>
1302 out/Coverage-iphonesimulator/url_unittests.app"
1303
Yuke Liao506e8822017-12-04 16:52:541304 Args:
1305 command: A command used to run a target.
1306
1307 Returns:
1308 A relative path to the binary.
1309 """
Yuke Liao545db322018-02-15 17:12:011310 xvfb_script_name = os.extsep.join(['xvfb', 'py'])
1311
Yuke Liaob2926832018-03-02 17:34:291312 command_parts = shlex.split(command)
Yuke Liao545db322018-02-15 17:12:011313 if os.path.basename(command_parts[0]) == 'python':
1314 assert os.path.basename(command_parts[1]) == xvfb_script_name, (
Abhishek Aryafb70b532018-05-06 17:47:401315 'This tool doesn\'t understand the command: "%s".' % command)
Yuke Liao545db322018-02-15 17:12:011316 return command_parts[2]
1317
1318 if os.path.basename(command_parts[0]) == xvfb_script_name:
1319 return command_parts[1]
1320
Yuke Liaob2926832018-03-02 17:34:291321 if _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:101322 # For a given application bundle, the binary resides in the bundle and has
1323 # the same name with the application without the .app extension.
Yuke Liao92107f02018-03-07 01:44:371324 app_path = command_parts[-1].rstrip(os.path.sep)
Yuke Liaoa0c8c2f2018-02-28 20:14:101325 app_name = os.path.splitext(os.path.basename(app_path))[0]
1326 return os.path.join(app_path, app_name)
1327
Yuke Liaob2926832018-03-02 17:34:291328 return command_parts[0]
Yuke Liao506e8822017-12-04 16:52:541329
1330
Yuke Liaob2926832018-03-02 17:34:291331def _IsIOSCommand(command):
Yuke Liaoa0c8c2f2018-02-28 20:14:101332 """Returns true if command is used to run tests on iOS platform."""
Yuke Liaob2926832018-03-02 17:34:291333 return os.path.basename(shlex.split(command)[0]) == 'iossim'
Yuke Liaoa0c8c2f2018-02-28 20:14:101334
1335
Yuke Liao95d13d72017-12-07 18:18:501336def _VerifyTargetExecutablesAreInBuildDirectory(commands):
1337 """Verifies that the target executables specified in the commands are inside
1338 the given build directory."""
Yuke Liao506e8822017-12-04 16:52:541339 for command in commands:
1340 binary_path = _GetBinaryPath(command)
Abhishek Arya03911092018-05-21 16:42:351341 binary_absolute_path = _GetFullPath(binary_path)
1342 assert binary_absolute_path.startswith(BUILD_DIR + os.sep), (
Yuke Liao95d13d72017-12-07 18:18:501343 'Target executable "%s" in command: "%s" is outside of '
1344 'the given build directory: "%s".' % (binary_path, command, BUILD_DIR))
Yuke Liao506e8822017-12-04 16:52:541345
1346
1347def _ValidateBuildingWithClangCoverage():
1348 """Asserts that targets are built with Clang coverage enabled."""
Yuke Liao80afff32018-03-07 01:26:201349 build_args = _GetBuildArgs()
Yuke Liao506e8822017-12-04 16:52:541350
1351 if (CLANG_COVERAGE_BUILD_ARG not in build_args or
1352 build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
Abhishek Arya1ec832c2017-12-05 18:06:591353 assert False, ('\'{} = true\' is required in args.gn.'
1354 ).format(CLANG_COVERAGE_BUILD_ARG)
Yuke Liao506e8822017-12-04 16:52:541355
1356
Yuke Liaoc60b2d02018-03-02 21:40:431357def _ValidateCurrentPlatformIsSupported():
1358 """Asserts that this script suports running on the current platform"""
1359 target_os = _GetTargetOS()
1360 if target_os:
1361 current_platform = target_os
1362 else:
1363 current_platform = _GetHostPlatform()
1364
1365 assert current_platform in [
1366 'linux', 'mac', 'chromeos', 'ios'
1367 ], ('Coverage is only supported on linux, mac, chromeos and ios.')
1368
1369
Yuke Liao80afff32018-03-07 01:26:201370def _GetBuildArgs():
Yuke Liao506e8822017-12-04 16:52:541371 """Parses args.gn file and returns results as a dictionary.
1372
1373 Returns:
1374 A dictionary representing the build args.
1375 """
Yuke Liao80afff32018-03-07 01:26:201376 global _BUILD_ARGS
1377 if _BUILD_ARGS is not None:
1378 return _BUILD_ARGS
1379
1380 _BUILD_ARGS = {}
Yuke Liao506e8822017-12-04 16:52:541381 build_args_path = os.path.join(BUILD_DIR, 'args.gn')
1382 assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
1383 'missing args.gn file.' % BUILD_DIR)
1384 with open(build_args_path) as build_args_file:
1385 build_args_lines = build_args_file.readlines()
1386
Yuke Liao506e8822017-12-04 16:52:541387 for build_arg_line in build_args_lines:
1388 build_arg_without_comments = build_arg_line.split('#')[0]
1389 key_value_pair = build_arg_without_comments.split('=')
1390 if len(key_value_pair) != 2:
1391 continue
1392
1393 key = key_value_pair[0].strip()
Yuke Liaoc60b2d02018-03-02 21:40:431394
1395 # Values are wrapped within a pair of double-quotes, so remove the leading
1396 # and trailing double-quotes.
1397 value = key_value_pair[1].strip().strip('"')
Yuke Liao80afff32018-03-07 01:26:201398 _BUILD_ARGS[key] = value
Yuke Liao506e8822017-12-04 16:52:541399
Yuke Liao80afff32018-03-07 01:26:201400 return _BUILD_ARGS
Yuke Liao506e8822017-12-04 16:52:541401
1402
Abhishek Arya16f059a2017-12-07 17:47:321403def _VerifyPathsAndReturnAbsolutes(paths):
1404 """Verifies that the paths specified in |paths| exist and returns absolute
1405 versions.
Yuke Liao66da1732017-12-05 22:19:421406
1407 Args:
1408 paths: A list of files or directories.
1409 """
Abhishek Arya16f059a2017-12-07 17:47:321410 absolute_paths = []
Yuke Liao66da1732017-12-05 22:19:421411 for path in paths:
Abhishek Arya16f059a2017-12-07 17:47:321412 absolute_path = os.path.join(SRC_ROOT_PATH, path)
1413 assert os.path.exists(absolute_path), ('Path: "%s" doesn\'t exist.' % path)
1414
1415 absolute_paths.append(absolute_path)
1416
1417 return absolute_paths
Yuke Liao66da1732017-12-05 22:19:421418
1419
Yuke Liaodd1ec0592018-02-02 01:26:371420def _GetRelativePathToDirectoryOfFile(target_path, base_path):
1421 """Returns a target path relative to the directory of base_path.
1422
1423 This method requires base_path to be a file, otherwise, one should call
1424 os.path.relpath directly.
1425 """
1426 assert os.path.dirname(base_path) != base_path, (
Yuke Liaoc7e607142018-02-05 20:26:141427 'Base path: "%s" is a directory, please call os.path.relpath directly.' %
Yuke Liaodd1ec0592018-02-02 01:26:371428 base_path)
Yuke Liaoc7e607142018-02-05 20:26:141429 base_dir = os.path.dirname(base_path)
1430 return os.path.relpath(target_path, base_dir)
Yuke Liaodd1ec0592018-02-02 01:26:371431
1432
Abhishek Arya64636af2018-05-04 14:42:131433def _GetBinaryPathsFromTargets(targets, build_dir):
1434 """Return binary paths from target names."""
1435 # FIXME: Derive output binary from target build definitions rather than
1436 # assuming that it is always the same name.
1437 binary_paths = []
1438 for target in targets:
1439 binary_path = os.path.join(build_dir, target)
1440 if _GetHostPlatform() == 'win':
1441 binary_path += '.exe'
1442
1443 if os.path.exists(binary_path):
1444 binary_paths.append(binary_path)
1445 else:
1446 logging.warning(
Abhishek Aryafb70b532018-05-06 17:47:401447 'Target binary "%s" not found in build directory, skipping.',
Abhishek Arya64636af2018-05-04 14:42:131448 os.path.basename(binary_path))
1449
1450 return binary_paths
1451
1452
Abhishek Arya03911092018-05-21 16:42:351453def _GetFullPath(path):
1454 """Return full absolute path."""
1455 return (os.path.abspath(
1456 os.path.realpath(os.path.expandvars(os.path.expanduser(path)))))
1457
1458
1459def _GetCommandForWebTests(arguments):
1460 """Return command to run for blink web tests."""
1461 command_list = [
1462 'python', 'testing/xvfb.py', 'python',
1463 'third_party/blink/tools/run_web_tests.py',
1464 '--additional-driver-flag=--no-sandbox',
Abhishek Aryabd0655d2018-05-21 19:55:241465 '--additional-env-var=LLVM_PROFILE_FILE=%s' %
1466 LLVM_PROFILE_FILE_PATH_SUBSTITUTION,
Abhishek Arya03911092018-05-21 16:42:351467 '--child-processes=%d' % max(1, int(multiprocessing.cpu_count() / 2)),
Abhishek Aryabd0655d2018-05-21 19:55:241468 '--disable-breakpad', '--no-show-results', '--skip-failing-tests',
Abhishek Arya03911092018-05-21 16:42:351469 '--target=%s' % os.path.basename(BUILD_DIR), '--time-out-ms=30000'
1470 ]
1471 if arguments.strip():
1472 command_list.append(arguments)
1473 return ' '.join(command_list)
1474
1475
1476def _GetBinaryPathForWebTests():
1477 """Return binary path used to run blink web tests."""
1478 host_platform = _GetHostPlatform()
1479 if host_platform == 'win':
1480 return os.path.join(BUILD_DIR, 'content_shell.exe')
1481 elif host_platform == 'linux':
1482 return os.path.join(BUILD_DIR, 'content_shell')
1483 elif host_platform == 'mac':
1484 return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS',
1485 'Content Shell')
1486 else:
1487 assert False, 'This platform is not supported for web tests.'
1488
1489
Yuke Liao506e8822017-12-04 16:52:541490def _ParseCommandArguments():
1491 """Adds and parses relevant arguments for tool comands.
1492
1493 Returns:
1494 A dictionary representing the arguments.
1495 """
1496 arg_parser = argparse.ArgumentParser()
1497 arg_parser.usage = __doc__
1498
Abhishek Arya1ec832c2017-12-05 18:06:591499 arg_parser.add_argument(
1500 '-b',
1501 '--build-dir',
1502 type=str,
1503 required=True,
1504 help='The build directory, the path needs to be relative to the root of '
1505 'the checkout.')
Yuke Liao506e8822017-12-04 16:52:541506
Abhishek Arya1ec832c2017-12-05 18:06:591507 arg_parser.add_argument(
1508 '-o',
1509 '--output-dir',
1510 type=str,
1511 required=True,
1512 help='Output directory for generated artifacts.')
Yuke Liao506e8822017-12-04 16:52:541513
Abhishek Arya1ec832c2017-12-05 18:06:591514 arg_parser.add_argument(
1515 '-c',
1516 '--command',
1517 action='append',
Abhishek Arya64636af2018-05-04 14:42:131518 required=False,
Abhishek Arya1ec832c2017-12-05 18:06:591519 help='Commands used to run test targets, one test target needs one and '
1520 'only one command, when specifying commands, one should assume the '
Abhishek Arya64636af2018-05-04 14:42:131521 'current working directory is the root of the checkout. This option is '
1522 'incompatible with -p/--profdata-file option.')
1523
1524 arg_parser.add_argument(
Abhishek Arya03911092018-05-21 16:42:351525 '-wt',
1526 '--web-tests',
1527 nargs='?',
1528 type=str,
1529 const=' ',
1530 required=False,
1531 help='Run blink web tests. Support passing arguments to run_web_tests.py')
1532
1533 arg_parser.add_argument(
Abhishek Arya64636af2018-05-04 14:42:131534 '-p',
1535 '--profdata-file',
1536 type=str,
1537 required=False,
1538 help='Path to profdata file to use for generating code coverage reports. '
1539 'This can be useful if you generated the profdata file seperately in '
1540 'your own test harness. This option is ignored if run command(s) are '
1541 'already provided above using -c/--command option.')
Yuke Liao506e8822017-12-04 16:52:541542
Abhishek Arya1ec832c2017-12-05 18:06:591543 arg_parser.add_argument(
Yuke Liao66da1732017-12-05 22:19:421544 '-f',
1545 '--filters',
1546 action='append',
Abhishek Arya16f059a2017-12-07 17:47:321547 required=False,
Yuke Liao66da1732017-12-05 22:19:421548 help='Directories or files to get code coverage for, and all files under '
1549 'the directories are included recursively.')
1550
1551 arg_parser.add_argument(
Yuke Liao0e4c8682018-04-18 21:06:591552 '-i',
1553 '--ignore-filename-regex',
1554 type=str,
1555 help='Skip source code files with file paths that match the given '
1556 'regular expression. For example, use -i=\'.*/out/.*|.*/third_party/.*\' '
1557 'to exclude files in third_party/ and out/ folders from the report.')
1558
1559 arg_parser.add_argument(
Yuke Liao1b852fd2018-05-11 17:07:321560 '--no-file-view',
1561 action='store_true',
1562 help='Don\'t generate the file view in the coverage report. When there '
1563 'are large number of html files, the file view becomes heavy and may '
1564 'cause the browser to freeze, and this argument comes handy.')
1565
1566 arg_parser.add_argument(
Yuke Liao082e99632018-05-18 15:40:401567 '--coverage-tools-dir',
1568 type=str,
1569 help='Path of the directory where LLVM coverage tools (llvm-cov, '
1570 'llvm-profdata) exist. This should be only needed if you are testing '
1571 'against a custom built clang revision. Otherwise, we pick coverage '
1572 'tools automatically from your current source checkout.')
1573
1574 arg_parser.add_argument(
Abhishek Arya1ec832c2017-12-05 18:06:591575 '-j',
1576 '--jobs',
1577 type=int,
1578 default=None,
1579 help='Run N jobs to build in parallel. If not specified, a default value '
1580 'will be derived based on CPUs availability. Please refer to '
1581 '\'ninja -h\' for more details.')
Yuke Liao506e8822017-12-04 16:52:541582
Abhishek Arya1ec832c2017-12-05 18:06:591583 arg_parser.add_argument(
Yuke Liao481d3482018-01-29 19:17:101584 '-v',
1585 '--verbose',
1586 action='store_true',
1587 help='Prints additional output for diagnostics.')
1588
1589 arg_parser.add_argument(
1590 '-l', '--log_file', type=str, help='Redirects logs to a file.')
1591
1592 arg_parser.add_argument(
Abhishek Aryac19bc5ef2018-05-04 22:10:021593 'targets',
1594 nargs='+',
1595 help='The names of the test targets to run. If multiple run commands are '
1596 'specified using the -c/--command option, then the order of targets and '
1597 'commands must match, otherwise coverage generation will fail.')
Yuke Liao506e8822017-12-04 16:52:541598
1599 args = arg_parser.parse_args()
1600 return args
1601
1602
1603def Main():
1604 """Execute tool commands."""
Yuke Liao082e99632018-05-18 15:40:401605 # Setup coverage binaries even when script is called with empty params. This
1606 # is used by coverage bot for initial setup.
1607 if len(sys.argv) == 1:
1608 DownloadCoverageToolsIfNeeded()
1609 print(__doc__)
1610 return
1611
Abhishek Arya64636af2018-05-04 14:42:131612 # Change directory to source root to aid in relative paths calculations.
Abhishek Arya03911092018-05-21 16:42:351613 global SRC_ROOT_PATH
1614 SRC_ROOT_PATH = _GetFullPath(
1615 os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
Abhishek Arya64636af2018-05-04 14:42:131616 os.chdir(SRC_ROOT_PATH)
Abhishek Arya8a0751a2018-05-03 18:53:111617
Yuke Liao506e8822017-12-04 16:52:541618 args = _ParseCommandArguments()
Abhishek Arya64636af2018-05-04 14:42:131619 _ConfigureLogging(args)
Yuke Liao082e99632018-05-18 15:40:401620 _ConfigureLLVMCoverageTools(args)
Abhishek Arya64636af2018-05-04 14:42:131621
Yuke Liao506e8822017-12-04 16:52:541622 global BUILD_DIR
Abhishek Arya03911092018-05-21 16:42:351623 BUILD_DIR = _GetFullPath(args.build_dir)
Yuke Liao506e8822017-12-04 16:52:541624 global OUTPUT_DIR
Abhishek Arya03911092018-05-21 16:42:351625 OUTPUT_DIR = _GetFullPath(args.output_dir)
Yuke Liao506e8822017-12-04 16:52:541626
Abhishek Arya03911092018-05-21 16:42:351627 assert args.web_tests or args.command or args.profdata_file, (
Abhishek Arya64636af2018-05-04 14:42:131628 'Need to either provide commands to run using -c/--command option OR '
Abhishek Arya03911092018-05-21 16:42:351629 'provide prof-data file as input using -p/--profdata-file option OR '
1630 'run web tests using -wt/--run-web-tests.')
Yuke Liaoc60b2d02018-03-02 21:40:431631
Abhishek Arya64636af2018-05-04 14:42:131632 assert not args.command or (len(args.targets) == len(args.command)), (
1633 'Number of targets must be equal to the number of test commands.')
Yuke Liaoc60b2d02018-03-02 21:40:431634
Abhishek Arya1ec832c2017-12-05 18:06:591635 assert os.path.exists(BUILD_DIR), (
Abhishek Aryafb70b532018-05-06 17:47:401636 'Build directory: "%s" doesn\'t exist. '
1637 'Please run "gn gen" to generate.' % BUILD_DIR)
Abhishek Arya64636af2018-05-04 14:42:131638
Yuke Liaoc60b2d02018-03-02 21:40:431639 _ValidateCurrentPlatformIsSupported()
Yuke Liao506e8822017-12-04 16:52:541640 _ValidateBuildingWithClangCoverage()
Abhishek Arya16f059a2017-12-07 17:47:321641
1642 absolute_filter_paths = []
Yuke Liao66da1732017-12-05 22:19:421643 if args.filters:
Abhishek Arya16f059a2017-12-07 17:47:321644 absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)
Yuke Liao66da1732017-12-05 22:19:421645
Max Moroz7c5354f2018-05-06 00:03:481646 if not os.path.exists(_GetCoverageReportRootDirPath()):
1647 os.makedirs(_GetCoverageReportRootDirPath())
Yuke Liao506e8822017-12-04 16:52:541648
Abhishek Arya03911092018-05-21 16:42:351649 # Get .profdata file and list of binary paths.
1650 if args.web_tests:
1651 commands = [_GetCommandForWebTests(args.web_tests)]
1652 profdata_file_path = _CreateCoverageProfileDataForTargets(
1653 args.targets, commands, args.jobs)
1654 binary_paths = [_GetBinaryPathForWebTests()]
1655 elif args.command:
1656 for i in range(len(args.command)):
1657 assert not 'run_web_tests.py' in args.command[i], (
1658 'run_web_tests.py is not supported via --command argument. '
1659 'Please use --run-web-tests argument instead.')
1660
Abhishek Arya64636af2018-05-04 14:42:131661 # A list of commands are provided. Run them to generate profdata file, and
1662 # create a list of binary paths from parsing commands.
1663 _VerifyTargetExecutablesAreInBuildDirectory(args.command)
1664 profdata_file_path = _CreateCoverageProfileDataForTargets(
1665 args.targets, args.command, args.jobs)
1666 binary_paths = [_GetBinaryPath(command) for command in args.command]
1667 else:
1668 # An input prof-data file is already provided. Just calculate binary paths.
1669 profdata_file_path = args.profdata_file
1670 binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir)
Yuke Liaoea228d02018-01-05 19:10:331671
Abhishek Arya78120bc2018-05-07 20:53:541672 binary_paths.extend(_GetSharedLibraries(binary_paths))
1673
Yuke Liao481d3482018-01-29 19:17:101674 logging.info('Generating code coverage report in html (this can take a while '
Abhishek Aryafb70b532018-05-06 17:47:401675 'depending on size of target!).')
Yuke Liaodd1ec0592018-02-02 01:26:371676 per_file_coverage_summary = _GeneratePerFileCoverageSummary(
Yuke Liao0e4c8682018-04-18 21:06:591677 binary_paths, profdata_file_path, absolute_filter_paths,
1678 args.ignore_filename_regex)
Yuke Liaodd1ec0592018-02-02 01:26:371679 _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
Yuke Liao0e4c8682018-04-18 21:06:591680 absolute_filter_paths,
1681 args.ignore_filename_regex)
Yuke Liao1b852fd2018-05-11 17:07:321682 if not args.no_file_view:
1683 _GenerateFileViewHtmlIndexFile(per_file_coverage_summary)
Yuke Liaodd1ec0592018-02-02 01:26:371684
1685 per_directory_coverage_summary = _CalculatePerDirectoryCoverageSummary(
1686 per_file_coverage_summary)
1687 _GeneratePerDirectoryCoverageInHtml(per_directory_coverage_summary,
Yuke Liao1b852fd2018-05-11 17:07:321688 per_file_coverage_summary,
1689 args.no_file_view)
Yuke Liaodd1ec0592018-02-02 01:26:371690 _GenerateDirectoryViewHtmlIndexFile()
1691
1692 component_to_directories = _ExtractComponentToDirectoriesMapping()
1693 per_component_coverage_summary = _CalculatePerComponentCoverageSummary(
1694 component_to_directories, per_directory_coverage_summary)
Yuke Liao1b852fd2018-05-11 17:07:321695 _GeneratePerComponentCoverageInHtml(
1696 per_component_coverage_summary, component_to_directories,
1697 per_directory_coverage_summary, args.no_file_view)
1698 _GenerateComponentViewHtmlIndexFile(per_component_coverage_summary,
1699 args.no_file_view)
Yuke Liaoea228d02018-01-05 19:10:331700
1701 # The default index file is generated only for the list of source files, needs
Yuke Liaodd1ec0592018-02-02 01:26:371702 # to overwrite it to display per directory coverage view by default.
Yuke Liaoea228d02018-01-05 19:10:331703 _OverwriteHtmlReportsIndexFile()
Max Moroz7c5354f2018-05-06 00:03:481704 _CleanUpOutputDir()
Yuke Liaoea228d02018-01-05 19:10:331705
Abhishek Arya03911092018-05-21 16:42:351706 html_index_file_path = 'file://' + _GetFullPath(_GetHtmlIndexPath())
Abhishek Aryafb70b532018-05-06 17:47:401707 logging.info('Index file for html report is generated as: "%s".',
Yuke Liao481d3482018-01-29 19:17:101708 html_index_file_path)
Yuke Liao506e8822017-12-04 16:52:541709
Abhishek Arya1ec832c2017-12-05 18:06:591710
Yuke Liao506e8822017-12-04 16:52:541711if __name__ == '__main__':
1712 sys.exit(Main())