__init__.py 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816
  1. """Command line options, ini-file and conftest.py processing."""
  2. import argparse
  3. import collections.abc
  4. import copy
  5. import dataclasses
  6. import enum
  7. import glob
  8. import inspect
  9. import os
  10. import re
  11. import shlex
  12. import sys
  13. import types
  14. import warnings
  15. from functools import lru_cache
  16. from pathlib import Path
  17. from textwrap import dedent
  18. from types import FunctionType
  19. from types import TracebackType
  20. from typing import Any
  21. from typing import Callable
  22. from typing import cast
  23. from typing import Dict
  24. from typing import Generator
  25. from typing import IO
  26. from typing import Iterable
  27. from typing import Iterator
  28. from typing import List
  29. from typing import Optional
  30. from typing import Sequence
  31. from typing import Set
  32. from typing import TextIO
  33. from typing import Tuple
  34. from typing import Type
  35. from typing import TYPE_CHECKING
  36. from typing import Union
  37. from pluggy import HookimplMarker
  38. from pluggy import HookspecMarker
  39. from pluggy import PluginManager
  40. import _pytest._code
  41. import _pytest.deprecated
  42. import _pytest.hookspec
  43. from .exceptions import PrintHelp as PrintHelp
  44. from .exceptions import UsageError as UsageError
  45. from .findpaths import determine_setup
  46. from _pytest._code import ExceptionInfo
  47. from _pytest._code import filter_traceback
  48. from _pytest._io import TerminalWriter
  49. from _pytest.compat import final
  50. from _pytest.compat import importlib_metadata # type: ignore[attr-defined]
  51. from _pytest.outcomes import fail
  52. from _pytest.outcomes import Skipped
  53. from _pytest.pathlib import absolutepath
  54. from _pytest.pathlib import bestrelpath
  55. from _pytest.pathlib import import_path
  56. from _pytest.pathlib import ImportMode
  57. from _pytest.pathlib import resolve_package_path
  58. from _pytest.pathlib import safe_exists
  59. from _pytest.stash import Stash
  60. from _pytest.warning_types import PytestConfigWarning
  61. from _pytest.warning_types import warn_explicit_for
  62. if TYPE_CHECKING:
  63. from _pytest._code.code import _TracebackStyle
  64. from _pytest.terminal import TerminalReporter
  65. from .argparsing import Argument
  66. _PluggyPlugin = object
  67. """A type to represent plugin objects.
  68. Plugins can be any namespace, so we can't narrow it down much, but we use an
  69. alias to make the intent clear.
  70. Ideally this type would be provided by pluggy itself.
  71. """
  72. hookimpl = HookimplMarker("pytest")
  73. hookspec = HookspecMarker("pytest")
  74. @final
  75. class ExitCode(enum.IntEnum):
  76. """Encodes the valid exit codes by pytest.
  77. Currently users and plugins may supply other exit codes as well.
  78. .. versionadded:: 5.0
  79. """
  80. #: Tests passed.
  81. OK = 0
  82. #: Tests failed.
  83. TESTS_FAILED = 1
  84. #: pytest was interrupted.
  85. INTERRUPTED = 2
  86. #: An internal error got in the way.
  87. INTERNAL_ERROR = 3
  88. #: pytest was misused.
  89. USAGE_ERROR = 4
  90. #: pytest couldn't find tests.
  91. NO_TESTS_COLLECTED = 5
  92. class ConftestImportFailure(Exception):
  93. def __init__(
  94. self,
  95. path: Path,
  96. excinfo: Tuple[Type[Exception], Exception, TracebackType],
  97. ) -> None:
  98. super().__init__(path, excinfo)
  99. self.path = path
  100. self.excinfo = excinfo
  101. def __str__(self) -> str:
  102. return "{}: {} (from {})".format(
  103. self.excinfo[0].__name__, self.excinfo[1], self.path
  104. )
  105. def filter_traceback_for_conftest_import_failure(
  106. entry: _pytest._code.TracebackEntry,
  107. ) -> bool:
  108. """Filter tracebacks entries which point to pytest internals or importlib.
  109. Make a special case for importlib because we use it to import test modules and conftest files
  110. in _pytest.pathlib.import_path.
  111. """
  112. return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep)
  113. def main(
  114. args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
  115. plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
  116. ) -> Union[int, ExitCode]:
  117. """Perform an in-process test run.
  118. :param args:
  119. List of command line arguments. If `None` or not given, defaults to reading
  120. arguments directly from the process command line (:data:`sys.argv`).
  121. :param plugins: List of plugin objects to be auto-registered during initialization.
  122. :returns: An exit code.
  123. """
  124. try:
  125. try:
  126. config = _prepareconfig(args, plugins)
  127. except ConftestImportFailure as e:
  128. exc_info = ExceptionInfo.from_exc_info(e.excinfo)
  129. tw = TerminalWriter(sys.stderr)
  130. tw.line(f"ImportError while loading conftest '{e.path}'.", red=True)
  131. exc_info.traceback = exc_info.traceback.filter(
  132. filter_traceback_for_conftest_import_failure
  133. )
  134. exc_repr = (
  135. exc_info.getrepr(style="short", chain=False)
  136. if exc_info.traceback
  137. else exc_info.exconly()
  138. )
  139. formatted_tb = str(exc_repr)
  140. for line in formatted_tb.splitlines():
  141. tw.line(line.rstrip(), red=True)
  142. return ExitCode.USAGE_ERROR
  143. else:
  144. try:
  145. ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
  146. config=config
  147. )
  148. try:
  149. return ExitCode(ret)
  150. except ValueError:
  151. return ret
  152. finally:
  153. config._ensure_unconfigure()
  154. except UsageError as e:
  155. tw = TerminalWriter(sys.stderr)
  156. for msg in e.args:
  157. tw.line(f"ERROR: {msg}\n", red=True)
  158. return ExitCode.USAGE_ERROR
  159. def console_main() -> int:
  160. """The CLI entry point of pytest.
  161. This function is not meant for programmable use; use `main()` instead.
  162. """
  163. # https://docs.python.org/3/library/signal.html#note-on-sigpipe
  164. try:
  165. code = main()
  166. sys.stdout.flush()
  167. return code
  168. except BrokenPipeError:
  169. # Python flushes standard streams on exit; redirect remaining output
  170. # to devnull to avoid another BrokenPipeError at shutdown
  171. devnull = os.open(os.devnull, os.O_WRONLY)
  172. os.dup2(devnull, sys.stdout.fileno())
  173. return 1 # Python exits with error code 1 on EPIPE
  174. class cmdline: # compatibility namespace
  175. main = staticmethod(main)
  176. def filename_arg(path: str, optname: str) -> str:
  177. """Argparse type validator for filename arguments.
  178. :path: Path of filename.
  179. :optname: Name of the option.
  180. """
  181. if os.path.isdir(path):
  182. raise UsageError(f"{optname} must be a filename, given: {path}")
  183. return path
  184. def directory_arg(path: str, optname: str) -> str:
  185. """Argparse type validator for directory arguments.
  186. :path: Path of directory.
  187. :optname: Name of the option.
  188. """
  189. if not os.path.isdir(path):
  190. raise UsageError(f"{optname} must be a directory, given: {path}")
  191. return path
  192. # Plugins that cannot be disabled via "-p no:X" currently.
  193. essential_plugins = (
  194. "mark",
  195. "main",
  196. "runner",
  197. "fixtures",
  198. "helpconfig", # Provides -p.
  199. )
  200. default_plugins = essential_plugins + (
  201. "python",
  202. "terminal",
  203. "debugging",
  204. "unittest",
  205. "capture",
  206. "skipping",
  207. "legacypath",
  208. "tmpdir",
  209. "monkeypatch",
  210. "recwarn",
  211. "pastebin",
  212. "nose",
  213. "assertion",
  214. "junitxml",
  215. "doctest",
  216. "cacheprovider",
  217. "freeze_support",
  218. "setuponly",
  219. "setupplan",
  220. "stepwise",
  221. "warnings",
  222. "logging",
  223. "reports",
  224. "python_path",
  225. *(["unraisableexception", "threadexception"] if sys.version_info >= (3, 8) else []),
  226. "faulthandler",
  227. )
  228. builtin_plugins = set(default_plugins)
  229. builtin_plugins.add("pytester")
  230. builtin_plugins.add("pytester_assertions")
  231. def get_config(
  232. args: Optional[List[str]] = None,
  233. plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
  234. ) -> "Config":
  235. # subsequent calls to main will create a fresh instance
  236. pluginmanager = PytestPluginManager()
  237. config = Config(
  238. pluginmanager,
  239. invocation_params=Config.InvocationParams(
  240. args=args or (),
  241. plugins=plugins,
  242. dir=Path.cwd(),
  243. ),
  244. )
  245. if args is not None:
  246. # Handle any "-p no:plugin" args.
  247. pluginmanager.consider_preparse(args, exclude_only=True)
  248. for spec in default_plugins:
  249. pluginmanager.import_plugin(spec)
  250. return config
  251. def get_plugin_manager() -> "PytestPluginManager":
  252. """Obtain a new instance of the
  253. :py:class:`pytest.PytestPluginManager`, with default plugins
  254. already loaded.
  255. This function can be used by integration with other tools, like hooking
  256. into pytest to run tests into an IDE.
  257. """
  258. return get_config().pluginmanager
  259. def _prepareconfig(
  260. args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
  261. plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
  262. ) -> "Config":
  263. if args is None:
  264. args = sys.argv[1:]
  265. elif isinstance(args, os.PathLike):
  266. args = [os.fspath(args)]
  267. elif not isinstance(args, list):
  268. msg = ( # type:ignore[unreachable]
  269. "`args` parameter expected to be a list of strings, got: {!r} (type: {})"
  270. )
  271. raise TypeError(msg.format(args, type(args)))
  272. config = get_config(args, plugins)
  273. pluginmanager = config.pluginmanager
  274. try:
  275. if plugins:
  276. for plugin in plugins:
  277. if isinstance(plugin, str):
  278. pluginmanager.consider_pluginarg(plugin)
  279. else:
  280. pluginmanager.register(plugin)
  281. config = pluginmanager.hook.pytest_cmdline_parse(
  282. pluginmanager=pluginmanager, args=args
  283. )
  284. return config
  285. except BaseException:
  286. config._ensure_unconfigure()
  287. raise
  288. def _get_directory(path: Path) -> Path:
  289. """Get the directory of a path - itself if already a directory."""
  290. if path.is_file():
  291. return path.parent
  292. else:
  293. return path
  294. def _get_legacy_hook_marks(
  295. method: Any,
  296. hook_type: str,
  297. opt_names: Tuple[str, ...],
  298. ) -> Dict[str, bool]:
  299. if TYPE_CHECKING:
  300. # abuse typeguard from importlib to avoid massive method type union thats lacking a alias
  301. assert inspect.isroutine(method)
  302. known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])}
  303. must_warn: list[str] = []
  304. opts: dict[str, bool] = {}
  305. for opt_name in opt_names:
  306. opt_attr = getattr(method, opt_name, AttributeError)
  307. if opt_attr is not AttributeError:
  308. must_warn.append(f"{opt_name}={opt_attr}")
  309. opts[opt_name] = True
  310. elif opt_name in known_marks:
  311. must_warn.append(f"{opt_name}=True")
  312. opts[opt_name] = True
  313. else:
  314. opts[opt_name] = False
  315. if must_warn:
  316. hook_opts = ", ".join(must_warn)
  317. message = _pytest.deprecated.HOOK_LEGACY_MARKING.format(
  318. type=hook_type,
  319. fullname=method.__qualname__,
  320. hook_opts=hook_opts,
  321. )
  322. warn_explicit_for(cast(FunctionType, method), message)
  323. return opts
  324. @final
  325. class PytestPluginManager(PluginManager):
  326. """A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with
  327. additional pytest-specific functionality:
  328. * Loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and
  329. ``pytest_plugins`` global variables found in plugins being loaded.
  330. * ``conftest.py`` loading during start-up.
  331. """
  332. def __init__(self) -> None:
  333. import _pytest.assertion
  334. super().__init__("pytest")
  335. # -- State related to local conftest plugins.
  336. # All loaded conftest modules.
  337. self._conftest_plugins: Set[types.ModuleType] = set()
  338. # All conftest modules applicable for a directory.
  339. # This includes the directory's own conftest modules as well
  340. # as those of its parent directories.
  341. self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {}
  342. # Cutoff directory above which conftests are no longer discovered.
  343. self._confcutdir: Optional[Path] = None
  344. # If set, conftest loading is skipped.
  345. self._noconftest = False
  346. # _getconftestmodules()'s call to _get_directory() causes a stat
  347. # storm when it's called potentially thousands of times in a test
  348. # session (#9478), often with the same path, so cache it.
  349. self._get_directory = lru_cache(256)(_get_directory)
  350. self._duplicatepaths: Set[Path] = set()
  351. # plugins that were explicitly skipped with pytest.skip
  352. # list of (module name, skip reason)
  353. # previously we would issue a warning when a plugin was skipped, but
  354. # since we refactored warnings as first citizens of Config, they are
  355. # just stored here to be used later.
  356. self.skipped_plugins: List[Tuple[str, str]] = []
  357. self.add_hookspecs(_pytest.hookspec)
  358. self.register(self)
  359. if os.environ.get("PYTEST_DEBUG"):
  360. err: IO[str] = sys.stderr
  361. encoding: str = getattr(err, "encoding", "utf8")
  362. try:
  363. err = open(
  364. os.dup(err.fileno()),
  365. mode=err.mode,
  366. buffering=1,
  367. encoding=encoding,
  368. )
  369. except Exception:
  370. pass
  371. self.trace.root.setwriter(err.write)
  372. self.enable_tracing()
  373. # Config._consider_importhook will set a real object if required.
  374. self.rewrite_hook = _pytest.assertion.DummyRewriteHook()
  375. # Used to know when we are importing conftests after the pytest_configure stage.
  376. self._configured = False
  377. def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str):
  378. # pytest hooks are always prefixed with "pytest_",
  379. # so we avoid accessing possibly non-readable attributes
  380. # (see issue #1073).
  381. if not name.startswith("pytest_"):
  382. return None
  383. # Ignore names which can not be hooks.
  384. if name == "pytest_plugins":
  385. return None
  386. opts = super().parse_hookimpl_opts(plugin, name)
  387. if opts is not None:
  388. return opts
  389. method = getattr(plugin, name)
  390. # Consider only actual functions for hooks (#3775).
  391. if not inspect.isroutine(method):
  392. return None
  393. # Collect unmarked hooks as long as they have the `pytest_' prefix.
  394. return _get_legacy_hook_marks( # type: ignore[return-value]
  395. method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper")
  396. )
  397. def parse_hookspec_opts(self, module_or_class, name: str):
  398. opts = super().parse_hookspec_opts(module_or_class, name)
  399. if opts is None:
  400. method = getattr(module_or_class, name)
  401. if name.startswith("pytest_"):
  402. opts = _get_legacy_hook_marks( # type: ignore[assignment]
  403. method,
  404. "spec",
  405. ("firstresult", "historic"),
  406. )
  407. return opts
  408. def register(
  409. self, plugin: _PluggyPlugin, name: Optional[str] = None
  410. ) -> Optional[str]:
  411. if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS:
  412. warnings.warn(
  413. PytestConfigWarning(
  414. "{} plugin has been merged into the core, "
  415. "please remove it from your requirements.".format(
  416. name.replace("_", "-")
  417. )
  418. )
  419. )
  420. return None
  421. ret: Optional[str] = super().register(plugin, name)
  422. if ret:
  423. self.hook.pytest_plugin_registered.call_historic(
  424. kwargs=dict(plugin=plugin, manager=self)
  425. )
  426. if isinstance(plugin, types.ModuleType):
  427. self.consider_module(plugin)
  428. return ret
  429. def getplugin(self, name: str):
  430. # Support deprecated naming because plugins (xdist e.g.) use it.
  431. plugin: Optional[_PluggyPlugin] = self.get_plugin(name)
  432. return plugin
  433. def hasplugin(self, name: str) -> bool:
  434. """Return whether a plugin with the given name is registered."""
  435. return bool(self.get_plugin(name))
  436. def pytest_configure(self, config: "Config") -> None:
  437. """:meta private:"""
  438. # XXX now that the pluginmanager exposes hookimpl(tryfirst...)
  439. # we should remove tryfirst/trylast as markers.
  440. config.addinivalue_line(
  441. "markers",
  442. "tryfirst: mark a hook implementation function such that the "
  443. "plugin machinery will try to call it first/as early as possible. "
  444. "DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.",
  445. )
  446. config.addinivalue_line(
  447. "markers",
  448. "trylast: mark a hook implementation function such that the "
  449. "plugin machinery will try to call it last/as late as possible. "
  450. "DEPRECATED, use @pytest.hookimpl(trylast=True) instead.",
  451. )
  452. self._configured = True
  453. #
  454. # Internal API for local conftest plugin handling.
  455. #
  456. def _set_initial_conftests(
  457. self,
  458. args: Sequence[Union[str, Path]],
  459. pyargs: bool,
  460. noconftest: bool,
  461. rootpath: Path,
  462. confcutdir: Optional[Path],
  463. importmode: Union[ImportMode, str],
  464. ) -> None:
  465. """Load initial conftest files given a preparsed "namespace".
  466. As conftest files may add their own command line options which have
  467. arguments ('--my-opt somepath') we might get some false positives.
  468. All builtin and 3rd party plugins will have been loaded, however, so
  469. common options will not confuse our logic here.
  470. """
  471. current = Path.cwd()
  472. self._confcutdir = absolutepath(current / confcutdir) if confcutdir else None
  473. self._noconftest = noconftest
  474. self._using_pyargs = pyargs
  475. foundanchor = False
  476. for intitial_path in args:
  477. path = str(intitial_path)
  478. # remove node-id syntax
  479. i = path.find("::")
  480. if i != -1:
  481. path = path[:i]
  482. anchor = absolutepath(current / path)
  483. # Ensure we do not break if what appears to be an anchor
  484. # is in fact a very long option (#10169, #11394).
  485. if safe_exists(anchor):
  486. self._try_load_conftest(anchor, importmode, rootpath)
  487. foundanchor = True
  488. if not foundanchor:
  489. self._try_load_conftest(current, importmode, rootpath)
  490. def _is_in_confcutdir(self, path: Path) -> bool:
  491. """Whether a path is within the confcutdir.
  492. When false, should not load conftest.
  493. """
  494. if self._confcutdir is None:
  495. return True
  496. return path not in self._confcutdir.parents
  497. def _try_load_conftest(
  498. self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path
  499. ) -> None:
  500. self._getconftestmodules(anchor, importmode, rootpath)
  501. # let's also consider test* subdirs
  502. if anchor.is_dir():
  503. for x in anchor.glob("test*"):
  504. if x.is_dir():
  505. self._getconftestmodules(x, importmode, rootpath)
  506. def _getconftestmodules(
  507. self, path: Path, importmode: Union[str, ImportMode], rootpath: Path
  508. ) -> Sequence[types.ModuleType]:
  509. if self._noconftest:
  510. return []
  511. directory = self._get_directory(path)
  512. # Optimization: avoid repeated searches in the same directory.
  513. # Assumes always called with same importmode and rootpath.
  514. existing_clist = self._dirpath2confmods.get(directory)
  515. if existing_clist is not None:
  516. return existing_clist
  517. # XXX these days we may rather want to use config.rootpath
  518. # and allow users to opt into looking into the rootdir parent
  519. # directories instead of requiring to specify confcutdir.
  520. clist = []
  521. for parent in reversed((directory, *directory.parents)):
  522. if self._is_in_confcutdir(parent):
  523. conftestpath = parent / "conftest.py"
  524. if conftestpath.is_file():
  525. mod = self._importconftest(conftestpath, importmode, rootpath)
  526. clist.append(mod)
  527. self._dirpath2confmods[directory] = clist
  528. return clist
  529. def _rget_with_confmod(
  530. self,
  531. name: str,
  532. path: Path,
  533. importmode: Union[str, ImportMode],
  534. rootpath: Path,
  535. ) -> Tuple[types.ModuleType, Any]:
  536. modules = self._getconftestmodules(path, importmode, rootpath=rootpath)
  537. for mod in reversed(modules):
  538. try:
  539. return mod, getattr(mod, name)
  540. except AttributeError:
  541. continue
  542. raise KeyError(name)
  543. def _importconftest(
  544. self, conftestpath: Path, importmode: Union[str, ImportMode], rootpath: Path
  545. ) -> types.ModuleType:
  546. existing = self.get_plugin(str(conftestpath))
  547. if existing is not None:
  548. return cast(types.ModuleType, existing)
  549. pkgpath = resolve_package_path(conftestpath)
  550. if pkgpath is None:
  551. _ensure_removed_sysmodule(conftestpath.stem)
  552. try:
  553. mod = import_path(conftestpath, mode=importmode, root=rootpath)
  554. except Exception as e:
  555. assert e.__traceback__ is not None
  556. exc_info = (type(e), e, e.__traceback__)
  557. raise ConftestImportFailure(conftestpath, exc_info) from e
  558. self._check_non_top_pytest_plugins(mod, conftestpath)
  559. self._conftest_plugins.add(mod)
  560. dirpath = conftestpath.parent
  561. if dirpath in self._dirpath2confmods:
  562. for path, mods in self._dirpath2confmods.items():
  563. if dirpath in path.parents or path == dirpath:
  564. assert mod not in mods
  565. mods.append(mod)
  566. self.trace(f"loading conftestmodule {mod!r}")
  567. self.consider_conftest(mod)
  568. return mod
  569. def _check_non_top_pytest_plugins(
  570. self,
  571. mod: types.ModuleType,
  572. conftestpath: Path,
  573. ) -> None:
  574. if (
  575. hasattr(mod, "pytest_plugins")
  576. and self._configured
  577. and not self._using_pyargs
  578. ):
  579. msg = (
  580. "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n"
  581. "It affects the entire test suite instead of just below the conftest as expected.\n"
  582. " {}\n"
  583. "Please move it to a top level conftest file at the rootdir:\n"
  584. " {}\n"
  585. "For more information, visit:\n"
  586. " https://docs.pytest.org/en/stable/deprecations.html#pytest-plugins-in-non-top-level-conftest-files"
  587. )
  588. fail(msg.format(conftestpath, self._confcutdir), pytrace=False)
  589. #
  590. # API for bootstrapping plugin loading
  591. #
  592. #
  593. def consider_preparse(
  594. self, args: Sequence[str], *, exclude_only: bool = False
  595. ) -> None:
  596. """:meta private:"""
  597. i = 0
  598. n = len(args)
  599. while i < n:
  600. opt = args[i]
  601. i += 1
  602. if isinstance(opt, str):
  603. if opt == "-p":
  604. try:
  605. parg = args[i]
  606. except IndexError:
  607. return
  608. i += 1
  609. elif opt.startswith("-p"):
  610. parg = opt[2:]
  611. else:
  612. continue
  613. parg = parg.strip()
  614. if exclude_only and not parg.startswith("no:"):
  615. continue
  616. self.consider_pluginarg(parg)
  617. def consider_pluginarg(self, arg: str) -> None:
  618. """:meta private:"""
  619. if arg.startswith("no:"):
  620. name = arg[3:]
  621. if name in essential_plugins:
  622. raise UsageError("plugin %s cannot be disabled" % name)
  623. # PR #4304: remove stepwise if cacheprovider is blocked.
  624. if name == "cacheprovider":
  625. self.set_blocked("stepwise")
  626. self.set_blocked("pytest_stepwise")
  627. self.set_blocked(name)
  628. if not name.startswith("pytest_"):
  629. self.set_blocked("pytest_" + name)
  630. else:
  631. name = arg
  632. # Unblock the plugin. None indicates that it has been blocked.
  633. # There is no interface with pluggy for this.
  634. if self._name2plugin.get(name, -1) is None:
  635. del self._name2plugin[name]
  636. if not name.startswith("pytest_"):
  637. if self._name2plugin.get("pytest_" + name, -1) is None:
  638. del self._name2plugin["pytest_" + name]
  639. self.import_plugin(arg, consider_entry_points=True)
  640. def consider_conftest(self, conftestmodule: types.ModuleType) -> None:
  641. """:meta private:"""
  642. self.register(conftestmodule, name=conftestmodule.__file__)
  643. def consider_env(self) -> None:
  644. """:meta private:"""
  645. self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
  646. def consider_module(self, mod: types.ModuleType) -> None:
  647. """:meta private:"""
  648. self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
  649. def _import_plugin_specs(
  650. self, spec: Union[None, types.ModuleType, str, Sequence[str]]
  651. ) -> None:
  652. plugins = _get_plugin_specs_as_list(spec)
  653. for import_spec in plugins:
  654. self.import_plugin(import_spec)
  655. def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None:
  656. """Import a plugin with ``modname``.
  657. If ``consider_entry_points`` is True, entry point names are also
  658. considered to find a plugin.
  659. """
  660. # Most often modname refers to builtin modules, e.g. "pytester",
  661. # "terminal" or "capture". Those plugins are registered under their
  662. # basename for historic purposes but must be imported with the
  663. # _pytest prefix.
  664. assert isinstance(modname, str), (
  665. "module name as text required, got %r" % modname
  666. )
  667. if self.is_blocked(modname) or self.get_plugin(modname) is not None:
  668. return
  669. importspec = "_pytest." + modname if modname in builtin_plugins else modname
  670. self.rewrite_hook.mark_rewrite(importspec)
  671. if consider_entry_points:
  672. loaded = self.load_setuptools_entrypoints("pytest11", name=modname)
  673. if loaded:
  674. return
  675. try:
  676. __import__(importspec)
  677. except ImportError as e:
  678. raise ImportError(
  679. f'Error importing plugin "{modname}": {e.args[0]}'
  680. ).with_traceback(e.__traceback__) from e
  681. except Skipped as e:
  682. self.skipped_plugins.append((modname, e.msg or ""))
  683. else:
  684. mod = sys.modules[importspec]
  685. self.register(mod, modname)
  686. def _get_plugin_specs_as_list(
  687. specs: Union[None, types.ModuleType, str, Sequence[str]]
  688. ) -> List[str]:
  689. """Parse a plugins specification into a list of plugin names."""
  690. # None means empty.
  691. if specs is None:
  692. return []
  693. # Workaround for #3899 - a submodule which happens to be called "pytest_plugins".
  694. if isinstance(specs, types.ModuleType):
  695. return []
  696. # Comma-separated list.
  697. if isinstance(specs, str):
  698. return specs.split(",") if specs else []
  699. # Direct specification.
  700. if isinstance(specs, collections.abc.Sequence):
  701. return list(specs)
  702. raise UsageError(
  703. "Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: %r"
  704. % specs
  705. )
  706. def _ensure_removed_sysmodule(modname: str) -> None:
  707. try:
  708. del sys.modules[modname]
  709. except KeyError:
  710. pass
  711. class Notset:
  712. def __repr__(self):
  713. return "<NOTSET>"
  714. notset = Notset()
  715. def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]:
  716. """Given an iterable of file names in a source distribution, return the "names" that should
  717. be marked for assertion rewrite.
  718. For example the package "pytest_mock/__init__.py" should be added as "pytest_mock" in
  719. the assertion rewrite mechanism.
  720. This function has to deal with dist-info based distributions and egg based distributions
  721. (which are still very much in use for "editable" installs).
  722. Here are the file names as seen in a dist-info based distribution:
  723. pytest_mock/__init__.py
  724. pytest_mock/_version.py
  725. pytest_mock/plugin.py
  726. pytest_mock.egg-info/PKG-INFO
  727. Here are the file names as seen in an egg based distribution:
  728. src/pytest_mock/__init__.py
  729. src/pytest_mock/_version.py
  730. src/pytest_mock/plugin.py
  731. src/pytest_mock.egg-info/PKG-INFO
  732. LICENSE
  733. setup.py
  734. We have to take in account those two distribution flavors in order to determine which
  735. names should be considered for assertion rewriting.
  736. More information:
  737. https://github.com/pytest-dev/pytest-mock/issues/167
  738. """
  739. package_files = list(package_files)
  740. seen_some = False
  741. for fn in package_files:
  742. is_simple_module = "/" not in fn and fn.endswith(".py")
  743. is_package = fn.count("/") == 1 and fn.endswith("__init__.py")
  744. if is_simple_module:
  745. module_name, _ = os.path.splitext(fn)
  746. # we ignore "setup.py" at the root of the distribution
  747. # as well as editable installation finder modules made by setuptools
  748. if module_name != "setup" and not module_name.startswith("__editable__"):
  749. seen_some = True
  750. yield module_name
  751. elif is_package:
  752. package_name = os.path.dirname(fn)
  753. seen_some = True
  754. yield package_name
  755. if not seen_some:
  756. # At this point we did not find any packages or modules suitable for assertion
  757. # rewriting, so we try again by stripping the first path component (to account for
  758. # "src" based source trees for example).
  759. # This approach lets us have the common case continue to be fast, as egg-distributions
  760. # are rarer.
  761. new_package_files = []
  762. for fn in package_files:
  763. parts = fn.split("/")
  764. new_fn = "/".join(parts[1:])
  765. if new_fn:
  766. new_package_files.append(new_fn)
  767. if new_package_files:
  768. yield from _iter_rewritable_modules(new_package_files)
  769. @final
  770. class Config:
  771. """Access to configuration values, pluginmanager and plugin hooks.
  772. :param PytestPluginManager pluginmanager:
  773. A pytest PluginManager.
  774. :param InvocationParams invocation_params:
  775. Object containing parameters regarding the :func:`pytest.main`
  776. invocation.
  777. """
  778. @final
  779. @dataclasses.dataclass(frozen=True)
  780. class InvocationParams:
  781. """Holds parameters passed during :func:`pytest.main`.
  782. The object attributes are read-only.
  783. .. versionadded:: 5.1
  784. .. note::
  785. Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts``
  786. ini option are handled by pytest, not being included in the ``args`` attribute.
  787. Plugins accessing ``InvocationParams`` must be aware of that.
  788. """
  789. args: Tuple[str, ...]
  790. """The command-line arguments as passed to :func:`pytest.main`."""
  791. plugins: Optional[Sequence[Union[str, _PluggyPlugin]]]
  792. """Extra plugins, might be `None`."""
  793. dir: Path
  794. """The directory from which :func:`pytest.main` was invoked."""
  795. def __init__(
  796. self,
  797. *,
  798. args: Iterable[str],
  799. plugins: Optional[Sequence[Union[str, _PluggyPlugin]]],
  800. dir: Path,
  801. ) -> None:
  802. object.__setattr__(self, "args", tuple(args))
  803. object.__setattr__(self, "plugins", plugins)
  804. object.__setattr__(self, "dir", dir)
  805. class ArgsSource(enum.Enum):
  806. """Indicates the source of the test arguments.
  807. .. versionadded:: 7.2
  808. """
  809. #: Command line arguments.
  810. ARGS = enum.auto()
  811. #: Invocation directory.
  812. INCOVATION_DIR = enum.auto()
  813. #: 'testpaths' configuration value.
  814. TESTPATHS = enum.auto()
  815. def __init__(
  816. self,
  817. pluginmanager: PytestPluginManager,
  818. *,
  819. invocation_params: Optional[InvocationParams] = None,
  820. ) -> None:
  821. from .argparsing import Parser, FILE_OR_DIR
  822. if invocation_params is None:
  823. invocation_params = self.InvocationParams(
  824. args=(), plugins=None, dir=Path.cwd()
  825. )
  826. self.option = argparse.Namespace()
  827. """Access to command line option as attributes.
  828. :type: argparse.Namespace
  829. """
  830. self.invocation_params = invocation_params
  831. """The parameters with which pytest was invoked.
  832. :type: InvocationParams
  833. """
  834. _a = FILE_OR_DIR
  835. self._parser = Parser(
  836. usage=f"%(prog)s [options] [{_a}] [{_a}] [...]",
  837. processopt=self._processopt,
  838. _ispytest=True,
  839. )
  840. self.pluginmanager = pluginmanager
  841. """The plugin manager handles plugin registration and hook invocation.
  842. :type: PytestPluginManager
  843. """
  844. self.stash = Stash()
  845. """A place where plugins can store information on the config for their
  846. own use.
  847. :type: Stash
  848. """
  849. # Deprecated alias. Was never public. Can be removed in a few releases.
  850. self._store = self.stash
  851. from .compat import PathAwareHookProxy
  852. self.trace = self.pluginmanager.trace.root.get("config")
  853. self.hook = PathAwareHookProxy(self.pluginmanager.hook)
  854. self._inicache: Dict[str, Any] = {}
  855. self._override_ini: Sequence[str] = ()
  856. self._opt2dest: Dict[str, str] = {}
  857. self._cleanup: List[Callable[[], None]] = []
  858. self.pluginmanager.register(self, "pytestconfig")
  859. self._configured = False
  860. self.hook.pytest_addoption.call_historic(
  861. kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
  862. )
  863. self.args_source = Config.ArgsSource.ARGS
  864. self.args: List[str] = []
  865. if TYPE_CHECKING:
  866. from _pytest.cacheprovider import Cache
  867. self.cache: Optional[Cache] = None
  868. @property
  869. def rootpath(self) -> Path:
  870. """The path to the :ref:`rootdir <rootdir>`.
  871. :type: pathlib.Path
  872. .. versionadded:: 6.1
  873. """
  874. return self._rootpath
  875. @property
  876. def inipath(self) -> Optional[Path]:
  877. """The path to the :ref:`configfile <configfiles>`.
  878. :type: Optional[pathlib.Path]
  879. .. versionadded:: 6.1
  880. """
  881. return self._inipath
  882. def add_cleanup(self, func: Callable[[], None]) -> None:
  883. """Add a function to be called when the config object gets out of
  884. use (usually coinciding with pytest_unconfigure)."""
  885. self._cleanup.append(func)
  886. def _do_configure(self) -> None:
  887. assert not self._configured
  888. self._configured = True
  889. with warnings.catch_warnings():
  890. warnings.simplefilter("default")
  891. self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
  892. def _ensure_unconfigure(self) -> None:
  893. if self._configured:
  894. self._configured = False
  895. self.hook.pytest_unconfigure(config=self)
  896. self.hook.pytest_configure._call_history = []
  897. while self._cleanup:
  898. fin = self._cleanup.pop()
  899. fin()
  900. def get_terminal_writer(self) -> TerminalWriter:
  901. terminalreporter: Optional[TerminalReporter] = self.pluginmanager.get_plugin(
  902. "terminalreporter"
  903. )
  904. assert terminalreporter is not None
  905. return terminalreporter._tw
  906. def pytest_cmdline_parse(
  907. self, pluginmanager: PytestPluginManager, args: List[str]
  908. ) -> "Config":
  909. try:
  910. self.parse(args)
  911. except UsageError:
  912. # Handle --version and --help here in a minimal fashion.
  913. # This gets done via helpconfig normally, but its
  914. # pytest_cmdline_main is not called in case of errors.
  915. if getattr(self.option, "version", False) or "--version" in args:
  916. from _pytest.helpconfig import showversion
  917. showversion(self)
  918. elif (
  919. getattr(self.option, "help", False) or "--help" in args or "-h" in args
  920. ):
  921. self._parser._getparser().print_help()
  922. sys.stdout.write(
  923. "\nNOTE: displaying only minimal help due to UsageError.\n\n"
  924. )
  925. raise
  926. return self
  927. def notify_exception(
  928. self,
  929. excinfo: ExceptionInfo[BaseException],
  930. option: Optional[argparse.Namespace] = None,
  931. ) -> None:
  932. if option and getattr(option, "fulltrace", False):
  933. style: _TracebackStyle = "long"
  934. else:
  935. style = "native"
  936. excrepr = excinfo.getrepr(
  937. funcargs=True, showlocals=getattr(option, "showlocals", False), style=style
  938. )
  939. res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo)
  940. if not any(res):
  941. for line in str(excrepr).split("\n"):
  942. sys.stderr.write("INTERNALERROR> %s\n" % line)
  943. sys.stderr.flush()
  944. def cwd_relative_nodeid(self, nodeid: str) -> str:
  945. # nodeid's are relative to the rootpath, compute relative to cwd.
  946. if self.invocation_params.dir != self.rootpath:
  947. fullpath = self.rootpath / nodeid
  948. nodeid = bestrelpath(self.invocation_params.dir, fullpath)
  949. return nodeid
  950. @classmethod
  951. def fromdictargs(cls, option_dict, args) -> "Config":
  952. """Constructor usable for subprocesses."""
  953. config = get_config(args)
  954. config.option.__dict__.update(option_dict)
  955. config.parse(args, addopts=False)
  956. for x in config.option.plugins:
  957. config.pluginmanager.consider_pluginarg(x)
  958. return config
  959. def _processopt(self, opt: "Argument") -> None:
  960. for name in opt._short_opts + opt._long_opts:
  961. self._opt2dest[name] = opt.dest
  962. if hasattr(opt, "default"):
  963. if not hasattr(self.option, opt.dest):
  964. setattr(self.option, opt.dest, opt.default)
  965. @hookimpl(trylast=True)
  966. def pytest_load_initial_conftests(self, early_config: "Config") -> None:
  967. # We haven't fully parsed the command line arguments yet, so
  968. # early_config.args it not set yet. But we need it for
  969. # discovering the initial conftests. So "pre-run" the logic here.
  970. # It will be done for real in `parse()`.
  971. args, args_source = early_config._decide_args(
  972. args=early_config.known_args_namespace.file_or_dir,
  973. pyargs=early_config.known_args_namespace.pyargs,
  974. testpaths=early_config.getini("testpaths"),
  975. invocation_dir=early_config.invocation_params.dir,
  976. rootpath=early_config.rootpath,
  977. warn=False,
  978. )
  979. self.pluginmanager._set_initial_conftests(
  980. args=args,
  981. pyargs=early_config.known_args_namespace.pyargs,
  982. noconftest=early_config.known_args_namespace.noconftest,
  983. rootpath=early_config.rootpath,
  984. confcutdir=early_config.known_args_namespace.confcutdir,
  985. importmode=early_config.known_args_namespace.importmode,
  986. )
  987. def _initini(self, args: Sequence[str]) -> None:
  988. ns, unknown_args = self._parser.parse_known_and_unknown_args(
  989. args, namespace=copy.copy(self.option)
  990. )
  991. rootpath, inipath, inicfg = determine_setup(
  992. ns.inifilename,
  993. ns.file_or_dir + unknown_args,
  994. rootdir_cmd_arg=ns.rootdir or None,
  995. config=self,
  996. )
  997. self._rootpath = rootpath
  998. self._inipath = inipath
  999. self.inicfg = inicfg
  1000. self._parser.extra_info["rootdir"] = str(self.rootpath)
  1001. self._parser.extra_info["inifile"] = str(self.inipath)
  1002. self._parser.addini("addopts", "Extra command line options", "args")
  1003. self._parser.addini("minversion", "Minimally required pytest version")
  1004. self._parser.addini(
  1005. "required_plugins",
  1006. "Plugins that must be present for pytest to run",
  1007. type="args",
  1008. default=[],
  1009. )
  1010. self._override_ini = ns.override_ini or ()
  1011. def _consider_importhook(self, args: Sequence[str]) -> None:
  1012. """Install the PEP 302 import hook if using assertion rewriting.
  1013. Needs to parse the --assert=<mode> option from the commandline
  1014. and find all the installed plugins to mark them for rewriting
  1015. by the importhook.
  1016. """
  1017. ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
  1018. mode = getattr(ns, "assertmode", "plain")
  1019. if mode == "rewrite":
  1020. import _pytest.assertion
  1021. try:
  1022. hook = _pytest.assertion.install_importhook(self)
  1023. except SystemError:
  1024. mode = "plain"
  1025. else:
  1026. self._mark_plugins_for_rewrite(hook)
  1027. self._warn_about_missing_assertion(mode)
  1028. def _mark_plugins_for_rewrite(self, hook) -> None:
  1029. """Given an importhook, mark for rewrite any top-level
  1030. modules or packages in the distribution package for
  1031. all pytest plugins."""
  1032. self.pluginmanager.rewrite_hook = hook
  1033. if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
  1034. # We don't autoload from setuptools entry points, no need to continue.
  1035. return
  1036. package_files = (
  1037. str(file)
  1038. for dist in importlib_metadata.distributions()
  1039. if any(ep.group == "pytest11" for ep in dist.entry_points)
  1040. for file in dist.files or []
  1041. )
  1042. for name in _iter_rewritable_modules(package_files):
  1043. hook.mark_rewrite(name)
  1044. def _validate_args(self, args: List[str], via: str) -> List[str]:
  1045. """Validate known args."""
  1046. self._parser._config_source_hint = via # type: ignore
  1047. try:
  1048. self._parser.parse_known_and_unknown_args(
  1049. args, namespace=copy.copy(self.option)
  1050. )
  1051. finally:
  1052. del self._parser._config_source_hint # type: ignore
  1053. return args
  1054. def _decide_args(
  1055. self,
  1056. *,
  1057. args: List[str],
  1058. pyargs: List[str],
  1059. testpaths: List[str],
  1060. invocation_dir: Path,
  1061. rootpath: Path,
  1062. warn: bool,
  1063. ) -> Tuple[List[str], ArgsSource]:
  1064. """Decide the args (initial paths/nodeids) to use given the relevant inputs.
  1065. :param warn: Whether can issue warnings.
  1066. """
  1067. if args:
  1068. source = Config.ArgsSource.ARGS
  1069. result = args
  1070. else:
  1071. if invocation_dir == rootpath:
  1072. source = Config.ArgsSource.TESTPATHS
  1073. if pyargs:
  1074. result = testpaths
  1075. else:
  1076. result = []
  1077. for path in testpaths:
  1078. result.extend(sorted(glob.iglob(path, recursive=True)))
  1079. if testpaths and not result:
  1080. if warn:
  1081. warning_text = (
  1082. "No files were found in testpaths; "
  1083. "consider removing or adjusting your testpaths configuration. "
  1084. "Searching recursively from the current directory instead."
  1085. )
  1086. self.issue_config_time_warning(
  1087. PytestConfigWarning(warning_text), stacklevel=3
  1088. )
  1089. else:
  1090. result = []
  1091. if not result:
  1092. source = Config.ArgsSource.INCOVATION_DIR
  1093. result = [str(invocation_dir)]
  1094. return result, source
  1095. def _preparse(self, args: List[str], addopts: bool = True) -> None:
  1096. if addopts:
  1097. env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
  1098. if len(env_addopts):
  1099. args[:] = (
  1100. self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
  1101. + args
  1102. )
  1103. self._initini(args)
  1104. if addopts:
  1105. args[:] = (
  1106. self._validate_args(self.getini("addopts"), "via addopts config") + args
  1107. )
  1108. self.known_args_namespace = self._parser.parse_known_args(
  1109. args, namespace=copy.copy(self.option)
  1110. )
  1111. self._checkversion()
  1112. self._consider_importhook(args)
  1113. self.pluginmanager.consider_preparse(args, exclude_only=False)
  1114. if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
  1115. # Don't autoload from setuptools entry point. Only explicitly specified
  1116. # plugins are going to be loaded.
  1117. self.pluginmanager.load_setuptools_entrypoints("pytest11")
  1118. self.pluginmanager.consider_env()
  1119. self.known_args_namespace = self._parser.parse_known_args(
  1120. args, namespace=copy.copy(self.known_args_namespace)
  1121. )
  1122. self._validate_plugins()
  1123. self._warn_about_skipped_plugins()
  1124. if self.known_args_namespace.strict:
  1125. self.issue_config_time_warning(
  1126. _pytest.deprecated.STRICT_OPTION, stacklevel=2
  1127. )
  1128. if self.known_args_namespace.confcutdir is None:
  1129. if self.inipath is not None:
  1130. confcutdir = str(self.inipath.parent)
  1131. else:
  1132. confcutdir = str(self.rootpath)
  1133. self.known_args_namespace.confcutdir = confcutdir
  1134. try:
  1135. self.hook.pytest_load_initial_conftests(
  1136. early_config=self, args=args, parser=self._parser
  1137. )
  1138. except ConftestImportFailure as e:
  1139. if self.known_args_namespace.help or self.known_args_namespace.version:
  1140. # we don't want to prevent --help/--version to work
  1141. # so just let is pass and print a warning at the end
  1142. self.issue_config_time_warning(
  1143. PytestConfigWarning(f"could not load initial conftests: {e.path}"),
  1144. stacklevel=2,
  1145. )
  1146. else:
  1147. raise
  1148. @hookimpl(hookwrapper=True)
  1149. def pytest_collection(self) -> Generator[None, None, None]:
  1150. # Validate invalid ini keys after collection is done so we take in account
  1151. # options added by late-loading conftest files.
  1152. yield
  1153. self._validate_config_options()
  1154. def _checkversion(self) -> None:
  1155. import pytest
  1156. minver = self.inicfg.get("minversion", None)
  1157. if minver:
  1158. # Imported lazily to improve start-up time.
  1159. from packaging.version import Version
  1160. if not isinstance(minver, str):
  1161. raise pytest.UsageError(
  1162. "%s: 'minversion' must be a single value" % self.inipath
  1163. )
  1164. if Version(minver) > Version(pytest.__version__):
  1165. raise pytest.UsageError(
  1166. "%s: 'minversion' requires pytest-%s, actual pytest-%s'"
  1167. % (
  1168. self.inipath,
  1169. minver,
  1170. pytest.__version__,
  1171. )
  1172. )
  1173. def _validate_config_options(self) -> None:
  1174. for key in sorted(self._get_unknown_ini_keys()):
  1175. self._warn_or_fail_if_strict(f"Unknown config option: {key}\n")
  1176. def _validate_plugins(self) -> None:
  1177. required_plugins = sorted(self.getini("required_plugins"))
  1178. if not required_plugins:
  1179. return
  1180. # Imported lazily to improve start-up time.
  1181. from packaging.version import Version
  1182. from packaging.requirements import InvalidRequirement, Requirement
  1183. plugin_info = self.pluginmanager.list_plugin_distinfo()
  1184. plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info}
  1185. missing_plugins = []
  1186. for required_plugin in required_plugins:
  1187. try:
  1188. req = Requirement(required_plugin)
  1189. except InvalidRequirement:
  1190. missing_plugins.append(required_plugin)
  1191. continue
  1192. if req.name not in plugin_dist_info:
  1193. missing_plugins.append(required_plugin)
  1194. elif not req.specifier.contains(
  1195. Version(plugin_dist_info[req.name]), prereleases=True
  1196. ):
  1197. missing_plugins.append(required_plugin)
  1198. if missing_plugins:
  1199. raise UsageError(
  1200. "Missing required plugins: {}".format(", ".join(missing_plugins)),
  1201. )
  1202. def _warn_or_fail_if_strict(self, message: str) -> None:
  1203. if self.known_args_namespace.strict_config:
  1204. raise UsageError(message)
  1205. self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3)
  1206. def _get_unknown_ini_keys(self) -> List[str]:
  1207. parser_inicfg = self._parser._inidict
  1208. return [name for name in self.inicfg if name not in parser_inicfg]
  1209. def parse(self, args: List[str], addopts: bool = True) -> None:
  1210. # Parse given cmdline arguments into this config object.
  1211. assert (
  1212. self.args == []
  1213. ), "can only parse cmdline args at most once per Config object"
  1214. self.hook.pytest_addhooks.call_historic(
  1215. kwargs=dict(pluginmanager=self.pluginmanager)
  1216. )
  1217. self._preparse(args, addopts=addopts)
  1218. # XXX deprecated hook:
  1219. self.hook.pytest_cmdline_preparse(config=self, args=args)
  1220. self._parser.after_preparse = True # type: ignore
  1221. try:
  1222. args = self._parser.parse_setoption(
  1223. args, self.option, namespace=self.option
  1224. )
  1225. self.args, self.args_source = self._decide_args(
  1226. args=args,
  1227. pyargs=self.known_args_namespace.pyargs,
  1228. testpaths=self.getini("testpaths"),
  1229. invocation_dir=self.invocation_params.dir,
  1230. rootpath=self.rootpath,
  1231. warn=True,
  1232. )
  1233. except PrintHelp:
  1234. pass
  1235. def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None:
  1236. """Issue and handle a warning during the "configure" stage.
  1237. During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item``
  1238. function because it is not possible to have hookwrappers around ``pytest_configure``.
  1239. This function is mainly intended for plugins that need to issue warnings during
  1240. ``pytest_configure`` (or similar stages).
  1241. :param warning: The warning instance.
  1242. :param stacklevel: stacklevel forwarded to warnings.warn.
  1243. """
  1244. if self.pluginmanager.is_blocked("warnings"):
  1245. return
  1246. cmdline_filters = self.known_args_namespace.pythonwarnings or []
  1247. config_filters = self.getini("filterwarnings")
  1248. with warnings.catch_warnings(record=True) as records:
  1249. warnings.simplefilter("always", type(warning))
  1250. apply_warning_filters(config_filters, cmdline_filters)
  1251. warnings.warn(warning, stacklevel=stacklevel)
  1252. if records:
  1253. frame = sys._getframe(stacklevel - 1)
  1254. location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name
  1255. self.hook.pytest_warning_recorded.call_historic(
  1256. kwargs=dict(
  1257. warning_message=records[0],
  1258. when="config",
  1259. nodeid="",
  1260. location=location,
  1261. )
  1262. )
  1263. def addinivalue_line(self, name: str, line: str) -> None:
  1264. """Add a line to an ini-file option. The option must have been
  1265. declared but might not yet be set in which case the line becomes
  1266. the first line in its value."""
  1267. x = self.getini(name)
  1268. assert isinstance(x, list)
  1269. x.append(line) # modifies the cached list inline
  1270. def getini(self, name: str):
  1271. """Return configuration value from an :ref:`ini file <configfiles>`.
  1272. If the specified name hasn't been registered through a prior
  1273. :func:`parser.addini <pytest.Parser.addini>` call (usually from a
  1274. plugin), a ValueError is raised.
  1275. """
  1276. try:
  1277. return self._inicache[name]
  1278. except KeyError:
  1279. self._inicache[name] = val = self._getini(name)
  1280. return val
  1281. # Meant for easy monkeypatching by legacypath plugin.
  1282. # Can be inlined back (with no cover removed) once legacypath is gone.
  1283. def _getini_unknown_type(self, name: str, type: str, value: Union[str, List[str]]):
  1284. msg = f"unknown configuration type: {type}"
  1285. raise ValueError(msg, value) # pragma: no cover
  1286. def _getini(self, name: str):
  1287. try:
  1288. description, type, default = self._parser._inidict[name]
  1289. except KeyError as e:
  1290. raise ValueError(f"unknown configuration value: {name!r}") from e
  1291. override_value = self._get_override_ini_value(name)
  1292. if override_value is None:
  1293. try:
  1294. value = self.inicfg[name]
  1295. except KeyError:
  1296. if default is not None:
  1297. return default
  1298. if type is None:
  1299. return ""
  1300. return []
  1301. else:
  1302. value = override_value
  1303. # Coerce the values based on types.
  1304. #
  1305. # Note: some coercions are only required if we are reading from .ini files, because
  1306. # the file format doesn't contain type information, but when reading from toml we will
  1307. # get either str or list of str values (see _parse_ini_config_from_pyproject_toml).
  1308. # For example:
  1309. #
  1310. # ini:
  1311. # a_line_list = "tests acceptance"
  1312. # in this case, we need to split the string to obtain a list of strings.
  1313. #
  1314. # toml:
  1315. # a_line_list = ["tests", "acceptance"]
  1316. # in this case, we already have a list ready to use.
  1317. #
  1318. if type == "paths":
  1319. # TODO: This assert is probably not valid in all cases.
  1320. assert self.inipath is not None
  1321. dp = self.inipath.parent
  1322. input_values = shlex.split(value) if isinstance(value, str) else value
  1323. return [dp / x for x in input_values]
  1324. elif type == "args":
  1325. return shlex.split(value) if isinstance(value, str) else value
  1326. elif type == "linelist":
  1327. if isinstance(value, str):
  1328. return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
  1329. else:
  1330. return value
  1331. elif type == "bool":
  1332. return _strtobool(str(value).strip())
  1333. elif type == "string":
  1334. return value
  1335. elif type is None:
  1336. return value
  1337. else:
  1338. return self._getini_unknown_type(name, type, value)
  1339. def _getconftest_pathlist(
  1340. self, name: str, path: Path, rootpath: Path
  1341. ) -> Optional[List[Path]]:
  1342. try:
  1343. mod, relroots = self.pluginmanager._rget_with_confmod(
  1344. name, path, self.getoption("importmode"), rootpath
  1345. )
  1346. except KeyError:
  1347. return None
  1348. assert mod.__file__ is not None
  1349. modpath = Path(mod.__file__).parent
  1350. values: List[Path] = []
  1351. for relroot in relroots:
  1352. if isinstance(relroot, os.PathLike):
  1353. relroot = Path(relroot)
  1354. else:
  1355. relroot = relroot.replace("/", os.sep)
  1356. relroot = absolutepath(modpath / relroot)
  1357. values.append(relroot)
  1358. return values
  1359. def _get_override_ini_value(self, name: str) -> Optional[str]:
  1360. value = None
  1361. # override_ini is a list of "ini=value" options.
  1362. # Always use the last item if multiple values are set for same ini-name,
  1363. # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2.
  1364. for ini_config in self._override_ini:
  1365. try:
  1366. key, user_ini_value = ini_config.split("=", 1)
  1367. except ValueError as e:
  1368. raise UsageError(
  1369. "-o/--override-ini expects option=value style (got: {!r}).".format(
  1370. ini_config
  1371. )
  1372. ) from e
  1373. else:
  1374. if key == name:
  1375. value = user_ini_value
  1376. return value
  1377. def getoption(self, name: str, default=notset, skip: bool = False):
  1378. """Return command line option value.
  1379. :param name: Name of the option. You may also specify
  1380. the literal ``--OPT`` option instead of the "dest" option name.
  1381. :param default: Default value if no option of that name exists.
  1382. :param skip: If True, raise pytest.skip if option does not exists
  1383. or has a None value.
  1384. """
  1385. name = self._opt2dest.get(name, name)
  1386. try:
  1387. val = getattr(self.option, name)
  1388. if val is None and skip:
  1389. raise AttributeError(name)
  1390. return val
  1391. except AttributeError as e:
  1392. if default is not notset:
  1393. return default
  1394. if skip:
  1395. import pytest
  1396. pytest.skip(f"no {name!r} option found")
  1397. raise ValueError(f"no option named {name!r}") from e
  1398. def getvalue(self, name: str, path=None):
  1399. """Deprecated, use getoption() instead."""
  1400. return self.getoption(name)
  1401. def getvalueorskip(self, name: str, path=None):
  1402. """Deprecated, use getoption(skip=True) instead."""
  1403. return self.getoption(name, skip=True)
  1404. def _warn_about_missing_assertion(self, mode: str) -> None:
  1405. if not _assertion_supported():
  1406. if mode == "plain":
  1407. warning_text = (
  1408. "ASSERTIONS ARE NOT EXECUTED"
  1409. " and FAILING TESTS WILL PASS. Are you"
  1410. " using python -O?"
  1411. )
  1412. else:
  1413. warning_text = (
  1414. "assertions not in test modules or"
  1415. " plugins will be ignored"
  1416. " because assert statements are not executed "
  1417. "by the underlying Python interpreter "
  1418. "(are you using python -O?)\n"
  1419. )
  1420. self.issue_config_time_warning(
  1421. PytestConfigWarning(warning_text),
  1422. stacklevel=3,
  1423. )
  1424. def _warn_about_skipped_plugins(self) -> None:
  1425. for module_name, msg in self.pluginmanager.skipped_plugins:
  1426. self.issue_config_time_warning(
  1427. PytestConfigWarning(f"skipped plugin {module_name!r}: {msg}"),
  1428. stacklevel=2,
  1429. )
  1430. def _assertion_supported() -> bool:
  1431. try:
  1432. assert False
  1433. except AssertionError:
  1434. return True
  1435. else:
  1436. return False # type: ignore[unreachable]
  1437. def create_terminal_writer(
  1438. config: Config, file: Optional[TextIO] = None
  1439. ) -> TerminalWriter:
  1440. """Create a TerminalWriter instance configured according to the options
  1441. in the config object.
  1442. Every code which requires a TerminalWriter object and has access to a
  1443. config object should use this function.
  1444. """
  1445. tw = TerminalWriter(file=file)
  1446. if config.option.color == "yes":
  1447. tw.hasmarkup = True
  1448. elif config.option.color == "no":
  1449. tw.hasmarkup = False
  1450. if config.option.code_highlight == "yes":
  1451. tw.code_highlight = True
  1452. elif config.option.code_highlight == "no":
  1453. tw.code_highlight = False
  1454. return tw
  1455. def _strtobool(val: str) -> bool:
  1456. """Convert a string representation of truth to True or False.
  1457. True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
  1458. are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
  1459. 'val' is anything else.
  1460. .. note:: Copied from distutils.util.
  1461. """
  1462. val = val.lower()
  1463. if val in ("y", "yes", "t", "true", "on", "1"):
  1464. return True
  1465. elif val in ("n", "no", "f", "false", "off", "0"):
  1466. return False
  1467. else:
  1468. raise ValueError(f"invalid truth value {val!r}")
  1469. @lru_cache(maxsize=50)
  1470. def parse_warning_filter(
  1471. arg: str, *, escape: bool
  1472. ) -> Tuple["warnings._ActionKind", str, Type[Warning], str, int]:
  1473. """Parse a warnings filter string.
  1474. This is copied from warnings._setoption with the following changes:
  1475. * Does not apply the filter.
  1476. * Escaping is optional.
  1477. * Raises UsageError so we get nice error messages on failure.
  1478. """
  1479. __tracebackhide__ = True
  1480. error_template = dedent(
  1481. f"""\
  1482. while parsing the following warning configuration:
  1483. {arg}
  1484. This error occurred:
  1485. {{error}}
  1486. """
  1487. )
  1488. parts = arg.split(":")
  1489. if len(parts) > 5:
  1490. doc_url = (
  1491. "https://docs.python.org/3/library/warnings.html#describing-warning-filters"
  1492. )
  1493. error = dedent(
  1494. f"""\
  1495. Too many fields ({len(parts)}), expected at most 5 separated by colons:
  1496. action:message:category:module:line
  1497. For more information please consult: {doc_url}
  1498. """
  1499. )
  1500. raise UsageError(error_template.format(error=error))
  1501. while len(parts) < 5:
  1502. parts.append("")
  1503. action_, message, category_, module, lineno_ = (s.strip() for s in parts)
  1504. try:
  1505. action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined]
  1506. except warnings._OptionError as e:
  1507. raise UsageError(error_template.format(error=str(e)))
  1508. try:
  1509. category: Type[Warning] = _resolve_warning_category(category_)
  1510. except Exception:
  1511. exc_info = ExceptionInfo.from_current()
  1512. exception_text = exc_info.getrepr(style="native")
  1513. raise UsageError(error_template.format(error=exception_text))
  1514. if message and escape:
  1515. message = re.escape(message)
  1516. if module and escape:
  1517. module = re.escape(module) + r"\Z"
  1518. if lineno_:
  1519. try:
  1520. lineno = int(lineno_)
  1521. if lineno < 0:
  1522. raise ValueError("number is negative")
  1523. except ValueError as e:
  1524. raise UsageError(
  1525. error_template.format(error=f"invalid lineno {lineno_!r}: {e}")
  1526. )
  1527. else:
  1528. lineno = 0
  1529. return action, message, category, module, lineno
  1530. def _resolve_warning_category(category: str) -> Type[Warning]:
  1531. """
  1532. Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors)
  1533. propagate so we can get access to their tracebacks (#9218).
  1534. """
  1535. __tracebackhide__ = True
  1536. if not category:
  1537. return Warning
  1538. if "." not in category:
  1539. import builtins as m
  1540. klass = category
  1541. else:
  1542. module, _, klass = category.rpartition(".")
  1543. m = __import__(module, None, None, [klass])
  1544. cat = getattr(m, klass)
  1545. if not issubclass(cat, Warning):
  1546. raise UsageError(f"{cat} is not a Warning subclass")
  1547. return cast(Type[Warning], cat)
  1548. def apply_warning_filters(
  1549. config_filters: Iterable[str], cmdline_filters: Iterable[str]
  1550. ) -> None:
  1551. """Applies pytest-configured filters to the warnings module"""
  1552. # Filters should have this precedence: cmdline options, config.
  1553. # Filters should be applied in the inverse order of precedence.
  1554. for arg in config_filters:
  1555. warnings.filterwarnings(*parse_warning_filter(arg, escape=False))
  1556. for arg in cmdline_filters:
  1557. warnings.filterwarnings(*parse_warning_filter(arg, escape=True))