summaryrefslogtreecommitdiff
path: root/chromium/tools/code_coverage
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:20:33 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:28:57 +0000
commitd17ea114e5ef69ad5d5d7413280a13e6428098aa (patch)
tree2c01a75df69f30d27b1432467cfe7c1467a498da /chromium/tools/code_coverage
parent8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (diff)
downloadqtwebengine-chromium-d17ea114e5ef69ad5d5d7413280a13e6428098aa.tar.gz
BASELINE: Update Chromium to 67.0.3396.47
Change-Id: Idcb1341782e417561a2473eeecc82642dafda5b7 Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'chromium/tools/code_coverage')
-rwxr-xr-xchromium/tools/code_coverage/coverage.py224
1 files changed, 143 insertions, 81 deletions
diff --git a/chromium/tools/code_coverage/coverage.py b/chromium/tools/code_coverage/coverage.py
index 69fc11ebc62..8bfdafe99d7 100755
--- a/chromium/tools/code_coverage/coverage.py
+++ b/chromium/tools/code_coverage/coverage.py
@@ -11,10 +11,9 @@
"use_clang_coverage=true" and "is_component_build=false" GN flags to args.gn
file in your build output directory (e.g. out/coverage).
- Clang Source-based Code Coverage requires "is_component_build=false" flag
- because: There will be no coverage info for libraries in component builds and
- "is_component_build" is set to true by "is_debug" unless it is explicitly set
- to false.
+ Existing implementation requires "is_component_build=false" flag because
+ coverage info for dynamic libraries may be missing and "is_component_build"
+ is set to true by "is_debug" unless it is explicitly set to false.
Example usage:
@@ -64,6 +63,8 @@ import argparse
import json
import logging
import os
+import re
+import shlex
import subprocess
import urllib2
@@ -121,6 +122,10 @@ FILE_VIEW_INDEX_FILE = os.extsep.join(['file_view_index', 'html'])
# Used to extract a mapping between directories and components.
COMPONENT_MAPPING_URL = 'https://storage.googleapis.com/chromium-owners/component_map.json'
+# Caches the results returned by _GetBuildArgs, don't use this variable
+# directly, call _GetBuildArgs instead.
+_BUILD_ARGS = None
+
class _CoverageSummary(object):
"""Encapsulates coverage summary representation."""
@@ -305,8 +310,11 @@ class _CoverageReportHtmlGenerator(object):
html_file.write(html_header + html_table + html_footer)
-def _GetPlatform():
- """Returns current running platform."""
+def _GetHostPlatform():
+ """Returns the host platform.
+
+ This is separate from the target platform/os that coverage is running for.
+ """
if sys.platform == 'win32' or sys.platform == 'cygwin':
return 'win'
if sys.platform.startswith('linux'):
@@ -316,10 +324,18 @@ def _GetPlatform():
return 'mac'
-def _IsTargetOsIos():
+def _GetTargetOS():
+ """Returns the target os specified in args.gn file.
+
+ Returns an empty string is target_os is not specified.
+ """
+ build_args = _GetBuildArgs()
+ return build_args['target_os'] if 'target_os' in build_args else ''
+
+
+def _IsIOS():
"""Returns true if the target_os specified in args.gn file is ios"""
- build_args = _ParseArgsGnFile()
- return 'target_os' in build_args and build_args['target_os'] == '"ios"'
+ return _GetTargetOS() == 'ios'
# TODO(crbug.com/759794): remove this function once tools get included to
@@ -328,7 +344,7 @@ def _IsTargetOsIos():
def DownloadCoverageToolsIfNeeded():
"""Temporary solution to download llvm-profdata and llvm-cov tools."""
- def _GetRevisionFromStampFile(stamp_file_path, platform):
+ def _GetRevisionFromStampFile(stamp_file_path):
"""Returns a pair of revision number by reading the build stamp file.
Args:
@@ -341,29 +357,23 @@ def DownloadCoverageToolsIfNeeded():
return 0, 0
with open(stamp_file_path) as stamp_file:
- for stamp_file_line in stamp_file.readlines():
- if ',' in stamp_file_line:
- package_version, target_os = stamp_file_line.rstrip().split(',')
- else:
- package_version = stamp_file_line.rstrip()
- target_os = ''
-
- if target_os and target_os != 'ios' and platform != target_os:
- continue
-
- clang_revision_str, clang_sub_revision_str = package_version.split('-')
- return int(clang_revision_str), int(clang_sub_revision_str)
+ stamp_file_line = stamp_file.readline()
+ if ',' in stamp_file_line:
+ package_version = stamp_file_line.rstrip().split(',')[0]
+ else:
+ package_version = stamp_file_line.rstrip()
- assert False, 'Coverage is only supported on target_os - linux, mac and ios'
+ clang_revision_str, clang_sub_revision_str = package_version.split('-')
+ return int(clang_revision_str), int(clang_sub_revision_str)
- platform = _GetPlatform()
+ host_platform = _GetHostPlatform()
clang_revision, clang_sub_revision = _GetRevisionFromStampFile(
- clang_update.STAMP_FILE, platform)
+ clang_update.STAMP_FILE)
coverage_revision_stamp_file = os.path.join(
os.path.dirname(clang_update.STAMP_FILE), 'cr_coverage_revision')
coverage_revision, coverage_sub_revision = _GetRevisionFromStampFile(
- coverage_revision_stamp_file, platform)
+ coverage_revision_stamp_file)
has_coverage_tools = (
os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH))
@@ -371,25 +381,27 @@ def DownloadCoverageToolsIfNeeded():
if (has_coverage_tools and coverage_revision == clang_revision and
coverage_sub_revision == clang_sub_revision):
# LLVM coverage tools are up to date, bail out.
- return clang_revision
+ return
package_version = '%d-%d' % (clang_revision, clang_sub_revision)
coverage_tools_file = 'llvm-code-coverage-%s.tgz' % package_version
# The code bellow follows the code from tools/clang/scripts/update.py.
- if platform == 'mac':
+ if host_platform == 'mac':
coverage_tools_url = clang_update.CDS_URL + '/Mac/' + coverage_tools_file
- else:
- assert platform == 'linux'
+ elif host_platform == 'linux':
coverage_tools_url = (
clang_update.CDS_URL + '/Linux_x64/' + coverage_tools_file)
+ else:
+ assert host_platform == 'win'
+ coverage_tools_url = (clang_update.CDS_URL + '/Win/' + coverage_tools_file)
try:
clang_update.DownloadAndUnpack(coverage_tools_url,
clang_update.LLVM_BUILD_DIR)
logging.info('Coverage tools %s unpacked', package_version)
with open(coverage_revision_stamp_file, 'w') as file_handle:
- file_handle.write('%s,%s' % (package_version, platform))
+ file_handle.write('%s,%s' % (package_version, host_platform))
file_handle.write('\n')
except urllib2.URLError:
raise Exception(
@@ -422,11 +434,7 @@ def _GeneratePerFileLineByLineCoverageInHtml(binary_paths, profdata_file_path,
]
subprocess_cmd.extend(
['-object=' + binary_path for binary_path in binary_paths[1:]])
- if _IsTargetOsIos():
- # iOS binaries are universal binaries, and it requires specifying the
- # architecture to use.
- subprocess_cmd.append('-arch=x86_64')
-
+ _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
subprocess_cmd.extend(filters)
subprocess.check_call(subprocess_cmd)
logging.debug('Finished running "llvm-cov show" command')
@@ -729,7 +737,7 @@ def _BuildTargets(targets, jobs_count):
Returns:
A boolean indicates whether goma is configured for building or not.
"""
- build_args = _ParseArgsGnFile()
+ build_args = _GetBuildArgs()
return 'use_goma' in build_args and build_args['use_goma'] == 'true'
logging.info('Building %s', str(targets))
@@ -771,11 +779,11 @@ def _GetProfileRawDataPathsByExecutingCommands(targets, commands):
logging.info('Running command: "%s", the output is redirected to "%s"',
command, output_file_path)
- if _IsIosCommand(command):
+ if _IsIOSCommand(command):
# On iOS platform, due to lack of write permissions, profraw files are
# generated outside of the OUTPUT_DIR, and the exact paths are contained
# in the output of the command execution.
- output = _ExecuteIosCommand(target, command)
+ output = _ExecuteIOSCommand(target, command)
profraw_file_paths.append(_GetProfrawDataFileByParsingOutput(output))
else:
# On other platforms, profraw files are generated inside the OUTPUT_DIR.
@@ -786,7 +794,7 @@ def _GetProfileRawDataPathsByExecutingCommands(targets, commands):
logging.debug('Finished executing the test commands')
- if _IsTargetOsIos():
+ if _IsIOS():
return profraw_file_paths
for file_or_dir in os.listdir(OUTPUT_DIR):
@@ -808,6 +816,9 @@ def _GetProfileRawDataPathsByExecutingCommands(targets, commands):
def _ExecuteCommand(target, command):
"""Runs a single command and generates a profraw data file."""
# Per Clang "Source-based Code Coverage" doc:
+ #
+ # "%p" expands out to the process ID.
+ #
# "%Nm" expands out to the instrumented binary's signature. When this pattern
# is specified, the runtime creates a pool of N raw profiles which are used
# for on-line profile merging. The runtime takes care of selecting a raw
@@ -816,16 +827,22 @@ def _ExecuteCommand(target, command):
# N must be between 1 and 9. The merge pool specifier can only occur once per
# filename pattern.
#
- # 4 is chosen because it creates some level of parallelism, but it's not too
- # big to consume too much computing resource or disk space.
+ # "%p" is used when tests run in single process, however, it can't be used for
+ # multi-process because each process produces an intermediate dump, which may
+ # consume hundreds of gigabytes of disk space.
+ #
+ # For "%Nm", 4 is chosen because it creates some level of parallelism, but
+ # it's not too big to consume too much computing resource or disk space.
+ profile_pattern_string = '%p' if _IsFuzzerTarget(target) else '%4m'
expected_profraw_file_name = os.extsep.join(
- [target, '%4m', PROFRAW_FILE_EXTENSION])
+ [target, profile_pattern_string, PROFRAW_FILE_EXTENSION])
expected_profraw_file_path = os.path.join(OUTPUT_DIR,
expected_profraw_file_name)
try:
output = subprocess.check_output(
- command.split(), env={'LLVM_PROFILE_FILE': expected_profraw_file_path})
+ shlex.split(command),
+ env={'LLVM_PROFILE_FILE': expected_profraw_file_path})
except subprocess.CalledProcessError as e:
output = e.output
logging.warning('Command: "%s" exited with non-zero return code', command)
@@ -833,7 +850,15 @@ def _ExecuteCommand(target, command):
return output
-def _ExecuteIosCommand(target, command):
+def _IsFuzzerTarget(target):
+ """Returns true if the target is a fuzzer target."""
+ build_args = _GetBuildArgs()
+ use_libfuzzer = ('use_libfuzzer' in build_args and
+ build_args['use_libfuzzer'] == 'true')
+ return use_libfuzzer and target.endswith('_fuzzer')
+
+
+def _ExecuteIOSCommand(target, command):
"""Runs a single iOS command and generates a profraw data file.
iOS application doesn't have write access to folders outside of the app, so
@@ -842,10 +867,18 @@ def _ExecuteIosCommand(target, command):
application's Documents folder, and the full path can be obtained by parsing
the output.
"""
- assert _IsIosCommand(command)
+ assert _IsIOSCommand(command)
+
+ # After running tests, iossim generates a profraw data file, it won't be
+ # needed anyway, so dump it into the OUTPUT_DIR to avoid polluting the
+ # checkout.
+ iossim_profraw_file_path = os.path.join(
+ OUTPUT_DIR, os.extsep.join(['iossim', PROFRAW_FILE_EXTENSION]))
try:
- output = subprocess.check_output(command.split())
+ output = subprocess.check_output(
+ shlex.split(command),
+ env={'LLVM_PROFILE_FILE': iossim_profraw_file_path})
except subprocess.CalledProcessError as e:
# iossim emits non-zero return code even if tests run successfully, so
# ignore the return code.
@@ -861,15 +894,15 @@ def _GetProfrawDataFileByParsingOutput(output):
have a single line containing the path to the generated profraw data file.
NOTE: This should only be called when target os is iOS.
"""
- assert _IsTargetOsIos()
+ assert _IsIOS()
- output_by_lines = ''.join(output).split('\n')
- profraw_file_identifier = 'Coverage data at '
+ output_by_lines = ''.join(output).splitlines()
+ profraw_file_pattern = re.compile('.*Coverage data at (.*coverage\.profraw).')
for line in output_by_lines:
- if profraw_file_identifier in line:
- profraw_file_path = line.split(profraw_file_identifier)[1][:-1]
- return profraw_file_path
+ result = profraw_file_pattern.match(line)
+ if result:
+ return result.group(1)
assert False, ('No profraw data file was generated, did you call '
'coverage_util::ConfigureCoverageReportPath() in test setup? '
@@ -922,11 +955,7 @@ def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters):
]
subprocess_cmd.extend(
['-object=' + binary_path for binary_path in binary_paths[1:]])
- if _IsTargetOsIos():
- # iOS binaries are universal binaries, and it requires specifying the
- # architecture to use.
- subprocess_cmd.append('-arch=x86_64')
-
+ _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths))
subprocess_cmd.extend(filters)
json_output = json.loads(subprocess.check_output(subprocess_cmd))
@@ -953,6 +982,16 @@ def _GeneratePerFileCoverageSummary(binary_paths, profdata_file_path, filters):
return per_file_coverage_summary
+def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
+ """Appends -arch arguments to the command list if it's ios platform.
+
+ iOS binaries are universal binaries, and require specifying the architecture
+ to use, and one architecture needs to be specified for each binary.
+ """
+ if _IsIOS():
+ cmd_list.extend(['-arch=x86_64'] * num_archs)
+
+
def _GetBinaryPath(command):
"""Returns a relative path to the binary to be run by the command.
@@ -961,9 +1000,12 @@ def _GetBinaryPath(command):
2. Use xvfb.
2.1. "python testing/xvfb.py out/coverage/url_unittests <arguments>"
2.2. "testing/xvfb.py out/coverage/url_unittests <arguments>"
- 3. Use iossim to run tests on iOS platform.
+ 3. Use iossim to run tests on iOS platform, please refer to testing/iossim.mm
+ for its usage.
3.1. "out/Coverage-iphonesimulator/iossim
- out/Coverage-iphonesimulator/url_unittests.app <arguments>"
+ <iossim_arguments> -c <app_arguments>
+ out/Coverage-iphonesimulator/url_unittests.app"
+
Args:
command: A command used to run a target.
@@ -973,7 +1015,7 @@ def _GetBinaryPath(command):
"""
xvfb_script_name = os.extsep.join(['xvfb', 'py'])
- command_parts = command.split()
+ command_parts = shlex.split(command)
if os.path.basename(command_parts[0]) == 'python':
assert os.path.basename(command_parts[1]) == xvfb_script_name, (
'This tool doesn\'t understand the command: "%s"' % command)
@@ -982,19 +1024,19 @@ def _GetBinaryPath(command):
if os.path.basename(command_parts[0]) == xvfb_script_name:
return command_parts[1]
- if _IsIosCommand(command):
+ if _IsIOSCommand(command):
# For a given application bundle, the binary resides in the bundle and has
# the same name with the application without the .app extension.
- app_path = command_parts[1]
+ app_path = command_parts[-1].rstrip(os.path.sep)
app_name = os.path.splitext(os.path.basename(app_path))[0]
return os.path.join(app_path, app_name)
- return command.split()[0]
+ return command_parts[0]
-def _IsIosCommand(command):
+def _IsIOSCommand(command):
"""Returns true if command is used to run tests on iOS platform."""
- return os.path.basename(command.split()[0]) == 'iossim'
+ return os.path.basename(shlex.split(command)[0]) == 'iossim'
def _VerifyTargetExecutablesAreInBuildDirectory(commands):
@@ -1010,7 +1052,7 @@ def _VerifyTargetExecutablesAreInBuildDirectory(commands):
def _ValidateBuildingWithClangCoverage():
"""Asserts that targets are built with Clang coverage enabled."""
- build_args = _ParseArgsGnFile()
+ build_args = _GetBuildArgs()
if (CLANG_COVERAGE_BUILD_ARG not in build_args or
build_args[CLANG_COVERAGE_BUILD_ARG] != 'true'):
@@ -1018,19 +1060,36 @@ def _ValidateBuildingWithClangCoverage():
).format(CLANG_COVERAGE_BUILD_ARG)
-def _ParseArgsGnFile():
+def _ValidateCurrentPlatformIsSupported():
+ """Asserts that this script suports running on the current platform"""
+ target_os = _GetTargetOS()
+ if target_os:
+ current_platform = target_os
+ else:
+ current_platform = _GetHostPlatform()
+
+ assert current_platform in [
+ 'linux', 'mac', 'chromeos', 'ios'
+ ], ('Coverage is only supported on linux, mac, chromeos and ios.')
+
+
+def _GetBuildArgs():
"""Parses args.gn file and returns results as a dictionary.
Returns:
A dictionary representing the build args.
"""
+ global _BUILD_ARGS
+ if _BUILD_ARGS is not None:
+ return _BUILD_ARGS
+
+ _BUILD_ARGS = {}
build_args_path = os.path.join(BUILD_DIR, 'args.gn')
assert os.path.exists(build_args_path), ('"%s" is not a build directory, '
'missing args.gn file.' % BUILD_DIR)
with open(build_args_path) as build_args_file:
build_args_lines = build_args_file.readlines()
- build_args = {}
for build_arg_line in build_args_lines:
build_arg_without_comments = build_arg_line.split('#')[0]
key_value_pair = build_arg_without_comments.split('=')
@@ -1038,10 +1097,13 @@ def _ParseArgsGnFile():
continue
key = key_value_pair[0].strip()
- value = key_value_pair[1].strip()
- build_args[key] = value
- return build_args
+ # Values are wrapped within a pair of double-quotes, so remove the leading
+ # and trailing double-quotes.
+ value = key_value_pair[1].strip().strip('"')
+ _BUILD_ARGS[key] = value
+
+ return _BUILD_ARGS
def _VerifyPathsAndReturnAbsolutes(paths):
@@ -1142,34 +1204,34 @@ def _ParseCommandArguments():
def Main():
"""Execute tool commands."""
- assert _GetPlatform() in [
- 'linux', 'mac'
- ], ('Coverage is only supported on linux and mac platforms.')
assert os.path.abspath(os.getcwd()) == SRC_ROOT_PATH, ('This script must be '
'called from the root '
'of checkout.')
- DownloadCoverageToolsIfNeeded()
-
args = _ParseCommandArguments()
global BUILD_DIR
BUILD_DIR = args.build_dir
global OUTPUT_DIR
OUTPUT_DIR = args.output_dir
+ assert len(args.targets) == len(args.command), ('Number of targets must be '
+ 'equal to the number of test '
+ 'commands.')
+
+ # logging should be configured before it is used.
log_level = logging.DEBUG if args.verbose else logging.INFO
- log_format = '[%(asctime)s] %(message)s'
+ log_format = '[%(asctime)s %(levelname)s] %(message)s'
log_file = args.log_file if args.log_file else None
logging.basicConfig(filename=log_file, level=log_level, format=log_format)
- assert len(args.targets) == len(args.command), ('Number of targets must be '
- 'equal to the number of test '
- 'commands.')
assert os.path.exists(BUILD_DIR), (
'Build directory: {} doesn\'t exist. '
'Please run "gn gen" to generate.').format(BUILD_DIR)
+ _ValidateCurrentPlatformIsSupported()
_ValidateBuildingWithClangCoverage()
_VerifyTargetExecutablesAreInBuildDirectory(args.command)
+ DownloadCoverageToolsIfNeeded()
+
absolute_filter_paths = []
if args.filters:
absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters)