| # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
| # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt |
| |
| """Plugin interfaces for coverage.py""" |
| |
| from coverage import files |
| from coverage.misc import contract, _needs_to_implement |
| |
| |
| class CoveragePlugin(object): |
| """Base class for coverage.py plugins. |
| |
| To write a coverage.py plugin, create a module with a subclass of |
| :class:`CoveragePlugin`. You will override methods in your class to |
| participate in various aspects of coverage.py's processing. |
| |
| Currently the only plugin type is a file tracer, for implementing |
| measurement support for non-Python files. File tracer plugins implement |
| the :meth:`file_tracer` method to claim files and the :meth:`file_reporter` |
| method to report on those files. |
| |
| Any plugin can optionally implement :meth:`sys_info` to provide debugging |
| information about their operation. |
| |
| Coverage.py will store its own information on your plugin object, using |
| attributes whose names start with ``_coverage_``. Don't be startled. |
| |
| To register your plugin, define a function called `coverage_init` in your |
| module:: |
| |
| def coverage_init(reg, options): |
| reg.add_file_tracer(MyPlugin()) |
| |
| You use the `reg` parameter passed to your `coverage_init` function to |
| register your plugin object. It has one method, `add_file_tracer`, which |
| takes a newly created instance of your plugin. |
| |
| If your plugin takes options, the `options` parameter is a dictionary of |
| your plugin's options from the coverage.py configuration file. Use them |
| however you want to configure your object before registering it. |
| |
| """ |
| |
| def file_tracer(self, filename): # pylint: disable=unused-argument |
| """Get a :class:`FileTracer` object for a file. |
| |
| Every Python source file is offered to the plugin to give it a chance |
| to take responsibility for tracing the file. If your plugin can handle |
| the file, then return a :class:`FileTracer` object. Otherwise return |
| None. |
| |
| There is no way to register your plugin for particular files. Instead, |
| this method is invoked for all files, and the plugin decides whether it |
| can trace the file or not. Be prepared for `filename` to refer to all |
| kinds of files that have nothing to do with your plugin. |
| |
| The file name will be a Python file being executed. There are two |
| broad categories of behavior for a plugin, depending on the kind of |
| files your plugin supports: |
| |
| * Static file names: each of your original source files has been |
| converted into a distinct Python file. Your plugin is invoked with |
| the Python file name, and it maps it back to its original source |
| file. |
| |
| * Dynamic file names: all of your source files are executed by the same |
| Python file. In this case, your plugin implements |
| :meth:`FileTracer.dynamic_source_filename` to provide the actual |
| source file for each execution frame. |
| |
| `filename` is a string, the path to the file being considered. This is |
| the absolute real path to the file. If you are comparing to other |
| paths, be sure to take this into account. |
| |
| Returns a :class:`FileTracer` object to use to trace `filename`, or |
| None if this plugin cannot trace this file. |
| |
| """ |
| return None |
| |
| def file_reporter(self, filename): # pylint: disable=unused-argument |
| """Get the :class:`FileReporter` class to use for a file. |
| |
| This will only be invoked if `filename` returns non-None from |
| :meth:`file_tracer`. It's an error to return None from this method. |
| |
| Returns a :class:`FileReporter` object to use to report on `filename`. |
| |
| """ |
| _needs_to_implement(self, "file_reporter") |
| |
| def sys_info(self): |
| """Get a list of information useful for debugging. |
| |
| This method will be invoked for ``--debug=sys``. Your |
| plugin can return any information it wants to be displayed. |
| |
| Returns a list of pairs: `[(name, value), ...]`. |
| |
| """ |
| return [] |
| |
| |
| class FileTracer(object): |
| """Support needed for files during the execution phase. |
| |
| You may construct this object from :meth:`CoveragePlugin.file_tracer` any |
| way you like. A natural choice would be to pass the file name given to |
| `file_tracer`. |
| |
| `FileTracer` objects should only be created in the |
| :meth:`CoveragePlugin.file_tracer` method. |
| |
| See :ref:`howitworks` for details of the different coverage.py phases. |
| |
| """ |
| |
| def source_filename(self): |
| """The source file name for this file. |
| |
| This may be any file name you like. A key responsibility of a plugin |
| is to own the mapping from Python execution back to whatever source |
| file name was originally the source of the code. |
| |
| See :meth:`CoveragePlugin.file_tracer` for details about static and |
| dynamic file names. |
| |
| Returns the file name to credit with this execution. |
| |
| """ |
| _needs_to_implement(self, "source_filename") |
| |
| def has_dynamic_source_filename(self): |
| """Does this FileTracer have dynamic source file names? |
| |
| FileTracers can provide dynamically determined file names by |
| implementing :meth:`dynamic_source_filename`. Invoking that function |
| is expensive. To determine whether to invoke it, coverage.py uses the |
| result of this function to know if it needs to bother invoking |
| :meth:`dynamic_source_filename`. |
| |
| See :meth:`CoveragePlugin.file_tracer` for details about static and |
| dynamic file names. |
| |
| Returns True if :meth:`dynamic_source_filename` should be called to get |
| dynamic source file names. |
| |
| """ |
| return False |
| |
| def dynamic_source_filename(self, filename, frame): # pylint: disable=unused-argument |
| """Get a dynamically computed source file name. |
| |
| Some plugins need to compute the source file name dynamically for each |
| frame. |
| |
| This function will not be invoked if |
| :meth:`has_dynamic_source_filename` returns False. |
| |
| Returns the source file name for this frame, or None if this frame |
| shouldn't be measured. |
| |
| """ |
| return None |
| |
| def line_number_range(self, frame): |
| """Get the range of source line numbers for a given a call frame. |
| |
| The call frame is examined, and the source line number in the original |
| file is returned. The return value is a pair of numbers, the starting |
| line number and the ending line number, both inclusive. For example, |
| returning (5, 7) means that lines 5, 6, and 7 should be considered |
| executed. |
| |
| This function might decide that the frame doesn't indicate any lines |
| from the source file were executed. Return (-1, -1) in this case to |
| tell coverage.py that no lines should be recorded for this frame. |
| |
| """ |
| lineno = frame.f_lineno |
| return lineno, lineno |
| |
| |
| class FileReporter(object): |
| """Support needed for files during the analysis and reporting phases. |
| |
| See :ref:`howitworks` for details of the different coverage.py phases. |
| |
| `FileReporter` objects should only be created in the |
| :meth:`CoveragePlugin.file_reporter` method. |
| |
| There are many methods here, but only :meth:`lines` is required, to provide |
| the set of executable lines in the file. |
| |
| """ |
| |
| def __init__(self, filename): |
| """Simple initialization of a `FileReporter`. |
| |
| The `filename` argument is the path to the file being reported. This |
| will be available as the `.filename` attribute on the object. Other |
| method implementations on this base class rely on this attribute. |
| |
| """ |
| self.filename = filename |
| |
| def __repr__(self): |
| return "<{0.__class__.__name__} filename={0.filename!r}>".format(self) |
| |
| def relative_filename(self): |
| """Get the relative file name for this file. |
| |
| This file path will be displayed in reports. The default |
| implementation will supply the actual project-relative file path. You |
| only need to supply this method if you have an unusual syntax for file |
| paths. |
| |
| """ |
| return files.relative_filename(self.filename) |
| |
| @contract(returns='unicode') |
| def source(self): |
| """Get the source for the file. |
| |
| Returns a Unicode string. |
| |
| The base implementation simply reads the `self.filename` file and |
| decodes it as UTF8. Override this method if your file isn't readable |
| as a text file, or if you need other encoding support. |
| |
| """ |
| with open(self.filename, "rb") as f: |
| return f.read().decode("utf8") |
| |
| def lines(self): |
| """Get the executable lines in this file. |
| |
| Your plugin must determine which lines in the file were possibly |
| executable. This method returns a set of those line numbers. |
| |
| Returns a set of line numbers. |
| |
| """ |
| _needs_to_implement(self, "lines") |
| |
| def excluded_lines(self): |
| """Get the excluded executable lines in this file. |
| |
| Your plugin can use any method it likes to allow the user to exclude |
| executable lines from consideration. |
| |
| Returns a set of line numbers. |
| |
| The base implementation returns the empty set. |
| |
| """ |
| return set() |
| |
| def translate_lines(self, lines): |
| """Translate recorded lines into reported lines. |
| |
| Some file formats will want to report lines slightly differently than |
| they are recorded. For example, Python records the last line of a |
| multi-line statement, but reports are nicer if they mention the first |
| line. |
| |
| Your plugin can optionally define this method to perform these kinds of |
| adjustment. |
| |
| `lines` is a sequence of integers, the recorded line numbers. |
| |
| Returns a set of integers, the adjusted line numbers. |
| |
| The base implementation returns the numbers unchanged. |
| |
| """ |
| return set(lines) |
| |
| def arcs(self): |
| """Get the executable arcs in this file. |
| |
| To support branch coverage, your plugin needs to be able to indicate |
| possible execution paths, as a set of line number pairs. Each pair is |
| a `(prev, next)` pair indicating that execution can transition from the |
| `prev` line number to the `next` line number. |
| |
| Returns a set of pairs of line numbers. The default implementation |
| returns an empty set. |
| |
| """ |
| return set() |
| |
| def no_branch_lines(self): |
| """Get the lines excused from branch coverage in this file. |
| |
| Your plugin can use any method it likes to allow the user to exclude |
| lines from consideration of branch coverage. |
| |
| Returns a set of line numbers. |
| |
| The base implementation returns the empty set. |
| |
| """ |
| return set() |
| |
| def translate_arcs(self, arcs): |
| """Translate recorded arcs into reported arcs. |
| |
| Similar to :meth:`translate_lines`, but for arcs. `arcs` is a set of |
| line number pairs. |
| |
| Returns a set of line number pairs. |
| |
| The default implementation returns `arcs` unchanged. |
| |
| """ |
| return arcs |
| |
| def exit_counts(self): |
| """Get a count of exits from that each line. |
| |
| To determine which lines are branches, coverage.py looks for lines that |
| have more than one exit. This function creates a dict mapping each |
| executable line number to a count of how many exits it has. |
| |
| To be honest, this feels wrong, and should be refactored. Let me know |
| if you attempt to implement this... |
| |
| """ |
| return {} |
| |
| def source_token_lines(self): |
| """Generate a series of tokenized lines, one for each line in `source`. |
| |
| These tokens are used for syntax-colored reports. |
| |
| Each line is a list of pairs, each pair is a token:: |
| |
| [('key', 'def'), ('ws', ' '), ('nam', 'hello'), ('op', '('), ... ] |
| |
| Each pair has a token class, and the token text. The token classes |
| are: |
| |
| * ``'com'``: a comment |
| * ``'key'``: a keyword |
| * ``'nam'``: a name, or identifier |
| * ``'num'``: a number |
| * ``'op'``: an operator |
| * ``'str'``: a string literal |
| * ``'txt'``: some other kind of text |
| |
| If you concatenate all the token texts, and then join them with |
| newlines, you should have your original source back. |
| |
| The default implementation simply returns each line tagged as |
| ``'txt'``. |
| |
| """ |
| for line in self.source().splitlines(): |
| yield [('txt', line)] |
| |
| # Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all |
| # of them defined. |
| |
| def __eq__(self, other): |
| return isinstance(other, FileReporter) and self.filename == other.filename |
| |
| def __ne__(self, other): |
| return not (self == other) |
| |
| def __lt__(self, other): |
| return self.filename < other.filename |
| |
| def __le__(self, other): |
| return self.filename <= other.filename |
| |
| def __gt__(self, other): |
| return self.filename > other.filename |
| |
| def __ge__(self, other): |
| return self.filename >= other.filename |