Commit d6341966 authored by Stefan Behnel's avatar Stefan Behnel

Refactor parsing function in code coverage plugin to split the actual parsing...

Refactor parsing function in code coverage plugin to split the actual parsing code from the lookup code.
parent 86904478
...@@ -82,7 +82,7 @@ class Plugin(CoveragePlugin): ...@@ -82,7 +82,7 @@ class Plugin(CoveragePlugin):
# is not from the main .pyx file but a file with a different # is not from the main .pyx file but a file with a different
# name than the .c file (which prevents us from finding the # name than the .c file (which prevents us from finding the
# .c file) # .c file)
_, code = self._parse_lines(c_file, filename) _, code = self._read_source_lines(c_file, filename)
if code is None: if code is None:
return None # no source found return None # no source found
...@@ -104,7 +104,7 @@ class Plugin(CoveragePlugin): ...@@ -104,7 +104,7 @@ class Plugin(CoveragePlugin):
c_file, _ = self._find_source_files(filename) c_file, _ = self._find_source_files(filename)
if not c_file: if not c_file:
return None # unknown file return None # unknown file
rel_file_path, code = self._parse_lines(c_file, filename) rel_file_path, code = self._read_source_lines(c_file, filename)
if code is None: if code is None:
return None # no source found return None # no source found
return CythonModuleReporter(c_file, filename, rel_file_path, code) return CythonModuleReporter(c_file, filename, rel_file_path, code)
...@@ -170,14 +170,14 @@ class Plugin(CoveragePlugin): ...@@ -170,14 +170,14 @@ class Plugin(CoveragePlugin):
for filename in os.listdir(dir_path): for filename in os.listdir(dir_path):
ext = splitext(filename)[1].lower() ext = splitext(filename)[1].lower()
if ext in C_FILE_EXTENSIONS: if ext in C_FILE_EXTENSIONS:
self._parse_lines(os.path.join(dir_path, filename), source_file) self._read_source_lines(os.path.join(dir_path, filename), source_file)
if source_file in self._c_files_map: if source_file in self._c_files_map:
return return
# not found? then try one package up # not found? then try one package up
if is_package_dir(dir_path): if is_package_dir(dir_path):
self._find_c_source_files(os.path.dirname(dir_path), source_file) self._find_c_source_files(os.path.dirname(dir_path), source_file)
def _parse_lines(self, c_file, sourcefile): def _read_source_lines(self, c_file, sourcefile):
""" """
Parse a Cython generated C/C++ source file and find the executable lines. Parse a Cython generated C/C++ source file and find the executable lines.
Each executable line starts with a comment header that states source file Each executable line starts with a comment header that states source file
...@@ -188,51 +188,7 @@ class Plugin(CoveragePlugin): ...@@ -188,51 +188,7 @@ class Plugin(CoveragePlugin):
if c_file in self._parsed_c_files: if c_file in self._parsed_c_files:
code_lines = self._parsed_c_files[c_file] code_lines = self._parsed_c_files[c_file]
else: else:
match_source_path_line = re.compile(r' */[*] +"(.*)":([0-9]+)$').match code_lines = self._parse_cfile_lines(c_file)
match_current_code_line = re.compile(r' *[*] (.*) # <<<<<<+$').match
match_comment_end = re.compile(r' *[*]/$').match
match_trace_line = re.compile(r' *__Pyx_TraceLine\(([0-9]+),').match
not_executable = re.compile(
r'\s*c(?:type)?def\s+'
r'(?:(?:public|external)\s+)?'
r'(?:struct|union|enum|class)'
r'(\s+[^:]+|)\s*:'
).match
code_lines = defaultdict(dict)
executable_lines = defaultdict(set)
current_filename = None
with open(c_file) as lines:
lines = iter(lines)
for line in lines:
match = match_source_path_line(line)
if not match:
if '__Pyx_TraceLine(' in line and current_filename is not None:
trace_line = match_trace_line(line)
if trace_line:
executable_lines[current_filename].add(int(trace_line.group(1)))
continue
filename, lineno = match.groups()
current_filename = filename
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
# Remove lines that generated code but are not traceable.
for filename, lines in code_lines.items():
dead_lines = set(lines).difference(executable_lines.get(filename, ()))
for lineno in dead_lines:
del lines[lineno]
self._parsed_c_files[c_file] = code_lines self._parsed_c_files[c_file] = code_lines
if self._c_files_map is None: if self._c_files_map is None:
...@@ -246,6 +202,57 @@ class Plugin(CoveragePlugin): ...@@ -246,6 +202,57 @@ class Plugin(CoveragePlugin):
return (None,) * 2 # e.g. shared library file return (None,) * 2 # e.g. shared library file
return self._c_files_map[sourcefile][1:] return self._c_files_map[sourcefile][1:]
def _parse_cfile_lines(self, c_file):
"""
Parse a C file and extract all source file lines that generated executable code.
"""
match_source_path_line = re.compile(r' */[*] +"(.*)":([0-9]+)$').match
match_current_code_line = re.compile(r' *[*] (.*) # <<<<<<+$').match
match_comment_end = re.compile(r' *[*]/$').match
match_trace_line = re.compile(r' *__Pyx_TraceLine\(([0-9]+),').match
not_executable = re.compile(
r'\s*c(?:type)?def\s+'
r'(?:(?:public|external)\s+)?'
r'(?:struct|union|enum|class)'
r'(\s+[^:]+|)\s*:'
).match
code_lines = defaultdict(dict)
executable_lines = defaultdict(set)
current_filename = None
with open(c_file) as lines:
lines = iter(lines)
for line in lines:
match = match_source_path_line(line)
if not match:
if '__Pyx_TraceLine(' in line and current_filename is not None:
trace_line = match_trace_line(line)
if trace_line:
executable_lines[current_filename].add(int(trace_line.group(1)))
continue
filename, lineno = match.groups()
current_filename = filename
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
# Remove lines that generated code but are not traceable.
for filename, lines in code_lines.items():
dead_lines = set(lines).difference(executable_lines.get(filename, ()))
for lineno in dead_lines:
del lines[lineno]
return code_lines
class CythonModuleTracer(FileTracer): class CythonModuleTracer(FileTracer):
""" """
......
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