Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cython
Commits
adb44bd0
Commit
adb44bd0
authored
Sep 07, 2020
by
Mathias Laurin
Committed by
GitHub
Sep 07, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Do not cover lines that were excluded in the coveragerc config file (GH-3682)
Closes #3680.
parent
65bbb6f2
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
106 additions
and
19 deletions
+106
-19
Cython/Coverage.py
Cython/Coverage.py
+36
-3
tests/run/coverage_cmd.srctree
tests/run/coverage_cmd.srctree
+70
-16
No files found.
Cython/Coverage.py
View file @
adb44bd0
...
...
@@ -57,10 +57,19 @@ class Plugin(CoveragePlugin):
_c_files_map
=
None
# map from parsed C files to their content
_parsed_c_files
=
None
# map from traced files to lines that are excluded from coverage
_excluded_lines_map
=
None
# list of regex patterns for lines to exclude
_excluded_line_patterns
=
()
def
sys_info
(
self
):
return
[(
'Cython version'
,
__version__
)]
def
configure
(
self
,
config
):
# Entry point for coverage "configurer".
# Read the regular expressions from the coverage config that match lines to be excluded from coverage.
self
.
_excluded_line_patterns
=
config
.
get_option
(
"report:exclude_lines"
)
def
file_tracer
(
self
,
filename
):
"""
Try to find a C source file for a file path found by the tracer.
...
...
@@ -108,7 +117,13 @@ class Plugin(CoveragePlugin):
rel_file_path
,
code
=
self
.
_read_source_lines
(
c_file
,
filename
)
if
code
is
None
:
return
None
# no source found
return
CythonModuleReporter
(
c_file
,
filename
,
rel_file_path
,
code
)
return
CythonModuleReporter
(
c_file
,
filename
,
rel_file_path
,
code
,
self
.
_excluded_lines_map
.
get
(
rel_file_path
,
frozenset
())
)
def
_find_source_files
(
self
,
filename
):
basename
,
ext
=
os
.
path
.
splitext
(
filename
)
...
...
@@ -218,10 +233,16 @@ class Plugin(CoveragePlugin):
r'
(
?
:
struct
|
union
|
enum
|
class
)
'
r'
(
\
s
+
[
^
:]
+|
)
\
s
*
:
'
).match
if self._excluded_line_patterns:
line_is_excluded = re.compile("|".join(["(?:%s)" % regex for regex in self._excluded_line_patterns])).search
else:
line_is_excluded = lambda line: False
code_lines = defaultdict(dict)
executable_lines = defaultdict(set)
current_filename = None
if self._excluded_lines_map is None:
self._excluded_lines_map = defaultdict(set)
with open(c_file) as lines:
lines = iter(lines)
...
...
@@ -242,6 +263,9 @@ class Plugin(CoveragePlugin):
code_line = match.group(1).rstrip()
if not_executable(code_line):
break
if line_is_excluded(code_line):
self._excluded_lines_map[filename].add(lineno)
break
code_lines[filename][lineno] = code_line
break
elif match_comment_end(comment_line):
...
...
@@ -298,11 +322,12 @@ class CythonModuleReporter(FileReporter):
"""
Provide detailed trace information for one source file to coverage.py.
"""
def __init__(self, c_file, source_file, rel_file_path, code):
def __init__(self, c_file, source_file, rel_file_path, code
, excluded_lines
):
super(CythonModuleReporter, self).__init__(source_file)
self.name = rel_file_path
self.c_file = c_file
self._code = code
self._excluded_lines = excluded_lines
def lines(self):
"""
...
...
@@ -310,6 +335,12 @@ class CythonModuleReporter(FileReporter):
"""
return set(self._code)
def excluded_lines(self):
"""
Return set of line numbers that are excluded from coverage.
"""
return self._excluded_lines
def _iter_source_tokens(self):
current_line = 1
for line_no, code_line in sorted(self._code.items()):
...
...
@@ -345,4 +376,6 @@ class CythonModuleReporter(FileReporter):
def coverage_init(reg, options):
reg.add_file_tracer(Plugin())
plugin = Plugin()
reg.add_configurer(plugin)
reg.add_file_tracer(plugin)
tests/run/coverage_cmd.srctree
View file @
adb44bd0
...
...
@@ -40,6 +40,13 @@ def func2(a):
return a * 2 # 11
def func3(a):
x = 1 # 15
a *= 2 # # pragma: no cover
a += x #
return a * 42 # 18 # pragma: no cover
######## pkg/coverage_test_pyx.pyx ########
# cython: linetrace=True
# distutils: define_macros=CYTHON_TRACE=1
...
...
@@ -54,6 +61,13 @@ def func2(int a):
return a * 2 # 11
def func3(int a):
cdef int x = 1 # 15
a *= 2 # # pragma: no cover
a += x #
return a * 42 # 18 # pragma: no cover
######## coverage_test_include_pyx.pyx ########
# cython: linetrace=True
# distutils: define_macros=CYTHON_TRACE=1
...
...
@@ -152,8 +166,9 @@ def run_report():
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']
report = files['coverage_test_pyx.pyx']
assert 7 not in report[-1], report
assert 12 not in report[-1], report
def run_xml_report():
...
...
@@ -170,35 +185,74 @@ def run_xml_report():
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']
report = files['pkg/coverage_test_pyx.pyx']
assert report[5] > 0, report
assert report[6] > 0, report
assert report[7] > 0, report
def run_json_report():
import coverage
if coverage.version_info < (5, 0):
# JSON output comes in coverage 5.0
return
stdout = run_coverage_command('json', '-o', '-')
import json
files = json.loads(stdout.decode("ascii"))['files']
for filename in [
'pkg/coverage_test_py.py',
'pkg/coverage_test_pyx.pyx',
]:
report = files[filename]
summary = report['summary']
assert summary['missing_lines'] == 2, summary
assert summary['excluded_lines'] == 2, summary
assert report['missing_lines'] == [15, 17], report
assert report['excluded_lines'] == [16, 18], report
assert not frozenset(
report['missing_lines'] + report['excluded_lines']
).intersection(report['executed_lines'])
def run_html_report():
from collections import defaultdict
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
r' class=["\'][^"\']*(?P<run>mis|run
|exc
)[^"\']*["\']').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)
report = defaultdict(set)
for line, state in _parse_lines(page):
report[state].add(int(line))
files[file_path] = report
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
for filename, report in files.items():
if "coverage_test_pyx" not in filename:
continue
executed = report["run"]
missing = report["mis"]
excluded = report["exc"]
assert executed
assert 5 in executed, executed
assert 6 in executed, executed
assert 7 in executed, executed
assert 15 in missing, missing
assert 16 in excluded, excluded
assert 17 in missing, missing
assert 18 in excluded, excluded
if __name__ == '__main__':
run_report()
run_xml_report()
run_json_report()
run_html_report()
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment