diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2022-02-06 17:42:53 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2022-02-06 17:43:26 -0500 |
commit | 7e85e782bc24fa487d77aff3356eaf04db764d21 (patch) | |
tree | d238edc7245c83ae73c2d3482507bf8423ad3710 | |
parent | 7bd23c8ae4f219136332501ecd1767ed16ceb559 (diff) | |
download | python-coveragepy-git-7e85e782bc24fa487d77aff3356eaf04db764d21.tar.gz |
debug: pybehave is now an option on `coverage debug`
-rw-r--r-- | CHANGES.rst | 3 | ||||
-rw-r--r-- | coverage/cmdline.py | 25 | ||||
-rw-r--r-- | coverage/config.py | 8 | ||||
-rw-r--r-- | coverage/control.py | 30 | ||||
-rw-r--r-- | coverage/debug.py | 13 | ||||
-rw-r--r-- | coverage/env.py | 14 | ||||
-rw-r--r-- | doc/cmd.rst | 8 | ||||
-rw-r--r-- | tests/test_cmdline.py | 12 | ||||
-rw-r--r-- | tests/test_debug.py | 9 |
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`.""" |