cython

Форк
0
/
coverage_cmd_src_layout.srctree 
167 строк · 4.6 Кб
1
# mode: run
2
# tag: coverage,trace
3

4
"""
5
PYTHON setup.py build_ext -i
6
PYTHON -m coverage run --source=src coverage_test.py
7
PYTHON collect_coverage.py
8
"""
9

10
######## setup.py ########
11

12
from distutils.core import setup
13
from Cython.Build import cythonize
14

15
setup(ext_modules = cythonize([
16
    'src/trivial_module.pyx',
17
]))
18

19

20
######## .coveragerc ########
21
[run]
22
plugins = Cython.Coverage
23

24

25
######## src/trivial_module.pyx ########
26
# cython: linetrace=True
27
# distutils: define_macros=CYTHON_TRACE=1 CYTHON_USE_SYS_MONITORING=0
28

29
def func1(int a, int b):
30
    cdef int x = 1      #  5
31
    c = func2(a) + b    #  6
32
    return x + c        #  7
33

34

35
def func2(int a):
36
    return a * 2        # 11
37

38

39
######## coverage_test.py ########
40

41
import os.path
42
import trivial_module
43

44

45
assert not any(
46
    trivial_module.__file__.endswith(ext)
47
    for ext in '.py .pyc .pyo .pyw .pyx .pxi'.split()
48
), module.__file__
49

50

51
def run_coverage(module):
52
    assert module.func1(1, 2) == (1 * 2) + 2 + 1
53
    assert module.func2(2) == 2 * 2
54

55

56
if __name__ == '__main__':
57
    run_coverage(trivial_module)
58

59

60
######## collect_coverage.py ########
61

62
import re
63
import sys
64
import os
65
import os.path
66
import subprocess
67
from glob import iglob
68

69

70
def run_coverage_command(*command):
71
    env = dict(os.environ, LANG='', LC_ALL='C')
72
    process = subprocess.Popen(
73
        [sys.executable, '-m', 'coverage'] + list(command),
74
        stdout=subprocess.PIPE, env=env)
75
    stdout, _ = process.communicate()
76
    return stdout
77

78

79
def run_report():
80
    stdout = run_coverage_command('report', '--show-missing')
81
    stdout = stdout.decode('iso8859-1')  # 'safe' decoding
82
    lines = stdout.splitlines()
83
    print(stdout)
84

85
    module_path = 'trivial_module.pyx'
86
    assert any(module_path in line for line in lines), (
87
        "'%s' not found in coverage report:\n\n%s" % (module_path, stdout))
88

89
    files = {}
90
    line_iter = iter(lines)
91
    for line in line_iter:
92
        if line.startswith('---'):
93
            break
94
    extend = [''] * 2
95
    for line in line_iter:
96
        if not line or line.startswith('---'):
97
            continue
98
        name, statements, missed, covered, _missing = (line.split(None, 4) + extend)[:5]
99
        missing = []
100
        for start, end in re.findall('([0-9]+)(?:-([0-9]+))?', _missing):
101
            if end:
102
                missing.extend(range(int(start), int(end)+1))
103
            else:
104
                missing.append(int(start))
105
        files[os.path.basename(name)] = (statements, missed, covered, missing)
106

107
    assert  5 not in files[module_path][-1], files[module_path]
108
    assert  6 not in files[module_path][-1], files[module_path]
109
    assert  7 not in files[module_path][-1], files[module_path]
110
    assert 11 not in files[module_path][-1], files[module_path]
111

112

113
def run_xml_report():
114
    stdout = run_coverage_command('xml', '-o', '-')
115
    print(stdout)
116

117
    import xml.etree.ElementTree as etree
118
    data = etree.fromstring(stdout)
119

120
    files = {}
121
    for module in data.iterfind('.//class'):
122
        files[module.get('filename').replace('\\', '/')] = dict(
123
            (int(line.get('number')), int(line.get('hits')))
124
            for line in module.findall('lines/line')
125
        )
126

127
    module_path = 'src/trivial_module.pyx'
128

129
    assert files[module_path][5] > 0, files[module_path]
130
    assert files[module_path][6] > 0, files[module_path]
131
    assert files[module_path][7] > 0, files[module_path]
132
    assert files[module_path][11] > 0, files[module_path]
133

134

135
def run_html_report():
136
    from collections import defaultdict
137

138
    stdout = run_coverage_command('html', '-d', 'html')
139
    # coverage 6.1+ changed the order of the attributes => need to parse them separately
140
    _parse_id = re.compile(r'id=["\'][^0-9"\']*(?P<id>[0-9]+)[^0-9"\']*["\']').search
141
    _parse_state = re.compile(r'class=["\'][^"\']*(?P<state>mis|run|exc)[^"\']*["\']').search
142

143
    files = {}
144
    for file_path in iglob('html/*.html'):
145
        with open(file_path) as f:
146
            page = f.read()
147
        report = defaultdict(set)
148
        for line in re.split(r'id=["\']source["\']', page)[-1].splitlines():
149
            lineno = _parse_id(line)
150
            state = _parse_state(line)
151
            if not lineno or not state:
152
                continue
153
            report[state.group('state')].add(int(lineno.group('id')))
154
        files[file_path] = (report['run'], report['mis'])
155

156
    executed, missing = [data for path, data in files.items() if 'trivial_module' in path][0]
157
    assert executed
158
    assert 5 in executed, executed
159
    assert 6 in executed, executed
160
    assert 7 in executed, executed
161
    assert 11 in executed, executed
162

163

164
if __name__ == '__main__':
165
    run_report()
166
    run_xml_report()
167
    run_html_report()
168

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.