Skip to content

Instantly share code, notes, and snippets.

@anp
Created September 20, 2016 16:13
Show Gist options
  • Save anp/07177baf5cea76c27783efa55e99da89 to your computer and use it in GitHub Desktop.
Save anp/07177baf5cea76c27783efa55e99da89 to your computer and use it in GitHub Desktop.
rust code coverage with kcov
#!/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)

Make sure kcov is installed and on your path first!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment