Make sure kcov is installed and on your path first!
Created
September 20, 2016 16:13
-
-
Save anp/07177baf5cea76c27783efa55e99da89 to your computer and use it in GitHub Desktop.
rust code coverage with kcov
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python2 | |
# butchered from https://github.com/huonw/travis-cargo | |
# under MIT license | |
from __future__ import print_function | |
import argparse | |
import os | |
import sys | |
import subprocess | |
import json | |
import re | |
def run(*args, **kwargs): | |
# use the current environment, but override the vars the caller | |
# specified | |
env = os.environ.copy() | |
env.update(kwargs.get('env', {})) | |
print(args) | |
ret = subprocess.call(args, stdout=sys.stdout, stderr=sys.stderr, env=env) | |
if ret != 0: | |
exit(ret) | |
def run_output(*args, **kwargs): | |
env = os.environ.copy() | |
env.update(kwargs.get('env', {})) | |
try: | |
output = subprocess.check_output(args, | |
stderr=subprocess.STDOUT, | |
env=env) | |
except subprocess.CalledProcessError as e: | |
print(e.output.decode('utf-8')) | |
exit(e.returncode) | |
return output.decode('utf-8') | |
def raw_coverage(verify, link_dead_code, test_args, | |
merge_msg, kcov_merge_args, kcov_merge_dir, | |
exclude_pattern, extra_kcov_args): | |
kcov = 'kcov' | |
test_binaries = [] | |
# look through the output of `cargo test` to find the test | |
# binaries. | |
# FIXME: the information cargo feeds us is | |
# inconsistent/inaccurate, so using the output of read-manifest is | |
# far too much trouble. | |
if link_dead_code: | |
env = {'RUSTFLAGS': ' '.join( | |
(os.environ.get('RUSTFLAGS', ''), '-C link-dead-code'))} | |
# we'll need to recompile everything with the new flag, so | |
# lets start from the start. | |
run('cargo', 'clean', '-v') | |
else: | |
env = {} | |
output = run_output('cargo', 'test', *test_args, env=env) | |
print(output) | |
running = re.compile('^ Running target/debug/(.*)$', re.M) | |
for line in running.finditer(output): | |
test_binaries.append(line.group(1)) | |
# issue #52: dylib dependencies don't get found properly when running kcov. | |
ld_library_path = os.environ.get('LD_LIBRARY_PATH', '') | |
if ld_library_path: | |
ld_library_path += ':' | |
ld_library_path += 'target/debug/deps' | |
# record coverage for each binary | |
for binary in test_binaries: | |
print('Recording %s' % binary) | |
kcov_args = [kcov] + extra_kcov_args | |
if verify: | |
kcov_args += ['--verify'] | |
exclude_pattern_arg = '--exclude-pattern=/.cargo,vendor/,tests/,bench/,include/' | |
if exclude_pattern: | |
exclude_pattern_arg += ',{}'.format(exclude_pattern) | |
kcov_args += [exclude_pattern_arg, 'target/kcov-' + binary, | |
'target/debug/' + binary] | |
print('Running: {}'.format(' '.join(kcov_args))) | |
run(*kcov_args, env={'LD_LIBRARY_PATH': ld_library_path}) | |
# merge all the coverages and upload in one go | |
print(merge_msg) | |
kcov_args = [kcov, '--merge'] + kcov_merge_args + [kcov_merge_dir] | |
kcov_args += ('target/kcov-' + b for b in test_binaries) | |
run(*kcov_args) | |
if __name__ == '__main__': | |
parser = argparse.ArgumentParser(description=''' | |
Collect test code coverage metrics. | |
''') | |
EXCLUDE_PATTERN = (['--exclude-pattern'], { | |
'metavar': 'PATTERN', | |
'help': 'pass additional comma-separated exclusionary patterns to kcov. ' | |
'See <https://github.com/SimonKagstrom/kcov#filtering-output> for how ' | |
'patterns work. By default, the /.cargo pattern is ignored. Example: ' | |
'--exclude-pattern="usr/lib/"' | |
}) | |
KCOV_OPTIONS = (['--kcov-options'], { | |
'action': 'append', | |
'metavar': 'OPTION', | |
'default': [], | |
'help': 'pass additional arguments to kcov, apart from --verify ' | |
'and --exclude-pattern, when recording coverage. Specify multiple ' | |
'times for multiple arguments. Example: --kcov-options="--debug=31"' | |
}) | |
VERIFY = (['--verify'], { | |
'action': 'store_true', | |
'default': False, | |
'help': 'pass `--verify` to kcov, to avoid some crashes. See ' | |
'<https://github.com/huonw/travis-cargo/issues/12>. This requires ' | |
'installing the `binutils-dev` package.' | |
}) | |
NO_LINK_DEAD_CODE = (['--no-link-dead-code'], { | |
'action': 'store_true', | |
'default': False, | |
'help': 'By default, travis_cargo passes `-C link-dead-code` to rustc ' | |
'during compilation. This can lead to more accurate code coverage if your ' | |
'compiler supports it. This flags allows users to opt out of linking dead ' | |
'code.' | |
}) | |
CARGO_ARGS = (['cargo_args'], { | |
'metavar': 'ARGS', | |
'nargs': '*', | |
'help': 'arguments to pass to `cargo test`' | |
}) | |
MERGE_INTO = (['-m', '--merge-into'], { | |
'metavar': 'DIR', | |
'default': 'target/kcov', | |
'help': 'the directory to put the final merged kcov ' | |
'result into (default `target/kcov`)' | |
}) | |
for name, options in [EXCLUDE_PATTERN, KCOV_OPTIONS, VERIFY, NO_LINK_DEAD_CODE, CARGO_ARGS, MERGE_INTO]: | |
parser.add_argument(*name, **options) | |
args = parser.parse_args() | |
raw_coverage(args.verify, not args.no_link_dead_code, args.cargo_args, | |
'Merging coverage', [], args.merge_into, | |
args.exclude_pattern, args.kcov_options) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment