Skip to content

Commit 7e85e78

Browse files
committed
debug: pybehave is now an option on coverage debug
1 parent 7bd23c8 commit 7e85e78

File tree

9 files changed

+76
-46
lines changed

9 files changed

+76
-46
lines changed

CHANGES.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ development at the same time, such as 4.5.x and 5.0.
2020
Unreleased
2121
----------
2222

23-
- Debug: added ``pybehave`` to the list of :ref:`cmd_run_debug` options.
23+
- Debug: added ``pybehave`` to the list of :ref:`cmd_debug` and
24+
:ref:`cmd_run_debug` options.
2425

2526

2627
.. _changes_631:

coverage/cmdline.py

+11-14
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from coverage.config import CoverageConfig
2020
from coverage.control import DEFAULT_DATAFILE
2121
from coverage.data import combinable_files, debug_data_file
22-
from coverage.debug import info_formatter, info_header, short_stack
22+
from coverage.debug import info_header, short_stack, write_formatted_info
2323
from coverage.exceptions import _BaseCoverageException, _ExceptionDuringRun, NoSource
2424
from coverage.execfile import PyRunner
2525
from coverage.results import Numbers, should_fail_under
@@ -400,7 +400,8 @@ def get_prog_name(self):
400400
"'data' to show a summary of the collected data; " +
401401
"'sys' to show installation information; " +
402402
"'config' to show the configuration; " +
403-
"'premain' to show what is calling coverage."
403+
"'premain' to show what is calling coverage; " +
404+
"'pybehave' to show internal flags describing Python behavior."
404405
),
405406
),
406407

@@ -843,32 +844,28 @@ def do_debug(self, args):
843844
"""Implementation of 'coverage debug'."""
844845

845846
if not args:
846-
show_help("What information would you like: config, data, sys, premain?")
847+
show_help("What information would you like: config, data, sys, premain, pybehave?")
847848
return ERR
848849
if args[1:]:
849850
show_help("Only one topic at a time, please")
850851
return ERR
851852

852-
if args[0] == 'sys':
853-
sys_info = self.coverage.sys_info()
854-
print(info_header("sys"))
855-
for line in info_formatter(sys_info):
856-
print(f" {line}")
857-
elif args[0] == 'data':
853+
if args[0] == "sys":
854+
write_formatted_info(print, "sys", self.coverage.sys_info())
855+
elif args[0] == "data":
858856
print(info_header("data"))
859857
data_file = self.coverage.config.data_file
860858
debug_data_file(data_file)
861859
for filename in combinable_files(data_file):
862860
print("-----")
863861
debug_data_file(filename)
864-
elif args[0] == 'config':
865-
print(info_header("config"))
866-
config_info = sorted(self.coverage.config.__dict__.items())
867-
for line in info_formatter(config_info):
868-
print(f" {line}")
862+
elif args[0] == "config":
863+
write_formatted_info(print, "config", self.coverage.config.debug_info())
869864
elif args[0] == "premain":
870865
print(info_header("premain"))
871866
print(short_stack())
867+
elif args[0] == "pybehave":
868+
write_formatted_info(print, "pybehave", env.debug_info())
872869
else:
873870
show_help(f"Don't know what you mean by {args[0]!r}")
874871
return ERR

coverage/config.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import re
1212

1313
from coverage.exceptions import ConfigError
14-
from coverage.misc import contract, isolate_module, substitute_variables
14+
from coverage.misc import contract, isolate_module, human_sorted_items, substitute_variables
1515

1616
from coverage.tomlconfig import TomlConfigParser, TomlDecodeError
1717

@@ -495,6 +495,12 @@ def post_process(self):
495495
for k, v in self.paths.items()
496496
)
497497

498+
def debug_info(self):
499+
"""Make a list of (name, value) pairs for writing debug info."""
500+
return human_sorted_items(
501+
(k, v) for k, v in self.__dict__.items() if not k.startswith("_")
502+
)
503+
498504

499505
def config_files_to_try(config_file):
500506
"""What config files should we try to read?

coverage/control.py

+10-20
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from coverage.inorout import InOrOut
3030
from coverage.jsonreport import JsonReporter
3131
from coverage.lcovreport import LcovReporter
32-
from coverage.misc import bool_or_none, join_regex, human_sorted, human_sorted_items
32+
from coverage.misc import bool_or_none, join_regex, human_sorted
3333
from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module
3434
from coverage.plugin import FileReporter
3535
from coverage.plugin_support import Plugins
@@ -315,35 +315,25 @@ def _write_startup_debug(self):
315315
"""Write out debug info at startup if needed."""
316316
wrote_any = False
317317
with self._debug.without_callers():
318-
if self._debug.should('config'):
319-
config_info = human_sorted_items(self.config.__dict__.items())
320-
config_info = [(k, v) for k, v in config_info if not k.startswith('_')]
321-
write_formatted_info(self._debug, "config", config_info)
318+
if self._debug.should("config"):
319+
config_info = self.config.debug_info()
320+
write_formatted_info(self._debug.write, "config", config_info)
322321
wrote_any = True
323322

324-
if self._debug.should('sys'):
325-
write_formatted_info(self._debug, "sys", self.sys_info())
323+
if self._debug.should("sys"):
324+
write_formatted_info(self._debug.write, "sys", self.sys_info())
326325
for plugin in self._plugins:
327326
header = "sys: " + plugin._coverage_plugin_name
328327
info = plugin.sys_info()
329-
write_formatted_info(self._debug, header, info)
328+
write_formatted_info(self._debug.write, header, info)
330329
wrote_any = True
331330

332-
if self._debug.should('pybehave'):
333-
info = [
334-
(name, value) for name, value in env.__dict__.items()
335-
if not name.startswith("_") and
336-
name != "PYBEHAVIOR" and
337-
not isinstance(value, type(os))
338-
] + [
339-
(name, value) for name, value in env.PYBEHAVIOR.__dict__.items()
340-
if not name.startswith("_")
341-
]
342-
write_formatted_info(self._debug, "pybehave", sorted(info))
331+
if self._debug.should("pybehave"):
332+
write_formatted_info(self._debug.write, "pybehave", env.debug_info())
343333
wrote_any = True
344334

345335
if wrote_any:
346-
write_formatted_info(self._debug, "end", ())
336+
write_formatted_info(self._debug.write, "end", ())
347337

348338
def _should_trace(self, filename, frame):
349339
"""Decide whether to trace execution in `filename`.

coverage/debug.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -130,17 +130,18 @@ def info_formatter(info):
130130
yield "%*s: %s" % (label_len, label, data)
131131

132132

133-
def write_formatted_info(writer, header, info):
133+
def write_formatted_info(write, header, info):
134134
"""Write a sequence of (label,data) pairs nicely.
135135
136-
`writer` has a .write(str) method. `header` is a string to start the
137-
section. `info` is a sequence of (label, data) pairs, where label
138-
is a str, and data can be a single value, or a list/set/tuple.
136+
`write` is a function write(str) that accepts each line of output.
137+
`header` is a string to start the section. `info` is a sequence of
138+
(label, data) pairs, where label is a str, and data can be a single
139+
value, or a list/set/tuple.
139140
140141
"""
141-
writer.write(info_header(header))
142+
write(info_header(header))
142143
for line in info_formatter(info):
143-
writer.write(" %s" % line)
144+
write(f" {line}")
144145

145146

146147
def short_stack(limit=None, skip=0):

coverage/env.py

+14
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,17 @@ class PYBEHAVIOR:
133133
and not bool(int(os.environ.get("COVERAGE_NO_CONTRACTS", 0)))
134134
and (PYVERSION < (3, 11))
135135
)
136+
137+
def debug_info():
138+
"""Return a list of (name, value) pairs for printing debug information."""
139+
info = [
140+
(name, value) for name, value in globals().items()
141+
if not name.startswith("_") and
142+
name not in {"PYBEHAVIOR", "debug_info"} and
143+
not isinstance(value, type(os))
144+
]
145+
info += [
146+
(name, value) for name, value in PYBEHAVIOR.__dict__.items()
147+
if not name.startswith("_")
148+
]
149+
return sorted(info)

doc/cmd.rst

+5-3
Original file line numberDiff line numberDiff line change
@@ -922,12 +922,13 @@ command can often help::
922922

923923
$ coverage debug sys > please_attach_to_bug_report.txt
924924

925-
Three types of information are available:
925+
A few types of information are available:
926926

927927
* ``config``: show coverage's configuration
928928
* ``sys``: show system configuration
929929
* ``data``: show a summary of the collected coverage data
930930
* ``premain``: show the call stack invoking coverage
931+
* ``pybehave``: show internal flags describing Python behavior
931932

932933
.. [[[cog show_help("debug") ]]]
933934
.. code::
@@ -938,15 +939,16 @@ Three types of information are available:
938939
Display information about the internals of coverage.py, for diagnosing
939940
problems. Topics are: 'data' to show a summary of the collected data; 'sys' to
940941
show installation information; 'config' to show the configuration; 'premain'
941-
to show what is calling coverage.
942+
to show what is calling coverage; 'pybehave' to show internal flags describing
943+
Python behavior.
942944
943945
Options:
944946
--debug=OPTS Debug options, separated by commas. [env: COVERAGE_DEBUG]
945947
-h, --help Get help on this command.
946948
--rcfile=RCFILE Specify configuration file. By default '.coveragerc',
947949
'setup.cfg', 'tox.ini', and 'pyproject.toml' are tried.
948950
[env: COVERAGE_RCFILE]
949-
.. [[[end]]] (checksum: 66c36bb462796800400d588fa5a71c5f)
951+
.. [[[end]]] (checksum: c9b8dfb644da3448830b1c99bffa6880)
950952
951953
.. _cmd_run_debug:
952954

tests/test_cmdline.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ def test_combine_doesnt_confuse_options_with_args(self):
272272
""")
273273

274274
@pytest.mark.parametrize("cmd, output", [
275-
("debug", "What information would you like: config, data, sys, premain?"),
275+
("debug", "What information would you like: config, data, sys, premain, pybehave?"),
276276
("debug foo", "Don't know what you mean by 'foo'"),
277277
("debug sys config", "Only one topic at a time, please"),
278278
])
@@ -292,6 +292,16 @@ def test_debug_config(self):
292292
assert "skip_covered:" in out
293293
assert "skip_empty:" in out
294294

295+
def test_debug_pybehave(self):
296+
self.command_line("debug pybehave")
297+
out = self.stdout()
298+
assert " CPYTHON:" in out
299+
assert " PYVERSION:" in out
300+
assert " pep626:" in out
301+
pyversion = next(l for l in out.splitlines() if " PYVERSION:" in l)
302+
vtuple = eval(pyversion.partition(":")[-1]) # pylint: disable=eval-used
303+
assert vtuple[:5] == sys.version_info
304+
295305
def test_debug_premain(self):
296306
self.command_line("debug premain")
297307
out = self.stdout()

tests/test_debug.py

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io
77
import os
88
import re
9+
import sys
910

1011
import pytest
1112

@@ -197,6 +198,14 @@ def test_debug_sys_ctracer(self):
197198
expected = "CTracer: unavailable"
198199
assert expected == tracer_line
199200

201+
def test_debug_pybehave(self):
202+
out_text = self.f1_debug_output(["pybehave"])
203+
out_lines = out_text.splitlines()
204+
assert 10 < len(out_lines) < 40
205+
pyversion = next(l for l in out_lines if " PYVERSION:" in l)
206+
vtuple = eval(pyversion.partition(":")[-1]) # pylint: disable=eval-used
207+
assert vtuple[:5] == sys.version_info
208+
200209

201210
def f_one(*args, **kwargs):
202211
"""First of the chain of functions for testing `short_stack`."""

0 commit comments

Comments
 (0)