Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" submit failure or test session information to a pastebin service. """ 

2import tempfile 

3from io import StringIO 

4from typing import IO 

5from typing import Union 

6 

7import pytest 

8from _pytest.config import Config 

9from _pytest.config import create_terminal_writer 

10from _pytest.config.argparsing import Parser 

11from _pytest.store import StoreKey 

12from _pytest.terminal import TerminalReporter 

13 

14 

15pastebinfile_key = StoreKey[IO[bytes]]() 

16 

17 

18def pytest_addoption(parser: Parser) -> None: 

19 group = parser.getgroup("terminal reporting") 

20 group._addoption( 

21 "--pastebin", 

22 metavar="mode", 

23 action="store", 

24 dest="pastebin", 

25 default=None, 

26 choices=["failed", "all"], 

27 help="send failed|all info to bpaste.net pastebin service.", 

28 ) 

29 

30 

31@pytest.hookimpl(trylast=True) 

32def pytest_configure(config: Config) -> None: 

33 if config.option.pastebin == "all": 

34 tr = config.pluginmanager.getplugin("terminalreporter") 

35 # if no terminal reporter plugin is present, nothing we can do here; 

36 # this can happen when this function executes in a worker node 

37 # when using pytest-xdist, for example 

38 if tr is not None: 

39 # pastebin file will be utf-8 encoded binary file 

40 config._store[pastebinfile_key] = tempfile.TemporaryFile("w+b") 

41 oldwrite = tr._tw.write 

42 

43 def tee_write(s, **kwargs): 

44 oldwrite(s, **kwargs) 

45 if isinstance(s, str): 

46 s = s.encode("utf-8") 

47 config._store[pastebinfile_key].write(s) 

48 

49 tr._tw.write = tee_write 

50 

51 

52def pytest_unconfigure(config: Config) -> None: 

53 if pastebinfile_key in config._store: 

54 pastebinfile = config._store[pastebinfile_key] 

55 # get terminal contents and delete file 

56 pastebinfile.seek(0) 

57 sessionlog = pastebinfile.read() 

58 pastebinfile.close() 

59 del config._store[pastebinfile_key] 

60 # undo our patching in the terminal reporter 

61 tr = config.pluginmanager.getplugin("terminalreporter") 

62 del tr._tw.__dict__["write"] 

63 # write summary 

64 tr.write_sep("=", "Sending information to Paste Service") 

65 pastebinurl = create_new_paste(sessionlog) 

66 tr.write_line("pastebin session-log: %s\n" % pastebinurl) 

67 

68 

69def create_new_paste(contents: Union[str, bytes]) -> str: 

70 """ 

71 Creates a new paste using bpaste.net service. 

72 

73 :contents: paste contents string 

74 :returns: url to the pasted contents or error message 

75 """ 

76 import re 

77 from urllib.request import urlopen 

78 from urllib.parse import urlencode 

79 

80 params = {"code": contents, "lexer": "text", "expiry": "1week"} 

81 url = "https://bpaste.net" 

82 try: 

83 response = ( 

84 urlopen(url, data=urlencode(params).encode("ascii")).read().decode("utf-8") 

85 ) # type: str 

86 except OSError as exc_info: # urllib errors 

87 return "bad response: %s" % exc_info 

88 m = re.search(r'href="/raw/(\w+)"', response) 

89 if m: 

90 return "{}/show/{}".format(url, m.group(1)) 

91 else: 

92 return "bad response: invalid format ('" + response + "')" 

93 

94 

95def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None: 

96 if terminalreporter.config.option.pastebin != "failed": 

97 return 

98 if "failed" in terminalreporter.stats: 

99 terminalreporter.write_sep("=", "Sending information to Paste Service") 

100 for rep in terminalreporter.stats["failed"]: 

101 try: 

102 msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc 

103 except AttributeError: 

104 msg = terminalreporter._getfailureheadline(rep) 

105 file = StringIO() 

106 tw = create_terminal_writer(terminalreporter.config, file) 

107 rep.toterminal(tw) 

108 s = file.getvalue() 

109 assert len(s) 

110 pastebinurl = create_new_paste(s) 

111 terminalreporter.write_line("{} --> {}".format(msg, pastebinurl))