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