Source code for doitpy.coverage
'''
create tasks for coverage.py
Add coverage related tasks to ``dodo.py``::
from doitpy.coverage import Coverage, PythonPackage
def task_coverage():
"""show coverage for all modules including tests"""
cov = Coverage([PythonPackage('my_pkg_name', 'tests')],
config={'branch':False, 'parallel':True,
'omit': ['tests/no_cover.py']},
)
yield cov.all() # create task `coverage`
yield cov.src() # create task `coverage_src`
yield cov.by_module() # create tasks `coverage_module:<path/to/test>`
'''
import glob
from configclass import Config
def sep(*args):
"""join strings or list of strings ignoring None values"""
return ' '.join(a for a in args if a)
class PythonFiles(object):
"""python code: a module or a package"""
def all_modules(self):
"""Yield all source and test modules."""
for mod in self.src + self.test:
yield mod
[docs]class PythonModule(PythonFiles):
"""reference to a single python module / test for the module
:ivar list-str src: list of path of source modules
:ivar list-str test: list of path of all modules from test folder
"""
[docs] def __init__(self, path, test_path):
"""
:param str/pathlib.Path path: path to python module.
:param str/pathlib.Path test_path: path to module with tests.
"""
self.src = [path]
self.test = [test_path]
[docs]class PythonPackage(PythonFiles):
"""Contain list of modules of the package (does not handle sub-packages)
:ivar str src_dir: path to dir containing package modules
:ivar str test_dir: path to dir containing package tests
:ivar list-str src: list of path of source modules
:ivar list-str test: list of path of all modules from test folder
"""
# TODO should track sub-packages
#: :class:`confclass.Config`
#:
#: :var str test_prefix: string prefix on name of files
#: :var str pkg_test_dir: path to location of test files
config = Config(
test_prefix = 'test_',
pkg_test_dir = 'tests',
)
[docs] def __init__(self, path, test_path=None, config=None):
"""
:param str/pathlib.Path path: dir path to package.
:param str/pathlib.Path test_path: if test_path is not given assume
it is on config.pkg_test_dir inside source package.
"""
self.config = self.config.make(config)
self.test_prefix = self.config['test_prefix']
self.src_dir = str(path) if path else ''
if test_path is None:
self.test_dir = '{}/{}'.format(self.src_dir,
self.config['pkg_test_dir'])
else:
self.test_dir = str(test_path)
self.src = glob.glob("{}/*.py".format(self.src_dir))
self.test = glob.glob("{}/*.py".format(self.test_dir))
[docs]class Coverage(object):
"""generate tasks for coverage.py"""
#: :class:`confclass.Config`
#:
#: :var string cmd_run_test: shell command used to run tests
#: :var branch bool: measure branche coverage
#: :var parallel bool: measure using `--parallel-mode` (needed for
#: subprocess and multiprocess coverage
#: :var concurrency str: --concurrency library
#: :var list-str omit: list of paths to omit from coverage
config = Config(
cmd_run_test = "`which py.test`",
branch=True,
parallel=False,
concurrency='',
omit=[])
[docs] def __init__(self, pkgs, config=None):
"""
:param list-PythonFiles pkgs: packages/modules to measure coverage
"""
self.config = self.config.make(config)
self.pkgs = []
for pkg in pkgs:
assert isinstance(pkg, PythonFiles)
self.pkgs.append(pkg)
def _action_list(self, modules, test=None):
"""return list of actions to be used in a doit task"""
run_options = []
if self.config['branch']:
run_options.append('--branch')
if self.config['parallel']:
run_options.append('--parallel-mode')
if self.config['concurrency']:
run_options.append('--concurrency')
run_options.append(self.config['concurrency'])
report_options = []
if self.config['omit']:
omit_list = ','.join(self.config['omit'])
report_options.append('--omit {}'.format(omit_list))
actions = [sep("coverage run", sep(*run_options),
self.config['cmd_run_test'], test)]
if self.config['parallel']:
actions.append('coverage combine')
actions.append(sep("coverage report --show-missing",
sep(*report_options), sep(*modules)))
return actions
[docs] def all(self, basename='coverage'):
"""show coverage for all modules including tests"""
all_modules = []
for pkg in self.pkgs:
for module in pkg.all_modules():
all_modules.append(module)
yield {
'basename': basename,
'actions': self._action_list(all_modules),
'verbosity': 2,
}
[docs] def src(self, basename='coverage_src'):
"""show coverage for all modules (exclude tests)"""
all_modules = []
for pkg in self.pkgs:
for module in pkg.src:
all_modules.append(module)
yield {
'basename': basename,
'actions': self._action_list(all_modules),
'verbosity': 2,
}
[docs] def by_module(self, basename='coverage_module'):
"""show coverage for individual modules"""
for pkg in self.pkgs:
prefix = '{}/{}'.format(pkg.test_dir, pkg.test_prefix)
to_strip = len(prefix)
tests = glob.glob('{}*.py'.format(prefix))
for test in tests:
source = pkg.src_dir + '/' + test[to_strip:]
yield {
'basename': basename,
'name': test,
'actions': self._action_list([source, test], test),
'verbosity': 2,
}