Gracefully handle HTTP errors from pastebin

We find that the --pastebin option to pytest sometimes fails with "HTTP
Error 400: Bad Request". We're still investigating the exact cause of
these errors, but in the meantime, a failure to upload to the pastebin
service should probably not crash pytest and cause a test failure in the
continuous-integration.

This patch catches exceptions like HTTPError that may be thrown while
trying to communicate with the pastebin service, and reports them as a
"bad response", without crashing with a backtrace or failing the entire
test suite.
This commit is contained in:
Michael Goerz 2019-08-18 13:32:46 -04:00
parent 5bf9f9a711
commit d47b9d04d4
4 changed files with 66 additions and 5 deletions

View File

@ -173,6 +173,7 @@ mbyt
Michael Aquilina Michael Aquilina
Michael Birtwell Michael Birtwell
Michael Droettboom Michael Droettboom
Michael Goerz
Michael Seifert Michael Seifert
Michal Wajszczuk Michal Wajszczuk
Mihai Capotă Mihai Capotă

View File

@ -0,0 +1 @@
New behavior of the ``--pastebin`` option: failures to connect to the pastebin server are reported, without failing the pytest run

View File

@ -59,7 +59,7 @@ def create_new_paste(contents):
Creates a new paste using bpaste.net service. Creates a new paste using bpaste.net service.
:contents: paste contents as utf-8 encoded bytes :contents: paste contents as utf-8 encoded bytes
:returns: url to the pasted contents :returns: url to the pasted contents or error message
""" """
import re import re
from urllib.request import urlopen from urllib.request import urlopen
@ -67,12 +67,17 @@ def create_new_paste(contents):
params = {"code": contents, "lexer": "python3", "expiry": "1week"} params = {"code": contents, "lexer": "python3", "expiry": "1week"}
url = "https://bpaste.net" url = "https://bpaste.net"
response = urlopen(url, data=urlencode(params).encode("ascii")).read() try:
m = re.search(r'href="/raw/(\w+)"', response.decode("utf-8")) response = (
urlopen(url, data=urlencode(params).encode("ascii")).read().decode("utf-8")
)
except OSError as exc_info: # urllib errors
return "bad response: %s" % exc_info
m = re.search(r'href="/raw/(\w+)"', response)
if m: if m:
return "{}/show/{}".format(url, m.group(1)) return "{}/show/{}".format(url, m.group(1))
else: else:
return "bad response: " + response.decode("utf-8") return "bad response: invalid format ('" + response + "')"
def pytest_terminal_summary(terminalreporter): def pytest_terminal_summary(terminalreporter):

View File

@ -82,6 +82,47 @@ class TestPaste:
def pastebin(self, request): def pastebin(self, request):
return request.config.pluginmanager.getplugin("pastebin") return request.config.pluginmanager.getplugin("pastebin")
@pytest.fixture
def mocked_urlopen_fail(self, monkeypatch):
"""
monkeypatch the actual urlopen call to emulate a HTTP Error 400
"""
calls = []
import urllib.error
import urllib.request
def mocked(url, data):
calls.append((url, data))
raise urllib.error.HTTPError(url, 400, "Bad request", None, None)
monkeypatch.setattr(urllib.request, "urlopen", mocked)
return calls
@pytest.fixture
def mocked_urlopen_invalid(self, monkeypatch):
"""
monkeypatch the actual urlopen calls done by the internal plugin
function that connects to bpaste service, but return a url in an
unexpected format
"""
calls = []
def mocked(url, data):
calls.append((url, data))
class DummyFile:
def read(self):
# part of html of a normal response
return b'View <a href="/invalid/3c0c6750bd">raw</a>.'
return DummyFile()
import urllib.request
monkeypatch.setattr(urllib.request, "urlopen", mocked)
return calls
@pytest.fixture @pytest.fixture
def mocked_urlopen(self, monkeypatch): def mocked_urlopen(self, monkeypatch):
""" """
@ -105,6 +146,19 @@ class TestPaste:
monkeypatch.setattr(urllib.request, "urlopen", mocked) monkeypatch.setattr(urllib.request, "urlopen", mocked)
return calls return calls
def test_pastebin_invalid_url(self, pastebin, mocked_urlopen_invalid):
result = pastebin.create_new_paste(b"full-paste-contents")
assert (
result
== "bad response: invalid format ('View <a href=\"/invalid/3c0c6750bd\">raw</a>.')"
)
assert len(mocked_urlopen_invalid) == 1
def test_pastebin_http_error(self, pastebin, mocked_urlopen_fail):
result = pastebin.create_new_paste(b"full-paste-contents")
assert result == "bad response: HTTP Error 400: Bad request"
assert len(mocked_urlopen_fail) == 1
def test_create_new_paste(self, pastebin, mocked_urlopen): def test_create_new_paste(self, pastebin, mocked_urlopen):
result = pastebin.create_new_paste(b"full-paste-contents") result = pastebin.create_new_paste(b"full-paste-contents")
assert result == "https://bpaste.net/show/3c0c6750bd" assert result == "https://bpaste.net/show/3c0c6750bd"
@ -127,4 +181,4 @@ class TestPaste:
monkeypatch.setattr(urllib.request, "urlopen", response) monkeypatch.setattr(urllib.request, "urlopen", response)
result = pastebin.create_new_paste(b"full-paste-contents") result = pastebin.create_new_paste(b"full-paste-contents")
assert result == "bad response: something bad occurred" assert result == "bad response: invalid format ('something bad occurred')"