summaryrefslogtreecommitdiff
path: root/tests/run/coverage_cmd_src_pkg_layout.srctree
diff options
context:
space:
mode:
Diffstat (limited to 'tests/run/coverage_cmd_src_pkg_layout.srctree')
-rw-r--r--tests/run/coverage_cmd_src_pkg_layout.srctree177
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()