summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2022-02-06 17:42:53 -0500
committerNed Batchelder <ned@nedbatchelder.com>2022-02-06 17:43:26 -0500
commit7e85e782bc24fa487d77aff3356eaf04db764d21 (patch)
treed238edc7245c83ae73c2d3482507bf8423ad3710
parent7bd23c8ae4f219136332501ecd1767ed16ceb559 (diff)
downloadpython-coveragepy-git-7e85e782bc24fa487d77aff3356eaf04db764d21.tar.gz
debug: pybehave is now an option on `coverage debug`
-rw-r--r--CHANGES.rst3
-rw-r--r--coverage/cmdline.py25
-rw-r--r--coverage/config.py8
-rw-r--r--coverage/control.py30
-rw-r--r--coverage/debug.py13
-rw-r--r--coverage/env.py14
-rw-r--r--doc/cmd.rst8
-rw-r--r--tests/test_cmdline.py12
-rw-r--r--tests/test_debug.py9
9 files changed, 76 insertions, 46 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index d391dc9e..2111d875 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -20,7 +20,8 @@ development at the same time, such as 4.5.x and 5.0.
Unreleased
----------
-- Debug: added ``pybehave`` to the list of :ref:`cmd_run_debug` options.
+- Debug: added ``pybehave`` to the list of :ref:`cmd_debug` and
+ :ref:`cmd_run_debug` options.
.. _changes_631:
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index 4c850801..0c44378a 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -19,7 +19,7 @@ from coverage.collector import CTracer
from coverage.config import CoverageConfig
from coverage.control import DEFAULT_DATAFILE
from coverage.data import combinable_files, debug_data_file
-from coverage.debug import info_formatter, info_header, short_stack
+from coverage.debug import info_header, short_stack, write_formatted_info
from coverage.exceptions import _BaseCoverageException, _ExceptionDuringRun, NoSource
from coverage.execfile import PyRunner
from coverage.results import Numbers, should_fail_under
@@ -400,7 +400,8 @@ COMMANDS = {
"'data' to show a summary of the collected data; " +
"'sys' to show installation information; " +
"'config' to show the configuration; " +
- "'premain' to show what is calling coverage."
+ "'premain' to show what is calling coverage; " +
+ "'pybehave' to show internal flags describing Python behavior."
),
),
@@ -843,32 +844,28 @@ class CoverageScript:
"""Implementation of 'coverage debug'."""
if not args:
- show_help("What information would you like: config, data, sys, premain?")
+ show_help("What information would you like: config, data, sys, premain, pybehave?")
return ERR
if args[1:]:
show_help("Only one topic at a time, please")
return ERR
- if args[0] == 'sys':
- sys_info = self.coverage.sys_info()
- print(info_header("sys"))
- for line in info_formatter(sys_info):
- print(f" {line}")
- elif args[0] == 'data':
+ if args[0] == "sys":
+ write_formatted_info(print, "sys", self.coverage.sys_info())
+ elif args[0] == "data":
print(info_header("data"))
data_file = self.coverage.config.data_file
debug_data_file(data_file)
for filename in combinable_files(data_file):
print("-----")
debug_data_file(filename)
- elif args[0] == 'config':
- print(info_header("config"))
- config_info = sorted(self.coverage.config.__dict__.items())
- for line in info_formatter(config_info):
- print(f" {line}")
+ elif args[0] == "config":
+ write_formatted_info(print, "config", self.coverage.config.debug_info())
elif args[0] == "premain":
print(info_header("premain"))
print(short_stack())
+ elif args[0] == "pybehave":
+ write_formatted_info(print, "pybehave", env.debug_info())
else:
show_help(f"Don't know what you mean by {args[0]!r}")
return ERR
diff --git a/coverage/config.py b/coverage/config.py
index 9909c530..fbfb59f4 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -11,7 +11,7 @@ import os.path
import re
from coverage.exceptions import ConfigError
-from coverage.misc import contract, isolate_module, substitute_variables
+from coverage.misc import contract, isolate_module, human_sorted_items, substitute_variables
from coverage.tomlconfig import TomlConfigParser, TomlDecodeError
@@ -495,6 +495,12 @@ class CoverageConfig:
for k, v in self.paths.items()
)
+ def debug_info(self):
+ """Make a list of (name, value) pairs for writing debug info."""
+ return human_sorted_items(
+ (k, v) for k, v in self.__dict__.items() if not k.startswith("_")
+ )
+
def config_files_to_try(config_file):
"""What config files should we try to read?
diff --git a/coverage/control.py b/coverage/control.py
index a3fda8d8..0f9f675e 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -29,7 +29,7 @@ from coverage.html import HtmlReporter
from coverage.inorout import InOrOut
from coverage.jsonreport import JsonReporter
from coverage.lcovreport import LcovReporter
-from coverage.misc import bool_or_none, join_regex, human_sorted, human_sorted_items
+from coverage.misc import bool_or_none, join_regex, human_sorted
from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module
from coverage.plugin import FileReporter
from coverage.plugin_support import Plugins
@@ -315,35 +315,25 @@ class Coverage:
"""Write out debug info at startup if needed."""
wrote_any = False
with self._debug.without_callers():
- if self._debug.should('config'):
- config_info = human_sorted_items(self.config.__dict__.items())
- config_info = [(k, v) for k, v in config_info if not k.startswith('_')]
- write_formatted_info(self._debug, "config", config_info)
+ if self._debug.should("config"):
+ config_info = self.config.debug_info()
+ write_formatted_info(self._debug.write, "config", config_info)
wrote_any = True
- if self._debug.should('sys'):
- write_formatted_info(self._debug, "sys", self.sys_info())
+ if self._debug.should("sys"):
+ write_formatted_info(self._debug.write, "sys", self.sys_info())
for plugin in self._plugins:
header = "sys: " + plugin._coverage_plugin_name
info = plugin.sys_info()
- write_formatted_info(self._debug, header, info)
+ write_formatted_info(self._debug.write, header, info)
wrote_any = True
- if self._debug.should('pybehave'):
- info = [
- (name, value) for name, value in env.__dict__.items()
- if not name.startswith("_") and
- name != "PYBEHAVIOR" and
- not isinstance(value, type(os))
- ] + [
- (name, value) for name, value in env.PYBEHAVIOR.__dict__.items()
- if not name.startswith("_")
- ]
- write_formatted_info(self._debug, "pybehave", sorted(info))
+ if self._debug.should("pybehave"):
+ write_formatted_info(self._debug.write, "pybehave", env.debug_info())
wrote_any = True
if wrote_any:
- write_formatted_info(self._debug, "end", ())
+ write_formatted_info(self._debug.write, "end", ())
def _should_trace(self, filename, frame):
"""Decide whether to trace execution in `filename`.
diff --git a/coverage/debug.py b/coverage/debug.py
index e6f93aa6..8c5e3839 100644
--- a/coverage/debug.py
+++ b/coverage/debug.py
@@ -130,17 +130,18 @@ def info_formatter(info):
yield "%*s: %s" % (label_len, label, data)
-def write_formatted_info(writer, header, info):
+def write_formatted_info(write, header, info):
"""Write a sequence of (label,data) pairs nicely.
- `writer` has a .write(str) method. `header` is a string to start the
- section. `info` is a sequence of (label, data) pairs, where label
- is a str, and data can be a single value, or a list/set/tuple.
+ `write` is a function write(str) that accepts each line of output.
+ `header` is a string to start the section. `info` is a sequence of
+ (label, data) pairs, where label is a str, and data can be a single
+ value, or a list/set/tuple.
"""
- writer.write(info_header(header))
+ write(info_header(header))
for line in info_formatter(info):
- writer.write(" %s" % line)
+ write(f" {line}")
def short_stack(limit=None, skip=0):
diff --git a/coverage/env.py b/coverage/env.py
index 3b24c390..1922d93f 100644
--- a/coverage/env.py
+++ b/coverage/env.py
@@ -133,3 +133,17 @@ USE_CONTRACTS = (
and not bool(int(os.environ.get("COVERAGE_NO_CONTRACTS", 0)))
and (PYVERSION < (3, 11))
)
+
+def debug_info():
+ """Return a list of (name, value) pairs for printing debug information."""
+ info = [
+ (name, value) for name, value in globals().items()
+ if not name.startswith("_") and
+ name not in {"PYBEHAVIOR", "debug_info"} and
+ not isinstance(value, type(os))
+ ]
+ info += [
+ (name, value) for name, value in PYBEHAVIOR.__dict__.items()
+ if not name.startswith("_")
+ ]
+ return sorted(info)
diff --git a/doc/cmd.rst b/doc/cmd.rst
index 461021b1..215af530 100644
--- a/doc/cmd.rst
+++ b/doc/cmd.rst
@@ -922,12 +922,13 @@ command can often help::
$ coverage debug sys > please_attach_to_bug_report.txt
-Three types of information are available:
+A few types of information are available:
* ``config``: show coverage's configuration
* ``sys``: show system configuration
* ``data``: show a summary of the collected coverage data
* ``premain``: show the call stack invoking coverage
+* ``pybehave``: show internal flags describing Python behavior
.. [[[cog show_help("debug") ]]]
.. code::
@@ -938,7 +939,8 @@ Three types of information are available:
Display information about the internals of coverage.py, for diagnosing
problems. Topics are: 'data' to show a summary of the collected data; 'sys' to
show installation information; 'config' to show the configuration; 'premain'
- to show what is calling coverage.
+ to show what is calling coverage; 'pybehave' to show internal flags describing
+ Python behavior.
Options:
--debug=OPTS Debug options, separated by commas. [env: COVERAGE_DEBUG]
@@ -946,7 +948,7 @@ Three types of information are available:
--rcfile=RCFILE Specify configuration file. By default '.coveragerc',
'setup.cfg', 'tox.ini', and 'pyproject.toml' are tried.
[env: COVERAGE_RCFILE]
-.. [[[end]]] (checksum: 66c36bb462796800400d588fa5a71c5f)
+.. [[[end]]] (checksum: c9b8dfb644da3448830b1c99bffa6880)
.. _cmd_run_debug:
diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py
index 7c5e1373..462cecee 100644
--- a/tests/test_cmdline.py
+++ b/tests/test_cmdline.py
@@ -272,7 +272,7 @@ class CmdLineTest(BaseCmdLineTest):
""")
@pytest.mark.parametrize("cmd, output", [
- ("debug", "What information would you like: config, data, sys, premain?"),
+ ("debug", "What information would you like: config, data, sys, premain, pybehave?"),
("debug foo", "Don't know what you mean by 'foo'"),
("debug sys config", "Only one topic at a time, please"),
])
@@ -292,6 +292,16 @@ class CmdLineTest(BaseCmdLineTest):
assert "skip_covered:" in out
assert "skip_empty:" in out
+ def test_debug_pybehave(self):
+ self.command_line("debug pybehave")
+ out = self.stdout()
+ assert " CPYTHON:" in out
+ assert " PYVERSION:" in out
+ assert " pep626:" in out
+ pyversion = next(l for l in out.splitlines() if " PYVERSION:" in l)
+ vtuple = eval(pyversion.partition(":")[-1]) # pylint: disable=eval-used
+ assert vtuple[:5] == sys.version_info
+
def test_debug_premain(self):
self.command_line("debug premain")
out = self.stdout()
diff --git a/tests/test_debug.py b/tests/test_debug.py
index 12315967..bade588e 100644
--- a/tests/test_debug.py
+++ b/tests/test_debug.py
@@ -6,6 +6,7 @@
import io
import os
import re
+import sys
import pytest
@@ -197,6 +198,14 @@ class DebugTraceTest(CoverageTest):
expected = "CTracer: unavailable"
assert expected == tracer_line
+ def test_debug_pybehave(self):
+ out_text = self.f1_debug_output(["pybehave"])
+ out_lines = out_text.splitlines()
+ assert 10 < len(out_lines) < 40
+ pyversion = next(l for l in out_lines if " PYVERSION:" in l)
+ vtuple = eval(pyversion.partition(":")[-1]) # pylint: disable=eval-used
+ assert vtuple[:5] == sys.version_info
+
def f_one(*args, **kwargs):
"""First of the chain of functions for testing `short_stack`."""