Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-coverage for openSUSE:Factory
checked in at 2024-06-07 15:02:08
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-coverage (Old)
and /work/SRC/openSUSE:Factory/.python-coverage.new.24587 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-coverage"
Fri Jun 7 15:02:08 2024 rev:63 rq:1178912 version:7.5.3
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-coverage/python-coverage.changes
2024-05-16 17:12:31.694165865 +0200
+++
/work/SRC/openSUSE:Factory/.python-coverage.new.24587/python-coverage.changes
2024-06-07 15:02:15.501350003 +0200
@@ -1,0 +2,30 @@
+Thu Jun 6 07:29:28 UTC 2024 - Dirk Müller <[email protected]>
+
+- update to 7.5.3:
+ * Performance improvements for combining data files, especially
+ when measuring line coverage. A few different quadratic
+ behaviors were eliminated. In one extreme case of combining
+ 700+ data files, the time dropped from more than three hours
+ to seven minutes. Thanks for Kraken Tech for funding the
+ fix.
+ * Performance improvements for generating HTML reports, with a
+ side benefit of reducing memory use, closing issue 1791.
+ Thanks to Daniel Diniz for helping to diagnose the problem.
+ * Fix: nested matches of exclude patterns could exclude too
+ much code, as reported in issue 1779. This is now fixed.
+ * Changed: previously, coverage.py would consider a module
+ docstring to be an executable statement if it appeared after
+ line 1 in the file, but not executable if it was the first
+ line. Now module docstrings are never counted as executable
+ statements. This can change coverage.py's count of the
+ number of statements in a file, which can slightly change the
+ coverage percentage reported.
+ * In the HTML report, the filter term and "hide covered"
+ checkbox settings are remembered between viewings, thanks to
+ Daniel Diniz.
+ * Python 3.13.0b1 is supported.
+ * Fix: parsing error handling is improved to ensure bizarre
+ source files are handled gracefully, and to unblock oss-fuzz
+ fuzzing, thanks to Liam DeVoe. Closes issue 1787.
+
+-------------------------------------------------------------------
Old:
----
coverage-7.5.1.tar.gz
New:
----
coverage-7.5.3.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-coverage.spec ++++++
--- /var/tmp/diff_new_pack.XK7TeO/_old 2024-06-07 15:02:17.021405379 +0200
+++ /var/tmp/diff_new_pack.XK7TeO/_new 2024-06-07 15:02:17.021405379 +0200
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-coverage
-Version: 7.5.1
+Version: 7.5.3
Release: 0
Summary: Code coverage measurement for Python
License: Apache-2.0
++++++ coverage-7.5.1.tar.gz -> coverage-7.5.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/.github/workflows/python-nightly.yml
new/coverage-7.5.3/.github/workflows/python-nightly.yml
--- old/coverage-7.5.1/.github/workflows/python-nightly.yml 2024-05-04
16:44:25.000000000 +0200
+++ new/coverage-7.5.3/.github/workflows/python-nightly.yml 2024-05-28
15:52:29.000000000 +0200
@@ -58,6 +58,7 @@
# https://launchpad.net/~deadsnakes/+archive/ubuntu/nightly/+packages
- "3.12-dev"
- "3.13-dev"
+ - "3.14-dev"
# https://github.com/actions/setup-python#available-versions-of-pypy
- "pypy-3.8-nightly"
- "pypy-3.9-nightly"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/CHANGES.rst
new/coverage-7.5.3/CHANGES.rst
--- old/coverage-7.5.1/CHANGES.rst 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/CHANGES.rst 2024-05-28 15:52:29.000000000 +0200
@@ -22,6 +22,53 @@
.. scriv-start-here
+.. _changes_7-5-3:
+
+Version 7.5.3 â 2024-05-28
+--------------------------
+
+- Performance improvements for combining data files, especially when measuring
+ line coverage. A few different quadratic behaviors were eliminated. In one
+ extreme case of combining 700+ data files, the time dropped from more than
+ three hours to seven minutes. Thanks for Kraken Tech for funding the fix.
+
+- Performance improvements for generating HTML reports, with a side benefit of
+ reducing memory use, closing `issue 1791`_. Thanks to Daniel Diniz for
+ helping to diagnose the problem.
+
+.. _issue 1791: https://github.com/nedbat/coveragepy/issues/1791
+
+
+.. _changes_7-5-2:
+
+Version 7.5.2 â 2024-05-24
+--------------------------
+
+- Fix: nested matches of exclude patterns could exclude too much code, as
+ reported in `issue 1779`_. This is now fixed.
+
+- Changed: previously, coverage.py would consider a module docstring to be an
+ executable statement if it appeared after line 1 in the file, but not
+ executable if it was the first line. Now module docstrings are never counted
+ as executable statements. This can change coverage.py's count of the number
+ of statements in a file, which can slightly change the coverage percentage
+ reported.
+
+- In the HTML report, the filter term and "hide covered" checkbox settings are
+ remembered between viewings, thanks to `Daniel Diniz <pull 1776_>`_.
+
+- Python 3.13.0b1 is supported.
+
+- Fix: parsing error handling is improved to ensure bizarre source files are
+ handled gracefully, and to unblock oss-fuzz fuzzing, thanks to `Liam DeVoe
+ <pull 1788_>`_. Closes `issue 1787`_.
+
+.. _pull 1776: https://github.com/nedbat/coveragepy/pull/1776
+.. _issue 1779: https://github.com/nedbat/coveragepy/issues/1779
+.. _issue 1787: https://github.com/nedbat/coveragepy/issues/1787
+.. _pull 1788: https://github.com/nedbat/coveragepy/pull/1788
+
+
.. _changes_7-5-1:
Version 7.5.1 â 2024-05-04
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/CONTRIBUTORS.txt
new/coverage-7.5.3/CONTRIBUTORS.txt
--- old/coverage-7.5.1/CONTRIBUTORS.txt 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/CONTRIBUTORS.txt 2024-05-28 15:52:29.000000000 +0200
@@ -132,6 +132,7 @@
Leonardo Pistone
Lewis Gaul
Lex Berezhny
+Liam DeVoe
Loïc Dachary
Lorenzo Micò
Louis Heredero
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/PKG-INFO new/coverage-7.5.3/PKG-INFO
--- old/coverage-7.5.1/PKG-INFO 2024-05-04 16:44:36.584669000 +0200
+++ new/coverage-7.5.3/PKG-INFO 2024-05-28 15:52:36.482906000 +0200
@@ -1,12 +1,12 @@
Metadata-Version: 2.1
Name: coverage
-Version: 7.5.1
+Version: 7.5.3
Summary: Code coverage measurement for Python
Home-page: https://github.com/nedbat/coveragepy
-Author: Ned Batchelder and 226 others
+Author: Ned Batchelder and 227 others
Author-email: [email protected]
License: Apache-2.0
-Project-URL: Documentation, https://coverage.readthedocs.io/en/7.5.1
+Project-URL: Documentation, https://coverage.readthedocs.io/en/7.5.3
Project-URL: Funding,
https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=pypi
Project-URL: Issues, https://github.com/nedbat/coveragepy/issues
Project-URL: Mastodon, https://hachyderm.io/@coveragepy
@@ -62,13 +62,13 @@
.. PYVERSIONS
-* Python 3.8 through 3.12, and 3.13.0a6 and up.
+* Python 3.8 through 3.12, and 3.13.0b1 and up.
* PyPy3 versions 3.8 through 3.10.
Documentation is on `Read the Docs`_. Code repository and issue tracker are on
`GitHub`_.
-.. _Read the Docs: https://coverage.readthedocs.io/en/7.5.1/
+.. _Read the Docs: https://coverage.readthedocs.io/en/7.5.3/
.. _GitHub: https://github.com/nedbat/coveragepy
**New in 7.x:**
@@ -112,7 +112,7 @@
Looking to run ``coverage`` on your test suite? See the `Quick Start section`_
of the docs.
-.. _Quick Start section: https://coverage.readthedocs.io/en/7.5.1/#quick-start
+.. _Quick Start section: https://coverage.readthedocs.io/en/7.5.3/#quick-start
Change history
@@ -120,7 +120,7 @@
The complete history of changes is on the `change history page`_.
-.. _change history page: https://coverage.readthedocs.io/en/7.5.1/changes.html
+.. _change history page: https://coverage.readthedocs.io/en/7.5.3/changes.html
Code of Conduct
@@ -139,7 +139,7 @@
Found a bug? Want to help improve the code or documentation? See the
`Contributing section`_ of the docs.
-.. _Contributing section:
https://coverage.readthedocs.io/en/7.5.1/contributing.html
+.. _Contributing section:
https://coverage.readthedocs.io/en/7.5.3/contributing.html
Security
@@ -167,7 +167,7 @@
:target: https://github.com/nedbat/coveragepy/actions/workflows/quality.yml
:alt: Quality check status
.. |docs| image::
https://readthedocs.org/projects/coverage/badge/?version=latest&style=flat
- :target: https://coverage.readthedocs.io/en/7.5.1/
+ :target: https://coverage.readthedocs.io/en/7.5.3/
:alt: Documentation
.. |kit| image:: https://img.shields.io/pypi/v/coverage
:target: https://pypi.org/project/coverage/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/README.rst
new/coverage-7.5.3/README.rst
--- old/coverage-7.5.1/README.rst 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/README.rst 2024-05-28 15:52:29.000000000 +0200
@@ -25,7 +25,7 @@
.. PYVERSIONS
-* Python 3.8 through 3.12, and 3.13.0a6 and up.
+* Python 3.8 through 3.12, and 3.13.0b1 and up.
* PyPy3 versions 3.8 through 3.10.
Documentation is on `Read the Docs`_. Code repository and issue tracker are on
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/coverage/control.py
new/coverage-7.5.3/coverage/control.py
--- old/coverage-7.5.1/coverage/control.py 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/coverage/control.py 2024-05-28 15:52:29.000000000
+0200
@@ -998,7 +998,7 @@
if self.config.paths:
mapped_data = CoverageData(warn=self._warn, debug=self._debug,
no_disk=True)
if self._data is not None:
- mapped_data.update(self._data, aliases=self._make_aliases())
+ mapped_data.update(self._data,
map_path=self._make_aliases().map)
self._data = mapped_data
def report(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/coverage/data.py
new/coverage-7.5.3/coverage/data.py
--- old/coverage-7.5.1/coverage/data.py 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/coverage/data.py 2024-05-28 15:52:29.000000000 +0200
@@ -12,6 +12,7 @@
from __future__ import annotations
+import functools
import glob
import hashlib
import os.path
@@ -134,6 +135,11 @@
if strict and not files_to_combine:
raise NoDataError("No data to combine")
+ if aliases is None:
+ map_path = None
+ else:
+ map_path = functools.lru_cache(maxsize=None)(aliases.map)
+
file_hashes = set()
combined_any = False
@@ -176,7 +182,7 @@
message(f"Couldn't combine data file {rel_file_name}:
{exc}")
delete_this_one = False
else:
- data.update(new_data, aliases=aliases)
+ data.update(new_data, map_path=map_path)
combined_any = True
if message:
message(f"Combined data file {rel_file_name}")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/coverage/html.py
new/coverage-7.5.3/coverage/html.py
--- old/coverage-7.5.1/coverage/html.py 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/coverage/html.py 2024-05-28 15:52:29.000000000 +0200
@@ -597,7 +597,7 @@
"regions": index_page.summaries,
"totals": index_page.totals,
"noun": index_page.noun,
- "column2": index_page.noun if index_page.noun != "file" else "",
+ "region_noun": index_page.noun if index_page.noun != "file" else
"",
"skip_covered": self.skip_covered,
"skipped_covered_msg": skipped_covered_msg,
"skipped_empty_msg": skipped_empty_msg,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/coverage/htmlfiles/coverage_html.js
new/coverage-7.5.3/coverage/htmlfiles/coverage_html.js
--- old/coverage-7.5.1/coverage/htmlfiles/coverage_html.js 2024-05-04
16:44:25.000000000 +0200
+++ new/coverage-7.5.3/coverage/htmlfiles/coverage_html.js 2024-05-28
15:52:29.000000000 +0200
@@ -125,6 +125,16 @@
// Create the events for the filter box.
coverage.wire_up_filter = function () {
+ // Populate the filter and hide100 inputs if there are saved values for
them.
+ const saved_filter_value = localStorage.getItem(coverage.FILTER_STORAGE);
+ if (saved_filter_value) {
+ document.getElementById("filter").value = saved_filter_value;
+ }
+ const saved_hide100_value = localStorage.getItem(coverage.HIDE100_STORAGE);
+ if (saved_hide100_value) {
+ document.getElementById("hide100").checked =
JSON.parse(saved_hide100_value);
+ }
+
// Cache elements.
const table = document.querySelector("table.index");
const table_body_rows = table.querySelectorAll("tbody tr");
@@ -138,8 +148,12 @@
totals[totals.length - 1] = { "numer": 0, "denom": 0 }; // nosemgrep:
eslint.detect-object-injection
var text = document.getElementById("filter").value;
+ // Store filter value
+ localStorage.setItem(coverage.FILTER_STORAGE, text);
const casefold = (text === text.toLowerCase());
const hide100 = document.getElementById("hide100").checked;
+ // Store hide value.
+ localStorage.setItem(coverage.HIDE100_STORAGE,
JSON.stringify(hide100));
// Hide / show elements.
table_body_rows.forEach(row => {
@@ -240,6 +254,8 @@
document.getElementById("filter").dispatchEvent(new Event("input"));
document.getElementById("hide100").dispatchEvent(new Event("input"));
};
+coverage.FILTER_STORAGE = "COVERAGE_FILTER_VALUE";
+coverage.HIDE100_STORAGE = "COVERAGE_HIDE100_VALUE";
// Set up the click-to-sort columns.
coverage.wire_up_sorting = function () {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/coverage/htmlfiles/index.html
new/coverage-7.5.3/coverage/htmlfiles/index.html
--- old/coverage-7.5.1/coverage/htmlfiles/index.html 2024-05-04
16:44:25.000000000 +0200
+++ new/coverage-7.5.3/coverage/htmlfiles/index.html 2024-05-28
15:52:29.000000000 +0200
@@ -31,7 +31,7 @@
<div class="keyhelp">
<p>
<kbd>f</kbd>
- {% if column2 %}
+ {% if region_noun %}
<kbd>n</kbd>
{% endif %}
<kbd>s</kbd>
@@ -83,8 +83,8 @@
{# The title="" attr doesn't work in Safari. #}
<tr class="tablehead" title="Click to sort">
<th id="file" class="name left" aria-sort="none"
data-shortcut="f">File<span class="arrows"></span></th>
- {% if column2 %}
- <th id="region" class="name left" aria-sort="none"
data-default-sort-order="ascending" data-shortcut="n">{{ column2 }}<span
class="arrows"></span></th>
+ {% if region_noun %}
+ <th id="region" class="name left" aria-sort="none"
data-default-sort-order="ascending" data-shortcut="n">{{ region_noun }}<span
class="arrows"></span></th>
{% endif %}
<th id="statements" aria-sort="none"
data-default-sort-order="descending" data-shortcut="s">statements<span
class="arrows"></span></th>
<th id="missing" aria-sort="none"
data-default-sort-order="descending" data-shortcut="m">missing<span
class="arrows"></span></th>
@@ -100,7 +100,7 @@
{% for region in regions %}
<tr class="region">
<td class="name left"><a
href="{{region.url}}">{{region.file}}</a></td>
- {% if column2 %}
+ {% if region_noun %}
<td class="name left"><a
href="{{region.url}}">{{region.description}}</a></td>
{% endif %}
<td>{{region.nums.n_statements}}</td>
@@ -117,7 +117,7 @@
<tfoot>
<tr class="total">
<td class="name left">Total</td>
- {% if column2 %}
+ {% if region_noun %}
<td class="name left"> </td>
{% endif %}
<td>{{totals.n_statements}}</td>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/coverage/misc.py
new/coverage-7.5.3/coverage/misc.py
--- old/coverage-7.5.1/coverage/misc.py 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/coverage/misc.py 2024-05-28 15:52:29.000000000 +0200
@@ -13,7 +13,6 @@
import importlib
import importlib.util
import inspect
-import locale
import os
import os.path
import re
@@ -22,7 +21,7 @@
from types import ModuleType
from typing import (
- Any, IO, Iterable, Iterator, Mapping, NoReturn, Sequence, TypeVar,
+ Any, Iterable, Iterator, Mapping, NoReturn, Sequence, TypeVar,
)
from coverage.exceptions import CoverageException
@@ -156,18 +155,6 @@
ensure_dir(os.path.dirname(path))
-def output_encoding(outfile: IO[str] | None = None) -> str:
- """Determine the encoding to use for output written to `outfile` or
stdout."""
- if outfile is None:
- outfile = sys.stdout
- encoding = (
- getattr(outfile, "encoding", None) or
- getattr(sys.__stdout__, "encoding", None) or
- locale.getpreferredencoding()
- )
- return encoding
-
-
class Hasher:
"""Hashes Python data for fingerprinting."""
def __init__(self) -> None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/coverage/parser.py
new/coverage-7.5.3/coverage/parser.py
--- old/coverage-7.5.1/coverage/parser.py 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/coverage/parser.py 2024-05-28 15:52:29.000000000
+0200
@@ -25,7 +25,7 @@
from coverage.bytecode import code_objects
from coverage.debug import short_stack
from coverage.exceptions import NoSource, NotPython
-from coverage.misc import join_regex, nice_pair
+from coverage.misc import nice_pair
from coverage.phystokens import generate_tokens
from coverage.types import TArc, TLineNo
@@ -62,8 +62,8 @@
self.exclude = exclude
- # The text lines of the parsed code.
- self.lines: list[str] = self.text.split("\n")
+ # The parsed AST of the text.
+ self._ast_root: ast.AST | None = None
# The normalized line numbers of the statements in the code. Exclusions
# are taken into account, and statements are adjusted to their first
@@ -101,19 +101,16 @@
self._all_arcs: set[TArc] | None = None
self._missing_arc_fragments: TArcFragments | None = None
- @functools.lru_cache()
- def lines_matching(self, *regexes: str) -> set[TLineNo]:
- """Find the lines matching one of a list of regexes.
+ def lines_matching(self, regex: str) -> set[TLineNo]:
+ """Find the lines matching a regex.
- Returns a set of line numbers, the lines that contain a match for one
- of the regexes in `regexes`. The entire line needn't match, just a
- part of it.
+ Returns a set of line numbers, the lines that contain a match for
+ `regex`. The entire line needn't match, just a part of it.
"""
- combined = join_regex(regexes)
- regex_c = re.compile(combined)
+ regex_c = re.compile(regex)
matches = set()
- for i, ltext in enumerate(self.lines, start=1):
+ for i, ltext in enumerate(self.text.split("\n"), start=1):
if regex_c.search(ltext):
matches.add(self._multiline.get(i, i))
return matches
@@ -127,26 +124,18 @@
# Find lines which match an exclusion pattern.
if self.exclude:
self.raw_excluded = self.lines_matching(self.exclude)
+ self.excluded = set(self.raw_excluded)
- # Tokenize, to find excluded suites, to find docstrings, and to find
- # multi-line statements.
-
- # The last token seen. Start with INDENT to get module docstrings
- prev_toktype: int = token.INDENT
# The current number of indents.
indent: int = 0
# An exclusion comment will exclude an entire clause at this indent.
exclude_indent: int = 0
# Are we currently excluding lines?
excluding: bool = False
- # Are we excluding decorators now?
- excluding_decorators: bool = False
# The line number of the first line in a multi-line statement.
first_line: int = 0
# Is the file empty?
empty: bool = True
- # Is this the first token on a line?
- first_on_line: bool = True
# Parenthesis (and bracket) nesting level.
nesting: int = 0
@@ -162,42 +151,22 @@
indent += 1
elif toktype == token.DEDENT:
indent -= 1
- elif toktype == token.NAME:
- if ttext == "class":
- # Class definitions look like branches in the bytecode, so
- # we need to exclude them. The simplest way is to note the
- # lines with the "class" keyword.
- self.raw_classdefs.add(slineno)
elif toktype == token.OP:
if ttext == ":" and nesting == 0:
should_exclude = (
- self.raw_excluded.intersection(range(first_line,
elineno + 1))
- or excluding_decorators
+ self.excluded.intersection(range(first_line, elineno +
1))
)
if not excluding and should_exclude:
# Start excluding a suite. We trigger off of the colon
# token so that the #pragma comment will be recognized
on
# the same line as the colon.
- self.raw_excluded.add(elineno)
+ self.excluded.add(elineno)
exclude_indent = indent
excluding = True
- excluding_decorators = False
- elif ttext == "@" and first_on_line:
- # A decorator.
- if elineno in self.raw_excluded:
- excluding_decorators = True
- if excluding_decorators:
- self.raw_excluded.add(elineno)
elif ttext in "([{":
nesting += 1
elif ttext in ")]}":
nesting -= 1
- elif toktype == token.STRING:
- if prev_toktype == token.INDENT:
- # Strings that are first on an indented line are
docstrings.
- # (a trick from trace.py in the stdlib.) This works for
- # 99.9999% of cases.
- self.raw_docstrings.update(range(slineno, elineno+1))
elif toktype == token.NEWLINE:
if first_line and elineno != first_line:
# We're at the end of a line, and we've ended on a
@@ -206,7 +175,6 @@
for l in range(first_line, elineno+1):
self._multiline[l] = first_line
first_line = 0
- first_on_line = True
if ttext.strip() and toktype != tokenize.COMMENT:
# A non-white-space token.
@@ -218,10 +186,7 @@
if excluding and indent <= exclude_indent:
excluding = False
if excluding:
- self.raw_excluded.add(elineno)
- first_on_line = False
-
- prev_toktype = toktype
+ self.excluded.add(elineno)
# Find the starts of the executable statements.
if not empty:
@@ -234,6 +199,34 @@
if env.PYBEHAVIOR.module_firstline_1 and self._multiline:
self._multiline[1] = min(self.raw_statements)
+ self.excluded = self.first_lines(self.excluded)
+
+ # AST lets us find classes, docstrings, and decorator-affected
+ # functions and classes.
+ assert self._ast_root is not None
+ for node in ast.walk(self._ast_root):
+ # Find class definitions.
+ if isinstance(node, ast.ClassDef):
+ self.raw_classdefs.add(node.lineno)
+ # Find docstrings.
+ if isinstance(node, (ast.ClassDef, ast.FunctionDef,
ast.AsyncFunctionDef, ast.Module)):
+ if node.body:
+ first = node.body[0]
+ if (
+ isinstance(first, ast.Expr)
+ and isinstance(first.value, ast.Constant)
+ and isinstance(first.value.value, str)
+ ):
+ self.raw_docstrings.update(
+ range(first.lineno, cast(int, first.end_lineno) +
1)
+ )
+ # Exclusions carry from decorators and signatures to the bodies of
+ # functions and classes.
+ if isinstance(node, (ast.ClassDef, ast.FunctionDef,
ast.AsyncFunctionDef)):
+ first_line = min((d.lineno for d in node.decorator_list),
default=node.lineno)
+ if self.excluded.intersection(range(first_line, node.lineno +
1)):
+ self.excluded.update(range(first_line, cast(int,
node.end_lineno) + 1))
+
@functools.lru_cache(maxsize=1000)
def first_line(self, lineno: TLineNo) -> TLineNo:
"""Return the first line number of the statement including `lineno`."""
@@ -268,6 +261,7 @@
"""
try:
+ self._ast_root = ast.parse(self.text)
self._raw_parse()
except (tokenize.TokenError, IndentationError, SyntaxError) as err:
if hasattr(err, "lineno"):
@@ -279,8 +273,6 @@
f"{err.args[0]!r} at line {lineno}",
) from err
- self.excluded = self.first_lines(self.raw_excluded)
-
ignore = self.excluded | self.raw_docstrings
starts = self.raw_statements - ignore
self.statements = self.first_lines(starts) - ignore
@@ -303,7 +295,8 @@
`_all_arcs` is the set of arcs in the code.
"""
- aaa = AstArcAnalyzer(self.text, self.raw_statements, self._multiline)
+ assert self._ast_root is not None
+ aaa = AstArcAnalyzer(self._ast_root, self.raw_statements,
self._multiline)
aaa.analyze()
self._all_arcs = set()
@@ -403,14 +396,9 @@
self.code = code
else:
assert filename is not None
- try:
- self.code = compile(text, filename, "exec", dont_inherit=True)
- except SyntaxError as synerr:
- raise NotPython(
- "Couldn't parse '%s' as Python source: '%s' at line %d" % (
- filename, synerr.msg, synerr.lineno or 0,
- ),
- ) from synerr
+ # We only get here if earlier ast parsing succeeded, so no need to
+ # catch errors.
+ self.code = compile(text, filename, "exec", dont_inherit=True)
def child_parsers(self) -> Iterable[ByteParser]:
"""Iterate over all the code objects nested within this one.
@@ -685,11 +673,11 @@
def __init__(
self,
- text: str,
+ root_node: ast.AST,
statements: set[TLineNo],
multiline: dict[TLineNo, TLineNo],
) -> None:
- self.root_node = ast.parse(text)
+ self.root_node = root_node
# TODO: I think this is happening in too many places.
self.statements = {multiline.get(l, l) for l in statements}
self.multiline = multiline
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/coverage/phystokens.py
new/coverage-7.5.3/coverage/phystokens.py
--- old/coverage-7.5.1/coverage/phystokens.py 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/coverage/phystokens.py 2024-05-28 15:52:29.000000000
+0200
@@ -6,7 +6,6 @@
from __future__ import annotations
import ast
-import functools
import io
import keyword
import re
@@ -163,20 +162,15 @@
yield line
[email protected]_cache(maxsize=100)
def generate_tokens(text: str) -> TokenInfos:
- """A cached version of `tokenize.generate_tokens`.
+ """A helper around `tokenize.generate_tokens`.
+
+ Originally this was used to cache the results, but it didn't seem to make
+ reporting go faster, and caused issues with using too much memory.
- When reporting, coverage.py tokenizes files twice, once to find the
- structure of the file, and once to syntax-color it. Tokenizing is
- expensive, and easily cached.
-
- Unfortunately, the HTML report code tokenizes all the files the first time
- before then tokenizing them a second time, so we cache many. Ideally we'd
- rearrange the code to tokenize each file twice before moving onto the next.
"""
readline = io.StringIO(text).readline
- return list(tokenize.generate_tokens(readline))
+ return tokenize.generate_tokens(readline)
def source_encoding(source: bytes) -> str:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/coverage/python.py
new/coverage-7.5.3/coverage/python.py
--- old/coverage-7.5.1/coverage/python.py 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/coverage/python.py 2024-05-28 15:52:29.000000000
+0200
@@ -206,8 +206,10 @@
def no_branch_lines(self) -> set[TLineNo]:
assert self.coverage is not None
no_branch = self.parser.lines_matching(
- join_regex(self.coverage.config.partial_list),
- join_regex(self.coverage.config.partial_always_list),
+ join_regex(
+ self.coverage.config.partial_list
+ + self.coverage.config.partial_always_list
+ )
)
return no_branch
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/coverage/pytracer.py
new/coverage-7.5.3/coverage/pytracer.py
--- old/coverage-7.5.1/coverage/pytracer.py 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/coverage/pytracer.py 2024-05-28 15:52:29.000000000
+0200
@@ -166,12 +166,12 @@
if event == "call":
# Should we start a new context?
if self.should_start_context and self.context is None:
- context_maybe = self.should_start_context(frame)
+ context_maybe = self.should_start_context(frame) # pylint:
disable=not-callable
if context_maybe is not None:
self.context = context_maybe
started_context = True
assert self.switch_context is not None
- self.switch_context(self.context)
+ self.switch_context(self.context) # pylint:
disable=not-callable
else:
started_context = False
else:
@@ -280,7 +280,7 @@
if self.started_context:
assert self.switch_context is not None
self.context = None
- self.switch_context(None)
+ self.switch_context(None) # pylint: disable=not-callable
return self._cached_bound_method_trace
def start(self) -> TTraceFn:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/coverage/sqldata.py
new/coverage-7.5.3/coverage/sqldata.py
--- old/coverage-7.5.1/coverage/sqldata.py 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/coverage/sqldata.py 2024-05-28 15:52:29.000000000
+0200
@@ -21,13 +21,12 @@
import zlib
from typing import (
- cast, Any, Collection, Mapping,
+ cast, Any, Callable, Collection, Mapping,
Sequence,
)
from coverage.debug import NoDebugging, auto_repr
from coverage.exceptions import CoverageException, DataError
-from coverage.files import PathAliases
from coverage.misc import file_be_gone, isolate_module
from coverage.numbits import numbits_to_nums, numbits_union, nums_to_numbits
from coverage.sqlitedb import SqliteDb
@@ -647,12 +646,16 @@
continue
con.execute_void(sql, (file_id,))
- def update(self, other_data: CoverageData, aliases: PathAliases | None =
None) -> None:
- """Update this data with data from several other :class:`CoverageData`
instances.
+ def update(
+ self,
+ other_data: CoverageData,
+ map_path: Callable[[str], str] | None = None,
+ ) -> None:
+ """Update this data with data from another :class:`CoverageData`.
- If `aliases` is provided, it's a `PathAliases` object that is used to
- re-map paths to match the local machine's. Note: `aliases` is None
- only when called directly from the test suite.
+ If `map_path` is provided, it's a function that re-map paths to match
+ the local machine's. Note: `map_path` is None only when called
+ directly from the test suite.
"""
if self._debug.should("dataop"):
@@ -664,7 +667,7 @@
if self._has_arcs and other_data._has_lines:
raise DataError("Can't combine line data with arc data")
- aliases = aliases or PathAliases()
+ map_path = map_path or (lambda p: p)
# Force the database we're writing to to exist before we start nesting
contexts.
self._start_using()
@@ -674,7 +677,7 @@
with other_data._connect() as con:
# Get files data.
with con.execute("select path from file") as cur:
- files = {path: aliases.map(path) for (path,) in cur}
+ files = {path: map_path(path) for (path,) in cur}
# Get contexts data.
with con.execute("select context from context") as cur:
@@ -729,7 +732,7 @@
"inner join file on file.id = tracer.file_id",
) as cur:
this_tracers.update({
- aliases.map(path): tracer
+ map_path(path): tracer
for path, tracer in cur
})
@@ -767,27 +770,15 @@
# Prepare arc and line rows to be inserted by converting the file
# and context strings with integer ids. Then use the efficient
# `executemany()` to insert all rows at once.
- arc_rows = (
- (file_ids[file], context_ids[context], fromno, tono)
- for file, context, fromno, tono in arcs
- )
-
- # Get line data.
- with con.execute(
- "select file.path, context.context, line_bits.numbits " +
- "from line_bits " +
- "inner join file on file.id = line_bits.file_id " +
- "inner join context on context.id = line_bits.context_id",
- ) as cur:
- for path, context, numbits in cur:
- key = (aliases.map(path), context)
- if key in lines:
- numbits = numbits_union(lines[key], numbits)
- lines[key] = numbits
if arcs:
self._choose_lines_or_arcs(arcs=True)
+ arc_rows = (
+ (file_ids[file], context_ids[context], fromno, tono)
+ for file, context, fromno, tono in arcs
+ )
+
# Write the combined data.
con.executemany_void(
"insert or ignore into arc " +
@@ -797,15 +788,25 @@
if lines:
self._choose_lines_or_arcs(lines=True)
- con.execute_void("delete from line_bits")
+
+ for (file, context), numbits in lines.items():
+ with con.execute(
+ "select numbits from line_bits where file_id = ? and
context_id = ?",
+ (file_ids[file], context_ids[context]),
+ ) as cur:
+ existing = list(cur)
+ if existing:
+ lines[(file, context)] = numbits_union(numbits,
existing[0][0])
+
con.executemany_void(
- "insert into line_bits " +
+ "insert or replace into line_bits " +
"(file_id, context_id, numbits) values (?, ?, ?)",
[
(file_ids[file], context_ids[context], numbits)
for (file, context), numbits in lines.items()
],
)
+
con.executemany_void(
"insert or ignore into tracer (file_id, tracer) values (?, ?)",
((file_ids[filename], tracer) for filename, tracer in
tracer_map.items()),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/coverage/version.py
new/coverage-7.5.3/coverage/version.py
--- old/coverage-7.5.1/coverage/version.py 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/coverage/version.py 2024-05-28 15:52:29.000000000
+0200
@@ -8,7 +8,7 @@
# version_info: same semantics as sys.version_info.
# _dev: the .devN suffix if any.
-version_info = (7, 5, 1, "final", 0)
+version_info = (7, 5, 3, "final", 0)
_dev = 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/coverage.egg-info/PKG-INFO
new/coverage-7.5.3/coverage.egg-info/PKG-INFO
--- old/coverage-7.5.1/coverage.egg-info/PKG-INFO 2024-05-04
16:44:36.000000000 +0200
+++ new/coverage-7.5.3/coverage.egg-info/PKG-INFO 2024-05-28
15:52:36.000000000 +0200
@@ -1,12 +1,12 @@
Metadata-Version: 2.1
Name: coverage
-Version: 7.5.1
+Version: 7.5.3
Summary: Code coverage measurement for Python
Home-page: https://github.com/nedbat/coveragepy
-Author: Ned Batchelder and 226 others
+Author: Ned Batchelder and 227 others
Author-email: [email protected]
License: Apache-2.0
-Project-URL: Documentation, https://coverage.readthedocs.io/en/7.5.1
+Project-URL: Documentation, https://coverage.readthedocs.io/en/7.5.3
Project-URL: Funding,
https://tidelift.com/subscription/pkg/pypi-coverage?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=pypi
Project-URL: Issues, https://github.com/nedbat/coveragepy/issues
Project-URL: Mastodon, https://hachyderm.io/@coveragepy
@@ -62,13 +62,13 @@
.. PYVERSIONS
-* Python 3.8 through 3.12, and 3.13.0a6 and up.
+* Python 3.8 through 3.12, and 3.13.0b1 and up.
* PyPy3 versions 3.8 through 3.10.
Documentation is on `Read the Docs`_. Code repository and issue tracker are on
`GitHub`_.
-.. _Read the Docs: https://coverage.readthedocs.io/en/7.5.1/
+.. _Read the Docs: https://coverage.readthedocs.io/en/7.5.3/
.. _GitHub: https://github.com/nedbat/coveragepy
**New in 7.x:**
@@ -112,7 +112,7 @@
Looking to run ``coverage`` on your test suite? See the `Quick Start section`_
of the docs.
-.. _Quick Start section: https://coverage.readthedocs.io/en/7.5.1/#quick-start
+.. _Quick Start section: https://coverage.readthedocs.io/en/7.5.3/#quick-start
Change history
@@ -120,7 +120,7 @@
The complete history of changes is on the `change history page`_.
-.. _change history page: https://coverage.readthedocs.io/en/7.5.1/changes.html
+.. _change history page: https://coverage.readthedocs.io/en/7.5.3/changes.html
Code of Conduct
@@ -139,7 +139,7 @@
Found a bug? Want to help improve the code or documentation? See the
`Contributing section`_ of the docs.
-.. _Contributing section:
https://coverage.readthedocs.io/en/7.5.1/contributing.html
+.. _Contributing section:
https://coverage.readthedocs.io/en/7.5.3/contributing.html
Security
@@ -167,7 +167,7 @@
:target: https://github.com/nedbat/coveragepy/actions/workflows/quality.yml
:alt: Quality check status
.. |docs| image::
https://readthedocs.org/projects/coverage/badge/?version=latest&style=flat
- :target: https://coverage.readthedocs.io/en/7.5.1/
+ :target: https://coverage.readthedocs.io/en/7.5.3/
:alt: Documentation
.. |kit| image:: https://img.shields.io/pypi/v/coverage
:target: https://pypi.org/project/coverage/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/doc/branch.rst
new/coverage-7.5.3/doc/branch.rst
--- old/coverage-7.5.1/doc/branch.rst 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/doc/branch.rst 2024-05-28 15:52:29.000000000 +0200
@@ -116,3 +116,16 @@
at some point. Coverage.py can't work that out on its own, but the "no branch"
pragma indicates that the branch is known to be partial, and the line is not
flagged.
+
+Generator expressions
+=====================
+
+Generator expressions may also report partial branch coverage. Consider the
+following example::
+
+ value = next(i in range(1))
+
+While we might expect this line of code to be reported as covered, the
+generator did not iterate until ``StopIteration`` is raised, the indication
+that the loop is complete. This is another case
+where adding ``# pragma: no branch`` may be desirable.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/doc/changes.rst
new/coverage-7.5.3/doc/changes.rst
--- old/coverage-7.5.1/doc/changes.rst 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/doc/changes.rst 2024-05-28 15:52:29.000000000 +0200
@@ -845,10 +845,10 @@
would cause a "No data to report" error, as reported in `issue 549`_. This is
now fixed; thanks, Loïc Dachary.
-- If-statements can be optimized away during compilation, for example, `if 0:`
- or `if __debug__:`. Coverage.py had problems properly understanding these
- statements which existed in the source, but not in the compiled bytecode.
- This problem, reported in `issue 522`_, is now fixed.
+- If-statements can be optimized away during compilation, for example,
+ ``if 0:`` or ``if __debug__:``. Coverage.py had problems properly
+ understanding these statements which existed in the source, but not in the
+ compiled bytecode. This problem, reported in `issue 522`_, is now fixed.
- If you specified ``--source`` as a directory, then coverage.py would look for
importable Python files in that directory, and could identify ones that had
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/doc/conf.py
new/coverage-7.5.3/doc/conf.py
--- old/coverage-7.5.1/doc/conf.py 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/doc/conf.py 2024-05-28 15:52:29.000000000 +0200
@@ -67,11 +67,11 @@
# @@@ editable
copyright = "2009â2024, Ned Batchelder" # pylint: disable=redefined-builtin
# The short X.Y.Z version.
-version = "7.5.1"
+version = "7.5.3"
# The full version, including alpha/beta/rc tags.
-release = "7.5.1"
+release = "7.5.3"
# The date of release, in "monthname day, year" format.
-release_date = "May 4, 2024"
+release_date = "May 28, 2024"
# @@@ end
rst_epilog = f"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/doc/index.rst
new/coverage-7.5.3/doc/index.rst
--- old/coverage-7.5.1/doc/index.rst 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/doc/index.rst 2024-05-28 15:52:29.000000000 +0200
@@ -18,7 +18,7 @@
.. PYVERSIONS
-* Python 3.8 through 3.12, and 3.13.0a6 and up.
+* Python 3.8 through 3.12, and 3.13.0b1 and up.
* PyPy3 versions 3.8 through 3.10.
.. ifconfig:: prerelease
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/doc/requirements.in
new/coverage-7.5.3/doc/requirements.in
--- old/coverage-7.5.1/doc/requirements.in 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/doc/requirements.in 2024-05-28 15:52:29.000000000
+0200
@@ -14,5 +14,6 @@
sphinx-autobuild
sphinx_rtd_theme
sphinx-code-tabs
+sphinx-lint
sphinxcontrib-restbuilder
sphinxcontrib-spelling
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/doc/requirements.pip
new/coverage-7.5.3/doc/requirements.pip
--- old/coverage-7.5.1/doc/requirements.pip 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/doc/requirements.pip 2024-05-28 15:52:29.000000000
+0200
@@ -12,7 +12,7 @@
# watchfiles
attrs==23.2.0
# via scriv
-babel==2.14.0
+babel==2.15.0
# via sphinx
certifi==2024.2.2
# via requests
@@ -45,7 +45,7 @@
# requests
imagesize==1.4.1
# via sphinx
-jinja2==3.1.3
+jinja2==3.1.4
# via
# scriv
# sphinx
@@ -59,14 +59,18 @@
# via sphinx
pbr==6.0.0
# via stevedore
+polib==1.2.0
+ # via sphinx-lint
pyenchant==3.2.2
# via
# -r doc/requirements.in
# sphinxcontrib-spelling
-pygments==2.17.2
+pygments==2.18.0
# via
# doc8
# sphinx
+regex==2024.4.28
+ # via sphinx-lint
requests==2.31.0
# via
# scriv
@@ -92,6 +96,8 @@
# via -r doc/requirements.in
sphinx-code-tabs==0.5.5
# via -r doc/requirements.in
+sphinx-lint==0.9.1
+ # via -r doc/requirements.in
sphinx-rtd-theme==2.0.0
# via -r doc/requirements.in
sphinxcontrib-applehelp==1.0.8
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/howto.txt new/coverage-7.5.3/howto.txt
--- old/coverage-7.5.1/howto.txt 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/howto.txt 2024-05-28 15:52:29.000000000 +0200
@@ -34,10 +34,11 @@
- check in the new sample html
$ make relcommit2
- Done with changes to source files
- - check them in on the release prep branch
- - wait for ci to finish
- - merge to master
- - git push
+ - g puo; gshipit
+ - check them in on the release prep branch
+ - wait for ci to finish
+ - merge to master
+ - git push
- Start the kits:
- opvars github
- Trigger the kit GitHub Action
@@ -77,10 +78,8 @@
- IF NOT PRE-RELEASE:
- @ https://readthedocs.org/dashboard/coverage/advanced/
- change the "default version" to the new version
- - @ https://readthedocs.org/projects/coverage/builds/
- - manually build "latest"
- - wait for the new tag build to finish successfully.
- Once CI passes, merge the bump-version branch to master and push it
+ - gshipit
- things to automate:
- readthedocs api to do the readthedocs changes
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/igor.py new/coverage-7.5.3/igor.py
--- old/coverage-7.5.1/igor.py 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/igor.py 2024-05-28 15:52:29.000000000 +0200
@@ -248,6 +248,7 @@
os.getenv("COVERAGE_DYNCTX") or os.getenv("COVERAGE_CONTEXT"),
)
cov.html_report(show_contexts=show_contexts)
+ cov.json_report(show_contexts=show_contexts, pretty_print=True)
def do_test_with_core(core, *runner_args):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/lab/extract_code.py
new/coverage-7.5.3/lab/extract_code.py
--- old/coverage-7.5.1/lab/extract_code.py 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/lab/extract_code.py 2024-05-28 15:52:29.000000000
+0200
@@ -5,8 +5,8 @@
Use this to copy some indented code from the coverage.py test suite into a
standalone file for deeper testing, or writing bug reports.
-Give it a file name and a line number, and it will find the indentend
-multiline string containing that line number, and output the dedented
+Give it a file name and a line number, and it will find the indented
+multi-line string containing that line number, and output the dedented
contents of the string.
If tests/test_arcs.py has this (partial) content::
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/lab/parser.py
new/coverage-7.5.3/lab/parser.py
--- old/coverage-7.5.1/lab/parser.py 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/lab/parser.py 2024-05-28 15:52:29.000000000 +0200
@@ -80,7 +80,7 @@
if options.dis:
print("Main code:")
- disassemble(pyparser)
+ disassemble(pyparser.text)
arcs = pyparser.arcs()
@@ -95,8 +95,8 @@
exit_counts = pyparser.exit_counts()
- for lineno, ltext in enumerate(pyparser.lines, start=1):
- marks = [' ', ' ', ' ', ' ', ' ']
+ for lineno, ltext in enumerate(pyparser.text.splitlines(),
start=1):
+ marks = [' '] * 6
a = ' '
if lineno in pyparser.raw_statements:
marks[0] = '-'
@@ -110,7 +110,13 @@
if lineno in pyparser.raw_classdefs:
marks[3] = 'C'
if lineno in pyparser.raw_excluded:
- marks[4] = 'x'
+ marks[4] = 'X'
+ elif lineno in pyparser.excluded:
+ marks[4] = 'Ã'
+ if lineno in pyparser._multiline.values():
+ marks[5] = 'o'
+ elif lineno in pyparser._multiline.keys():
+ marks[5] = '.'
if arc_chars:
a = arc_chars[lineno].ljust(arc_width)
@@ -173,13 +179,13 @@
yield code
-def disassemble(pyparser):
+def disassemble(text):
"""Disassemble code, for ad-hoc experimenting."""
- code = compile(pyparser.text, "", "exec", dont_inherit=True)
+ code = compile(text, "", "exec", dont_inherit=True)
for code_obj in all_code_objects(code):
- if pyparser.text:
- srclines = pyparser.text.splitlines()
+ if text:
+ srclines = text.splitlines()
else:
srclines = None
print("\n%s: " % code_obj)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/tests/coveragetest.py
new/coverage-7.5.3/tests/coveragetest.py
--- old/coverage-7.5.1/tests/coveragetest.py 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/tests/coveragetest.py 2024-05-28 15:52:29.000000000
+0200
@@ -151,7 +151,7 @@
self,
text: str,
lines: Sequence[TLineNo] | Sequence[list[TLineNo]] | None = None,
- missing: str | Sequence[str] = "",
+ missing: str = "",
report: str = "",
excludes: Iterable[str] | None = None,
partials: Iterable[str] = (),
@@ -226,15 +226,8 @@
assert False, f"None of the lines choices matched
{statements!r}"
missing_formatted = analysis.missing_formatted()
- if isinstance(missing, str):
- msg = f"missing: {missing_formatted!r} != {missing!r}"
- assert missing_formatted == missing, msg
- else:
- for missing_list in missing:
- if missing_formatted == missing_list:
- break
- else:
- assert False, f"None of the missing choices matched
{missing_formatted!r}"
+ msg = f"missing: {missing_formatted!r} != {missing!r}"
+ assert missing_formatted == missing, msg
if arcs is not None:
# print("Possible arcs:")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/coverage-7.5.1/tests/gold/html/support/coverage_html.js
new/coverage-7.5.3/tests/gold/html/support/coverage_html.js
--- old/coverage-7.5.1/tests/gold/html/support/coverage_html.js 2024-05-04
16:44:25.000000000 +0200
+++ new/coverage-7.5.3/tests/gold/html/support/coverage_html.js 2024-05-28
15:52:29.000000000 +0200
@@ -125,6 +125,16 @@
// Create the events for the filter box.
coverage.wire_up_filter = function () {
+ // Populate the filter and hide100 inputs if there are saved values for
them.
+ const saved_filter_value = localStorage.getItem(coverage.FILTER_STORAGE);
+ if (saved_filter_value) {
+ document.getElementById("filter").value = saved_filter_value;
+ }
+ const saved_hide100_value = localStorage.getItem(coverage.HIDE100_STORAGE);
+ if (saved_hide100_value) {
+ document.getElementById("hide100").checked =
JSON.parse(saved_hide100_value);
+ }
+
// Cache elements.
const table = document.querySelector("table.index");
const table_body_rows = table.querySelectorAll("tbody tr");
@@ -138,8 +148,12 @@
totals[totals.length - 1] = { "numer": 0, "denom": 0 }; // nosemgrep:
eslint.detect-object-injection
var text = document.getElementById("filter").value;
+ // Store filter value
+ localStorage.setItem(coverage.FILTER_STORAGE, text);
const casefold = (text === text.toLowerCase());
const hide100 = document.getElementById("hide100").checked;
+ // Store hide value.
+ localStorage.setItem(coverage.HIDE100_STORAGE,
JSON.stringify(hide100));
// Hide / show elements.
table_body_rows.forEach(row => {
@@ -240,6 +254,8 @@
document.getElementById("filter").dispatchEvent(new Event("input"));
document.getElementById("hide100").dispatchEvent(new Event("input"));
};
+coverage.FILTER_STORAGE = "COVERAGE_FILTER_VALUE";
+coverage.HIDE100_STORAGE = "COVERAGE_HIDE100_VALUE";
// Set up the click-to-sort columns.
coverage.wire_up_sorting = function () {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/tests/helpers.py
new/coverage-7.5.3/tests/helpers.py
--- old/coverage-7.5.1/tests/helpers.py 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/tests/helpers.py 2024-05-28 15:52:29.000000000 +0200
@@ -9,6 +9,7 @@
import contextlib
import dis
import io
+import locale
import os
import os.path
import re
@@ -28,7 +29,6 @@
from coverage import env
from coverage.debug import DebugControl
from coverage.exceptions import CoverageWarning
-from coverage.misc import output_encoding
from coverage.types import TArc, TLineNo
@@ -44,11 +44,13 @@
with open("/tmp/processes.txt", "a") as proctxt: # type:
ignore[unreachable]
print(os.getenv("PYTEST_CURRENT_TEST", "unknown"), file=proctxt,
flush=True)
+ encoding = os.device_encoding(1) or locale.getpreferredencoding()
+
# In some strange cases (PyPy3 in a virtualenv!?) the stdout encoding of
# the subprocess is set incorrectly to ascii. Use an environment variable
# to force the encoding to be the same as ours.
sub_env = dict(os.environ)
- sub_env['PYTHONIOENCODING'] = output_encoding()
+ sub_env['PYTHONIOENCODING'] = encoding
proc = subprocess.Popen(
cmd,
@@ -62,7 +64,7 @@
status = proc.returncode
# Get the output, and canonicalize it to strings with newlines.
- output_str = output.decode(output_encoding()).replace("\r", "")
+ output_str = output.decode(encoding).replace("\r", "")
return status, output_str
@@ -114,8 +116,11 @@
print(f"# {os.path.abspath(filename)}", file=fdis)
cur_test = os.getenv("PYTEST_CURRENT_TEST", "unknown")
print(f"# PYTEST_CURRENT_TEST = {cur_test}", file=fdis)
+ kwargs = {}
+ if env.PYVERSION >= (3, 13):
+ kwargs["show_offsets"] = True
try:
- dis.dis(text, file=fdis)
+ dis.dis(text, file=fdis, **kwargs)
except Exception as exc:
# Some tests make .py files that aren't Python, so dis will
# fail, which is expected.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/tests/test_coverage.py
new/coverage-7.5.3/tests/test_coverage.py
--- old/coverage-7.5.1/tests/test_coverage.py 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/tests/test_coverage.py 2024-05-28 15:52:29.000000000
+0200
@@ -41,15 +41,6 @@
[1,2,3],
missing="3",
)
- # You can specify a list of possible missing lines.
- self.check_coverage("""\
- a = 1
- if a == 2:
- a = 3
- """,
- [1,2,3],
- missing=("47-49", "3", "100,102"),
- )
def test_failed_coverage(self) -> None:
# If the lines are wrong, the message shows right and wrong.
@@ -79,17 +70,6 @@
[1,2,3],
missing="37",
)
- # If the missing lines possibilities are wrong, the msg shows right.
- msg = r"None of the missing choices matched '3'"
- with pytest.raises(AssertionError, match=msg):
- self.check_coverage("""\
- a = 1
- if a == 2:
- a = 3
- """,
- [1,2,3],
- missing=("37", "4-10"),
- )
def test_exceptions_really_fail(self) -> None:
# An assert in the checked code will really raise up to us.
@@ -502,6 +482,7 @@
)
def test_strange_unexecuted_continue(self) -> None:
+ # This used to be true, but no longer is:
# Peephole optimization of jumps to jumps can mean that some statements
# never hit the line tracer. The behavior is different in different
# versions of Python, so be careful when running this test.
@@ -529,7 +510,7 @@
assert a == 33 and b == 50 and c == 50
""",
lines=[1,2,3,4,5,6,8,9,10, 12,13,14,15,16,17,19,20,21],
- missing=["", "6"],
+ missing="",
)
def test_import(self) -> None:
@@ -682,14 +663,13 @@
""",
[2, 3],
)
- lines = [2, 3, 4]
self.check_coverage("""\
- # Start with a comment, because it changes the behavior(!?)
+ # Start with a comment, even though it doesn't change the behavior.
'''I am a module docstring.'''
a = 3
b = 4
""",
- lines,
+ [3, 4],
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/tests/test_parser.py
new/coverage-7.5.3/tests/test_parser.py
--- old/coverage-7.5.1/tests/test_parser.py 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/tests/test_parser.py 2024-05-28 15:52:29.000000000
+0200
@@ -124,30 +124,23 @@
""")
assert parser.exit_counts() == { 1:1, 2:1, 3:1, 6:1 }
- def test_indentation_error(self) -> None:
- msg = (
- "Couldn't parse '<code>' as Python source: " +
- "'unindent does not match any outer indentation level.*' at line 3"
- )
- with pytest.raises(NotPython, match=msg):
- _ = self.parse_text("""\
- 0 spaces
- 2
- 1
- """)
-
- def test_token_error(self) -> None:
- submsgs = [
- r"EOF in multi-line string",
# before 3.12.0b1
- r"unterminated triple-quoted string literal .detected at line 1.",
# after 3.12.0b1
- ]
- msg = (
- r"Couldn't parse '<code>' as Python source: '"
- + r"(" + "|".join(submsgs) + ")"
- + r"' at line 1"
- )
+ @pytest.mark.parametrize("text", [
+ pytest.param("0 spaces\n 2\n 1", id="bad_indent"),
+ pytest.param("'''", id="string_eof"),
+ pytest.param("$hello", id="dollar"),
+ # on 3.10 this passes ast.parse but fails on tokenize.generate_tokens
+ pytest.param(
+ "\r'\\\n'''",
+ id="leading_newline_eof",
+ marks=[
+ pytest.mark.skipif(env.PYVERSION >= (3, 12), reason="parses
fine in 3.12"),
+ ]
+ )
+ ])
+ def test_not_python(self, text: str) -> None:
+ msg = r"Couldn't parse '<code>' as Python source: '.*' at line \d+"
with pytest.raises(NotPython, match=msg):
- _ = self.parse_text("'''")
+ _ = self.parse_text(text)
def test_empty_decorated_function(self) -> None:
parser = self.parse_text("""\
@@ -180,6 +173,20 @@
assert expected_arcs == parser.arcs()
assert expected_exits == parser.exit_counts()
+ def test_module_docstrings(self) -> None:
+ parser = self.parse_text("""\
+ '''The docstring on line 1'''
+ a = 2
+ """)
+ assert {2} == parser.statements
+
+ parser = self.parse_text("""\
+ # Docstring is not line 1
+ '''The docstring on line 2'''
+ a = 3
+ """)
+ assert {3} == parser.statements
+
def test_fuzzed_double_parse(self) -> None:
# https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=50381
# The second parse used to raise `TypeError: 'NoneType' object is not
iterable`
@@ -740,6 +747,10 @@
assert parser.raw_statements == raw_statements
assert parser.statements == set()
+ @pytest.mark.xfail(
+ env.PYPY and env.PYVERSION[:2] == (3, 8),
+ reason="AST doesn't mark end of classes correctly",
+ )
def test_class_decorator_pragmas(self) -> None:
parser = self.parse_text("""\
class Foo(object):
@@ -754,6 +765,22 @@
assert parser.raw_statements == {1, 2, 3, 5, 6, 7, 8}
assert parser.statements == {1, 2, 3}
+ def test_over_exclusion_bug1779(self) -> None:
+ # https://github.com/nedbat/coveragepy/issues/1779
+ parser = self.parse_text("""\
+ import abc
+
+ class MyProtocol: # nocover 3
+ @abc.abstractmethod # nocover 4
+ def my_method(self) -> int:
+ ... # 6
+
+ def function() -> int:
+ return 9
+ """)
+ assert parser.raw_statements == {1, 3, 4, 5, 6, 8, 9}
+ assert parser.statements == {1, 8, 9}
+
class ParserMissingArcDescriptionTest(PythonParserTestBase):
"""Tests for PythonParser.missing_arc_description."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/tests/test_report.py
new/coverage-7.5.3/tests/test_report.py
--- old/coverage-7.5.1/tests/test_report.py 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/tests/test_report.py 2024-05-28 15:52:29.000000000
+0200
@@ -668,6 +668,34 @@
assert "not_covered.py 3 3 0.000000%" in report
assert "TOTAL 3 3 0.000000%" in report
+ def test_report_module_docstrings(self) -> None:
+ self.make_file("main.py", """\
+ # Line 1
+ '''Line 2 docstring.'''
+ import other
+ a = 4
+ """)
+ self.make_file("other.py", """\
+ '''Line 1'''
+ a = 2
+ """)
+ cov = coverage.Coverage()
+ self.start_import_stop(cov, "main")
+ report = self.get_report(cov)
+
+ # Name Stmts Miss Cover
+ # ------------------------------
+ # main.py 2 0 100%
+ # other.py 1 0 100%
+ # ------------------------------
+ # TOTAL 3 0 100%
+
+ assert self.line_count(report) == 6, report
+ squeezed = self.squeezed_lines(report)
+ assert squeezed[2] == "main.py 2 0 100%"
+ assert squeezed[3] == "other.py 1 0 100%"
+ assert squeezed[5] == "TOTAL 3 0 100%"
+
def test_dotpy_not_python(self) -> None:
# We run a .py file, and when reporting, we can't parse it as Python.
# We should get an error message in the report.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/tests/test_setup.py
new/coverage-7.5.3/tests/test_setup.py
--- old/coverage-7.5.1/tests/test_setup.py 2024-05-04 16:44:25.000000000
+0200
+++ new/coverage-7.5.3/tests/test_setup.py 2024-05-28 15:52:29.000000000
+0200
@@ -9,7 +9,10 @@
from typing import List, cast
+import pytest
+
import coverage
+from coverage import env
from tests.coveragetest import CoverageTest
@@ -35,6 +38,10 @@
assert "github.com/nedbat/coveragepy" in out[2]
assert "Ned Batchelder" in out[3]
+ @pytest.mark.skipif(
+ env.PYVERSION[3:5] == ("alpha", 0),
+ reason="don't expect classifiers until labelled builds",
+ )
def test_more_metadata(self) -> None:
# Let's be sure we pick up our own setup.py
# CoverageTest restores the original sys.path for us.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/coverage-7.5.1/tox.ini new/coverage-7.5.3/tox.ini
--- old/coverage-7.5.1/tox.ini 2024-05-04 16:44:25.000000000 +0200
+++ new/coverage-7.5.3/tox.ini 2024-05-28 15:52:29.000000000 +0200
@@ -46,7 +46,7 @@
python -m pip install {env:COVERAGE_PIP_ARGS} -q -e .
python igor.py test_with_core ctrace {posargs}
- py3{12,13},anypy: python igor.py test_with_core sysmon {posargs}
+ py3{12,13,14},anypy: python igor.py test_with_core sysmon {posargs}
# Remove the C extension so that we can test the PyTracer
python igor.py remove_extension
@@ -76,6 +76,7 @@
# If this command fails, see the comment at the top of doc/cmd.rst
python -m cogapp -cP --check --verbosity=1 doc/*.rst
doc8 -q --ignore-path 'doc/_*' doc CHANGES.rst README.rst
+ sphinx-lint doc CHANGES.rst README.rst
sphinx-build -b html -aEnqW doc doc/_build/html
rst2html.py --strict README.rst doc/_build/trash
- sphinx-build -b html -b linkcheck -aEnq doc doc/_build/html
@@ -96,7 +97,7 @@
# If this command fails, see the comment at the top of doc/cmd.rst
python -m cogapp -cP --check --verbosity=1 doc/*.rst
python -m cogapp -cP --check --verbosity=1 .github/workflows/*.yml
- python -m pylint --notes= --ignore-paths 'doc/_build/.*' {env:LINTABLE}
+ python -m pylint -j 0 --notes= --ignore-paths 'doc/_build/.*'
{env:LINTABLE}
check-manifest --ignore 'doc/sample_html/*,.treerc'
# If 'build -q' becomes a thing (https://github.com/pypa/build/issues/188),
# this can be simplified:
@@ -128,4 +129,5 @@
3.11 = py311
3.12 = py312
3.13 = py313
+ 3.14 = py314
pypy-3 = pypy3