| | |
| | """Version info, help messages, tracing configuration.""" |
| |
|
| | from __future__ import annotations |
| |
|
| | from argparse import Action |
| | import os |
| | import sys |
| | from typing import Generator |
| |
|
| | from _pytest.config import Config |
| | from _pytest.config import ExitCode |
| | from _pytest.config import PrintHelp |
| | from _pytest.config.argparsing import Parser |
| | from _pytest.terminal import TerminalReporter |
| | import pytest |
| |
|
| |
|
| | class HelpAction(Action): |
| | """An argparse Action that will raise an exception in order to skip the |
| | rest of the argument parsing when --help is passed. |
| | |
| | This prevents argparse from quitting due to missing required arguments |
| | when any are defined, for example by ``pytest_addoption``. |
| | This is similar to the way that the builtin argparse --help option is |
| | implemented by raising SystemExit. |
| | """ |
| |
|
| | def __init__(self, option_strings, dest=None, default=False, help=None): |
| | super().__init__( |
| | option_strings=option_strings, |
| | dest=dest, |
| | const=True, |
| | default=default, |
| | nargs=0, |
| | help=help, |
| | ) |
| |
|
| | def __call__(self, parser, namespace, values, option_string=None): |
| | setattr(namespace, self.dest, self.const) |
| |
|
| | |
| | if getattr(parser._parser, "after_preparse", False): |
| | raise PrintHelp |
| |
|
| |
|
| | def pytest_addoption(parser: Parser) -> None: |
| | group = parser.getgroup("debugconfig") |
| | group.addoption( |
| | "--version", |
| | "-V", |
| | action="count", |
| | default=0, |
| | dest="version", |
| | help="Display pytest version and information about plugins. " |
| | "When given twice, also display information about plugins.", |
| | ) |
| | group._addoption( |
| | "-h", |
| | "--help", |
| | action=HelpAction, |
| | dest="help", |
| | help="Show help message and configuration info", |
| | ) |
| | group._addoption( |
| | "-p", |
| | action="append", |
| | dest="plugins", |
| | default=[], |
| | metavar="name", |
| | help="Early-load given plugin module name or entry point (multi-allowed). " |
| | "To avoid loading of plugins, use the `no:` prefix, e.g. " |
| | "`no:doctest`.", |
| | ) |
| | group.addoption( |
| | "--traceconfig", |
| | "--trace-config", |
| | action="store_true", |
| | default=False, |
| | help="Trace considerations of conftest.py files", |
| | ) |
| | group.addoption( |
| | "--debug", |
| | action="store", |
| | nargs="?", |
| | const="pytestdebug.log", |
| | dest="debug", |
| | metavar="DEBUG_FILE_NAME", |
| | help="Store internal tracing debug information in this log file. " |
| | "This file is opened with 'w' and truncated as a result, care advised. " |
| | "Default: pytestdebug.log.", |
| | ) |
| | group._addoption( |
| | "-o", |
| | "--override-ini", |
| | dest="override_ini", |
| | action="append", |
| | help='Override ini option with "option=value" style, ' |
| | "e.g. `-o xfail_strict=True -o cache_dir=cache`.", |
| | ) |
| |
|
| |
|
| | @pytest.hookimpl(wrapper=True) |
| | def pytest_cmdline_parse() -> Generator[None, Config, Config]: |
| | config = yield |
| |
|
| | if config.option.debug: |
| | |
| | path = config.option.debug |
| | debugfile = open(path, "w", encoding="utf-8") |
| | debugfile.write( |
| | "versions pytest-{}, " |
| | "python-{}\ninvocation_dir={}\ncwd={}\nargs={}\n\n".format( |
| | pytest.__version__, |
| | ".".join(map(str, sys.version_info)), |
| | config.invocation_params.dir, |
| | os.getcwd(), |
| | config.invocation_params.args, |
| | ) |
| | ) |
| | config.trace.root.setwriter(debugfile.write) |
| | undo_tracing = config.pluginmanager.enable_tracing() |
| | sys.stderr.write(f"writing pytest debug information to {path}\n") |
| |
|
| | def unset_tracing() -> None: |
| | debugfile.close() |
| | sys.stderr.write(f"wrote pytest debug information to {debugfile.name}\n") |
| | config.trace.root.setwriter(None) |
| | undo_tracing() |
| |
|
| | config.add_cleanup(unset_tracing) |
| |
|
| | return config |
| |
|
| |
|
| | def showversion(config: Config) -> None: |
| | if config.option.version > 1: |
| | sys.stdout.write( |
| | f"This is pytest version {pytest.__version__}, imported from {pytest.__file__}\n" |
| | ) |
| | plugininfo = getpluginversioninfo(config) |
| | if plugininfo: |
| | for line in plugininfo: |
| | sys.stdout.write(line + "\n") |
| | else: |
| | sys.stdout.write(f"pytest {pytest.__version__}\n") |
| |
|
| |
|
| | def pytest_cmdline_main(config: Config) -> int | ExitCode | None: |
| | if config.option.version > 0: |
| | showversion(config) |
| | return 0 |
| | elif config.option.help: |
| | config._do_configure() |
| | showhelp(config) |
| | config._ensure_unconfigure() |
| | return 0 |
| | return None |
| |
|
| |
|
| | def showhelp(config: Config) -> None: |
| | import textwrap |
| |
|
| | reporter: TerminalReporter | None = config.pluginmanager.get_plugin( |
| | "terminalreporter" |
| | ) |
| | assert reporter is not None |
| | tw = reporter._tw |
| | tw.write(config._parser.optparser.format_help()) |
| | tw.line() |
| | tw.line( |
| | "[pytest] ini-options in the first " |
| | "pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:" |
| | ) |
| | tw.line() |
| |
|
| | columns = tw.fullwidth |
| | indent_len = 24 |
| | indent = " " * indent_len |
| | for name in config._parser._ininames: |
| | help, type, default = config._parser._inidict[name] |
| | if type is None: |
| | type = "string" |
| | if help is None: |
| | raise TypeError(f"help argument cannot be None for {name}") |
| | spec = f"{name} ({type}):" |
| | tw.write(f" {spec}") |
| | spec_len = len(spec) |
| | if spec_len > (indent_len - 3): |
| | |
| | tw.line() |
| | helplines = textwrap.wrap( |
| | help, |
| | columns, |
| | initial_indent=indent, |
| | subsequent_indent=indent, |
| | break_on_hyphens=False, |
| | ) |
| |
|
| | for line in helplines: |
| | tw.line(line) |
| | else: |
| | |
| | tw.write(" " * (indent_len - spec_len - 2)) |
| | wrapped = textwrap.wrap(help, columns - indent_len, break_on_hyphens=False) |
| |
|
| | if wrapped: |
| | tw.line(wrapped[0]) |
| | for line in wrapped[1:]: |
| | tw.line(indent + line) |
| |
|
| | tw.line() |
| | tw.line("Environment variables:") |
| | vars = [ |
| | ( |
| | "CI", |
| | "When set (regardless of value), pytest knows it is running in a " |
| | "CI process and does not truncate summary info", |
| | ), |
| | ("BUILD_NUMBER", "Equivalent to CI"), |
| | ("PYTEST_ADDOPTS", "Extra command line options"), |
| | ("PYTEST_PLUGINS", "Comma-separated plugins to load during startup"), |
| | ("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "Set to disable plugin auto-loading"), |
| | ("PYTEST_DEBUG", "Set to enable debug tracing of pytest's internals"), |
| | ] |
| | for name, help in vars: |
| | tw.line(f" {name:<24} {help}") |
| | tw.line() |
| | tw.line() |
| |
|
| | tw.line("to see available markers type: pytest --markers") |
| | tw.line("to see available fixtures type: pytest --fixtures") |
| | tw.line( |
| | "(shown according to specified file_or_dir or current dir " |
| | "if not specified; fixtures with leading '_' are only shown " |
| | "with the '-v' option" |
| | ) |
| |
|
| | for warningreport in reporter.stats.get("warnings", []): |
| | tw.line("warning : " + warningreport.message, red=True) |
| |
|
| |
|
| | conftest_options = [("pytest_plugins", "list of plugin names to load")] |
| |
|
| |
|
| | def getpluginversioninfo(config: Config) -> list[str]: |
| | lines = [] |
| | plugininfo = config.pluginmanager.list_plugin_distinfo() |
| | if plugininfo: |
| | lines.append("registered third-party plugins:") |
| | for plugin, dist in plugininfo: |
| | loc = getattr(plugin, "__file__", repr(plugin)) |
| | content = f"{dist.project_name}-{dist.version} at {loc}" |
| | lines.append(" " + content) |
| | return lines |
| |
|
| |
|
| | def pytest_report_header(config: Config) -> list[str]: |
| | lines = [] |
| | if config.option.debug or config.option.traceconfig: |
| | lines.append(f"using: pytest-{pytest.__version__}") |
| |
|
| | verinfo = getpluginversioninfo(config) |
| | if verinfo: |
| | lines.extend(verinfo) |
| |
|
| | if config.option.traceconfig: |
| | lines.append("active plugins:") |
| | items = config.pluginmanager.list_name_plugin() |
| | for name, plugin in items: |
| | if hasattr(plugin, "__file__"): |
| | r = plugin.__file__ |
| | else: |
| | r = repr(plugin) |
| | lines.append(f" {name:<20}: {r}") |
| | return lines |
| |
|