diff options
Diffstat (limited to 'tests/run/coverage_cmd_src_pkg_layout.srctree')
-rw-r--r-- | tests/run/coverage_cmd_src_pkg_layout.srctree | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/tests/run/coverage_cmd_src_pkg_layout.srctree b/tests/run/coverage_cmd_src_pkg_layout.srctree new file mode 100644 index 000000000..e2c58691a --- /dev/null +++ b/tests/run/coverage_cmd_src_pkg_layout.srctree @@ -0,0 +1,177 @@ +# mode: run +# tag: coverage,trace + +""" +PYTHON -m pip install . +PYTHON setup.py build_ext --inplace +PYTHON -m coverage run --source=pkg coverage_test.py +PYTHON collect_coverage.py +""" + +######## setup.py ######## + +from setuptools import Extension, find_packages, setup +from Cython.Build import cythonize + +MODULES = [ + Extension("pkg.module1", ["src/pkg/module1.pyx"]), + ] + +setup( + name="pkg", + zip_safe=False, + packages=find_packages('src'), + package_data={'pkg': ['*.pxd', '*.pyx']}, + package_dir={'': 'src'}, + ext_modules= cythonize(MODULES) + ) + + +######## .coveragerc ######## +[run] +plugins = Cython.Coverage + +######## src/pkg/__init__.py ######## + +######## src/pkg/module1.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.py ######## + +import os.path +from pkg import module1 + + +assert not any( + module1.__file__.endswith(ext) + for ext in '.py .pyc .pyo .pyw .pyx .pxi'.split() +), module.__file__ + + +def run_coverage(module): + assert module.func1(1, 2) == (1 * 2) + 2 + 1 + assert module.func2(2) == 2 * 2 + + +if __name__ == '__main__': + run_coverage(module1) + + +######## 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) + + module_path = 'module1.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 5 not in files[module_path][-1], files[module_path] + assert 6 not in files[module_path][-1], files[module_path] + assert 7 not in files[module_path][-1], files[module_path] + assert 11 not in files[module_path][-1], files[module_path] + + +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') + ) + + module_path = 'src/pkg/module1.pyx' + + assert files[module_path][5] > 0, files[module_path] + assert files[module_path][6] > 0, files[module_path] + assert files[module_path][7] > 0, files[module_path] + assert files[module_path][11] > 0, files[module_path] + + +def run_html_report(): + from collections import defaultdict + + stdout = run_coverage_command('html', '-d', 'html') + # coverage 6.1+ changed the order of the attributes => need to parse them separately + _parse_id = re.compile(r'id=["\'][^0-9"\']*(?P<id>[0-9]+)[^0-9"\']*["\']').search + _parse_state = re.compile(r'class=["\'][^"\']*(?P<state>mis|run|exc)[^"\']*["\']').search + + files = {} + for file_path in iglob('html/*.html'): + with open(file_path) as f: + page = f.read() + report = defaultdict(set) + for line in re.split(r'id=["\']source["\']', page)[-1].splitlines(): + lineno = _parse_id(line) + state = _parse_state(line) + if not lineno or not state: + continue + report[state.group('state')].add(int(lineno.group('id'))) + files[file_path] = report + + file_report = [data for path, data in files.items() if 'module1' in path][0] + executed, missing = file_report["run"], file_report["mis"] + assert executed + assert 5 in executed, executed + assert 6 in executed, executed + assert 7 in executed, executed + assert 11 in executed, executed + + +if __name__ == '__main__': + run_report() + run_xml_report() + run_html_report() |