123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- """Version info, help messages, tracing configuration."""
- import os
- import sys
- from argparse import Action
- from typing import List
- from typing import Optional
- from typing import Union
- import pytest
- 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
- 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)
- # We should only skip the rest of the parsing after preparse is done.
- 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(hookwrapper=True)
- def pytest_cmdline_parse():
- outcome = yield
- config: Config = outcome.get_result()
- if config.option.debug:
- # --debug | --debug <file.log> was provided.
- path = config.option.debug
- debugfile = open(path, "w", encoding="utf-8")
- debugfile.write(
- "versions pytest-%s, "
- "python-%s\ncwd=%s\nargs=%s\n\n"
- % (
- pytest.__version__,
- ".".join(map(str, sys.version_info)),
- os.getcwd(),
- config.invocation_params.args,
- )
- )
- config.trace.root.setwriter(debugfile.write)
- undo_tracing = config.pluginmanager.enable_tracing()
- sys.stderr.write("writing pytest debug information to %s\n" % path)
- def unset_tracing() -> None:
- debugfile.close()
- sys.stderr.write("wrote pytest debug information to %s\n" % debugfile.name)
- config.trace.root.setwriter(None)
- undo_tracing()
- config.add_cleanup(unset_tracing)
- def showversion(config: Config) -> None:
- if config.option.version > 1:
- sys.stdout.write(
- "This is pytest version {}, imported from {}\n".format(
- pytest.__version__, pytest.__file__
- )
- )
- 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) -> Optional[Union[int, ExitCode]]:
- 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: Optional[TerminalReporter] = 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 # costly call
- indent_len = 24 # based on argparse's max_help_position=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(" %s" % spec)
- spec_len = len(spec)
- if spec_len > (indent_len - 3):
- # Display help starting at a new line.
- 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:
- # Display help starting after the spec, following lines indented.
- 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 = [
- ("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)
- return
- 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("setuptools registered 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
|