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 = coverage_utils.GetHostPlatform() assert current_platform in [ 'linux', 'mac', 'chromeos', 'ios', 'win' ], ('Coverage is only supported on linux, mac, chromeos, ios and win.')
def _ConfigureLLVMCoverageTools(args): """Configures llvm coverage tools.""" if args.coverage_tools_dir: llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir) global LLVM_COV_PATH global LLVM_PROFDATA_PATH LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov') LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata') else: update.UpdatePackage('coverage_tools', coverage_utils.GetHostPlatform()) if coverage_utils.GetHostPlatform() == 'win': LLVM_COV_PATH += '.exe' LLVM_PROFDATA_PATH += '.exe' coverage_tools_exist = ( os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH)) assert coverage_tools_exist, ('Cannot find coverage tools, please make sure ' 'both \'%s\' and \'%s\' exist.') % ( LLVM_COV_PATH, LLVM_PROFDATA_PATH)
def _GetBinaryPathForWebTests(): """Return binary path used to run blink web tests.""" host_platform = coverage_utils.GetHostPlatform() if host_platform == 'win': return os.path.join(BUILD_DIR, 'content_shell.exe') elif host_platform == 'linux': return os.path.join(BUILD_DIR, 'content_shell') elif host_platform == 'mac': return os.path.join(BUILD_DIR, 'Content Shell.app', 'Contents', 'MacOS', 'Content Shell') else: assert False, 'This platform is not supported for web tests.'
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 = coverage_utils.GetHostPlatform() supported_platforms = ['android', 'chromeos', 'ios', 'linux', 'mac', 'win'] assert current_platform in supported_platforms, ('Coverage is only' 'supported on %s' % supported_platforms)
def setUp(self): self.maxDiff = 1000 self.COVERAGE_TOOLS_DIR = os.path.abspath(os.path.dirname(__file__)) self.COVERAGE_SCRIPT = os.path.join(self.COVERAGE_TOOLS_DIR, 'coverage.py') self.COVERAGE_UTILS = os.path.join(self.COVERAGE_TOOLS_DIR, 'coverage_utils.py') self.CHROMIUM_SRC_DIR = os.path.dirname( os.path.dirname(self.COVERAGE_TOOLS_DIR)) self.BUILD_DIR = os.path.join(self.CHROMIUM_SRC_DIR, 'out', 'code_coverage_tools_test') self.REPORT_DIR_1 = os.path.join(self.BUILD_DIR, 'report1') self.REPORT_DIR_1_NO_COMPONENTS = os.path.join( self.BUILD_DIR, 'report1_no_components') self.REPORT_DIR_2 = os.path.join(self.BUILD_DIR, 'report2') self.REPORT_DIR_3 = os.path.join(self.BUILD_DIR, 'report3') self.REPORT_DIR_4 = os.path.join(self.BUILD_DIR, 'report4') self.LLVM_COV = os.path.join(self.CHROMIUM_SRC_DIR, 'third_party', 'llvm-build', 'Release+Asserts', 'bin', 'llvm-cov') self.PYTHON = 'python' self.PLATFORM = coverage_utils.GetHostPlatform() if self.PLATFORM == 'win32': self.LLVM_COV += '.exe' self.PYTHON += '.exe' # Even though 'is_component_build=false' is recommended, we intentionally # use 'is_component_build=true' to test handling of shared libraries. self.GN_ARGS = """use_clang_coverage=true dcheck_always_on=true ffmpeg_branding=\"ChromeOS\" is_component_build=true is_debug=false proprietary_codecs=true use_libfuzzer=true""" shutil.rmtree(self.BUILD_DIR, ignore_errors=True) gn_gen_cmd = ['gn', 'gen', self.BUILD_DIR, '--args=%s' % self.GN_ARGS] self.run_cmd(gn_gen_cmd) build_cmd = [ 'autoninja', '-C', self.BUILD_DIR, 'crypto_unittests', 'libpng_read_fuzzer' ] self.run_cmd(build_cmd)
def _GeneratePerFileLineByLineCoverageInFormat(binary_paths, profdata_file_path, filters, ignore_filename_regex, output_format): """Generates per file line-by-line coverage in html or text using 'llvm-cov show'. For a file with absolute path /a/b/x.cc, a html/txt report is generated as: OUTPUT_DIR/coverage/a/b/x.cc.[html|txt]. For html format, an index html file is also generated as: OUTPUT_DIR/index.html. Args: binary_paths: A list of paths to the instrumented binaries. profdata_file_path: A path to the profdata file. filters: A list of directories and files to get coverage for. ignore_filename_regex: A regular expression for skipping source code files with certain file paths. output_format: The output format of generated report files. """ # llvm-cov show [options] -instr-profile PROFILE BIN [-object BIN,...] # [[-object BIN]] [SOURCES] # NOTE: For object files, the first one is specified as a positional argument, # and the rest are specified as keyword argument. logging.debug('Generating per file line by line coverage reports using ' '"llvm-cov show" command.') subprocess_cmd = [ LLVM_COV_PATH, 'show', '-format={}'.format(output_format), '-compilation-dir={}'.format(BUILD_DIR), '-output-dir={}'.format(OUTPUT_DIR), '-instr-profile={}'.format(profdata_file_path), binary_paths[0] ] subprocess_cmd.extend( ['-object=' + binary_path for binary_path in binary_paths[1:]]) _AddArchArgumentForIOSIfNeeded(subprocess_cmd, len(binary_paths)) if coverage_utils.GetHostPlatform() in ['linux', 'mac']: subprocess_cmd.extend(['-Xdemangler', 'c++filt', '-Xdemangler', '-n']) subprocess_cmd.extend(filters) if ignore_filename_regex: subprocess_cmd.append('-ignore-filename-regex=%s' % ignore_filename_regex) subprocess.check_call(subprocess_cmd) logging.debug('Finished running "llvm-cov show" command.')
def _GetBinaryPathsFromTargets(targets, build_dir): """Return binary paths from target names.""" # FIXME: Derive output binary from target build definitions rather than # assuming that it is always the same name. binary_paths = [] for target in targets: binary_path = os.path.join(build_dir, target) if coverage_utils.GetHostPlatform() == 'win': binary_path += '.exe' if os.path.exists(binary_path): binary_paths.append(binary_path) else: logging.warning( 'Target binary "%s" not found in build directory, skipping.', os.path.basename(binary_path)) return binary_paths
def _GetBinaryPath(command): """Returns a relative path to the binary to be run by the command. Currently, following types of commands are supported (e.g. url_unittests): 1. Run test binary direcly: "out/coverage/url_unittests <arguments>" 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, please refer to testing/iossim.mm for its usage. 3.1. "out/Coverage-iphonesimulator/iossim <iossim_arguments> -c <app_arguments> out/Coverage-iphonesimulator/url_unittests.app" Args: command: A command used to run a target. Returns: A relative path to the binary. """ xvfb_script_name = os.extsep.join(['xvfb', 'py']) command_parts = _SplitCommand(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) return command_parts[2] if os.path.basename(command_parts[0]) == xvfb_script_name: return command_parts[1] 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].rstrip(os.path.sep) app_name = os.path.splitext(os.path.basename(app_path))[0] return os.path.join(app_path, app_name) if coverage_utils.GetHostPlatform() == 'win' \ and not command_parts[0].endswith('.exe'): return command_parts[0] + '.exe' return command_parts[0]
def _BuildTargets(targets, jobs_count): """Builds target with Clang coverage instrumentation. This function requires current working directory to be the root of checkout. Args: targets: A list of targets to build with coverage instrumentation. jobs_count: Number of jobs to run in parallel for compilation. If None, a default value is derived based on CPUs availability. """ logging.info('Building %s.', str(targets)) autoninja = 'autoninja' if coverage_utils.GetHostPlatform() == 'win': autoninja += '.bat' subprocess_cmd = [autoninja, '-C', BUILD_DIR] if jobs_count is not None: subprocess_cmd.append('-j' + str(jobs_count)) subprocess_cmd.extend(targets) subprocess.check_call(subprocess_cmd) logging.debug('Finished building %s.', str(targets))
def _ConfigureLLVMCoverageTools(args): """Configures llvm coverage tools.""" if args.coverage_tools_dir: llvm_bin_dir = coverage_utils.GetFullPath(args.coverage_tools_dir) global LLVM_COV_PATH global LLVM_PROFDATA_PATH LLVM_COV_PATH = os.path.join(llvm_bin_dir, 'llvm-cov') LLVM_PROFDATA_PATH = os.path.join(llvm_bin_dir, 'llvm-profdata') else: subprocess.check_call([ sys.executable, 'tools/clang/scripts/update.py', '--package', 'coverage_tools' ]) if coverage_utils.GetHostPlatform() == 'win': LLVM_COV_PATH += '.exe' LLVM_PROFDATA_PATH += '.exe' coverage_tools_exist = (os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH)) assert coverage_tools_exist, ( 'Cannot find coverage tools, please make sure ' 'both \'%s\' and \'%s\' exist.') % (LLVM_COV_PATH, LLVM_PROFDATA_PATH)
def _SplitCommand(command): """Split a command string into parts in a platform-specific way.""" if coverage_utils.GetHostPlatform() == 'win': return command.split() return shlex.split(command)
def DownloadCoverageToolsIfNeeded(): """Temporary solution to download llvm-profdata and llvm-cov tools.""" def _GetRevisionFromStampFile(stamp_file_path): """Returns a pair of revision number by reading the build stamp file. Args: stamp_file_path: A path the build stamp file created by tools/clang/scripts/update.py. Returns: A pair of integers represeting the main and sub revision respectively. """ if not os.path.exists(stamp_file_path): return 0, 0 with open(stamp_file_path) as stamp_file: 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() clang_revision_str, clang_sub_revision_str = package_version.split('-') return int(clang_revision_str), int(clang_sub_revision_str) host_platform = coverage_utils.GetHostPlatform() clang_revision, clang_sub_revision = _GetRevisionFromStampFile( 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) has_coverage_tools = ( os.path.exists(LLVM_COV_PATH) and os.path.exists(LLVM_PROFDATA_PATH)) 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 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 host_platform == 'mac': coverage_tools_url = clang_update.CDS_URL + '/Mac/' + coverage_tools_file 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) with open(coverage_revision_stamp_file, 'w') as file_handle: file_handle.write('%s,%s' % (package_version, host_platform)) file_handle.write('\n') except urllib2.URLError: raise Exception( 'Failed to download coverage tools: %s.' % coverage_tools_url)
def DownloadCoverageToolsIfNeeded(): """Temporary solution to download llvm-profdata and llvm-cov tools.""" def _GetRevisionFromStampFile(stamp_file_path): """Returns revision by reading the build stamp file. Args: stamp_file_path: A path the build stamp file created by tools/clang/scripts/update.py. Returns: A string represeting the revision of the tool, such as 361212-67510fac-2. """ if not os.path.exists(stamp_file_path): return '' with open(stamp_file_path) as stamp_file: 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() return package_version cov_path = os.path.join(clang_update.LLVM_BUILD_DIR, 'llvm-cov') profdata_path = os.path.join(clang_update.LLVM_BUILD_DIR, 'llvm-profdata') host_platform = coverage_utils.GetHostPlatform() clang_revision = _GetRevisionFromStampFile(clang_update.STAMP_FILE) coverage_revision_stamp_file = os.path.join( os.path.dirname(clang_update.STAMP_FILE), 'cr_coverage_revision') coverage_revision = _GetRevisionFromStampFile(coverage_revision_stamp_file) has_coverage_tools = (os.path.exists(cov_path) and os.path.exists(profdata_path)) if (has_coverage_tools and clang_revision == coverage_revision): # LLVM coverage tools are up to date, bail out. return package_version = clang_revision coverage_tools_file = 'llvm-code-coverage-%s.tgz' % package_version # The code below follows the code from tools/clang/scripts/update.py. if host_platform == 'mac': coverage_tools_url = clang_update.CDS_URL + '/Mac/' + coverage_tools_file 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) with open(coverage_revision_stamp_file, 'w') as file_handle: file_handle.write('%s,%s' % (package_version, host_platform)) file_handle.write('\n') except urllib2.URLError: raise Exception('Failed to download coverage tools: %s.' % coverage_tools_url)
def Main(): """Execute tool commands.""" # Setup coverage binaries even when script is called with empty params. This # is used by coverage bot for initial setup. if len(sys.argv) == 1: update.UpdatePackage('coverage_tools', coverage_utils.GetHostPlatform()) print(__doc__) return # Change directory to source root to aid in relative paths calculations. global SRC_ROOT_PATH SRC_ROOT_PATH = coverage_utils.GetFullPath( os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)) os.chdir(SRC_ROOT_PATH) args = _ParseCommandArguments() coverage_utils.ConfigureLogging(verbose=args.verbose, log_file=args.log_file) _ConfigureLLVMCoverageTools(args) global BUILD_DIR BUILD_DIR = coverage_utils.GetFullPath(args.build_dir) global OUTPUT_DIR OUTPUT_DIR = coverage_utils.GetFullPath(args.output_dir) assert args.web_tests or args.command or args.profdata_file, ( 'Need to either provide commands to run using -c/--command option OR ' 'provide prof-data file as input using -p/--profdata-file option OR ' 'run web tests using -wt/--run-web-tests.') assert not args.command or (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: "%s" doesn\'t exist. ' 'Please run "gn gen" to generate.' % BUILD_DIR) _ValidateCurrentPlatformIsSupported() _ValidateBuildingWithClangCoverage() absolute_filter_paths = [] if args.filters: absolute_filter_paths = _VerifyPathsAndReturnAbsolutes(args.filters) _SetupOutputDir() # Get .profdata file and list of binary paths. if args.web_tests: commands = [_GetCommandForWebTests(args.web_tests)] profdata_file_path = _CreateCoverageProfileDataForTargets( args.targets, commands, args.jobs) binary_paths = [_GetBinaryPathForWebTests()] elif args.command: for i in range(len(args.command)): assert not 'run_web_tests.py' in args.command[i], ( 'run_web_tests.py is not supported via --command argument. ' 'Please use --run-web-tests argument instead.') # A list of commands are provided. Run them to generate profdata file, and # create a list of binary paths from parsing commands. _VerifyTargetExecutablesAreInBuildDirectory(args.command) profdata_file_path = _CreateCoverageProfileDataForTargets( args.targets, args.command, args.jobs) binary_paths = [_GetBinaryPath(command) for command in args.command] else: # An input prof-data file is already provided. Just calculate binary paths. profdata_file_path = args.profdata_file binary_paths = _GetBinaryPathsFromTargets(args.targets, args.build_dir) # If the checkout uses the hermetic xcode binaries, then otool must be # directly invoked. The indirection via /usr/bin/otool won't work unless # there's an actual system install of Xcode. otool_path = None if sys.platform == 'darwin': hermetic_otool_path = os.path.join( SRC_ROOT_PATH, 'build', 'mac_files', 'xcode_binaries', 'Contents', 'Developer', 'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin', 'otool') if os.path.exists(hermetic_otool_path): otool_path = hermetic_otool_path if sys.platform.startswith('linux') or sys.platform.startswith('darwin'): binary_paths.extend( coverage_utils.GetSharedLibraries(binary_paths, BUILD_DIR, otool_path)) assert args.format == 'html' or args.format == 'text', ( '%s is not a valid output format for "llvm-cov show". Only "text" and ' '"html" formats are supported.' % (args.format)) logging.info('Generating code coverage report in %s (this can take a while ' 'depending on size of target!).' % (args.format)) per_file_summary_data = _GeneratePerFileCoverageSummary( binary_paths, profdata_file_path, absolute_filter_paths, args.ignore_filename_regex) _GeneratePerFileLineByLineCoverageInFormat( binary_paths, profdata_file_path, absolute_filter_paths, args.ignore_filename_regex, args.format) component_mappings = None if not args.no_component_view: component_mappings = json.load(urllib2.urlopen(COMPONENT_MAPPING_URL)) # Call prepare here. processor = coverage_utils.CoverageReportPostProcessor( OUTPUT_DIR, SRC_ROOT_PATH, per_file_summary_data, no_component_view=args.no_component_view, no_file_view=args.no_file_view, component_mappings=component_mappings) if args.format == 'html': processor.PrepareHtmlReport()