coverage: updated coverage.py to 4.5.1.

Sat, 12 Jan 2019 11:26:32 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 12 Jan 2019 11:26:32 +0100
changeset 6649
f1b3a73831c9
parent 6648
c09e6c6006eb
child 6650
1dd52aa8897c

coverage: updated coverage.py to 4.5.1.

DebugClients/Python/coverage/cmdline.py file | annotate | diff | comparison | revisions
DebugClients/Python/coverage/control.py file | annotate | diff | comparison | revisions
DebugClients/Python/coverage/doc/AUTHORS.txt file | annotate | diff | comparison | revisions
DebugClients/Python/coverage/doc/CHANGES.rst file | annotate | diff | comparison | revisions
DebugClients/Python/coverage/doc/CONTRIBUTORS.txt file | annotate | diff | comparison | revisions
DebugClients/Python/coverage/doc/README.rst file | annotate | diff | comparison | revisions
DebugClients/Python/coverage/env.py file | annotate | diff | comparison | revisions
DebugClients/Python/coverage/execfile.py file | annotate | diff | comparison | revisions
DebugClients/Python/coverage/files.py file | annotate | diff | comparison | revisions
DebugClients/Python/coverage/parser.py file | annotate | diff | comparison | revisions
DebugClients/Python/coverage/python.py file | annotate | diff | comparison | revisions
DebugClients/Python/coverage/version.py file | annotate | diff | comparison | revisions
DebugClients/Python/coverage/xmlreport.py file | annotate | diff | comparison | revisions
changelog file | annotate | diff | comparison | revisions
--- a/DebugClients/Python/coverage/cmdline.py	Thu Jan 10 18:01:19 2019 +0100
+++ b/DebugClients/Python/coverage/cmdline.py	Sat Jan 12 11:26:32 2019 +0100
@@ -115,7 +115,10 @@
     )
     rcfile = optparse.make_option(
         '', '--rcfile', action='store',
-        help="Specify configuration file.  Defaults to '.coveragerc'",
+        help=(
+            "Specify configuration file.  "
+            "By default '.coveragerc', 'setup.cfg' and 'tox.ini' are tried."
+        ),
     )
     source = optparse.make_option(
         '', '--source', action='store', metavar="SRC1,SRC2,...",
--- a/DebugClients/Python/coverage/control.py	Thu Jan 10 18:01:19 2019 +0100
+++ b/DebugClients/Python/coverage/control.py	Sat Jan 12 11:26:32 2019 +0100
@@ -855,8 +855,7 @@
         # Find files that were never executed at all.
         for pkg in self.source_pkgs:
             if (not pkg in sys.modules or
-                not hasattr(sys.modules[pkg], '__file__') or
-                not os.path.exists(sys.modules[pkg].__file__)):
+                not module_has_file(sys.modules[pkg])):
                 continue
             pkg_file = source_for_file(sys.modules[pkg].__file__)
             self._find_unexecuted_files(self._canonical_path(pkg_file))
@@ -878,15 +877,12 @@
             self._warn("Module %s was never imported." % pkg, slug="module-not-imported")
             return
 
-        is_namespace = hasattr(mod, '__path__') and not hasattr(mod, '__file__')
-        has_file = hasattr(mod, '__file__') and os.path.exists(mod.__file__)
-
-        if is_namespace:
+        if module_is_namespace(mod):
             # A namespace package. It's OK for this not to have been traced,
             # since there is no code directly in it.
             return
 
-        if not has_file:
+        if not module_has_file(mod):
             self._warn("Module %s has no Python source." % pkg, slug="module-not-python")
             return
 
@@ -1204,6 +1200,19 @@
         return info
 
 
+def module_is_namespace(mod):
+    """Is the module object `mod` a PEP420 namespace module?"""
+    return hasattr(mod, '__path__') and getattr(mod, '__file__', None) is None
+
+
+def module_has_file(mod):
+    """Does the module object `mod` have an existing __file__ ?"""
+    mod__file__ = getattr(mod, '__file__', None)
+    if mod__file__ is None:
+        return False
+    return os.path.exists(mod__file__)
+
+
 # FileDisposition "methods": FileDisposition is a pure value object, so it can
 # be implemented in either C or Python.  Acting on them is done with these
 # functions.
--- a/DebugClients/Python/coverage/doc/AUTHORS.txt	Thu Jan 10 18:01:19 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-Coverage.py was originally written by Gareth Rees, and since 2004 has been
-extended and maintained by Ned Batchelder.
-
-Other contributions have been made by:
-
-Adi Roiban
-Alex Gaynor
-Alexander Todorov
-Anthony Sottile
-Arcadiy Ivanov
-Ben Finney
-Bill Hart
-Brandon Rhodes
-Brett Cannon
-Buck Evan
-Carl Gieringer
-Catherine Proulx
-Chris Adams
-Chris Rose
-Christian Heimes
-Christine Lytwynec
-Christoph Zwerschke
-Conrad Ho
-Danek Duvall
-Danny Allen
-David Christian
-David Stanek
-Detlev Offenbach
-Devin Jeanpierre
-Dmitry Shishov
-Dmitry Trofimov
-Eduardo Schettino
-Edward Loper
-Geoff Bache
-George Paci
-George Song
-Greg Rogers
-Guillaume Chazarain
-Ilia Meerovich
-Imri Goldberg
-Ionel Cristian Mărieș
-JT Olds
-Jessamyn Smith
-Jon Chappell
-Joseph Tate
-Julian Berman
-Krystian Kichewko
-Leonardo Pistone
-Lex Berezhny
-Marc Abramowitz
-Marcus Cobden
-Mark van der Wal
-Martin Fuzzey
-Matthew Desmarais
-Max Linke
-Mickie Betz
-Noel O'Boyle
-Pablo Carballo
-Patrick Mezard
-Peter Portante
-Rodrigue Cloutier
-Roger Hu
-Ross Lawley
-Sandra Martocchia
-Sigve Tjora
-Stan Hu
-Stefan Behnel
-Steve Leonard
-Steve Peak
-Ted Wexler
-Titus Brown
-Yury Selivanov
-Zooko Wilcox-O'Hearn
--- a/DebugClients/Python/coverage/doc/CHANGES.rst	Thu Jan 10 18:01:19 2019 +0100
+++ b/DebugClients/Python/coverage/doc/CHANGES.rst	Sat Jan 12 11:26:32 2019 +0100
@@ -16,6 +16,27 @@
     ..  ----------------------------
 
 
+.. _changes_452:
+
+Version 4.5.2 --- 2018-11-12
+----------------------------
+
+- Namespace packages are supported on Python 3.7, where they used to cause
+  TypeErrors about path being None. Fixes `issue 700`_.
+
+- Python 3.8 (as of today!) passes all tests.  Fixes `issue 707` and
+  `issue 714`_.
+
+- Development moved from `Bitbucket`_ to `GitHub`_.
+
+.. _issue 700: https://github.com/nedbat/coveragepy/issues/700
+.. _issue 707: https://github.com/nedbat/coveragepy/issues/707
+.. _issue 714: https://github.com/nedbat/coveragepy/issues/714
+
+.. _Bitbucket: https://bitbucket.org/ned/coveragepy
+.. _GitHub: https://github.com/nedbat/coveragepy
+
+
 .. _changes_451:
 
 Version 4.5.1 --- 2018-02-10
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DebugClients/Python/coverage/doc/CONTRIBUTORS.txt	Sat Jan 12 11:26:32 2019 +0100
@@ -0,0 +1,105 @@
+Coverage.py was originally written by Gareth Rees, and since 2004 has been
+extended and maintained by Ned Batchelder.
+
+Other contributions, including writing code, updating docs, and submitting
+useful bug reports, have been made by:
+
+Adi Roiban
+Alex Gaynor
+Alex Groce
+Alex Sandro
+Alexander Todorov
+Andrew Hoos
+Anthony Sottile
+Arcadiy Ivanov
+Aron Griffis
+Artem Dayneko
+Ben Finney
+Bill Hart
+Brandon Rhodes
+Brett Cannon
+Buck Evan
+Calen Pennington
+Carl Gieringer
+Catherine Proulx
+Chris Adams
+Chris Jerdonek
+Chris Rose
+Chris Warrick
+Christian Heimes
+Christine Lytwynec
+Christoph Zwerschke
+Conrad Ho
+Cosimo Lupo
+Dan Riti
+Dan Wandschneider
+Danek Duvall
+Daniel Hahler
+Danny Allen
+David Christian
+David MacIver
+David Stanek
+Detlev Offenbach
+Devin Jeanpierre
+Dirk Thomas
+Dmitry Shishov
+Dmitry Trofimov
+Eduardo Schettino
+Emil Madsen
+Edward Loper
+Geoff Bache
+George Paci
+George Song
+Greg Rogers
+Guillaume Chazarain
+Ilia Meerovich
+Imri Goldberg
+Ionel Cristian Mărieș
+JT Olds
+Jessamyn Smith
+Joe Doherty
+Jon Chappell
+Jon Dufresne
+Joseph Tate
+Josh Williams
+Julian Berman
+Krystian Kichewko
+Kyle Altendorf
+Lars Hupfeldt Nielsen
+Leonardo Pistone
+Lex Berezhny
+Loïc Dachary
+Marc Abramowitz
+Marcus Cobden
+Mark van der Wal
+Martin Fuzzey
+Matthew Boehm
+Matthew Desmarais
+Max Linke
+Mickie Betz
+Nathan Land
+Noel O'Boyle
+Olivier Grisel
+Pablo Carballo
+Patrick Mezard
+Peter Baughman
+Peter Ebden
+Peter Portante
+Rodrigue Cloutier
+Roger Hu
+Ross Lawley
+Roy Williams
+Sandra Martocchia
+Scott Belden
+Sigve Tjora
+Stan Hu
+Stefan Behnel
+Stephen Finucane
+Steve Leonard
+Steve Peak
+Ted Wexler
+Titus Brown
+Ville Skyttä
+Yury Selivanov
+Zac Hatfield-Dodds
+Zooko Wilcox-O'Hearn
--- a/DebugClients/Python/coverage/doc/README.rst	Thu Jan 10 18:01:19 2019 +0100
+++ b/DebugClients/Python/coverage/doc/README.rst	Sat Jan 12 11:26:32 2019 +0100
@@ -9,7 +9,8 @@
 
 |  |license| |versions| |status| |docs|
 |  |ci-status| |win-ci-status| |codecov|
-|  |kit| |format| |saythanks|
+|  |kit| |format| |repos|
+|  |tidelift| |saythanks|
 
 .. downloads badge seems to be broken... |downloads|
 
@@ -17,9 +18,25 @@
 the code analysis tools and tracing hooks provided in the Python standard
 library to determine which lines are executable, and which have been executed.
 
+.. |tideliftlogo| image:: doc/media/Tidelift_Logos_RGB_Tidelift_Shorthand_On-White_small.png
+   :width: 75
+   :alt: Tidelift
+
+.. list-table::
+   :widths: 10 100
+
+   * - |tideliftlogo|
+     - Professional support for coverage.py is available as part of the `Tidelift
+       Subscription`_.  Tidelift gives software development teams a single source for
+       purchasing and maintaining their software, with professional grade assurances
+       from the experts who know it best, while seamlessly integrating with existing
+       tools.
+
+.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=readme
+
 Coverage.py runs on many versions of Python:
 
-* CPython 2.6, 2.7 and 3.3 through 3.7.
+* CPython 2.6, 2.7 and 3.3 through pre-alpha 3.8.
 * PyPy2 5.10 and PyPy3 5.10.
 * Jython 2.7.1, though not for reporting.
 * IronPython 2.7.7, though not for reporting.
@@ -105,6 +122,12 @@
 .. |codecov| image:: http://codecov.io/github/nedbat/coveragepy/coverage.svg?branch=master&precision=2
     :target: http://codecov.io/github/nedbat/coveragepy?branch=master
     :alt: Coverage!
+.. |repos| image:: https://repology.org/badge/tiny-repos/python:coverage.svg
+    :target: https://repology.org/metapackage/python:coverage/versions
+    :alt: Packaging status
 .. |saythanks| image:: https://img.shields.io/badge/saythanks.io-%E2%98%BC-1EAEDB.svg
     :target: https://saythanks.io/to/nedbat
     :alt: Say thanks :)
+.. |tidelift| image:: https://tidelift.com/badges/github/nedbat/coveragepy
+    :target: https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=readme
+    :alt: Tidelift
--- a/DebugClients/Python/coverage/env.py	Thu Jan 10 18:01:19 2019 +0100
+++ b/DebugClients/Python/coverage/env.py	Sat Jan 12 11:26:32 2019 +0100
@@ -24,6 +24,24 @@
 PY2 = PYVERSION < (3, 0)
 PY3 = PYVERSION >= (3, 0)
 
+# Python behavior
+class PYBEHAVIOR(object):
+    """Flags indicating this Python's behavior."""
+
+    # When a break/continue/return statement in a try block jumps to a finally
+    # block, does the finally block do the break/continue/return (pre-3.8), or
+    # does the finally jump back to the break/continue/return (3.8) to do the
+    # work?
+    finally_jumps_back = (PYVERSION >= (3, 8))
+
+    # When a function is decorated, does the trace function get called for the
+    # @-line and also the def-line (new behavior in 3.8)? Or just the @-line
+    # (old behavior)?
+    trace_decorated_def = (PYVERSION >= (3, 8))
+
+    # Are while-true loops optimized into absolute jumps with no loop setup?
+    nix_while_true = (PYVERSION >= (3, 8))
+
 # Coverage.py specifics.
 
 # Are we using the C-implemented trace function?
--- a/DebugClients/Python/coverage/execfile.py	Thu Jan 10 18:01:19 2019 +0100
+++ b/DebugClients/Python/coverage/execfile.py	Sat Jan 12 11:26:32 2019 +0100
@@ -111,7 +111,15 @@
 
     pathname = os.path.abspath(pathname)
     args[0] = pathname
-    run_python_file(pathname, args, package=packagename, modulename=modulename, path0="")
+    # Python 3.7.0b3 changed the behavior of the sys.path[0] entry for -m. It
+    # used to be an empty string (meaning the current directory). It changed
+    # to be the actual path to the current directory, so that os.chdir wouldn't
+    # affect the outcome.
+    if sys.version_info >= (3, 7, 0, 'beta', 3):
+        path0 = os.getcwd()
+    else:
+        path0 = ""
+    run_python_file(pathname, args, package=packagename, modulename=modulename, path0=path0)
 
 
 def run_python_file(filename, args, package=None, modulename=None, path0=None):
--- a/DebugClients/Python/coverage/files.py	Thu Jan 10 18:01:19 2019 +0100
+++ b/DebugClients/Python/coverage/files.py	Sat Jan 12 11:26:32 2019 +0100
@@ -260,19 +260,8 @@
 class FnmatchMatcher(object):
     """A matcher for files by file name pattern."""
     def __init__(self, pats):
-        self.pats = pats[:]
-        # fnmatch is platform-specific. On Windows, it does the Windows thing
-        # of treating / and \ as equivalent. But on other platforms, we need to
-        # take care of that ourselves.
-        fnpats = (fnmatch.translate(p) for p in pats)
-        # Python3.7 fnmatch translates "/" as "/", before that, it translates as "\/",
-        # so we have to deal with maybe a backslash.
-        fnpats = (re.sub(r"\\?/", r"[\\\\/]", p) for p in fnpats)
-        flags = 0
-        if env.WINDOWS:
-            # Windows is also case-insensitive, so make the regex case-insensitive.
-            flags |= re.IGNORECASE
-        self.re = re.compile(join_regex(fnpats), flags=flags)
+        self.pats = list(pats)
+        self.re = fnmatches_to_regex(self.pats, case_insensitive=env.WINDOWS)
 
     def __repr__(self):
         return "<FnmatchMatcher %r>" % self.pats
@@ -296,6 +285,39 @@
     return the_sep
 
 
+def fnmatches_to_regex(patterns, case_insensitive=False, partial=False):
+    """Convert fnmatch patterns to a compiled regex that matches any of them.
+
+    Slashes are always converted to match either slash or backslash, for
+    Windows support, even when running elsewhere.
+
+    If `partial` is true, then the pattern will match if the target string
+    starts with the pattern. Otherwise, it must match the entire string.
+
+    Returns: a compiled regex object.  Use the .match method to compare target
+    strings.
+
+    """
+    regexes = (fnmatch.translate(pattern) for pattern in patterns)
+    # Python3.7 fnmatch translates "/" as "/". Before that, it translates as "\/",
+    # so we have to deal with maybe a backslash.
+    regexes = (re.sub(r"\\?/", r"[\\\\/]", regex) for regex in regexes)
+
+    if partial:
+        # fnmatch always adds a \Z to match the whole string, which we don't
+        # want, so we remove the \Z.  While removing it, we only replace \Z if
+        # followed by paren (introducing flags), or at end, to keep from
+        # destroying a literal \Z in the pattern.
+        regexes = (re.sub(r'\\Z(\(\?|$)', r'\1', regex) for regex in regexes)
+
+    flags = 0
+    if case_insensitive:
+        flags |= re.IGNORECASE
+    compiled = re.compile(join_regex(regexes), flags=flags)
+
+    return compiled
+
+
 class PathAliases(object):
     """A collection of aliases for paths.
 
@@ -343,18 +365,8 @@
         if not pattern.endswith(pattern_sep):
             pattern += pattern_sep
 
-        # Make a regex from the pattern.  fnmatch always adds a \Z to
-        # match the whole string, which we don't want, so we remove the \Z.
-        # While removing it, we only replace \Z if followed by paren, or at
-        # end, to keep from destroying a literal \Z in the pattern.
-        regex_pat = fnmatch.translate(pattern)
-        regex_pat = re.sub(r'\\Z(\(|$)', r'\1', regex_pat)
-
-        # We want */a/b.py to match on Windows too, so change slash to match
-        # either separator.
-        regex_pat = regex_pat.replace(r"\/", r"[\\/]")
-        # We want case-insensitive matching, so add that flag.
-        regex = re.compile(r"(?i)" + regex_pat)
+        # Make a regex from the pattern.
+        regex = fnmatches_to_regex([pattern], case_insensitive=True, partial=True)
 
         # Normalize the result: it must end with a path separator.
         result_sep = sep(result)
--- a/DebugClients/Python/coverage/parser.py	Thu Jan 10 18:01:19 2019 +0100
+++ b/DebugClients/Python/coverage/parser.py	Sat Jan 12 11:26:32 2019 +0100
@@ -409,6 +409,8 @@
                     yield (byte_num, line_num)
                     last_line_num = line_num
                 byte_num += byte_incr
+            if env.PYVERSION >= (3, 6) and line_incr >= 0x80:
+                line_incr -= 0x100
             line_num += line_incr
         if line_num != last_line_num:
             yield (byte_num, line_num)
@@ -503,6 +505,10 @@
         self.lineno = body[0].lineno
 
 
+# TODO: some add_arcs methods here don't add arcs, they return them. Rename them.
+# TODO: the cause messages have too many commas.
+# TODO: Shouldn't the cause messages join with "and" instead of "or"?
+
 class AstArcAnalyzer(object):
     """Analyze source text with an AST to find executable code paths."""
 
@@ -544,6 +550,7 @@
             if code_object_handler is not None:
                 code_object_handler(node)
 
+    @contract(start=int, end=int)
     def add_arc(self, start, end, smsg=None, emsg=None):
         """Add an arc, including message fragments to use if it is missing."""
         if self.debug:                      # pragma: debugging
@@ -572,9 +579,19 @@
         else:
             return node.lineno
 
+    def _line_decorated(self, node):
+        """Compute first line number for things that can be decorated (classes and functions)."""
+        lineno = node.lineno
+        if env.PYBEHAVIOR.trace_decorated_def:
+            if node.decorator_list:
+                lineno = node.decorator_list[0].lineno
+        return lineno
+
     def _line__Assign(self, node):
         return self.line_for_node(node.value)
 
+    _line__ClassDef = _line_decorated
+
     def _line__Dict(self, node):
         # Python 3.5 changed how dict literals are made.
         if env.PYVERSION >= (3, 5) and node.keys:
@@ -587,6 +604,8 @@
         else:
             return node.lineno
 
+    _line__FunctionDef = _line_decorated
+
     def _line__List(self, node):
         if node.elts:
             return self.line_for_node(node.elts[0])
@@ -690,6 +709,13 @@
             node = None
         return node
 
+    # Missing nodes: _missing__*
+    #
+    # Entire statements can be optimized away by Python. They will appear in
+    # the AST, but not the bytecode.  These functions are called (by
+    # find_non_missing_node) to find a node to use instead of the missing
+    # node.  They can return None if the node should truly be gone.
+
     def _missing__If(self, node):
         # If the if-node is missing, then one of its children might still be
         # here, but not both. So return the first of the two that isn't missing.
@@ -717,10 +743,24 @@
             return non_missing_children[0]
         return NodeList(non_missing_children)
 
+    def _missing__While(self, node):
+        body_nodes = self.find_non_missing_node(NodeList(node.body))
+        if not body_nodes:
+            return None
+        # Make a synthetic While-true node.
+        new_while = ast.While()
+        new_while.lineno = body_nodes.lineno
+        new_while.test = ast.Name()
+        new_while.test.lineno = body_nodes.lineno
+        new_while.test.id = "True"
+        new_while.body = body_nodes.body
+        new_while.orelse = None
+        return new_while
+
     def is_constant_expr(self, node):
         """Is this a compile-time constant?"""
         node_name = node.__class__.__name__
-        if node_name in ["NameConstant", "Num"]:
+        if node_name in ["Constant", "NameConstant", "Num"]:
             return "Num"
         elif node_name == "Name":
             if node.id in ["True", "False", "None", "__debug__"]:
@@ -805,10 +845,10 @@
     # Handlers: _handle__*
     #
     # Each handler deals with a specific AST node type, dispatched from
-    # add_arcs.  Each deals with a particular kind of node type, and returns
-    # the set of exits from that node. These functions mirror the Python
-    # semantics of each syntactic construct.  See the docstring for add_arcs to
-    # understand the concept of exits from a node.
+    # add_arcs.  Handlers return the set of exits from that node, and can
+    # also call self.add_arc to record arcs they find.  These functions mirror
+    # the Python semantics of each syntactic construct.  See the docstring
+    # for add_arcs to understand the concept of exits from a node.
 
     @contract(returns='ArcStarts')
     def _handle__Break(self, node):
@@ -820,13 +860,18 @@
     @contract(returns='ArcStarts')
     def _handle_decorated(self, node):
         """Add arcs for things that can be decorated (classes and functions)."""
-        last = self.line_for_node(node)
+        main_line = last = node.lineno
         if node.decorator_list:
+            if env.PYBEHAVIOR.trace_decorated_def:
+                last = None
             for dec_node in node.decorator_list:
                 dec_start = self.line_for_node(dec_node)
-                if dec_start != last:
+                if last is not None and dec_start != last:
                     self.add_arc(last, dec_start)
-                    last = dec_start
+                last = dec_start
+            if env.PYBEHAVIOR.trace_decorated_def:
+                self.add_arc(last, main_line)
+                last = main_line
             # The definition line may have been missed, but we should have it
             # in `self.statements`.  For some constructs, `line_for_node` is
             # not what we'd think of as the first line in the statement, so map
@@ -968,21 +1013,45 @@
             final_exits = self.add_body_arcs(node.finalbody, prev_starts=final_from)
 
             if try_block.break_from:
-                self.process_break_exits(
-                    self._combine_finally_starts(try_block.break_from, final_exits)
-                )
+                if env.PYBEHAVIOR.finally_jumps_back:
+                    for break_line in try_block.break_from:
+                        lineno = break_line.lineno
+                        cause = break_line.cause.format(lineno=lineno)
+                        for final_exit in final_exits:
+                            self.add_arc(final_exit.lineno, lineno, cause)
+                    breaks = try_block.break_from
+                else:
+                    breaks = self._combine_finally_starts(try_block.break_from, final_exits)
+                self.process_break_exits(breaks)
+
             if try_block.continue_from:
-                self.process_continue_exits(
-                    self._combine_finally_starts(try_block.continue_from, final_exits)
-                )
+                if env.PYBEHAVIOR.finally_jumps_back:
+                    for continue_line in try_block.continue_from:
+                        lineno = continue_line.lineno
+                        cause = continue_line.cause.format(lineno=lineno)
+                        for final_exit in final_exits:
+                            self.add_arc(final_exit.lineno, lineno, cause)
+                    continues = try_block.continue_from
+                else:
+                    continues = self._combine_finally_starts(try_block.continue_from, final_exits)
+                self.process_continue_exits(continues)
+
             if try_block.raise_from:
                 self.process_raise_exits(
                     self._combine_finally_starts(try_block.raise_from, final_exits)
                 )
+
             if try_block.return_from:
-                self.process_return_exits(
-                    self._combine_finally_starts(try_block.return_from, final_exits)
-                )
+                if env.PYBEHAVIOR.finally_jumps_back:
+                    for return_line in try_block.return_from:
+                        lineno = return_line.lineno
+                        cause = return_line.cause.format(lineno=lineno)
+                        for final_exit in final_exits:
+                            self.add_arc(final_exit.lineno, lineno, cause)
+                    returns = try_block.return_from
+                else:
+                    returns = self._combine_finally_starts(try_block.return_from, final_exits)
+                self.process_return_exits(returns)
 
             if exits:
                 # The finally clause's exits are only exits for the try block
--- a/DebugClients/Python/coverage/python.py	Thu Jan 10 18:01:19 2019 +0100
+++ b/DebugClients/Python/coverage/python.py	Sat Jan 12 11:26:32 2019 +0100
@@ -135,7 +135,7 @@
     def __init__(self, morf, coverage=None):
         self.coverage = coverage
 
-        if hasattr(morf, '__file__'):
+        if hasattr(morf, '__file__') and morf.__file__:
             filename = morf.__file__
         elif isinstance(morf, types.ModuleType):
             # A module should have had .__file__, otherwise we can't use it.
--- a/DebugClients/Python/coverage/version.py	Thu Jan 10 18:01:19 2019 +0100
+++ b/DebugClients/Python/coverage/version.py	Sat Jan 12 11:26:32 2019 +0100
@@ -5,7 +5,7 @@
 # This file is exec'ed in setup.py, don't import anything!
 
 # Same semantics as sys.version_info.
-version_info = (4, 5, 1, 'final', 0)
+version_info = (4, 5, 2, 'final', 0)
 
 
 def _make_version(major, minor, micro, releaselevel, serial):
--- a/DebugClients/Python/coverage/xmlreport.py	Thu Jan 10 18:01:19 2019 +0100
+++ b/DebugClients/Python/coverage/xmlreport.py	Sat Jan 12 11:26:32 2019 +0100
@@ -1,3 +1,4 @@
+# coding: utf-8
 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
 
@@ -5,6 +6,7 @@
 
 import os
 import os.path
+import re
 import sys
 import time
 import xml.dom.minidom
@@ -123,11 +125,8 @@
             xcoverage.setAttribute("branch-rate", "0")
         xcoverage.setAttribute("complexity", "0")
 
-        # Use the DOM to write the output file.
-        out = self.xml_out.toprettyxml()
-        if env.PY2:
-            out = out.encode("utf8")
-        outfile.write(out)
+        # Write the output file.
+        outfile.write(serialize_xml(self.xml_out))
 
         # Return the total percentage.
         denom = lnum_tot + bnum_tot
@@ -218,3 +217,23 @@
         package[2] += class_lines
         package[3] += class_br_hits
         package[4] += class_branches
+
+
+def serialize_xml(dom):
+    """Serialize a minidom node to XML."""
+    out = dom.toprettyxml()
+    if env.PY2:
+        out = out.encode("utf8")
+    # In Python 3.8, minidom lost the sorting of attributes: https://bugs.python.org/issue34160
+    # For the limited kinds of XML we produce, this re-sorts them.
+    if env.PYVERSION >= (3, 8):
+        rx_attr = r' [\w-]+="[^"]*"'
+        rx_attrs = r'(' + rx_attr + ')+'
+        fixed_lines = []
+        for line in out.splitlines(True):
+            hollow_line = re.sub(rx_attrs, u"☺", line)
+            attrs = sorted(re.findall(rx_attr, line))
+            new_line = hollow_line.replace(u"☺", "".join(attrs))
+            fixed_lines.append(new_line)
+        out = "".join(fixed_lines)
+    return out
--- a/changelog	Thu Jan 10 18:01:19 2019 +0100
+++ b/changelog	Sat Jan 12 11:26:32 2019 +0100
@@ -4,6 +4,8 @@
 - bug fixes
 - Editor
   -- added cpability to suppress some markers in the marker map
+- Third Party packages
+  -- updated coverage.py to 4.5.2
 
 Version 19.01:
 - bug fixes

eric ide

mercurial