pastebin.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. """Submit failure or test session information to a pastebin service."""
  2. import tempfile
  3. from io import StringIO
  4. from typing import IO
  5. from typing import Union
  6. import pytest
  7. from _pytest.config import Config
  8. from _pytest.config import create_terminal_writer
  9. from _pytest.config.argparsing import Parser
  10. from _pytest.stash import StashKey
  11. from _pytest.terminal import TerminalReporter
  12. pastebinfile_key = StashKey[IO[bytes]]()
  13. def pytest_addoption(parser: Parser) -> None:
  14. group = parser.getgroup("terminal reporting")
  15. group._addoption(
  16. "--pastebin",
  17. metavar="mode",
  18. action="store",
  19. dest="pastebin",
  20. default=None,
  21. choices=["failed", "all"],
  22. help="Send failed|all info to bpaste.net pastebin service",
  23. )
  24. @pytest.hookimpl(trylast=True)
  25. def pytest_configure(config: Config) -> None:
  26. if config.option.pastebin == "all":
  27. tr = config.pluginmanager.getplugin("terminalreporter")
  28. # If no terminal reporter plugin is present, nothing we can do here;
  29. # this can happen when this function executes in a worker node
  30. # when using pytest-xdist, for example.
  31. if tr is not None:
  32. # pastebin file will be UTF-8 encoded binary file.
  33. config.stash[pastebinfile_key] = tempfile.TemporaryFile("w+b")
  34. oldwrite = tr._tw.write
  35. def tee_write(s, **kwargs):
  36. oldwrite(s, **kwargs)
  37. if isinstance(s, str):
  38. s = s.encode("utf-8")
  39. config.stash[pastebinfile_key].write(s)
  40. tr._tw.write = tee_write
  41. def pytest_unconfigure(config: Config) -> None:
  42. if pastebinfile_key in config.stash:
  43. pastebinfile = config.stash[pastebinfile_key]
  44. # Get terminal contents and delete file.
  45. pastebinfile.seek(0)
  46. sessionlog = pastebinfile.read()
  47. pastebinfile.close()
  48. del config.stash[pastebinfile_key]
  49. # Undo our patching in the terminal reporter.
  50. tr = config.pluginmanager.getplugin("terminalreporter")
  51. del tr._tw.__dict__["write"]
  52. # Write summary.
  53. tr.write_sep("=", "Sending information to Paste Service")
  54. pastebinurl = create_new_paste(sessionlog)
  55. tr.write_line("pastebin session-log: %s\n" % pastebinurl)
  56. def create_new_paste(contents: Union[str, bytes]) -> str:
  57. """Create a new paste using the bpaste.net service.
  58. :contents: Paste contents string.
  59. :returns: URL to the pasted contents, or an error message.
  60. """
  61. import re
  62. from urllib.request import urlopen
  63. from urllib.parse import urlencode
  64. params = {"code": contents, "lexer": "text", "expiry": "1week"}
  65. url = "https://bpa.st"
  66. try:
  67. response: str = (
  68. urlopen(url, data=urlencode(params).encode("ascii")).read().decode("utf-8")
  69. )
  70. except OSError as exc_info: # urllib errors
  71. return "bad response: %s" % exc_info
  72. m = re.search(r'href="/raw/(\w+)"', response)
  73. if m:
  74. return f"{url}/show/{m.group(1)}"
  75. else:
  76. return "bad response: invalid format ('" + response + "')"
  77. def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None:
  78. if terminalreporter.config.option.pastebin != "failed":
  79. return
  80. if "failed" in terminalreporter.stats:
  81. terminalreporter.write_sep("=", "Sending information to Paste Service")
  82. for rep in terminalreporter.stats["failed"]:
  83. try:
  84. msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc
  85. except AttributeError:
  86. msg = terminalreporter._getfailureheadline(rep)
  87. file = StringIO()
  88. tw = create_terminal_writer(terminalreporter.config, file)
  89. rep.toterminal(tw)
  90. s = file.getvalue()
  91. assert len(s)
  92. pastebinurl = create_new_paste(s)
  93. terminalreporter.write_line(f"{msg} --> {pastebinurl}")