You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

140 lines
4.4KB

  1. import sys
  2. import warnings
  3. from contextlib import contextmanager
  4. from typing import Generator
  5. from typing import Optional
  6. from typing import TYPE_CHECKING
  7. import pytest
  8. from _pytest.config import apply_warning_filters
  9. from _pytest.config import Config
  10. from _pytest.config import parse_warning_filter
  11. from _pytest.main import Session
  12. from _pytest.nodes import Item
  13. from _pytest.terminal import TerminalReporter
  14. if TYPE_CHECKING:
  15. from typing_extensions import Literal
  16. def pytest_configure(config: Config) -> None:
  17. config.addinivalue_line(
  18. "markers",
  19. "filterwarnings(warning): add a warning filter to the given test. "
  20. "see https://docs.pytest.org/en/stable/warnings.html#pytest-mark-filterwarnings ",
  21. )
  22. @contextmanager
  23. def catch_warnings_for_item(
  24. config: Config,
  25. ihook,
  26. when: "Literal['config', 'collect', 'runtest']",
  27. item: Optional[Item],
  28. ) -> Generator[None, None, None]:
  29. """Context manager that catches warnings generated in the contained execution block.
  30. ``item`` can be None if we are not in the context of an item execution.
  31. Each warning captured triggers the ``pytest_warning_recorded`` hook.
  32. """
  33. config_filters = config.getini("filterwarnings")
  34. cmdline_filters = config.known_args_namespace.pythonwarnings or []
  35. with warnings.catch_warnings(record=True) as log:
  36. # mypy can't infer that record=True means log is not None; help it.
  37. assert log is not None
  38. if not sys.warnoptions:
  39. # If user is not explicitly configuring warning filters, show deprecation warnings by default (#2908).
  40. warnings.filterwarnings("always", category=DeprecationWarning)
  41. warnings.filterwarnings("always", category=PendingDeprecationWarning)
  42. apply_warning_filters(config_filters, cmdline_filters)
  43. # apply filters from "filterwarnings" marks
  44. nodeid = "" if item is None else item.nodeid
  45. if item is not None:
  46. for mark in item.iter_markers(name="filterwarnings"):
  47. for arg in mark.args:
  48. warnings.filterwarnings(*parse_warning_filter(arg, escape=False))
  49. yield
  50. for warning_message in log:
  51. ihook.pytest_warning_captured.call_historic(
  52. kwargs=dict(
  53. warning_message=warning_message,
  54. when=when,
  55. item=item,
  56. location=None,
  57. )
  58. )
  59. ihook.pytest_warning_recorded.call_historic(
  60. kwargs=dict(
  61. warning_message=warning_message,
  62. nodeid=nodeid,
  63. when=when,
  64. location=None,
  65. )
  66. )
  67. def warning_record_to_str(warning_message: warnings.WarningMessage) -> str:
  68. """Convert a warnings.WarningMessage to a string."""
  69. warn_msg = warning_message.message
  70. msg = warnings.formatwarning(
  71. str(warn_msg),
  72. warning_message.category,
  73. warning_message.filename,
  74. warning_message.lineno,
  75. warning_message.line,
  76. )
  77. return msg
  78. @pytest.hookimpl(hookwrapper=True, tryfirst=True)
  79. def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
  80. with catch_warnings_for_item(
  81. config=item.config, ihook=item.ihook, when="runtest", item=item
  82. ):
  83. yield
  84. @pytest.hookimpl(hookwrapper=True, tryfirst=True)
  85. def pytest_collection(session: Session) -> Generator[None, None, None]:
  86. config = session.config
  87. with catch_warnings_for_item(
  88. config=config, ihook=config.hook, when="collect", item=None
  89. ):
  90. yield
  91. @pytest.hookimpl(hookwrapper=True)
  92. def pytest_terminal_summary(
  93. terminalreporter: TerminalReporter,
  94. ) -> Generator[None, None, None]:
  95. config = terminalreporter.config
  96. with catch_warnings_for_item(
  97. config=config, ihook=config.hook, when="config", item=None
  98. ):
  99. yield
  100. @pytest.hookimpl(hookwrapper=True)
  101. def pytest_sessionfinish(session: Session) -> Generator[None, None, None]:
  102. config = session.config
  103. with catch_warnings_for_item(
  104. config=config, ihook=config.hook, when="config", item=None
  105. ):
  106. yield
  107. @pytest.hookimpl(hookwrapper=True)
  108. def pytest_load_initial_conftests(
  109. early_config: "Config",
  110. ) -> Generator[None, None, None]:
  111. with catch_warnings_for_item(
  112. config=early_config, ihook=early_config.hook, when="config", item=None
  113. ):
  114. yield