Third Party packages: updated coverage.py to 5.3.0.

Thu, 17 Sep 2020 19:10:36 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 17 Sep 2020 19:10:36 +0200
changeset 7702
f8b97639deb5
parent 7701
25f42e208e08
child 7703
1f800f8295ea

Third Party packages: updated coverage.py to 5.3.0.

docs/changelog file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/backunittest.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/backward.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/cmdline.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/collector.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/config.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/control.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/env.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/html.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/inorout.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/jsonreport.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/multiproc.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/parser.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/plugin.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/sqldata.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/summary.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/version.py file | annotate | diff | comparison | revisions
eric6/DebugClients/Python/coverage/xmlreport.py file | annotate | diff | comparison | revisions
--- a/docs/changelog	Tue Sep 15 19:09:05 2020 +0200
+++ b/docs/changelog	Thu Sep 17 19:10:36 2020 +0200
@@ -7,6 +7,7 @@
      and allowing to navigate in the code
 - Third Party packages
   -- updated Pygments to 2.7.0
+  -- updated coverage.py to 5.3.0
 
 Version 20.9:
 - bug fixes
--- a/eric6/DebugClients/Python/coverage/backunittest.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/backunittest.py	Thu Sep 17 19:10:36 2020 +0200
@@ -18,7 +18,7 @@
     `unittest` doesn't have them.
 
     """
-    # pylint: disable=arguments-differ, deprecated-method
+    # pylint: disable=signature-differs, deprecated-method
 
     if not unittest_has('assertCountEqual'):
         def assertCountEqual(self, *args, **kwargs):
--- a/eric6/DebugClients/Python/coverage/backward.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/backward.py	Thu Sep 17 19:10:36 2020 +0200
@@ -9,6 +9,8 @@
 import os
 import sys
 
+from datetime import datetime
+
 from coverage import env
 
 
@@ -217,6 +219,17 @@
             return self.__dict__ == other.__dict__
 
 
+def format_local_datetime(dt):
+    """Return a string with local timezone representing the date.
+    If python version is lower than 3.6, the time zone is not included.
+    """
+    try:
+        return dt.astimezone().strftime('%Y-%m-%d %H:%M %z')
+    except (TypeError, ValueError):
+        # Datetime.astimezone in Python 3.5 can not handle naive datetime
+        return dt.strftime('%Y-%m-%d %H:%M')
+
+
 def invalidate_import_caches():
     """Invalidate any import caches that may or may not exist."""
     if importlib and hasattr(importlib, "invalidate_caches"):
--- a/eric6/DebugClients/Python/coverage/cmdline.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/cmdline.py	Thu Sep 17 19:10:36 2020 +0200
@@ -18,7 +18,7 @@
 from coverage import env
 from coverage.collector import CTracer
 from coverage.data import line_counts
-from coverage.debug import info_formatter, info_header
+from coverage.debug import info_formatter, info_header, short_stack
 from coverage.execfile import PyRunner
 from coverage.misc import BaseCoverageException, ExceptionDuringRun, NoSource, output_encoding
 from coverage.results import should_fail_under
@@ -85,6 +85,11 @@
             "which isn't done by default."
         ),
     )
+    sort = optparse.make_option(
+        '--sort', action='store', metavar='COLUMN',
+        help="Sort the report by the named column: name, stmts, miss, branch, brpart, or cover. "
+             "Default is name."
+    )
     show_missing = optparse.make_option(
         '-m', '--show-missing', action='store_true',
         help="Show line numbers of statements in each module that weren't executed.",
@@ -93,6 +98,10 @@
         '--skip-covered', action='store_true',
         help="Skip files with 100% coverage.",
     )
+    no_skip_covered = optparse.make_option(
+        '--no-skip-covered', action='store_false', dest='skip_covered',
+        help="Disable --skip-covered.",
+    )
     skip_empty = optparse.make_option(
         '--skip-empty', action='store_true',
         help="Skip files with no code.",
@@ -146,6 +155,13 @@
             "to be run as 'python -m' would run it."
         ),
     )
+    precision = optparse.make_option(
+        '', '--precision', action='store', metavar='N', type=int,
+        help=(
+            "Number of digits after the decimal point to display for "
+            "reported coverage percentages."
+        ),
+    )
     rcfile = optparse.make_option(
         '', '--rcfile', action='store',
         help=(
@@ -203,12 +219,14 @@
             omit=None,
             contexts=None,
             parallel_mode=None,
+            precision=None,
             pylib=None,
             rcfile=True,
             show_missing=None,
             skip_covered=None,
             skip_empty=None,
             show_contexts=None,
+            sort=None,
             source=None,
             timid=None,
             title=None,
@@ -331,10 +349,13 @@
         "debug", GLOBAL_ARGS,
         usage="<topic>",
         description=(
-            "Display information on the internals of coverage.py, "
+            "Display information about the internals of coverage.py, "
             "for diagnosing problems. "
-            "Topics are 'data' to show a summary of the collected data, "
-            "or 'sys' to show installation information."
+            "Topics are: "
+                "'data' to show a summary of the collected data; "
+                "'sys' to show installation information; "
+                "'config' to show the configuration; "
+                "'premain' to show what is calling coverage."
         ),
     ),
 
@@ -358,8 +379,10 @@
             Opts.ignore_errors,
             Opts.include,
             Opts.omit,
+            Opts.precision,
             Opts.show_contexts,
             Opts.skip_covered,
+            Opts.no_skip_covered,
             Opts.skip_empty,
             Opts.title,
             ] + GLOBAL_ARGS,
@@ -395,8 +418,11 @@
             Opts.ignore_errors,
             Opts.include,
             Opts.omit,
+            Opts.precision,
+            Opts.sort,
             Opts.show_missing,
             Opts.skip_covered,
+            Opts.no_skip_covered,
             Opts.skip_empty,
             ] + GLOBAL_ARGS,
         usage="[options] [modules]",
@@ -430,6 +456,7 @@
             Opts.include,
             Opts.omit,
             Opts.output_xml,
+            Opts.skip_empty,
             ] + GLOBAL_ARGS,
         usage="[options] [modules]",
         description="Generate an XML report of coverage results."
@@ -583,6 +610,8 @@
                 show_missing=options.show_missing,
                 skip_covered=options.skip_covered,
                 skip_empty=options.skip_empty,
+                precision=options.precision,
+                sort=options.sort,
                 **report_args
                 )
         elif options.action == "annotate":
@@ -594,11 +623,15 @@
                 skip_covered=options.skip_covered,
                 skip_empty=options.skip_empty,
                 show_contexts=options.show_contexts,
+                precision=options.precision,
                 **report_args
                 )
         elif options.action == "xml":
             outfile = options.outfile
-            total = self.coverage.xml_report(outfile=outfile, **report_args)
+            total = self.coverage.xml_report(
+                outfile=outfile, skip_empty=options.skip_empty,
+                **report_args
+                )
         elif options.action == "json":
             outfile = options.outfile
             total = self.coverage.json_report(
@@ -617,6 +650,10 @@
             fail_under = self.coverage.get_option("report:fail_under")
             precision = self.coverage.get_option("report:precision")
             if should_fail_under(total, fail_under, precision):
+                msg = "total of {total:.{p}f} is less than fail-under={fail_under:.{p}f}".format(
+                    total=total, fail_under=fail_under, p=precision,
+                )
+                print("Coverage failure:", msg)
                 return FAIL_UNDER
 
         return OK
@@ -749,7 +786,6 @@
                     print(" %s" % line)
             elif info == "premain":
                 print(info_header("premain"))
-                from coverage.debug import short_stack
                 print(short_stack())
             else:
                 show_help("Don't know what you mean by %r" % info)
@@ -795,6 +831,7 @@
         Commands:
             annotate    Annotate source files with execution information.
             combine     Combine a number of data files.
+            debug       Display information about the internals of coverage.py
             erase       Erase previously collected coverage data.
             help        Get help on using coverage.py.
             html        Create an HTML report.
@@ -807,7 +844,7 @@
     """,
 
     'minimum_help': """\
-        Code coverage for Python.  Use '{program_name} help' for help.
+        Code coverage for Python, version {__version__} {extension_modifier}.  Use '{program_name} help' for help.
     """,
 
     'version': """\
@@ -858,8 +895,8 @@
 
     def main(argv=None):                                    # pylint: disable=function-redefined
         """A wrapper around main that profiles."""
+        profiler = SimpleLauncher.launch()
         try:
-            profiler = SimpleLauncher.launch()
             return original_main(argv)
         finally:
             data, _ = profiler.query(re_filter='coverage', max_records=100)
--- a/eric6/DebugClients/Python/coverage/collector.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/collector.py	Thu Sep 17 19:10:36 2020 +0200
@@ -196,6 +196,8 @@
         # handle them.
         self.file_tracers = {}
 
+        self.disabled_plugins = set()
+
         # The .should_trace_cache attribute is a cache from file names to
         # coverage.FileDisposition objects, or None.  When a file is first
         # considered for tracing, a FileDisposition is obtained from
@@ -256,6 +258,8 @@
         if hasattr(tracer, 'should_start_context'):
             tracer.should_start_context = self.should_start_context
             tracer.switch_context = self.switch_context
+        if hasattr(tracer, 'disable_plugin'):
+            tracer.disable_plugin = self.disable_plugin
 
         fn = tracer.start()
         self.tracers.append(tracer)
@@ -381,6 +385,15 @@
             context = new_context
         self.covdata.set_context(context)
 
+    def disable_plugin(self, disposition):
+        """Disable the plugin mentioned in `disposition`."""
+        file_tracer = disposition.file_tracer
+        plugin = file_tracer._coverage_plugin
+        plugin_name = plugin._coverage_plugin_name
+        self.warn("Disabling plug-in {!r} due to previous exception".format(plugin_name))
+        plugin._coverage_enabled = False
+        disposition.trace = False
+
     def cached_mapped_file(self, filename):
         """A locally cached version of file names mapped through file_mapper."""
         key = (type(filename), filename)
@@ -408,6 +421,10 @@
 
         return dict((self.cached_mapped_file(k), v) for k, v in items if v)
 
+    def plugin_was_disabled(self, plugin):
+        """Record that `plugin` was disabled during the run."""
+        self.disabled_plugins.add(plugin._coverage_plugin_name)
+
     def flush_data(self):
         """Save the collected data to our associated `CoverageData`.
 
@@ -423,7 +440,12 @@
             self.covdata.add_arcs(self.mapped_file_dict(self.data))
         else:
             self.covdata.add_lines(self.mapped_file_dict(self.data))
-        self.covdata.add_file_tracers(self.mapped_file_dict(self.file_tracers))
+
+        file_tracers = {
+            k: v for k, v in self.file_tracers.items()
+            if v not in self.disabled_plugins
+        }
+        self.covdata.add_file_tracers(self.mapped_file_dict(file_tracers))
 
         self._clear_data()
         return True
--- a/eric6/DebugClients/Python/coverage/config.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/config.py	Thu Sep 17 19:10:36 2020 +0200
@@ -72,7 +72,7 @@
             d[opt] = self.get(section, opt)
         return d
 
-    def get(self, section, option, *args, **kwargs):        # pylint: disable=arguments-differ
+    def get(self, section, option, *args, **kwargs):
         """Get a value, replacing environment variables also.
 
         The arguments are the same as `RawConfigParser.get`, but in the found
@@ -195,6 +195,7 @@
         self.run_include = None
         self.run_omit = None
         self.source = None
+        self.source_pkgs = []
         self.timid = False
         self._crash = None
 
@@ -211,6 +212,7 @@
         self.show_missing = False
         self.skip_covered = False
         self.skip_empty = False
+        self.sort = None
 
         # Defaults for [html]
         self.extra_css = None
@@ -325,7 +327,7 @@
 
         if used:
             self.config_file = os.path.abspath(filename)
-            with open(filename) as f:
+            with open(filename, "rb") as f:
                 self._config_contents = f.read()
 
         return used
@@ -360,6 +362,7 @@
         ('run_include', 'run:include', 'list'),
         ('run_omit', 'run:omit', 'list'),
         ('source', 'run:source', 'list'),
+        ('source_pkgs', 'run:source_pkgs', 'list'),
         ('timid', 'run:timid', 'boolean'),
         ('_crash', 'run:_crash'),
 
@@ -421,6 +424,10 @@
         `value` is the new value for the option.
 
         """
+        # Special-cased options.
+        if option_name == "paths":
+            self.paths = value
+            return
 
         # Check all the hard-coded options.
         for option_spec in self.CONFIG_FILE_OPTIONS:
@@ -448,6 +455,10 @@
         Returns the value of the option.
 
         """
+        # Special-cased options.
+        if option_name == "paths":
+            return self.paths
+
         # Check all the hard-coded options.
         for option_spec in self.CONFIG_FILE_OPTIONS:
             attr, where = option_spec[:2]
--- a/eric6/DebugClients/Python/coverage/control.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/control.py	Thu Sep 17 19:10:36 2020 +0200
@@ -99,9 +99,9 @@
     def __init__(
         self, data_file=_DEFAULT_DATAFILE, data_suffix=None, cover_pylib=None,
         auto_data=False, timid=None, branch=None, config_file=True,
-        source=None, omit=None, include=None, debug=None,
+        source=None, source_pkgs=None, omit=None, include=None, debug=None,
         concurrency=None, check_preimported=False, context=None,
-    ):
+    ):  # pylint: disable=too-many-arguments
         """
         Many of these arguments duplicate and override values that can be
         provided in a configuration file.  Parameters that are missing here
@@ -146,6 +146,10 @@
         in the trees indicated by the file paths or package names will be
         measured.
 
+        `source_pkgs` is a list of package names. It works the same as
+        `source`, but can be used to name packages where the name can also be
+        interpreted as a file path.
+
         `include` and `omit` are lists of file name patterns. Files that match
         `include` will be measured, files that match `omit` will not.  Each
         will also accept a single string argument.
@@ -176,6 +180,9 @@
         .. versionadded:: 5.0
             The `check_preimported` and `context` parameters.
 
+        .. versionadded:: 5.3
+            The `source_pkgs` parameter.
+
         """
         # data_file=None means no disk file at all. data_file missing means
         # use the value from the config file.
@@ -188,7 +195,7 @@
             config_file=config_file,
             data_file=data_file, cover_pylib=cover_pylib, timid=timid,
             branch=branch, parallel=bool_or_none(data_suffix),
-            source=source, run_omit=omit, run_include=include, debug=debug,
+            source=source, source_pkgs=source_pkgs, run_omit=omit, run_include=include, debug=debug,
             report_omit=omit, report_include=include,
             concurrency=concurrency, context=context,
             )
@@ -212,7 +219,6 @@
         self._data = self._collector = None
         self._plugins = None
         self._inorout = None
-        self._inorout_class = InOrOut
         self._data_suffix = self._run_suffix = None
         self._exclude_re = None
         self._debug = None
@@ -367,7 +373,11 @@
         option name.  For example, the ``branch`` option in the ``[run]``
         section of the config file would be indicated with `"run:branch"`.
 
-        Returns the value of the option.
+        Returns the value of the option.  The type depends on the option
+        selected.
+
+        As a special case, an `option_name` of ``"paths"`` will return an
+        OrderedDict with the entire ``[paths]`` section value.
 
         .. versionadded:: 4.0
 
@@ -394,6 +404,9 @@
             [run]
             branch = True
 
+        As a special case, an `option_name` of ``"paths"`` will replace the
+        entire ``[paths]`` section.  The value should be an OrderedDict.
+
         .. versionadded:: 4.0
 
         """
@@ -476,7 +489,10 @@
                 plugin._coverage_enabled = False
 
         # Create the file classifying substructure.
-        self._inorout = self._inorout_class(warn=self._warn)
+        self._inorout = InOrOut(
+            warn=self._warn,
+            debug=(self._debug if self._debug.should('trace') else None),
+        )
         self._inorout.configure(self.config)
         self._inorout.plugins = self._plugins
         self._inorout.disp_class = self._collector.file_disposition_class
@@ -545,7 +561,7 @@
     def _atexit(self):
         """Clean up on process shutdown."""
         if self._debug.should("process"):
-            self._debug.write("atexit: {!r}".format(self))
+            self._debug.write("atexit: pid: {}, instance: {!r}".format(os.getpid(), self))
         if self._started:
             self.stop()
         if self._auto_save:
@@ -693,6 +709,10 @@
         self._init_data(suffix=None)
         self._post_init()
 
+        for plugin in self._plugins:
+            if not plugin._coverage_enabled:
+                self._collector.plugin_was_disabled(plugin)
+
         if self._collector and self._collector.flush_data():
             self._post_save_work()
 
@@ -822,7 +842,7 @@
     def report(
         self, morfs=None, show_missing=None, ignore_errors=None,
         file=None, omit=None, include=None, skip_covered=None,
-        contexts=None, skip_empty=None,
+        contexts=None, skip_empty=None, precision=None, sort=None
     ):
         """Write a textual summary report to `file`.
 
@@ -850,6 +870,9 @@
         expressions (using :func:`re.search <python:re.search>`) will be
         included in the report.
 
+        `precision` is the number of digits to display after the decimal
+        point for percentages.
+
         All of the arguments default to the settings read from the
         :ref:`configuration file <config>`.
 
@@ -861,12 +884,16 @@
         .. versionadded:: 5.0
             The `contexts` and `skip_empty` parameters.
 
+        .. versionadded:: 5.2
+            The `precision` parameter.
+
         """
         with override_config(
             self,
             ignore_errors=ignore_errors, report_omit=omit, report_include=include,
             show_missing=show_missing, skip_covered=skip_covered,
-            report_contexts=contexts, skip_empty=skip_empty,
+            report_contexts=contexts, skip_empty=skip_empty, precision=precision,
+            sort=sort
         ):
             reporter = SummaryReporter(self)
             return reporter.report(morfs, outfile=file)
@@ -892,10 +919,12 @@
             reporter = AnnotateReporter(self)
             reporter.report(morfs, directory=directory)
 
-    def html_report(self, morfs=None, directory=None, ignore_errors=None,
-                    omit=None, include=None, extra_css=None, title=None,
-                    skip_covered=None, show_contexts=None, contexts=None,
-                    skip_empty=None):
+    def html_report(
+        self, morfs=None, directory=None, ignore_errors=None,
+        omit=None, include=None, extra_css=None, title=None,
+        skip_covered=None, show_contexts=None, contexts=None,
+        skip_empty=None, precision=None,
+    ):
         """Generate an HTML report.
 
         The HTML is written to `directory`.  The file "index.html" is the
@@ -923,14 +952,14 @@
             ignore_errors=ignore_errors, report_omit=omit, report_include=include,
             html_dir=directory, extra_css=extra_css, html_title=title,
             skip_covered=skip_covered, show_contexts=show_contexts, report_contexts=contexts,
-            skip_empty=skip_empty,
+            skip_empty=skip_empty, precision=precision,
         ):
             reporter = HtmlReporter(self)
             return reporter.report(morfs)
 
     def xml_report(
         self, morfs=None, outfile=None, ignore_errors=None,
-        omit=None, include=None, contexts=None,
+        omit=None, include=None, contexts=None, skip_empty=None,
     ):
         """Generate an XML report of coverage results.
 
@@ -946,7 +975,7 @@
         """
         with override_config(self,
             ignore_errors=ignore_errors, report_omit=omit, report_include=include,
-            xml_output=outfile, report_contexts=contexts,
+            xml_output=outfile, report_contexts=contexts, skip_empty=skip_empty,
         ):
             return render_report(self.config.xml_output, XmlReporter(self), morfs)
 
@@ -1077,8 +1106,7 @@
     # flag (an attribute on this function) to indicate that coverage.py has
     # already been started, so we can avoid doing it twice.
     #
-    # https://bitbucket.org/ned/coveragepy/issue/340/keyerror-subpy has more
-    # details.
+    # https://github.com/nedbat/coveragepy/issues/340 has more details.
 
     if hasattr(process_startup, "coverage"):
         # We've annotated this function before, so we must have already
--- a/eric6/DebugClients/Python/coverage/env.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/env.py	Thu Sep 17 19:10:36 2020 +0200
@@ -57,7 +57,7 @@
     unpackings_pep448 = (PYVERSION >= (3, 5))
 
     # Can co_lnotab have negative deltas?
-    negative_lnotab = (PYVERSION >= (3, 6))
+    negative_lnotab = (PYVERSION >= (3, 6)) and not (PYPY and PYPYVERSION < (7, 2))
 
     # Do .pyc files conform to PEP 552? Hash-based pyc's.
     hashed_pyc_pep552 = (PYVERSION >= (3, 7, 0, 'alpha', 4))
@@ -85,10 +85,6 @@
     # Python 3.9a1 made sys.argv[0] and other reported files absolute paths.
     report_absolute_files = (PYVERSION >= (3, 9))
 
-    # Python 3.9a2 changed how return/finally was traced, but it was
-    # temporary.
-    bpo39114 = (PYVERSION == (3, 9, 0, 'alpha', 2, 0))
-
 # Coverage.py specifics.
 
 # Are we using the C-implemented trace function?
--- a/eric6/DebugClients/Python/coverage/html.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/html.py	Thu Sep 17 19:10:36 2020 +0200
@@ -11,7 +11,7 @@
 
 import coverage
 from coverage import env
-from coverage.backward import iitems, SimpleNamespace
+from coverage.backward import iitems, SimpleNamespace, format_local_datetime
 from coverage.data import add_data_to_hash
 from coverage.files import flat_rootname
 from coverage.misc import CoverageException, ensure_dir, file_be_gone, Hasher, isolate_module
@@ -200,7 +200,7 @@
             '__url__': coverage.__url__,
             '__version__': coverage.__version__,
             'title': title,
-            'time_stamp': datetime.datetime.now().strftime('%Y-%m-%d %H:%M'),
+            'time_stamp': format_local_datetime(datetime.datetime.now()),
             'extra_css': self.extra_css,
             'has_arcs': self.has_arcs,
             'show_contexts': self.config.show_contexts,
--- a/eric6/DebugClients/Python/coverage/inorout.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/inorout.py	Thu Sep 17 19:10:36 2020 +0200
@@ -111,8 +111,9 @@
 class InOrOut(object):
     """Machinery for determining what files to measure."""
 
-    def __init__(self, warn):
+    def __init__(self, warn, debug):
         self.warn = warn
+        self.debug = debug
 
         # The matchers for should_trace.
         self.source_match = None
@@ -131,6 +132,7 @@
 
     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))
@@ -177,19 +179,33 @@
             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:
-            self.source_match = TreeMatcher(self.source)
-            self.source_pkgs_match = ModuleMatcher(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))
 
     def should_trace(self, filename, frame=None):
         """Decide whether to trace execution in `filename`, with a reason.
@@ -309,12 +325,21 @@
         # about the outer bound of what to measure and we don't have to apply
         # any canned exclusions. If they didn't, then we have to exclude the
         # stdlib and coverage.py directories.
-        if self.source_match:
-            if self.source_pkgs_match.match(modulename):
-                if modulename in self.source_pkgs_unmatched:
-                    self.source_pkgs_unmatched.remove(modulename)
-            elif not self.source_match.match(filename):
-                return "falls outside the --source trees"
+        if self.source_match or self.source_pkgs_match:
+            extra = ""
+            ok = False
+            if self.source_pkgs_match:
+                if self.source_pkgs_match.match(modulename):
+                    ok = True
+                    if modulename in self.source_pkgs_unmatched:
+                        self.source_pkgs_unmatched.remove(modulename)
+                else:
+                    extra = "module {!r} ".format(modulename)
+            if not ok and self.source_match:
+                if self.source_match.match(filename):
+                    ok = True
+            if not ok:
+                return extra + "falls outside the --source spec"
         elif self.include_match:
             if not self.include_match.match(filename):
                 return "falls outside the --include trees"
--- a/eric6/DebugClients/Python/coverage/jsonreport.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/jsonreport.py	Thu Sep 17 19:10:36 2020 +0200
@@ -60,6 +60,8 @@
             self.report_data["totals"].update({
                 'num_branches': self.total.n_branches,
                 'num_partial_branches': self.total.n_partial_branches,
+                'covered_branches': self.total.n_executed_branches,
+                'missing_branches': self.total.n_missing_branches,
             })
 
         json.dump(
@@ -95,5 +97,7 @@
             reported_file['summary'].update({
                 'num_branches': nums.n_branches,
                 'num_partial_branches': nums.n_partial_branches,
+                'covered_branches': nums.n_executed_branches,
+                'missing_branches': nums.n_missing_branches,
             })
         return reported_file
--- a/eric6/DebugClients/Python/coverage/multiproc.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/multiproc.py	Thu Sep 17 19:10:36 2020 +0200
@@ -28,7 +28,7 @@
 class ProcessWithCoverage(OriginalProcess):         # pylint: disable=abstract-method
     """A replacement for multiprocess.Process that starts coverage."""
 
-    def _bootstrap(self, *args, **kwargs):          # pylint: disable=arguments-differ
+    def _bootstrap(self, *args, **kwargs):          # pylint: disable=signature-differs
         """Wrapper around _bootstrap to start coverage."""
         try:
             from coverage import Coverage       # avoid circular import
--- a/eric6/DebugClients/Python/coverage/parser.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/parser.py	Thu Sep 17 19:10:36 2020 +0200
@@ -610,6 +610,7 @@
             return node.lineno
 
     _line__FunctionDef = _line_decorated
+    _line__AsyncFunctionDef = _line_decorated
 
     def _line__List(self, node):
         if node.elts:
--- a/eric6/DebugClients/Python/coverage/plugin.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/plugin.py	Thu Sep 17 19:10:36 2020 +0200
@@ -496,6 +496,7 @@
         * ``'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
--- a/eric6/DebugClients/Python/coverage/sqldata.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/sqldata.py	Thu Sep 17 19:10:36 2020 +0200
@@ -268,7 +268,7 @@
         """Read the metadata from a database so that we are ready to use it."""
         with self._dbs[get_thread_id()] as db:
             try:
-                schema_version, = db.execute("select version from coverage_schema").fetchone()
+                schema_version, = db.execute_one("select version from coverage_schema")
             except Exception as exc:
                 raise CoverageException(
                     "Data file {!r} doesn't seem to be a coverage data file: {}".format(
@@ -374,7 +374,7 @@
         assert context is not None
         self._start_using()
         with self._connect() as con:
-            row = con.execute("select id from context where context = ?", (context,)).fetchone()
+            row = con.execute_one("select id from context where context = ?", (context,))
             if row is not None:
                 return row[0]
             else:
@@ -789,7 +789,7 @@
             file_id = self._file_id(filename)
             if file_id is None:
                 return None
-            row = con.execute("select tracer from tracer where file_id = ?", (file_id,)).fetchone()
+            row = con.execute_one("select tracer from tracer where file_id = ?", (file_id,))
             if row is not None:
                 return row[0] or ""
             return ""   # File was measured, but no tracer associated.
@@ -952,11 +952,13 @@
 
         """
         with SqliteDb(":memory:", debug=NoDebugging()) as db:
+            temp_store = [row[0] for row in db.execute("pragma temp_store")]
             compile_options = [row[0] for row in db.execute("pragma compile_options")]
 
         return [
             ('sqlite3_version', sqlite3.version),
             ('sqlite3_sqlite_version', sqlite3.sqlite_version),
+            ('sqlite3_temp_store', temp_store),
             ('sqlite3_compile_options', compile_options),
         ]
 
@@ -1062,6 +1064,23 @@
                 self.debug.write("EXCEPTION from execute: {}".format(msg))
             raise CoverageException("Couldn't use data file {!r}: {}".format(self.filename, msg))
 
+    def execute_one(self, sql, parameters=()):
+        """Execute a statement and return the one row that results.
+
+        This is like execute(sql, parameters).fetchone(), except it is
+        correct in reading the entire result set.  This will raise an
+        exception if more than one row results.
+
+        Returns a row, or None if there were no rows.
+        """
+        rows = list(self.execute(sql, parameters))
+        if len(rows) == 0:
+            return None
+        elif len(rows) == 1:
+            return rows[0]
+        else:
+            raise CoverageException("Sql {!r} shouldn't return {} rows".format(sql, len(rows)))
+
     def executemany(self, sql, data):
         """Same as :meth:`python:sqlite3.Connection.executemany`."""
         if self.debug:
--- a/eric6/DebugClients/Python/coverage/summary.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/summary.py	Thu Sep 17 19:10:36 2020 +0200
@@ -104,10 +104,18 @@
 
         # Sort the lines and write them out.
         if getattr(self.config, 'sort', None):
-            position = column_order.get(self.config.sort.lower())
+            sort_option = self.config.sort.lower()
+            reverse = False
+            if sort_option[0] == '-':
+                reverse = True
+                sort_option = sort_option[1:]
+            elif sort_option[0] == '+':
+                sort_option = sort_option[1:]
+
+            position = column_order.get(sort_option)
             if position is None:
                 raise CoverageException("Invalid sorting option: {!r}".format(self.config.sort))
-            lines.sort(key=lambda l: (l[1][position], l[0]))
+            lines.sort(key=lambda l: (l[1][position], l[0]), reverse=reverse)
 
         for line in lines:
             self.writeout(line[0])
--- a/eric6/DebugClients/Python/coverage/version.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/version.py	Thu Sep 17 19:10:36 2020 +0200
@@ -5,7 +5,7 @@
 # This file is exec'ed in setup.py, don't import anything!
 
 # Same semantics as sys.version_info.
-version_info = (5, 0, 3, 'final', 0)
+version_info = (5, 3, 0, "final", 0)
 
 
 def _make_version(major, minor, micro, releaselevel, serial):
--- a/eric6/DebugClients/Python/coverage/xmlreport.py	Tue Sep 15 19:09:05 2020 +0200
+++ b/eric6/DebugClients/Python/coverage/xmlreport.py	Thu Sep 17 19:10:36 2020 +0200
@@ -41,7 +41,9 @@
         if self.config.source:
             for src in self.config.source:
                 if os.path.exists(src):
-                    self.source_paths.add(files.canonical_filename(src))
+                    if not self.config.relative_files:
+                        src = files.canonical_filename(src)
+                    self.source_paths.add(src)
         self.packages = {}
         self.xml_out = None
 
@@ -140,22 +142,26 @@
     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("/", ".")
 
-        if rel_name != fr.filename:
-            self.source_paths.add(fr.filename[:-len(rel_name)].rstrip(r"\/"))
         package = self.packages.setdefault(package_name, [{}, 0, 0, 0, 0])
 
         xclass = self.xml_out.createElement("class")

eric ide

mercurial