Commit 2ce60b9a authored by Stefan Behnel's avatar Stefan Behnel

coverage: cache parsed C files to avoid parsing them again

parent 7d0e4def
...@@ -37,10 +37,12 @@ def _find_dep_file_path(main_file, file_path): ...@@ -37,10 +37,12 @@ def _find_dep_file_path(main_file, file_path):
class Plugin(CoveragePlugin): class Plugin(CoveragePlugin):
# map from traced file paths to corresponding C files
_c_files_map = None
# map from traced file paths to absolute file paths # map from traced file paths to absolute file paths
_file_path_map = None _file_path_map = None
# map from traced file paths to corresponding C files
_c_files_map = None
# map from parsed C files to their content
_parsed_c_files = None
def sys_info(self): def sys_info(self):
return [('Cython version', __version__)] return [('Cython version', __version__)]
...@@ -155,48 +157,55 @@ class Plugin(CoveragePlugin): ...@@ -155,48 +157,55 @@ class Plugin(CoveragePlugin):
Each executable line starts with a comment header that states source file Each executable line starts with a comment header that states source file
and line number, as well as the surrounding range of source code lines. and line number, as well as the surrounding range of source code lines.
""" """
match_source_path_line = re.compile(r' */[*] +"(.*)":([0-9]+)$').match if self._parsed_c_files is None:
match_current_code_line = re.compile(r' *[*] (.*) # <<<<<<+$').match self._parsed_c_files = {}
match_comment_end = re.compile(r' *[*]/$').match if c_file in self._parsed_c_files:
not_executable = re.compile( code_lines = self._parsed_c_files[c_file]
r'\s*c(?:type)?def\s+' else:
r'(?:(?:public|external)\s+)?' match_source_path_line = re.compile(r' */[*] +"(.*)":([0-9]+)$').match
r'(?:struct|union|enum|class)' match_current_code_line = re.compile(r' *[*] (.*) # <<<<<<+$').match
r'(\s+[^:]+|)\s*:' match_comment_end = re.compile(r' *[*]/$').match
).match not_executable = re.compile(
r'\s*c(?:type)?def\s+'
code_lines = defaultdict(dict) r'(?:(?:public|external)\s+)?'
filenames = set() r'(?:struct|union|enum|class)'
with open(c_file) as lines: r'(\s+[^:]+|)\s*:'
lines = iter(lines) ).match
for line in lines:
match = match_source_path_line(line) code_lines = defaultdict(dict)
if not match: filenames = set()
continue with open(c_file) as lines:
filename, lineno = match.groups() lines = iter(lines)
filenames.add(filename) for line in lines:
lineno = int(lineno) match = match_source_path_line(line)
for comment_line in lines: if not match:
match = match_current_code_line(comment_line) continue
if match: filename, lineno = match.groups()
code_line = match.group(1).rstrip() filenames.add(filename)
if not_executable(code_line): lineno = int(lineno)
for comment_line in lines:
match = match_current_code_line(comment_line)
if match:
code_line = match.group(1).rstrip()
if not_executable(code_line):
break
code_lines[filename][lineno] = code_line
break
elif match_comment_end(comment_line):
# unexpected comment format - false positive?
break break
code_lines[filename][lineno] = code_line
break self._parsed_c_files[c_file] = code_lines
elif match_comment_end(comment_line):
# unexpected comment format - false positive?
break
if self._c_files_map is None: if self._c_files_map is None:
self._c_files_map = {} self._c_files_map = {}
for filename in filenames: for filename, code in code_lines.iteritems():
abs_path = _find_dep_file_path(c_file, filename) abs_path = _find_dep_file_path(c_file, filename)
self._c_files_map[abs_path] = (c_file, filename, code_lines[filename]) self._c_files_map[abs_path] = (c_file, filename, code)
if sourcefile not in self._c_files_map: if sourcefile not in self._c_files_map:
return (None,) * 2 # shouldn't happen ... return (None,) * 2 # e.g. shared library file
return self._c_files_map[sourcefile][1:] return self._c_files_map[sourcefile][1:]
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment