From 150f8a921c224c9702c6ec94f3f76e1177aab9c7 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Tue, 11 Feb 2025 09:49:34 -0500 Subject: [PATCH 01/50] build: bump version to 7.6.13 --- CHANGES.rst | 6 ++++++ coverage/version.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index fcd12b863..b0b100a44 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,6 +20,12 @@ upgrading your version of coverage.py. .. Version 9.8.1 — 2027-07-27 .. -------------------------- +Unreleased +---------- + +Nothing yet. + + .. start-releases .. _changes_7-6-12: diff --git a/coverage/version.py b/coverage/version.py index e81549634..b7f776e5c 100644 --- a/coverage/version.py +++ b/coverage/version.py @@ -8,8 +8,8 @@ # version_info: same semantics as sys.version_info. # _dev: the .devN suffix if any. -version_info = (7, 6, 12, "final", 0) -_dev = 0 +version_info = (7, 6, 13, "alpha", 0) +_dev = 1 def _make_version( From 830d2eb48dbcc3050dc8d5398fbaf31e1b1b0344 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Thu, 13 Feb 2025 19:11:03 -0500 Subject: [PATCH 02/50] refactor: no need for ast nodes that don't exist anymore --- coverage/parser.py | 14 +++++++------- tests/test_parser.py | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/coverage/parser.py b/coverage/parser.py index a8bcbf1a6..de7e6f714 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -655,13 +655,13 @@ def __init__(self, body: Sequence[ast.AST]) -> None: def is_constant_test_expr(node: ast.AST) -> bool: - """Is this a compile-time constant test expression?""" - node_name = node.__class__.__name__ - if node_name in [ # in PYVERSIONS: - "Constant", # all - "NameConstant", # 9 10 11, gone in 12 - "Num", # 9 10 11, gone in 12 - ]: + """Is this a compile-time constant test expression? + + We don't try to mimic all of CPython's optimizations. We just have to + handle the kinds of constant expressions people might actually use. + + """ + if isinstance(node, ast.Constant): return True elif isinstance(node, ast.Name): if node.id in ["True", "False", "None", "__debug__"]: diff --git a/tests/test_parser.py b/tests/test_parser.py index eb228bf85..f566b2603 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1200,6 +1200,8 @@ def test_os_error(self) -> None: [ ("True", True), ("False", True), + ("1", True), + ("0", True), ("__debug__", True), ("not __debug__", True), ("not(__debug__)", True), From 1bf0db200d84ed980bb9434ccdaad7383e4cb188 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 15 Feb 2025 18:42:54 -0500 Subject: [PATCH 03/50] test: more convenience for when gold files need to change --- tests/gold/README.rst | 25 ++++++++++++------------- tests/gold/html/Makefile | 2 +- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/gold/README.rst b/tests/gold/README.rst index 00b43ff28..b80bc0f77 100644 --- a/tests/gold/README.rst +++ b/tests/gold/README.rst @@ -14,19 +14,18 @@ output is in the tests/actual directory. Those files are ignored by git. There's a Makefile in the html directory for working with gold files and their associated support files. -To view the tests/actual files, you need to tentatively copy them to the gold -directories, and then add the supporting files so they can be viewed as -complete output. For example:: +The gold files and the actual output files are not viewable as-is: they are +missing the support files (css etc) they need. You can copy those support +files with:: - cp tests/actual/html/contexts/* tests/gold/html/contexts cd tests/gold/html make complete -If the new actual output is correct, you can use "make update-gold" to copy the -actual output as the new gold files. +If the new actual output is correct, you can use ``make update-gold`` to copy +the actual output as the new gold files. -If you have changed some of the supporting files (.css or .js), then "make -update-support" will copy the updated files to the tests/gold/html/support +If you have changed some of the supporting files (.css or .js), then ``make +update-support`` will copy the updated files to the tests/gold/html/support directory for checking test output. If you have added a gold test, you'll need to manually copy the tests/actual @@ -39,11 +38,11 @@ again, you can run just the failed tests again with:: The saved HTML files in the html directories can't be viewed properly without the supporting CSS and Javascript files. But we don't want to save copies of -those files in every subdirectory. The make target "make complete" in +those files in every subdirectory. The make target ``make complete`` in tests/gold/html will copy the support files so you can open the HTML files to -see how they look. When you are done checking the output, you can use "make -clean" to remove the support files from the gold directories. +see how they look. When you are done checking the output, you can use ``make +clean`` to remove the support files from the gold directories. -If the output files are correct, you can update the gold files with "make -update-gold". If there are version-specific gold files (for example, +If the output files are correct, you can update the gold files with ``make +update-gold``. If there are version-specific gold files (for example, bom/2/\*), you'll need to update them manually. diff --git a/tests/gold/html/Makefile b/tests/gold/html/Makefile index 5ae08b44e..d9668ce68 100644 --- a/tests/gold/html/Makefile +++ b/tests/gold/html/Makefile @@ -6,7 +6,7 @@ help: @grep '^[a-zA-Z]' $(MAKEFILE_LIST) | sort | awk -F ':.*?## ' 'NF==2 {printf " %-26s%s\n", $$1, $$2}' complete: ## Copy support files into directories so the HTML can be viewed properly. - @for sub in *; do \ + @for sub in * ../../actual/html/*; do \ if [ -f "$$sub/index.html" ]; then \ echo Copying into $$sub ; \ cp -n support/* $$sub ; \ From 3e87fab41c9532e2bf8e5a5ccf2b9c3570a55cf4 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 15 Feb 2025 18:48:44 -0500 Subject: [PATCH 04/50] fix: better understanding of constant conditions --- CHANGES.rst | 5 +- coverage/parser.py | 31 +++++--- tests/gold/html/partial/class_index.html | 12 +-- tests/gold/html/partial/function_index.html | 12 +-- tests/gold/html/partial/index.html | 12 +-- tests/gold/html/partial/partial_py.html | 8 +- tests/gold/html/partial_626/class_index.html | 22 +++--- .../gold/html/partial_626/function_index.html | 22 +++--- tests/gold/html/partial_626/index.html | 22 +++--- tests/gold/html/partial_626/partial_py.html | 16 ++-- tests/test_arcs.py | 74 +++++++++++-------- tests/test_html.py | 2 +- tests/test_parser.py | 30 ++++---- 13 files changed, 147 insertions(+), 121 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index b0b100a44..bddce7bb3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -23,7 +23,10 @@ upgrading your version of coverage.py. Unreleased ---------- -Nothing yet. +- Many constant tests in if statements are now recognized as being optimized + away. For example, previously ``if 13:`` would have been considered a branch + with one path not taken. Now it is understood as always true and no coverage + is missing. .. start-releases diff --git a/coverage/parser.py b/coverage/parser.py index de7e6f714..37bedf341 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -654,7 +654,7 @@ def __init__(self, body: Sequence[ast.AST]) -> None: # TODO: Shouldn't the cause messages join with "and" instead of "or"? -def is_constant_test_expr(node: ast.AST) -> bool: +def is_constant_test_expr(node: ast.AST) -> tuple[bool, bool]: """Is this a compile-time constant test expression? We don't try to mimic all of CPython's optimizations. We just have to @@ -662,15 +662,20 @@ def is_constant_test_expr(node: ast.AST) -> bool: """ if isinstance(node, ast.Constant): - return True + return True, bool(node.value) elif isinstance(node, ast.Name): if node.id in ["True", "False", "None", "__debug__"]: - return True + return True, eval(node.id) # pylint: disable=eval-used elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not): - return is_constant_test_expr(node.operand) + is_constant, val = is_constant_test_expr(node.operand) + return is_constant, not val elif isinstance(node, ast.BoolOp): - return all(is_constant_test_expr(v) for v in node.values) - return False + rets = [is_constant_test_expr(v) for v in node.values] + is_constant = all(is_const for is_const, _ in rets) + if is_constant: + op = any if isinstance(node.op, ast.Or) else all + return True, op(v for _, v in rets) + return False, False class AstArcAnalyzer: @@ -1156,10 +1161,14 @@ def _handle__For(self, node: ast.For) -> set[ArcStart]: def _handle__If(self, node: ast.If) -> set[ArcStart]: start = self.line_for_node(node.test) - from_start = ArcStart(start, cause="the condition on line {lineno} was never true") - exits = self.process_body(node.body, from_start=from_start) - from_start = ArcStart(start, cause="the condition on line {lineno} was always true") - exits |= self.process_body(node.orelse, from_start=from_start) + constant_test, val = is_constant_test_expr(node.test) + exits = set() + if not constant_test or val: + from_start = ArcStart(start, cause="the condition on line {lineno} was never true") + exits |= self.process_body(node.body, from_start=from_start) + if not constant_test or not val: + from_start = ArcStart(start, cause="the condition on line {lineno} was always true") + exits |= self.process_body(node.orelse, from_start=from_start) return exits if sys.version_info >= (3, 10): @@ -1271,7 +1280,7 @@ def _handle__Try(self, node: ast.Try) -> set[ArcStart]: def _handle__While(self, node: ast.While) -> set[ArcStart]: start = to_top = self.line_for_node(node.test) - constant_test = is_constant_test_expr(node.test) + constant_test, _ = is_constant_test_expr(node.test) top_is_body0 = False if constant_test: top_is_body0 = True diff --git a/tests/gold/html/partial/class_index.html b/tests/gold/html/partial/class_index.html index e455ca6ba..14307fd8a 100644 --- a/tests/gold/html/partial/class_index.html +++ b/tests/gold/html/partial/class_index.html @@ -4,8 +4,8 @@ Coverage report - - + +
@@ -56,8 +56,8 @@

Classes

- coverage.py v7.5.1a0.dev1, - created at 2024-04-29 17:40 -0300 + coverage.py v7.6.13a0.dev1, + created at 2025-02-15 17:25 -0500

@@ -107,8 +107,8 @@

- coverage.py v7.5.1a0.dev1, - created at 2024-04-29 17:40 -0300 + coverage.py v7.6.13a0.dev1, + created at 2025-02-15 17:25 -0500

diff --git a/doc/sample_html/z_7b071bdc2a35fa80_utils_py.html b/doc/sample_html/z_7b071bdc2a35fa80_utils_py.html index a3a1f7013..2a6e08c45 100644 --- a/doc/sample_html/z_7b071bdc2a35fa80_utils_py.html +++ b/doc/sample_html/z_7b071bdc2a35fa80_utils_py.html @@ -66,8 +66,8 @@

^ index     » next       - coverage.py v7.6.12, - created at 2025-02-11 08:59 -0500 + coverage.py v7.7.0, + created at 2025-03-16 13:28 -0400