mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
This solves deprecation warnings in build-script: ``` DeprecationWarning: 'pipes' is deprecated and slated for removal in Python 3.13 ``` This change assumes that the minimum version of Python3 is 3.3, which has `shlex.quote`. Since our build bots currently use 3.6 as their Python version, we can't yet use `shlex.join` to further simplify some of the code using quote.
247 lines
8.2 KiB
Python
Executable File
247 lines
8.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# utils/coverage/coverage-generate-data - Generate, parse test run profdata
|
|
#
|
|
# This source file is part of the Swift.org open source project
|
|
#
|
|
# Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
# Licensed under Apache License v2.0 with Runtime Library Exception
|
|
#
|
|
# See https://swift.org/LICENSE.txt for license information
|
|
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
|
|
import argparse
|
|
import logging
|
|
import multiprocessing
|
|
import os
|
|
import platform
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
import timeit
|
|
from multiprocessing import Pool
|
|
|
|
NUM_CORES = multiprocessing.cpu_count()
|
|
|
|
logging_format = '%(asctime)s %(levelname)s %(message)s'
|
|
logging.basicConfig(level=logging.DEBUG,
|
|
format=logging_format,
|
|
filename='/tmp/%s.log' % os.path.basename(__file__),
|
|
filemode='w')
|
|
console = logging.StreamHandler()
|
|
console.setLevel(logging.INFO)
|
|
formatter = logging.Formatter(logging_format)
|
|
console.setFormatter(formatter)
|
|
logging.getLogger().addHandler(console)
|
|
|
|
global_build_subdir = ''
|
|
|
|
|
|
def quote_shell_cmd(cmd):
|
|
"""Return `cmd` as a properly quoted shell string"""
|
|
return ' '.join([shlex.quote(a) for a in cmd])
|
|
|
|
|
|
def call(cmd, verbose=True, show_cmd=True):
|
|
"""Call `cmd` and optionally log debug info"""
|
|
formatted_cmd = quote_shell_cmd(cmd) if isinstance(cmd, list) else cmd
|
|
if show_cmd:
|
|
logging.info('$ ' + formatted_cmd)
|
|
start_time = timeit.default_timer()
|
|
process = subprocess.Popen(
|
|
cmd,
|
|
shell=(not isinstance(cmd, list)),
|
|
bufsize=1,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT
|
|
)
|
|
for line in iter(process.stdout.readline, b''):
|
|
if verbose:
|
|
logging.info('STDOUT: ' + line.rstrip())
|
|
end_time = timeit.default_timer()
|
|
logging.debug('END $ ' + formatted_cmd)
|
|
logging.debug('Return code: %s', process.returncode)
|
|
logging.debug('Elapsed time: %s', end_time - start_time)
|
|
return process.returncode
|
|
|
|
|
|
def check_output(cmd, verbose=True, show_cmd=True):
|
|
"""Return output of calling `cmd` and optionally log debug info"""
|
|
output = []
|
|
formatted_cmd = quote_shell_cmd(cmd) if isinstance(cmd, list) else cmd
|
|
if show_cmd:
|
|
logging.info('$ ' + formatted_cmd)
|
|
start_time = timeit.default_timer()
|
|
process = subprocess.Popen(
|
|
cmd,
|
|
shell=(not isinstance(cmd, list)),
|
|
bufsize=1,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT
|
|
)
|
|
for line in iter(process.stdout.readline, b''):
|
|
if verbose:
|
|
logging.info('STDOUT: ' + line.rstrip())
|
|
output.append(line)
|
|
end_time = timeit.default_timer()
|
|
logging.debug('Return code: %s', process.returncode)
|
|
logging.debug('Elapsed time: %s', end_time - start_time)
|
|
return (process.returncode, ''.join(output))
|
|
|
|
|
|
def xcrun_find(cmd):
|
|
"""Return path of `cmd` using xcrun -f"""
|
|
return check_output(['xcrun', '-f', cmd])[1].strip()
|
|
|
|
|
|
llvm_cov = xcrun_find('llvm-cov')
|
|
llvm_profdata = xcrun_find('llvm-profdata')
|
|
|
|
|
|
def dump_coverage_data(merged_file):
|
|
"""Dump coverage data of file at path `merged_file` using llvm-cov"""
|
|
try:
|
|
swift = os.path.join(global_build_subdir,
|
|
'swift-macosx-{}/bin/swift'.format(platform.machine()))
|
|
coverage_log = os.path.join(os.path.dirname(merged_file),
|
|
'coverage.log')
|
|
testname = os.path.basename(os.path.dirname(merged_file))
|
|
logging.info('Searching for covered files: %s', testname)
|
|
(returncode, output) = check_output(
|
|
[llvm_cov, 'report', '-instr-profile=%s' % merged_file, swift],
|
|
verbose=False,
|
|
show_cmd=False
|
|
)
|
|
output = [line.split()[0]
|
|
for line in output.split()
|
|
if '0.00' not in line and '/swift' in line]
|
|
with open(coverage_log, 'w') as f:
|
|
logging.info('Dumping coverage data: %s', testname)
|
|
(returncode2, dumped) = check_output(
|
|
quote_shell_cmd(
|
|
[llvm_cov, 'show', '-line-coverage-gt=0',
|
|
'-instr-profile=%s' % merged_file, swift] +
|
|
output
|
|
),
|
|
verbose=False,
|
|
show_cmd=False
|
|
)
|
|
f.write(dumped)
|
|
except Exception as e:
|
|
logging.debug(str(e))
|
|
|
|
|
|
def find_folders(root_path, suffix):
|
|
"""Return a list of folder paths ending in `suffix` rooted at
|
|
`root_path`"""
|
|
found_folders = []
|
|
for root, folders, files in os.walk(root_path):
|
|
for folder in folders:
|
|
if folder.endswith(suffix):
|
|
folderpath = os.path.join(root, folder)
|
|
logging.debug('Found %s', folderpath)
|
|
found_folders.append(folderpath)
|
|
logging.info('Found %s "%s" folders', len(found_folders), suffix)
|
|
return found_folders
|
|
|
|
|
|
def find_files(root_path, suffix):
|
|
"""Return a list of file paths ending in `suffix` rooted at
|
|
`root_path`"""
|
|
found_files = []
|
|
for root, folders, files in os.walk(root_path):
|
|
for f in files:
|
|
if f.endswith(suffix):
|
|
fpath = os.path.join(root, f)
|
|
logging.debug('Found %s', fpath)
|
|
found_files.append(fpath)
|
|
logging.info('Found %s "%s" files', len(found_files), suffix)
|
|
return found_files
|
|
|
|
|
|
def merge_profdir(profdir_path):
|
|
"""Merge swift-*.profraw files contained in `profdir_path` into
|
|
merged.profraw"""
|
|
logging.info('Merging %s', profdir_path)
|
|
if not os.path.exists(os.path.join(profdir_path, 'merged.profraw')):
|
|
call('set -x; '
|
|
'cd %s; '
|
|
'%s merge -output merged.profraw swift-*.profraw && '
|
|
'rm swift-*.profraw' % (profdir_path, llvm_profdata))
|
|
|
|
|
|
def demangle_coverage_data(coverage_log_path):
|
|
"""Demangle coverage dump at `coverage_log_path` using c++filt"""
|
|
logging.info('Demangling %s', coverage_log_path)
|
|
cppfilt = '/usr/bin/c++filt'
|
|
demangled_log_path = coverage_log_path + '.demangled'
|
|
returncode = 1
|
|
with open(coverage_log_path) as cf, open(demangled_log_path, 'w') as df:
|
|
process = subprocess.Popen(
|
|
[cppfilt, '-n'],
|
|
stdin=subprocess.PIPE,
|
|
stdout=df,
|
|
stderr=subprocess.PIPE
|
|
)
|
|
for line in cf:
|
|
process.stdin.write(line)
|
|
process.stdin.close()
|
|
returncode = process.wait()
|
|
return returncode
|
|
|
|
|
|
def main():
|
|
global global_build_subdir
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description='Generate, parse test run profdata')
|
|
parser.add_argument('swift_dir', metavar='swift-dir')
|
|
parser.add_argument('--log',
|
|
help='the level of information to log (default: info)',
|
|
metavar='LEVEL',
|
|
default='info',
|
|
choices=['info', 'debug', 'warning', 'error',
|
|
'critical'])
|
|
args = parser.parse_args()
|
|
|
|
console.setLevel(level=args.log.upper())
|
|
logging.debug(args)
|
|
|
|
swift_dir = os.path.realpath(os.path.abspath(args.swift_dir))
|
|
build_dir = os.path.realpath(os.path.join(os.path.dirname(swift_dir),
|
|
'build'))
|
|
build_subdir = os.path.join(build_dir, 'buildbot_incremental_coverage')
|
|
|
|
global_build_subdir = build_subdir
|
|
|
|
build_script_cmd = [
|
|
os.path.join(swift_dir, 'utils/build-script'),
|
|
'--preset=buildbot_incremental,tools=RDA,stdlib=RDA,coverage',
|
|
]
|
|
|
|
call(build_script_cmd)
|
|
|
|
assert global_build_subdir
|
|
|
|
pool = Pool(NUM_CORES)
|
|
|
|
logging.info('Starting merge on %s', build_dir)
|
|
folders = find_folders(build_dir, '.profdir')
|
|
pool.map_async(merge_profdir, folders).get(999999)
|
|
|
|
logging.info('Starting coverage data dump...')
|
|
merged_profraw_files = find_files(build_dir, 'merged.profraw')
|
|
pool.map_async(dump_coverage_data, merged_profraw_files).get(999999)
|
|
|
|
logging.info('Starting coverage data dump demangling...')
|
|
coverage_log_files = find_files(build_dir, 'coverage.log')
|
|
pool.map_async(demangle_coverage_data, coverage_log_files).get(999999)
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
sys.exit(main())
|
|
except Exception as e:
|
|
logging.debug(str(e))
|
|
sys.exit(1)
|