Control: tags 1024148 + patch
Control: tags 1024148 + pending

Dear maintainer,

I've prepared an NMU for python-coverage (versioned as 6.2+dfsg1-2.1) and
uploaded it to DELAYED/5. Please feel free to tell me if I
should delay it longer.

BTW, it looks like 3.11 support is fully available from 6.4.2 onwards.

Regards.

SR
diff -Nru python-coverage-6.2+dfsg1/debian/changelog python-coverage-6.2+dfsg1/debian/changelog
--- python-coverage-6.2+dfsg1/debian/changelog	2022-01-14 01:43:38.000000000 +0200
+++ python-coverage-6.2+dfsg1/debian/changelog	2022-11-16 22:20:27.000000000 +0200
@@ -1,3 +1,10 @@
+python-coverage (6.2+dfsg1-2.1) UNRELEASED; urgency=medium
+
+  * Non-maintainer upload.
+  * Patch: Python 3.11 support. Closes: #1024148.
+
+ -- Stefano Rivera <stefa...@debian.org>  Wed, 16 Nov 2022 22:20:27 +0200
+
 python-coverage (6.2+dfsg1-2) unstable; urgency=medium
 
   * Correct autopkgtest control, for removal of ‘pypy-coverage’.
diff -Nru python-coverage-6.2+dfsg1/debian/patches/04.python3.11.patch python-coverage-6.2+dfsg1/debian/patches/04.python3.11.patch
--- python-coverage-6.2+dfsg1/debian/patches/04.python3.11.patch	1970-01-01 02:00:00.000000000 +0200
+++ python-coverage-6.2+dfsg1/debian/patches/04.python3.11.patch	2022-11-16 22:11:13.000000000 +0200
@@ -0,0 +1,145 @@
+From d723b46460dc7ffb4abf54806087ffd614b81331 Mon Sep 17 00:00:00 2001
+From: Ned Batchelder <n...@nedbatchelder.com>
+Date: Sun, 9 Jan 2022 11:37:29 -0500
+Subject: [PATCH] fix: 3.11 now traces decorator lines as the decorators
+ execute
+
+See: https://bugs.python.org/issue46234
+---
+ coverage/env.py      |  4 ++++
+ coverage/parser.py   | 10 ++++++++--
+ tests/test_arcs.py   | 30 ++++++++++++++++++++++--------
+ tests/test_parser.py |  4 ++++
+ 4 files changed, 38 insertions(+), 10 deletions(-)
+
+--- a/coverage/env.py
++++ b/coverage/env.py
+@@ -112,6 +112,10 @@
+     # Some words are keywords in some places, identifiers in other places.
+     soft_keywords = (PYVERSION >= (3, 10))
+ 
++    # CPython 3.11 now jumps to the decorator line again while executing
++    # the decorator.
++    trace_decorator_line_again = (PYVERSION > (3, 11, 0, 'alpha', 3, 0))
++
+ 
+ # Coverage.py specifics.
+ 
+--- a/coverage/parser.py
++++ b/coverage/parser.py
+@@ -944,10 +944,11 @@
+     def _handle_decorated(self, node):
+         """Add arcs for things that can be decorated (classes and functions)."""
+         main_line = last = node.lineno
+-        if node.decorator_list:
++        decs = node.decorator_list
++        if decs:
+             if env.PYBEHAVIOR.trace_decorated_def:
+                 last = None
+-            for dec_node in node.decorator_list:
++            for dec_node in decs:
+                 dec_start = self.line_for_node(dec_node)
+                 if last is not None and dec_start != last:
+                     self.add_arc(last, dec_start)
+@@ -955,6 +956,11 @@
+             if env.PYBEHAVIOR.trace_decorated_def:
+                 self.add_arc(last, main_line)
+                 last = main_line
++            if env.PYBEHAVIOR.trace_decorator_line_again:
++                for top, bot in zip(decs, decs[1:]):
++                    self.add_arc(self.line_for_node(bot), self.line_for_node(top))
++                self.add_arc(self.line_for_node(decs[0]), main_line)
++                self.add_arc(main_line, self.line_for_node(decs[-1]))
+             # 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
+--- a/tests/test_arcs.py
++++ b/tests/test_arcs.py
+@@ -1667,6 +1667,13 @@
+     """Tests of arcs with decorators."""
+ 
+     def test_function_decorator(self):
++        arcz = (
++            ".1 16 67 7A AE EF F. "     # main line
++            ".2 24 4.   -23 3-2 "       # decorators
++            "-6D D-6 "                  # my_function
++        )
++        if env.PYBEHAVIOR.trace_decorator_line_again:
++            arcz += "A7 76 6A "
+         self.check_coverage("""\
+             def decorator(arg):
+                 def _dec(f):
+@@ -1684,13 +1691,17 @@
+             a = 14
+             my_function()
+             """,
+-            arcz=
+-                ".1 16 67 7A AE EF F. "     # main line
+-                ".2 24 4.   -23 3-2 "       # decorators
+-                "-6D D-6 ",                 # my_function
++            arcz=arcz,
+         )
+ 
+     def test_class_decorator(self):
++        arcz = (
++            ".1 16 67 6D 7A AE E. "     # main line
++            ".2 24 4.   -23 3-2 "       # decorators
++            "-66 D-6 "                  # MyObject
++        )
++        if env.PYBEHAVIOR.trace_decorator_line_again:
++            arcz += "A7 76 6A "
+         self.check_coverage("""\
+             def decorator(arg):
+                 def _dec(c):
+@@ -1707,10 +1718,7 @@
+                 X = 13
+             a = 14
+             """,
+-            arcz=
+-                ".1 16 67 6D 7A AE E. "     # main line
+-                ".2 24 4.   -23 3-2 "       # decorators
+-                "-66 D-6 ",                 # MyObject
++            arcz=arcz,
+         )
+ 
+     def test_bug_466(self):
+@@ -1720,6 +1728,8 @@
+             arcz = ".1 1A A.  13 34 4.  -35 58 8-3"
+         else:
+             arcz = ".1 1A A.  13 3.     -35 58 8-3"
++        if env.PYBEHAVIOR.trace_decorator_line_again:
++            arcz += "43 "
+         self.check_coverage("""\
+             class Parser(object):
+ 
+@@ -1738,6 +1748,8 @@
+             arcz = ".1 1A A.  13 34 4.  -35 58 8-3"
+         else:
+             arcz = ".1 1A A.  13 3.     -35 58 8-3"
++        if env.PYBEHAVIOR.trace_decorator_line_again:
++            arcz += "43 "
+         self.check_coverage("""\
+             class Parser(object):
+ 
+@@ -1927,6 +1939,8 @@
+             arcz = ".1 14 45 5.  .2 2.  -46 6-4"
+         else:
+             arcz = ".1 14 4.     .2 2.  -46 6-4"
++        if env.PYBEHAVIOR.trace_decorator_line_again:
++            arcz += "54 "
+         self.check_coverage("""\
+             def wrap(f):        # 1
+                 return f
+--- a/tests/test_parser.py
++++ b/tests/test_parser.py
+@@ -240,6 +240,10 @@
+             expected_arcs.update(set(arcz_to_arcs("-46 6-4")))
+             expected_exits.update({6: 1})
+ 
++        if env.PYBEHAVIOR.trace_decorator_line_again:
++            expected_arcs.update(set(arcz_to_arcs("54 98")))
++            expected_exits.update({9: 2, 5: 2})
++
+         assert expected_statements == parser.statements
+         assert expected_arcs == parser.arcs()
+         assert expected_exits == parser.exit_counts()
diff -Nru python-coverage-6.2+dfsg1/debian/patches/05.python3.11.patch python-coverage-6.2+dfsg1/debian/patches/05.python3.11.patch
--- python-coverage-6.2+dfsg1/debian/patches/05.python3.11.patch	1970-01-01 02:00:00.000000000 +0200
+++ python-coverage-6.2+dfsg1/debian/patches/05.python3.11.patch	2022-11-16 22:13:48.000000000 +0200
@@ -0,0 +1,74 @@
+From e3be855a07685b383f020e2f0f2924326cb48268 Mon Sep 17 00:00:00 2001
+From: Ned Batchelder <n...@nedbatchelder.com>
+Date: Sat, 15 Jan 2022 11:17:30 -0500
+Subject: [PATCH] fix: update pytracer.py for Python 3.11
+
+---
+ coverage/pytracer.py | 30 +++++++++++++++++++++++++-----
+ 1 file changed, 25 insertions(+), 5 deletions(-)
+
+diff --git a/coverage/pytracer.py b/coverage/pytracer.py
+index 0a362591..94712b24 100644
+--- a/coverage/pytracer.py
++++ b/coverage/pytracer.py
+@@ -10,14 +10,18 @@ import sys
+ from coverage import env
+ 
+ # We need the YIELD_VALUE opcode below, in a comparison-friendly form.
+-YIELD_VALUE = dis.opmap['YIELD_VALUE']
++RESUME = dis.opmap.get('RESUME')
++RETURN_VALUE = dis.opmap['RETURN_VALUE']
++if RESUME is None:
++    YIELD_VALUE = dis.opmap['YIELD_VALUE']
++    YIELD_FROM = dis.opmap['YIELD_FROM']
++    YIELD_FROM_OFFSET = 0 if env.PYPY else 2
+ 
+ # When running meta-coverage, this file can try to trace itself, which confuses
+ # everything.  Don't trace ourselves.
+ 
+ THIS_FILE = __file__.rstrip("co")
+ 
+-
+ class PyTracer:
+     """Python implementation of the raw data tracer."""
+ 
+@@ -160,7 +164,14 @@ class PyTracer:
+             # function calls and re-entering generators.  The f_lasti field is
+             # -1 for calls, and a real offset for generators.  Use <0 as the
+             # line number for calls, and the real line number for generators.
+-            if getattr(frame, 'f_lasti', -1) < 0:
++            if RESUME is not None:
++                # The current opcode is guaranteed to be RESUME. The argument
++                # determines what kind of resume it is.
++                oparg = frame.f_code.co_code[frame.f_lasti + 1]
++                true_call = (oparg == 0)
++            else:
++                true_call = (getattr(frame, 'f_lasti', -1) < 0)
++            if true_call:
+                 self.last_line = -frame.f_code.co_firstlineno
+             else:
+                 self.last_line = frame.f_lineno
+@@ -178,9 +189,18 @@ class PyTracer:
+             if self.trace_arcs and self.cur_file_data:
+                 # Record an arc leaving the function, but beware that a
+                 # "return" event might just mean yielding from a generator.
+-                # Jython seems to have an empty co_code, so just assume return.
+                 code = frame.f_code.co_code
+-                if (not code) or code[frame.f_lasti] != YIELD_VALUE:
++                lasti = frame.f_lasti
++                if RESUME is not None:
++                    if len(code) == lasti + 2:
++                        # A return from the end of a code object is a real return.
++                        true_return = True
++                    else:
++                        # it's a real return.
++                        true_return = (code[lasti + 2] != RESUME)
++                else:
++                    true_return = not ( (code[lasti] == YIELD_VALUE) or ((len(code) > lasti + YIELD_FROM_OFFSET) and code[lasti + YIELD_FROM_OFFSET] == YIELD_FROM) )
++                if true_return:
+                     first = frame.f_code.co_firstlineno
+                     self.cur_file_data.add((self.last_line, -first))
+             # Leaving this function, pop the filename stack.
+-- 
+2.35.1
+
diff -Nru python-coverage-6.2+dfsg1/debian/patches/07.python3.11.patch python-coverage-6.2+dfsg1/debian/patches/07.python3.11.patch
--- python-coverage-6.2+dfsg1/debian/patches/07.python3.11.patch	1970-01-01 02:00:00.000000000 +0200
+++ python-coverage-6.2+dfsg1/debian/patches/07.python3.11.patch	2022-11-16 22:15:18.000000000 +0200
@@ -0,0 +1,148 @@
+From 37ef7c7d8625ee7f364774110e3c467e82444d9b Mon Sep 17 00:00:00 2001
+From: Ned Batchelder <n...@nedbatchelder.com>
+Date: Sat, 15 Jan 2022 13:58:53 -0500
+Subject: [PATCH] fix: proper tracing of call/return for Python 3.11.0a4
+
+Version 3.11.0a4 introduced RESUME, so returns and calls are different now.
+This change also fixes some mishandling of yield-from in previous releases.
+---
+ CHANGES.rst               |  4 +++-
+ coverage/ctracer/tracer.c | 49 ++++++++++++++++++++++++++++++++-------
+ coverage/pytracer.py      | 23 ++++++++++++------
+ tests/test_arcs.py        |  2 --
+ 4 files changed, 59 insertions(+), 19 deletions(-)
+
+--- a/coverage/ctracer/tracer.c
++++ b/coverage/ctracer/tracer.c
+@@ -520,10 +520,24 @@
+     Py_XSETREF(frame->f_trace, (PyObject*)self);
+ 
+     /* A call event is really a "start frame" event, and can happen for
+-     * re-entering a generator also.  f_lasti is -1 for a true call, and a
+-     * real byte offset for a generator re-entry.
++     * re-entering a generator also.  How we tell the difference depends on
++     * the version of Python.
+      */
+-    if (MyFrame_lasti(frame) < 0) {
++    BOOL real_call = FALSE;
++
++#ifdef RESUME   // 3.11.0a4
++    /*
++     * The current opcode is guaranteed to be RESUME. The argument
++     * determines what kind of resume it is.
++     */
++    PyObject * pCode = MyFrame_GetCode(frame)->co_code;
++    real_call = (PyBytes_AS_STRING(pCode)[MyFrame_lasti(frame) + 1] == 0);
++#else
++    // f_lasti is -1 for a true call, and a real byte offset for a generator re-entry.
++    real_call = (MyFrame_lasti(frame) < 0);
++#endif
++
++    if (real_call) {
+         self->pcur_entry->last_line = -MyFrame_GetCode(frame)->co_firstlineno;
+     }
+     else {
+@@ -683,19 +697,36 @@
+ 
+     if (self->pdata_stack->depth >= 0) {
+         if (self->tracing_arcs && self->pcur_entry->file_data) {
++            BOOL real_return = FALSE;
++            PyObject * pCode = MyFrame_GetCode(frame)->co_code;
++            int lasti = MyFrame_lasti(frame);
++            Py_ssize_t code_size = PyBytes_GET_SIZE(pCode);
++            unsigned char * code_bytes = (unsigned char *)PyBytes_AS_STRING(pCode);
++#ifdef RESUME
++            if (lasti == code_size - 2) {
++                real_return = TRUE;
++            }
++            else {
++                real_return = (code_bytes[lasti + 2] != RESUME);
++            }
++#else
+             /* Need to distinguish between RETURN_VALUE and YIELD_VALUE. Read
+              * the current bytecode to see what it is.  In unusual circumstances
+              * (Cython code), co_code can be the empty string, so range-check
+              * f_lasti before reading the byte.
+              */
+-            int bytecode = RETURN_VALUE;
+-            PyObject * pCode = MyFrame_GetCode(frame)->co_code;
+-            int lasti = MyFrame_lasti(frame);
++            BOOL is_yield = FALSE;
++            BOOL is_yield_from = FALSE;
+ 
+-            if (lasti < PyBytes_GET_SIZE(pCode)) {
+-                bytecode = PyBytes_AS_STRING(pCode)[lasti];
++            if (lasti < code_size) {
++                is_yield = (code_bytes[lasti] == YIELD_VALUE);
++                if (lasti + 2 < code_size) {
++                    is_yield_from = (code_bytes[lasti + 2] == YIELD_FROM);
++                }
+             }
+-            if (bytecode != YIELD_VALUE) {
++            real_return = !(is_yield || is_yield_from);
++#endif
++            if (real_return) {
+                 int first = MyFrame_GetCode(frame)->co_firstlineno;
+                 if (CTracer_record_pair(self, self->pcur_entry->last_line, -first) < 0) {
+                     goto error;
+--- a/coverage/pytracer.py
++++ b/coverage/pytracer.py
+@@ -168,10 +168,10 @@
+                 # The current opcode is guaranteed to be RESUME. The argument
+                 # determines what kind of resume it is.
+                 oparg = frame.f_code.co_code[frame.f_lasti + 1]
+-                true_call = (oparg == 0)
++                real_call = (oparg == 0)
+             else:
+-                true_call = (getattr(frame, 'f_lasti', -1) < 0)
+-            if true_call:
++                real_call = (getattr(frame, 'f_lasti', -1) < 0)
++            if real_call:
+                 self.last_line = -frame.f_code.co_firstlineno
+             else:
+                 self.last_line = frame.f_lineno
+@@ -194,13 +194,22 @@
+                 if RESUME is not None:
+                     if len(code) == lasti + 2:
+                         # A return from the end of a code object is a real return.
+-                        true_return = True
++                        real_return = True
+                     else:
+                         # it's a real return.
+-                        true_return = (code[lasti + 2] != RESUME)
++                        real_return = (code[lasti + 2] != RESUME)
+                 else:
+-                    true_return = not ( (code[lasti] == YIELD_VALUE) or ((len(code) > lasti + YIELD_FROM_OFFSET) and code[lasti + YIELD_FROM_OFFSET] == YIELD_FROM) )
+-                if true_return:
++                    if code[lasti] == RETURN_VALUE:
++                        real_return = True
++                    elif code[lasti] == YIELD_VALUE:
++                        real_return = False
++                    elif len(code) <= lasti + YIELD_FROM_OFFSET:
++                        real_return = True
++                    elif code[lasti + YIELD_FROM_OFFSET] == YIELD_FROM:
++                        real_return = False
++                    else:
++                        real_return = True
++                if real_return:
+                     first = frame.f_code.co_firstlineno
+                     self.cur_file_data.add((self.last_line, -first))
+             # Leaving this function, pop the filename stack.
+--- a/tests/test_arcs.py
++++ b/tests/test_arcs.py
+@@ -1304,7 +1304,6 @@
+             list(gen([1,2,3]))
+             """,
+             arcz=".1 19 9.  .2 23 34 45 56 63 37 7.",
+-            arcz_unpredicted="5.",
+         )
+ 
+     def test_abandoned_yield(self):
+@@ -1876,7 +1875,6 @@
+                 ".1 13 38 8E EF FG G. " +
+                 "-34 45 56 6-3 " +
+                 "-89 9C C-8",
+-            arcz_unpredicted="5-3 9-8",
+         )
+         assert self.stdout() == "Compute 1 + 2 ...\n1 + 2 = 3\n"
+ 
diff -Nru python-coverage-6.2+dfsg1/debian/patches/08.python3.11.patch python-coverage-6.2+dfsg1/debian/patches/08.python3.11.patch
--- python-coverage-6.2+dfsg1/debian/patches/08.python3.11.patch	1970-01-01 02:00:00.000000000 +0200
+++ python-coverage-6.2+dfsg1/debian/patches/08.python3.11.patch	2022-11-16 22:17:13.000000000 +0200
@@ -0,0 +1,124 @@
+From 956f0fde6178100925e79d5d894e531e2a73ec2f Mon Sep 17 00:00:00 2001
+From: Ned Batchelder <n...@nedbatchelder.com>
+Date: Mon, 2 May 2022 06:44:32 -0400
+Subject: [PATCH] fix: fix compilation errors on latest 3.11.0
+
+---
+ .github/workflows/coverage.yml |  2 +-
+ .github/workflows/kit.yml      |  2 +-
+ CHANGES.rst                    |  5 ++++-
+ README.rst                     |  2 +-
+ coverage/ctracer/tracer.c      | 21 +++++++++++++++------
+ coverage/ctracer/util.h        | 18 +++++++++++++++---
+ doc/index.rst                  |  2 +-
+ 7 files changed, 38 insertions(+), 14 deletions(-)
+
+--- a/coverage/ctracer/tracer.c
++++ b/coverage/ctracer/tracer.c
+@@ -306,6 +306,9 @@
+     PyObject * plugin = NULL;
+     PyObject * plugin_name = NULL;
+     PyObject * next_tracename = NULL;
++#ifdef RESUME
++    PyObject * pCode = NULL;
++#endif
+ 
+     /* Borrowed references. */
+     PyObject * filename = NULL;
+@@ -525,16 +528,16 @@
+      */
+     BOOL real_call = FALSE;
+ 
+-#ifdef RESUME   // 3.11.0a4
++#ifdef RESUME
+     /*
+      * The current opcode is guaranteed to be RESUME. The argument
+      * determines what kind of resume it is.
+      */
+-    PyObject * pCode = MyFrame_GetCode(frame)->co_code;
+-    real_call = (PyBytes_AS_STRING(pCode)[MyFrame_lasti(frame) + 1] == 0);
++    pCode = MyCode_GetCode(MyFrame_GetCode(frame));
++    real_call = (PyBytes_AS_STRING(pCode)[MyFrame_GetLasti(frame) + 1] == 0);
+ #else
+     // f_lasti is -1 for a true call, and a real byte offset for a generator re-entry.
+-    real_call = (MyFrame_lasti(frame) < 0);
++    real_call = (MyFrame_GetLasti(frame) < 0);
+ #endif
+ 
+     if (real_call) {
+@@ -548,6 +551,9 @@
+     ret = RET_OK;
+ 
+ error:
++#ifdef RESUME
++    MyCode_FreeCode(pCode);
++#endif
+     Py_XDECREF(next_tracename);
+     Py_XDECREF(disposition);
+     Py_XDECREF(plugin);
+@@ -688,6 +694,8 @@
+ {
+     int ret = RET_ERROR;
+ 
++    PyObject * pCode = NULL;
++
+     STATS( self->stats.returns++; )
+     /* A near-copy of this code is above in the missing-return handler. */
+     if (CTracer_set_pdata_stack(self) < 0) {
+@@ -698,8 +706,8 @@
+     if (self->pdata_stack->depth >= 0) {
+         if (self->tracing_arcs && self->pcur_entry->file_data) {
+             BOOL real_return = FALSE;
+-            PyObject * pCode = MyFrame_GetCode(frame)->co_code;
+-            int lasti = MyFrame_lasti(frame);
++            pCode = MyCode_GetCode(MyFrame_GetCode(frame));
++            int lasti = MyFrame_GetLasti(frame);
+             Py_ssize_t code_size = PyBytes_GET_SIZE(pCode);
+             unsigned char * code_bytes = (unsigned char *)PyBytes_AS_STRING(pCode);
+ #ifdef RESUME
+@@ -759,6 +767,7 @@
+ 
+ error:
+ 
++    MyCode_FreeCode(pCode);
+     return ret;
+ }
+ 
+--- a/coverage/ctracer/util.h
++++ b/coverage/ctracer/util.h
+@@ -17,13 +17,17 @@
+ // to make this work, but it's all I've got until https://bugs.python.org/issue40421
+ // is resolved.
+ #include <internal/pycore_frame.h>
+-#define MyFrame_lasti(f)    ((f)->f_frame->f_lasti * 2)
++#if PY_VERSION_HEX >= 0x030B00A7
++#define MyFrame_GetLasti(f)     (PyFrame_GetLasti(f))
++#else
++#define MyFrame_GetLasti(f)     ((f)->f_frame->f_lasti * 2)
++#endif
+ #elif PY_VERSION_HEX >= 0x030A00A7
+ // The f_lasti field changed meaning in 3.10.0a7. It had been bytes, but
+ // now is instructions, so we need to adjust it to use it as a byte index.
+-#define MyFrame_lasti(f)    ((f)->f_lasti * 2)
++#define MyFrame_GetLasti(f)     ((f)->f_lasti * 2)
+ #else
+-#define MyFrame_lasti(f)    ((f)->f_lasti)
++#define MyFrame_GetLasti(f)     ((f)->f_lasti)
+ #endif
+ 
+ // Access f_code should be done through a helper starting in 3.9.
+@@ -33,6 +37,14 @@
+ #define MyFrame_GetCode(f)      ((f)->f_code)
+ #endif
+ 
++#if PY_VERSION_HEX >= 0x030B00A7
++#define MyCode_GetCode(co)      (PyObject_GetAttrString((PyObject *)(co), "co_code"))
++#define MyCode_FreeCode(code)   Py_XDECREF(code)
++#else
++#define MyCode_GetCode(co)      ((co)->co_code)
++#define MyCode_FreeCode(code)
++#endif
++
+ /* The values returned to indicate ok or error. */
+ #define RET_OK      0
+ #define RET_ERROR   -1
diff -Nru python-coverage-6.2+dfsg1/debian/patches/09.python3.11.patch python-coverage-6.2+dfsg1/debian/patches/09.python3.11.patch
--- python-coverage-6.2+dfsg1/debian/patches/09.python3.11.patch	1970-01-01 02:00:00.000000000 +0200
+++ python-coverage-6.2+dfsg1/debian/patches/09.python3.11.patch	2022-11-16 22:19:12.000000000 +0200
@@ -0,0 +1,49 @@
+From 0d6449874cd4d3003ce908d66fa654b64bfea0c0 Mon Sep 17 00:00:00 2001
+From: Ned Batchelder <n...@nedbatchelder.com>
+Date: Tue, 12 Jul 2022 08:05:49 -0400
+Subject: [PATCH] fix: 3.11.0b4 has 0-numbered lines. Fixes #1419
+
+CPython added these lines in
+https://github.com/python/cpython/commit/1bfe83a114da3939c00746fc44dc5da7f56f525f
+---
+ CHANGES.rst        |  7 +++++++
+ coverage/env.py    |  3 +++
+ coverage/parser.py |  2 +-
+ tests/test_arcs.py |  2 ++
+ tests/test_lcov.py | 36 +++++++++++++++++++++++++-----------
+ 5 files changed, 38 insertions(+), 12 deletions(-)
+
+--- a/coverage/env.py
++++ b/coverage/env.py
+@@ -116,6 +116,9 @@
+     # the decorator.
+     trace_decorator_line_again = (PYVERSION > (3, 11, 0, 'alpha', 3, 0))
+ 
++    # Modules start with a line numbered zero. This means empty modules have
++    # only a 0-number line, which is ignored, giving a truly empty module.
++    empty_is_empty = (PYVERSION >= (3, 11, 0, 'beta', 4))
+ 
+ # Coverage.py specifics.
+ 
+--- a/coverage/parser.py
++++ b/coverage/parser.py
+@@ -377,7 +377,7 @@
+         """
+         if hasattr(self.code, "co_lines"):
+             for _, _, line in self.code.co_lines():
+-                if line is not None:
++                if line:
+                     yield line
+         else:
+             # Adapted from dis.py in the standard library.
+--- a/tests/test_arcs.py
++++ b/tests/test_arcs.py
+@@ -151,6 +151,8 @@
+         if env.JYTHON:
+             # Jython reports no lines for an empty file.
+             arcz_missing=".1 1."                    # pragma: only jython
++        elif env.PYBEHAVIOR.empty_is_empty:
++            arcz_missing=".1 1."
+         else:
+             # Other Pythons report one line.
+             arcz_missing=""
diff -Nru python-coverage-6.2+dfsg1/debian/patches/series python-coverage-6.2+dfsg1/debian/patches/series
--- python-coverage-6.2+dfsg1/debian/patches/series	2022-01-14 01:43:38.000000000 +0200
+++ python-coverage-6.2+dfsg1/debian/patches/series	2022-11-16 22:18:40.000000000 +0200
@@ -1,3 +1,8 @@
 01.omit-resource-files-from-distutils-setup.patch
 02.rename-public-programs.patch
 03.sphinx-add-code-path.patch
+04.python3.11.patch
+05.python3.11.patch
+07.python3.11.patch
+08.python3.11.patch
+09.python3.11.patch

Reply via email to