1# mode: run
2# tag: coverage,trace
3
4"""
5PYTHON -c "import shutil; shutil.copy('pkg/coverage_test_pyx.pyx', 'pkg/coverage_test_pyx.pxi')"
6PYTHON setup.py build_ext -i
7PYTHON coverage_test.py
8"""
9
10######## setup.py ########
11
12from distutils.core import setup
13from Cython.Build import cythonize
14
15setup(ext_modules = cythonize([
16    'coverage_test_*.py*',
17    'pkg/coverage_test_*.py*',
18    'Package2/CoverageTest_*.py*'
19]))
20
21
22######## .coveragerc ########
23[run]
24plugins = Cython.Coverage
25
26
27######## pkg/__init__.py ########
28
29######## pkg/coverage_test_py.py ########
30# cython: linetrace=True
31# distutils: define_macros=CYTHON_TRACE=1
32
33def func1(a, b):
34    x = 1               #  5
35    c = func2(a) + b    #  6
36    return x + c        #  7
37
38
39def func2(a):
40    return a * 2        # 11
41
42
43######## pkg/coverage_test_pyx.pyx ########
44# cython: linetrace=True
45# distutils: define_macros=CYTHON_TRACE=1
46
47def func1(int a, int b):
48    cdef int x = 1      #  5
49    c = func2(a) + b    #  6
50    return x + c        #  7
51
52
53def func2(int a):
54    return a * 2        # 11
55
56
57######## coverage_test_include_pyx.pyx ########
58# cython: linetrace=True
59# distutils: define_macros=CYTHON_TRACE=1
60
61cdef int x = 5                                   #  4
62
63cdef int cfunc1(int x):                          #  6
64    return x * 3                                 #  7
65
66include "pkg/coverage_test_pyx.pxi"              #  9
67
68def main_func(int x):                            # 11
69    return cfunc1(x) + func1(x, 4) + func2(x)    # 12
70
71
72######## Package2/__init__.py ########
73# Add MixedCase package and filenames to test if the files are found
74
75######## Package2/CoverageTest_py.py ########
76# cython: linetrace=True
77# distutils: define_macros=CYTHON_TRACE=1
78
79def func1(a, b):
80    x = 1               #  5
81    c = func2(a) + b    #  6
82    return x + c        #  7
83
84
85def func2(a):
86    return a * 2        # 11
87
88
89######## Package2/CoverageTest_pyx.pyx ########
90# cython: linetrace=True
91# distutils: define_macros=CYTHON_TRACE=1
92
93def func1(int a, int b):
94    cdef int x = 1      #  5
95    c = func2(a) + b    #  6
96    return x + c        #  7
97
98
99def func2(int a):
100    return a * 2        # 11
101
102
103######## coverage_test_include_pyx.pyx ########
104# cython: linetrace=True
105# distutils: define_macros=CYTHON_TRACE=1
106
107cdef int x = 5                                   #  4
108
109cdef int cfunc1(int x):                          #  6
110    return x * 3                                 #  7
111
112include "pkg/coverage_test_pyx.pxi"              #  9
113
114def main_func(int x):                            # 11
115    return cfunc1(x) + func1(x, 4) + func2(x)    # 12
116
117
118######## coverage_test.py ########
119
120import re
121import os.path
122try:
123    # io.StringIO in Py2.x cannot handle str ...
124    from StringIO import StringIO
125except ImportError:
126    from io import StringIO
127
128from coverage import coverage
129
130from pkg import coverage_test_py
131from pkg import coverage_test_pyx
132import coverage_test_include_pyx
133
134# test the MixedCase Files and packages
135from Package2 import CoverageTest_py
136from Package2 import CoverageTest_pyx
137
138for module in [coverage_test_py, coverage_test_pyx, coverage_test_include_pyx,
139               CoverageTest_py, CoverageTest_pyx]:
140    assert not any(module.__file__.endswith(ext) for ext in '.py .pyc .pyo .pyw .pyx .pxi'.split()), \
141        module.__file__
142
143
144def source_file_for(module):
145    module_name = module.__name__
146    path, ext = os.path.splitext(module.__file__)
147    if ext == '.so':
148        # Linux/Unix/Mac extension module
149        platform_suffix = re.search(r'[.](?:cpython|pypy)-[0-9]+[-_a-z0-9]*$', path, re.I)
150        if platform_suffix:
151            path = path[:platform_suffix.start()]
152    elif ext == '.pyd':
153        # Windows extension module
154        platform_suffix = re.search(r'[.]cp[0-9]+-win[_a-z0-9]*$', path, re.I)
155        if platform_suffix:
156            path = path[:platform_suffix.start()]
157    source_filepath = path + '.' + module_name.rsplit('_', 1)[-1]
158    return source_filepath
159
160
161def run_coverage(module):
162    module_name = module.__name__
163    module_path = module_name.replace('.', os.path.sep) + '.' + module_name.rsplit('_', 1)[-1]
164
165    cov = coverage()
166    cov.start()
167    assert module.func1(1, 2) == (1 * 2) + 2 + 1
168    assert module.func2(2) == 2 * 2
169    if '_include_' in module_name:
170        assert module.main_func(2) == (2 * 3) + ((2 * 2) + 4 + 1) + (2 * 2)
171    cov.stop()
172
173    out = StringIO()
174    cov.report(file=out)
175    #cov.report([module], file=out)
176    lines = out.getvalue().splitlines()
177    assert any(module_path in line for line in lines), "'%s' not found in coverage report:\n\n%s" % (
178        module_path, out.getvalue())
179
180    mod_file, exec_lines, excl_lines, missing_lines, _ = cov.analysis2(source_file_for(module))
181    assert module_path in mod_file
182
183    if '_include_' in module_name:
184        executed = set(exec_lines) - set(missing_lines)
185        assert all(line in executed for line in [7, 12]), '%s / %s' % (exec_lines, missing_lines)
186
187        # rest of test if for include file
188        mod_file, exec_lines, excl_lines, missing_lines, _ = cov.analysis2(
189            os.path.join(os.path.dirname(module.__file__), "pkg", "coverage_test_pyx.pxi"))
190
191    executed = set(exec_lines) - set(missing_lines)
192    assert all(line in executed for line in [5, 6, 7, 11]), '%s / %s' % (exec_lines, missing_lines)
193
194
195if __name__ == '__main__':
196    run_coverage(coverage_test_py)
197    run_coverage(coverage_test_pyx)
198    run_coverage(coverage_test_include_pyx)
199    run_coverage(CoverageTest_py)
200    run_coverage(CoverageTest_pyx)
201