From d6341966d991e9077446258e4c727341098ed84a Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 2 Mar 2019 10:05:55 +0100 Subject: [PATCH] Refactor parsing function in code coverage plugin to split the actual parsing code from the lookup code. --- Cython/Coverage.py | 105 ++++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/Cython/Coverage.py b/Cython/Coverage.py index 43b1bc9c59b..87266461d97 100644 --- a/Cython/Coverage.py +++ b/Cython/Coverage.py @@ -82,7 +82,7 @@ def file_tracer(self, filename): # is not from the main .pyx file but a file with a different # name than the .c file (which prevents us from finding the # .c file) - _, code = self._parse_lines(c_file, filename) + _, code = self._read_source_lines(c_file, filename) if code is None: return None # no source found @@ -104,7 +104,7 @@ def file_reporter(self, filename): c_file, _ = self._find_source_files(filename) if not c_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: return None # no source found return CythonModuleReporter(c_file, filename, rel_file_path, code) @@ -170,14 +170,14 @@ def _find_c_source_files(self, dir_path, source_file): for filename in os.listdir(dir_path): ext = splitext(filename)[1].lower() 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: return # not found? then try one package up if is_package_dir(dir_path): 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. Each executable line starts with a comment header that states source file @@ -188,51 +188,7 @@ def _parse_lines(self, c_file, sourcefile): if c_file in self._parsed_c_files: code_lines = self._parsed_c_files[c_file] else: - 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] - + code_lines = self._parse_cfile_lines(c_file) self._parsed_c_files[c_file] = code_lines if self._c_files_map is None: @@ -246,6 +202,57 @@ def _parse_lines(self, c_file, sourcefile): return (None,) * 2 # e.g. shared library file 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): """