warnings.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. from __future__ import absolute_import, division, print_function
  2. import warnings
  3. from contextlib import contextmanager
  4. import pytest
  5. from _pytest import compat
  6. def _setoption(wmod, arg):
  7. """
  8. Copy of the warning._setoption function but does not escape arguments.
  9. """
  10. parts = arg.split(":")
  11. if len(parts) > 5:
  12. raise wmod._OptionError("too many fields (max 5): %r" % (arg,))
  13. while len(parts) < 5:
  14. parts.append("")
  15. action, message, category, module, lineno = [s.strip() for s in parts]
  16. action = wmod._getaction(action)
  17. category = wmod._getcategory(category)
  18. if lineno:
  19. try:
  20. lineno = int(lineno)
  21. if lineno < 0:
  22. raise ValueError
  23. except (ValueError, OverflowError):
  24. raise wmod._OptionError("invalid lineno %r" % (lineno,))
  25. else:
  26. lineno = 0
  27. wmod.filterwarnings(action, message, category, module, lineno)
  28. def pytest_addoption(parser):
  29. group = parser.getgroup("pytest-warnings")
  30. group.addoption(
  31. "-W",
  32. "--pythonwarnings",
  33. action="append",
  34. help="set which warnings to report, see -W option of python itself.",
  35. )
  36. parser.addini(
  37. "filterwarnings",
  38. type="linelist",
  39. help="Each line specifies a pattern for "
  40. "warnings.filterwarnings. "
  41. "Processed after -W and --pythonwarnings.",
  42. )
  43. def pytest_configure(config):
  44. config.addinivalue_line(
  45. "markers",
  46. "filterwarnings(warning): add a warning filter to the given test. "
  47. "see https://docs.pytest.org/en/latest/warnings.html#pytest-mark-filterwarnings ",
  48. )
  49. @contextmanager
  50. def catch_warnings_for_item(item):
  51. """
  52. catches the warnings generated during setup/call/teardown execution
  53. of the given item and after it is done posts them as warnings to this
  54. item.
  55. """
  56. args = item.config.getoption("pythonwarnings") or []
  57. inifilters = item.config.getini("filterwarnings")
  58. with warnings.catch_warnings(record=True) as log:
  59. for arg in args:
  60. warnings._setoption(arg)
  61. for arg in inifilters:
  62. _setoption(warnings, arg)
  63. for mark in item.iter_markers(name="filterwarnings"):
  64. for arg in mark.args:
  65. warnings._setoption(arg)
  66. yield
  67. for warning in log:
  68. warn_msg = warning.message
  69. unicode_warning = False
  70. if compat._PY2 and any(
  71. isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args
  72. ):
  73. new_args = []
  74. for m in warn_msg.args:
  75. new_args.append(
  76. compat.ascii_escaped(m)
  77. if isinstance(m, compat.UNICODE_TYPES)
  78. else m
  79. )
  80. unicode_warning = list(warn_msg.args) != new_args
  81. warn_msg.args = new_args
  82. msg = warnings.formatwarning(
  83. warn_msg,
  84. warning.category,
  85. warning.filename,
  86. warning.lineno,
  87. warning.line,
  88. )
  89. item.warn("unused", msg)
  90. if unicode_warning:
  91. warnings.warn(
  92. "Warning is using unicode non convertible to ascii, "
  93. "converting to a safe representation:\n %s" % msg,
  94. UnicodeWarning,
  95. )
  96. @pytest.hookimpl(hookwrapper=True)
  97. def pytest_runtest_protocol(item):
  98. with catch_warnings_for_item(item):
  99. yield