eric7/DebugClients/Python/coverage/plugin.py

branch
eric7
changeset 8312
800c432b34c8
parent 7702
f8b97639deb5
child 8775
0802ae193343
diff -r 4e8b98454baa -r 800c432b34c8 eric7/DebugClients/Python/coverage/plugin.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/DebugClients/Python/coverage/plugin.py	Sat May 15 18:45:04 2021 +0200
@@ -0,0 +1,533 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""
+.. versionadded:: 4.0
+
+Plug-in interfaces for coverage.py.
+
+Coverage.py supports a few different kinds of plug-ins that change its
+behavior:
+
+* File tracers implement tracing of non-Python file types.
+
+* Configurers add custom configuration, using Python code to change the
+  configuration.
+
+* Dynamic context switchers decide when the dynamic context has changed, for
+  example, to record what test function produced the coverage.
+
+To write a coverage.py plug-in, create a module with a subclass of
+:class:`~coverage.CoveragePlugin`.  You will override methods in your class to
+participate in various aspects of coverage.py's processing.
+Different types of plug-ins have to override different methods.
+
+Any plug-in can optionally implement :meth:`~coverage.CoveragePlugin.sys_info`
+to provide debugging information about their operation.
+
+Your module must also contain a ``coverage_init`` function that registers an
+instance of your plug-in class::
+
+    import coverage
+
+    class MyPlugin(coverage.CoveragePlugin):
+        ...
+
+    def coverage_init(reg, options):
+        reg.add_file_tracer(MyPlugin())
+
+You use the `reg` parameter passed to your ``coverage_init`` function to
+register your plug-in object.  The registration method you call depends on
+what kind of plug-in it is.
+
+If your plug-in takes options, the `options` parameter is a dictionary of your
+plug-in's options from the coverage.py configuration file.  Use them however
+you want to configure your object before registering it.
+
+Coverage.py will store its own information on your plug-in object, using
+attributes whose names start with ``_coverage_``.  Don't be startled.
+
+.. warning::
+    Plug-ins are imported by coverage.py before it begins measuring code.
+    If you write a plugin in your own project, it might import your product
+    code before coverage.py can start measuring.  This can result in your
+    own code being reported as missing.
+
+    One solution is to put your plugins in your project tree, but not in
+    your importable Python package.
+
+
+.. _file_tracer_plugins:
+
+File Tracers
+============
+
+File tracers implement measurement support for non-Python files.  File tracers
+implement the :meth:`~coverage.CoveragePlugin.file_tracer` method to claim
+files and the :meth:`~coverage.CoveragePlugin.file_reporter` method to report
+on those files.
+
+In your ``coverage_init`` function, use the ``add_file_tracer`` method to
+register your file tracer.
+
+
+.. _configurer_plugins:
+
+Configurers
+===========
+
+.. versionadded:: 4.5
+
+Configurers modify the configuration of coverage.py during start-up.
+Configurers implement the :meth:`~coverage.CoveragePlugin.configure` method to
+change the configuration.
+
+In your ``coverage_init`` function, use the ``add_configurer`` method to
+register your configurer.
+
+
+.. _dynamic_context_plugins:
+
+Dynamic Context Switchers
+=========================
+
+.. versionadded:: 5.0
+
+Dynamic context switcher plugins implement the
+:meth:`~coverage.CoveragePlugin.dynamic_context` method to dynamically compute
+the context label for each measured frame.
+
+Computed context labels are useful when you want to group measured data without
+modifying the source code.
+
+For example, you could write a plugin that checks `frame.f_code` to inspect
+the currently executed method, and set the context label to a fully qualified
+method name if it's an instance method of `unittest.TestCase` and the method
+name starts with 'test'.  Such a plugin would provide basic coverage grouping
+by test and could be used with test runners that have no built-in coveragepy
+support.
+
+In your ``coverage_init`` function, use the ``add_dynamic_context`` method to
+register your dynamic context switcher.
+
+"""
+
+from coverage import files
+from coverage.misc import contract, _needs_to_implement
+
+
+class CoveragePlugin(object):
+    """Base class for coverage.py plug-ins."""
+
+    def file_tracer(self, filename):        # pylint: disable=unused-argument
+        """Get a :class:`FileTracer` object for a file.
+
+        Plug-in type: file tracer.
+
+        Every Python source file is offered to your plug-in to give it a chance
+        to take responsibility for tracing the file.  If your plug-in can
+        handle the file, it should return a :class:`FileTracer` object.
+        Otherwise return None.
+
+        There is no way to register your plug-in for particular files.
+        Instead, this method is invoked for all  files as they are executed,
+        and the plug-in 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 plug-in.
+
+        The file name will be a Python file being executed.  There are two
+        broad categories of behavior for a plug-in, depending on the kind of
+        files your plug-in supports:
+
+        * Static file names: each of your original source files has been
+          converted into a distinct Python file.  Your plug-in 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 plug-in 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 plug-in 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.
+
+        Plug-in type: file tracer.
+
+        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`,
+        or the string `"python"` to have coverage.py treat the file as Python.
+
+        """
+        _needs_to_implement(self, "file_reporter")
+
+    def dynamic_context(self, frame):       # pylint: disable=unused-argument
+        """Get the dynamically computed context label for `frame`.
+
+        Plug-in type: dynamic context.
+
+        This method is invoked for each frame when outside of a dynamic
+        context, to see if a new dynamic context should be started.  If it
+        returns a string, a new context label is set for this and deeper
+        frames.  The dynamic context ends when this frame returns.
+
+        Returns a string to start a new dynamic context, or None if no new
+        context should be started.
+
+        """
+        return None
+
+    def find_executable_files(self, src_dir):       # pylint: disable=unused-argument
+        """Yield all of the executable files in `src_dir`, recursively.
+
+        Plug-in type: file tracer.
+
+        Executability is a plug-in-specific property, but generally means files
+        which would have been considered for coverage analysis, had they been
+        included automatically.
+
+        Returns or yields a sequence of strings, the paths to files that could
+        have been executed, including files that had been executed.
+
+        """
+        return []
+
+    def configure(self, config):
+        """Modify the configuration of coverage.py.
+
+        Plug-in type: configurer.
+
+        This method is called during coverage.py start-up, to give your plug-in
+        a chance to change the configuration.  The `config` parameter is an
+        object with :meth:`~coverage.Coverage.get_option` and
+        :meth:`~coverage.Coverage.set_option` methods.  Do not call any other
+        methods on the `config` object.
+
+        """
+        pass
+
+    def sys_info(self):
+        """Get a list of information useful for debugging.
+
+        Plug-in type: any.
+
+        This method will be invoked for ``--debug=sys``.  Your
+        plug-in 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.
+
+    File tracer plug-ins implement subclasses of FileTracer to return from
+    their :meth:`~CoveragePlugin.file_tracer` method.
+
+    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 plug-in
+        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 plug-ins 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.
+
+    File tracer plug-ins implement a subclass of `FileReporter`, and return
+    instances from their :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.
+
+    See :ref:`howitworks` for details of the different coverage.py phases.
+
+    """
+
+    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 plug-in 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 plug-in 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 plug-in 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 plug-in 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 plug-in 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 method in your plug-in...
+
+        """
+        return {}
+
+    def missing_arc_description(self, start, end, executed_arcs=None):     # pylint: disable=unused-argument
+        """Provide an English sentence describing a missing arc.
+
+        The `start` and `end` arguments are the line numbers of the missing
+        arc. Negative numbers indicate entering or exiting code objects.
+
+        The `executed_arcs` argument is a set of line number pairs, the arcs
+        that were executed in this file.
+
+        By default, this simply returns the string "Line {start} didn't jump
+        to {end}".
+
+        """
+        return "Line {start} didn't jump to line {end}".format(start=start, end=end)
+
+    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
+        * ``'ws'``: some white space
+        * ``'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
+
+    __hash__ = None     # This object doesn't need to be hashed.

eric ide

mercurial