# mode: run
# tag: coverage,trace
"""
PYTHON -c "import shutil; shutil.copy('pkg/coverage_test_pyx.pyx', 'pkg/coverage_test_pyx.pxi')"
PYTHON setup.py build_ext -i
PYTHON -m coverage run coverage_test.py
PYTHON collect_coverage.py
"""
######## setup.py ########
from distutils.core import setup
from Cython.Build import cythonize
setup(ext_modules = cythonize([
'coverage_test_*.py*',
'pkg/coverage_test_*.py*'
]))
######## .coveragerc ########
[run]
plugins = Cython.Coverage
######## pkg/__init__.py ########
######## pkg/coverage_test_py.py ########
# cython: linetrace=True
# distutils: define_macros=CYTHON_TRACE=1
def func1(a, b):
x = 1 # 5
c = func2(a) + b # 6
return x + c # 7
def func2(a):
return a * 2 # 11
######## pkg/coverage_test_pyx.pyx ########
# cython: linetrace=True
# distutils: define_macros=CYTHON_TRACE=1
def func1(int a, int b):
cdef int x = 1 # 5
c = func2(a) + b # 6
return x + c # 7
def func2(int a):
return a * 2 # 11
######## coverage_test_include_pyx.pyx ########
# cython: linetrace=True
# distutils: define_macros=CYTHON_TRACE=1
cdef int x = 5 # 4
cdef int cfunc1(int x): # 6
return x * 3 # 7
include "pkg/coverage_test_pyx.pxi" # 9
def main_func(int x): # 11
return cfunc1(x) + func1(x, 4) + func2(x) # 12
######## coverage_test.py ########
import os.path
try:
# io.StringIO in Py2.x cannot handle str ...
from StringIO import StringIO
except ImportError:
from io import StringIO
from pkg import coverage_test_py
from pkg import coverage_test_pyx
import coverage_test_include_pyx
for module in [coverage_test_py, coverage_test_pyx, coverage_test_include_pyx]:
assert not any(module.__file__.endswith(ext) for ext in '.py .pyc .pyo .pyw .pyx .pxi'.split()), \
module.__file__
def run_coverage(module):
module_name = module.__name__
module_path = module_name.replace('.', os.path.sep) + '.' + module_name.rsplit('_', 1)[-1]
assert module.func1(1, 2) == (1 * 2) + 2 + 1
assert module.func2(2) == 2 * 2
if '_include_' in module_name:
assert module.main_func(2) == (2 * 3) + ((2 * 2) + 4 + 1) + (2 * 2)
if __name__ == '__main__':
run_coverage(coverage_test_py)
run_coverage(coverage_test_pyx)
run_coverage(coverage_test_include_pyx)
######## collect_coverage.py ########
import re
import sys
import os
import os.path
import subprocess
from glob import iglob
def run_coverage_command(*command):
env = dict(os.environ, LANG='', LC_ALL='C')
process = subprocess.Popen(
[sys.executable, '-m', 'coverage'] + list(command),
stdout=subprocess.PIPE, env=env)
stdout, _ = process.communicate()
return stdout
def run_report():
stdout = run_coverage_command('report', '--show-missing')
stdout = stdout.decode('iso8859-1') # 'safe' decoding
lines = stdout.splitlines()
print(stdout)
# FIXME: 'coverage_test_pyx.pxi' may not be found if coverage.py requests it before the .pyx file
for module_path in ('coverage_test_py.py', 'coverage_test_pyx.pyx', 'coverage_test_include_pyx.pyx'):
assert any(module_path in line for line in lines), "'%s' not found in coverage report:\n\n%s" % (
module_path, stdout)
files = {}
line_iter = iter(lines)
for line in line_iter:
if line.startswith('---'):
break
extend = [''] * 2
for line in line_iter:
if not line or line.startswith('---'):
continue
name, statements, missed, covered, _missing = (line.split(None, 4) + extend)[:5]
missing = []
for start, end in re.findall('([0-9]+)(?:-([0-9]+))?', _missing):
if end:
missing.extend(range(int(start), int(end)+1))
else:
missing.append(int(start))
files[os.path.basename(name)] = (statements, missed, covered, missing)
assert 7 not in files['coverage_test_pyx.pyx'][-1], files['coverage_test_pyx.pyx']
assert 12 not in files['coverage_test_pyx.pyx'][-1], files['coverage_test_pyx.pyx']
def run_xml_report():
stdout = run_coverage_command('xml', '-o', '-')
print(stdout)
import xml.etree.ElementTree as etree
data = etree.fromstring(stdout)
files = {}
for module in data.iterfind('.//class'):
files[module.get('filename').replace('\\', '/')] = dict(
(int(line.get('number')), int(line.get('hits')))
for line in module.findall('lines/line')
)
assert files['pkg/coverage_test_pyx.pyx'][5] > 0, files['pkg/coverage_test_pyx.pyx']
assert files['pkg/coverage_test_pyx.pyx'][6] > 0, files['pkg/coverage_test_pyx.pyx']
assert files['pkg/coverage_test_pyx.pyx'][7] > 0, files['pkg/coverage_test_pyx.pyx']
def run_html_report():
stdout = run_coverage_command('html', '-d', 'html')
_parse_lines = re.compile(
r'<p[^>]* id=["\'][^0-9"\']*(?P<id>[0-9]+)[^0-9"\']*["\'][^>]*'
r' class=["\'][^"\']*(?P<run>mis|run)[^"\']*["\']').findall
files = {}
for file_path in iglob('html/*.html'):
with open(file_path) as f:
page = f.read()
executed = set()
missing = set()
for line, has_run in _parse_lines(page):
(executed if has_run == 'run' else missing).add(int(line))
files[file_path] = (executed, missing)
executed, missing = [data for path, data in files.items() if 'coverage_test_pyx' in path][0]
assert executed
assert 5 in executed, executed
assert 6 in executed, executed
assert 7 in executed, executed
if __name__ == '__main__':
run_report()
run_xml_report()
run_html_report()