def __init__(self, coverage): self.coverage = coverage self.config = self.coverage.config self.source_paths = set() if self.config.source: for src in self.config.source: if os.path.exists(src): if not self.config.relative_files: src = files.canonical_filename(src) self.source_paths.add(src) self.packages = {} self.xml_out = None
def _find_executable_files(self, src_dir): """Find executable files in `src_dir`. Search for files in `src_dir` that can be executed because they are probably importable. Don't include ones that have been omitted by the configuration. Yield the file path, and the plugin name that handles the file. """ py_files = ((py_file, None) for py_file in find_python_files(src_dir)) plugin_files = self._find_plugin_files(src_dir) for file_path, plugin_name in itertools.chain(py_files, plugin_files): file_path = canonical_filename(file_path) if self.omit_match and self.omit_match.match(file_path): # Turns out this file was omitted, so don't pull it back # in as unexecuted. continue yield file_path, plugin_name
def __init__(self, morf, coverage=None): self.coverage = coverage filename = source_for_morf(morf) super(PythonFileReporter, self).__init__(files.canonical_filename(filename)) if hasattr(morf, '__name__'): name = morf.__name__.replace(".", os.sep) if os.path.basename(filename).startswith('__init__.'): name += os.sep + "__init__" name += ".py" name = files.unicode_filename(name) else: name = files.relative_filename(filename) self.relname = name self._source = None self._parser = None self._excluded = None
def xml_file(self, fr, analysis, has_arcs): """Add to the XML report for a single file.""" if self.config.skip_empty: if analysis.numbers.n_statements == 0: return # Create the 'lines' and 'package' XML elements, which # are populated later. Note that a package == a directory. filename = fr.filename.replace("\\", "/") for source_path in self.source_paths: source_path = files.canonical_filename(source_path) if filename.startswith(source_path.replace("\\", "/") + "/"): rel_name = filename[len(source_path) + 1:] break else: rel_name = fr.relative_filename() self.source_paths.add(fr.filename[:-len(rel_name)].rstrip(r"\/")) dirname = os.path.dirname(rel_name) or u"." dirname = "/".join(dirname.split("/")[:self.config.xml_package_depth]) package_name = dirname.replace("/", ".") package = self.packages.setdefault(package_name, [{}, 0, 0, 0, 0]) xclass = self.xml_out.createElement("class") xclass.appendChild(self.xml_out.createElement("methods")) xlines = self.xml_out.createElement("lines") xclass.appendChild(xlines) xclass.setAttribute("name", os.path.relpath(rel_name, dirname)) xclass.setAttribute("filename", rel_name.replace("\\", "/")) xclass.setAttribute("complexity", "0") branch_stats = analysis.branch_stats() missing_branch_arcs = analysis.missing_branch_arcs() # For each statement, create an XML 'line' element. for line in sorted(analysis.statements): xline = self.xml_out.createElement("line") xline.setAttribute("number", str(line)) # Q: can we get info about the number of times a statement is # executed? If so, that should be recorded here. xline.setAttribute("hits", str(int(line not in analysis.missing))) if has_arcs: if line in branch_stats: total, taken = branch_stats[line] xline.setAttribute("branch", "true") xline.setAttribute( "condition-coverage", "%d%% (%d/%d)" % (100 * taken // total, taken, total)) if line in missing_branch_arcs: annlines = [ "exit" if b < 0 else str(b) for b in missing_branch_arcs[line] ] xline.setAttribute("missing-branches", ",".join(annlines)) xlines.appendChild(xline) class_lines = len(analysis.statements) class_hits = class_lines - len(analysis.missing) if has_arcs: class_branches = sum(t for t, k in branch_stats.values()) missing_branches = sum(t - k for t, k in branch_stats.values()) class_br_hits = class_branches - missing_branches else: class_branches = 0.0 class_br_hits = 0.0 # Finalize the statistics that are collected in the XML DOM. xclass.setAttribute("line-rate", rate(class_hits, class_lines)) if has_arcs: branch_rate = rate(class_br_hits, class_branches) else: branch_rate = "0" xclass.setAttribute("branch-rate", branch_rate) package[0][rel_name] = xclass package[1] += class_hits package[2] += class_lines package[3] += class_br_hits package[4] += class_branches
def should_trace(self, filename, frame=None): """Decide whether to trace execution in `filename`, with a reason. This function is called from the trace function. As each new file name is encountered, this function determines whether it is traced or not. Returns a FileDisposition object. """ original_filename = filename disp = disposition_init(self.disp_class, filename) def nope(disp, reason): """Simple helper to make it easy to return NO.""" disp.trace = False disp.reason = reason return disp if frame is not None: # Compiled Python files have two file names: frame.f_code.co_filename is # the file name at the time the .pyc was compiled. The second name is # __file__, which is where the .pyc was actually loaded from. Since # .pyc files can be moved after compilation (for example, by being # installed), we look for __file__ in the frame and prefer it to the # co_filename value. dunder_file = frame.f_globals and frame.f_globals.get('__file__') if dunder_file: filename = source_for_file(dunder_file) if original_filename and not original_filename.startswith('<'): orig = os.path.basename(original_filename) if orig != os.path.basename(filename): # Files shouldn't be renamed when moved. This happens when # exec'ing code. If it seems like something is wrong with # the frame's file name, then just use the original. filename = original_filename if not filename: # Empty string is pretty useless. return nope(disp, "empty string isn't a file name") if filename.startswith('memory:'): return nope(disp, "memory isn't traceable") if filename.startswith('<'): # Lots of non-file execution is represented with artificial # file names like "<string>", "<doctest readme.txt[0]>", or # "<exec_function>". Don't ever trace these executions, since we # can't do anything with the data later anyway. return nope(disp, "not a real file name") # pyexpat does a dumb thing, calling the trace function explicitly from # C code with a C file name. if re.search(r"[/\\]Modules[/\\]pyexpat.c", filename): return nope(disp, "pyexpat lies about itself") # Jython reports the .class file to the tracer, use the source file. if filename.endswith("$py.class"): filename = filename[:-9] + ".py" canonical = canonical_filename(filename) disp.canonical_filename = canonical # Try the plugins, see if they have an opinion about the file. plugin = None for plugin in self.plugins.file_tracers: if not plugin._coverage_enabled: continue try: file_tracer = plugin.file_tracer(canonical) if file_tracer is not None: file_tracer._coverage_plugin = plugin disp.trace = True disp.file_tracer = file_tracer if file_tracer.has_dynamic_source_filename(): disp.has_dynamic_filename = True else: disp.source_filename = canonical_filename( file_tracer.source_filename()) break except Exception: self.warn("Disabling plug-in %r due to an exception:" % (plugin._coverage_plugin_name)) traceback.print_exc() plugin._coverage_enabled = False continue else: # No plugin wanted it: it's Python. disp.trace = True disp.source_filename = canonical if not disp.has_dynamic_filename: if not disp.source_filename: raise CoverageException( "Plugin %r didn't set source_filename for %r" % (plugin, disp.original_filename)) reason = self.check_include_omit_etc(disp.source_filename, frame) if reason: nope(disp, reason) return disp
def configure(self, config): """Apply the configuration to get ready for decision-time.""" self.source_pkgs.extend(config.source_pkgs) for src in config.source or []: if os.path.isdir(src): self.source.append(canonical_filename(src)) else: self.source_pkgs.append(src) self.source_pkgs_unmatched = self.source_pkgs[:] self.omit = prep_patterns(config.run_omit) self.include = prep_patterns(config.run_include) # The directories for files considered "installed with the interpreter". self.pylib_paths = set() if not config.cover_pylib: # Look at where some standard modules are located. That's the # indication for "installed with the interpreter". In some # environments (virtualenv, for example), these modules may be # spread across a few locations. Look at all the candidate modules # we've imported, and take all the different ones. for m in (atexit, inspect, os, platform, _pypy_irc_topic, re, _structseq, traceback): if m is not None and hasattr(m, "__file__"): self.pylib_paths.add(canonical_path(m, directory=True)) if _structseq and not hasattr(_structseq, '__file__'): # PyPy 2.4 has no __file__ in the builtin modules, but the code # objects still have the file names. So dig into one to find # the path to exclude. The "filename" might be synthetic, # don't be fooled by those. structseq_file = code_object( _structseq.structseq_new).co_filename if not structseq_file.startswith("<"): self.pylib_paths.add(canonical_path(structseq_file)) # To avoid tracing the coverage.py code itself, we skip anything # located where we are. self.cover_paths = [canonical_path(__file__, directory=True)] if env.TESTING: # Don't include our own test code. self.cover_paths.append(os.path.join(self.cover_paths[0], "tests")) # When testing, we use PyContracts, which should be considered # part of coverage.py, and it uses six. Exclude those directories # just as we exclude ourselves. import contracts import six for mod in [contracts, six]: self.cover_paths.append(canonical_path(mod)) def debug(msg): if self.debug: self.debug.write(msg) # Create the matchers we need for should_trace if self.source or self.source_pkgs: against = [] if self.source: self.source_match = TreeMatcher(self.source) against.append("trees {!r}".format(self.source_match)) if self.source_pkgs: self.source_pkgs_match = ModuleMatcher(self.source_pkgs) against.append("modules {!r}".format(self.source_pkgs_match)) debug("Source matching against " + " and ".join(against)) else: if self.cover_paths: self.cover_match = TreeMatcher(self.cover_paths) debug("Coverage code matching: {!r}".format(self.cover_match)) if self.pylib_paths: self.pylib_match = TreeMatcher(self.pylib_paths) debug("Python stdlib matching: {!r}".format(self.pylib_match)) if self.include: self.include_match = FnmatchMatcher(self.include) debug("Include matching: {!r}".format(self.include_match)) if self.omit: self.omit_match = FnmatchMatcher(self.omit) debug("Omit matching: {!r}".format(self.omit_match))