Files
swift-mirror/utils/coverage/coverage-generate-data
Kavon Farvardin b5e0f95a18 eliminate pipes in favor of shlex
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.
2023-07-25 15:24:54 -07:00

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)