Skip to content

Commit 20e1454

Browse files
authored
httpserver: propagate event handler exceptions to httpserver (#64)
* httpserver: propagate event handler exceptions to httpserver * fix import error and add check() method
1 parent d98a6c4 commit 20e1454

3 files changed

Lines changed: 138 additions & 3 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ doc/_build/
1313
.tox/
1414
.idea/
1515
.python-version
16+
__pycache__/

pytest_httpserver/httpserver.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ def __init__(self, host=DEFAULT_LISTEN_HOST, port=DEFAULT_LISTEN_PORT, ssl_conte
576576
self.server = None
577577
self.server_thread = None
578578
self.assertions = []
579+
self.handler_errors = []
579580
self.log = []
580581
self.ordered_handlers = []
581582
self.oneshot_handlers = RequestHandlerList()
@@ -598,6 +599,7 @@ def clear(self):
598599
599600
"""
600601
self.clear_assertions()
602+
self.clear_handler_errors()
601603
self.clear_log()
602604
self.clear_all_handlers()
603605
self.permanently_failed = False
@@ -610,6 +612,13 @@ def clear_assertions(self):
610612

611613
self.assertions = []
612614

615+
def clear_handler_errors(self):
616+
"""
617+
Clears the list of collected errors from handler invocations
618+
"""
619+
620+
self.handler_errors = []
621+
613622
def clear_log(self):
614623
"""
615624
Clears the list of log entries
@@ -893,10 +902,19 @@ def add_assertion(self, obj):
893902
Assertions can be added here, and when :py:meth:`check_assertions` is called,
894903
it will raise AssertionError for pytest with the object specified here.
895904
896-
:param obj: An object which will be passed to AssertionError.
905+
:param obj: An AssertionError, or an object which will be passed to an AssertionError.
897906
"""
898907
self.assertions.append(obj)
899908

909+
def check(self):
910+
"""
911+
Raises AssertionError or Errors raised in handlers.
912+
913+
Runs both :py:meth:`check_assertions` and :py:meth:`check_handler_errors`
914+
"""
915+
self.check_assertions()
916+
self.check_handler_errors()
917+
900918
def check_assertions(self):
901919
"""
902920
Raise AssertionError when at least one assertion added
@@ -909,7 +927,21 @@ def check_assertions(self):
909927
"""
910928

911929
if self.assertions:
912-
raise AssertionError(self.assertions.pop(0))
930+
assertion = self.assertions.pop(0)
931+
if isinstance(assertion, AssertionError):
932+
raise assertion
933+
934+
raise AssertionError(assertion)
935+
936+
def check_handler_errors(self):
937+
"""
938+
Re-Raises any errors caused in request handlers
939+
940+
The first error raised by a handler will be re-raised here, and then
941+
removed from the list.
942+
"""
943+
if self.handler_errors:
944+
raise self.handler_errors.pop(0)
913945

914946
def format_matchers(self) -> str:
915947
"""
@@ -1007,7 +1039,17 @@ def dispatch(self, request: Request) -> Response:
10071039
if not handler:
10081040
return self.respond_nohandler(request)
10091041

1010-
response = handler.respond(request)
1042+
try:
1043+
response = handler.respond(request)
1044+
except Error:
1045+
# don't collect package-internal errors
1046+
raise
1047+
except AssertionError as e:
1048+
self.add_assertion(e)
1049+
raise
1050+
except Exception as e:
1051+
self.handler_errors.append(e)
1052+
raise
10111053

10121054
if response is None:
10131055
response = Response("")

tests/test_handler_errors.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import pytest
2+
import requests
3+
import werkzeug
4+
5+
from pytest_httpserver import HTTPServer
6+
7+
8+
def test_check_assertions_raises_handler_assertions(httpserver: HTTPServer):
9+
def handler(_):
10+
assert 1 == 2
11+
12+
httpserver.expect_request("/foobar").respond_with_handler(handler)
13+
14+
requests.get(httpserver.url_for("/foobar"))
15+
16+
with pytest.raises(AssertionError):
17+
httpserver.check_assertions()
18+
19+
httpserver.check_handler_errors()
20+
21+
22+
def test_check_handler_errors_raises_handler_error(httpserver: HTTPServer):
23+
def handler(_) -> werkzeug.Response:
24+
raise ValueError("should be propagated")
25+
26+
httpserver.expect_request("/foobar").respond_with_handler(handler)
27+
28+
requests.get(httpserver.url_for("/foobar"))
29+
30+
httpserver.check_assertions()
31+
32+
with pytest.raises(ValueError):
33+
httpserver.check_handler_errors()
34+
35+
36+
def test_check_handler_errors_correct_order(httpserver: HTTPServer):
37+
def handler1(_) -> werkzeug.Response:
38+
raise ValueError("should be propagated")
39+
40+
def handler2(_) -> werkzeug.Response:
41+
raise OSError("should be propagated")
42+
43+
httpserver.expect_request("/foobar1").respond_with_handler(handler1)
44+
httpserver.expect_request("/foobar2").respond_with_handler(handler2)
45+
46+
requests.get(httpserver.url_for("/foobar1"))
47+
requests.get(httpserver.url_for("/foobar2"))
48+
49+
httpserver.check_assertions()
50+
51+
with pytest.raises(ValueError):
52+
httpserver.check_handler_errors()
53+
54+
with pytest.raises(OSError):
55+
httpserver.check_handler_errors()
56+
57+
httpserver.check_handler_errors()
58+
59+
60+
def test_missing_matcher_raises_exception(httpserver):
61+
requests.get(httpserver.url_for("/foobar"))
62+
63+
# missing handlers should not raise handler exception here
64+
httpserver.check_handler_errors()
65+
66+
with pytest.raises(AssertionError):
67+
httpserver.check_assertions()
68+
69+
70+
def test_check_raises_errors_in_order(httpserver):
71+
def handler1(_):
72+
assert 1 == 2
73+
74+
def handler2(_):
75+
pass # does nothing
76+
77+
def handler3(_):
78+
raise ValueError
79+
80+
httpserver.expect_request("/foobar1").respond_with_handler(handler1)
81+
httpserver.expect_request("/foobar2").respond_with_handler(handler2)
82+
httpserver.expect_request("/foobar3").respond_with_handler(handler3)
83+
84+
requests.get(httpserver.url_for("/foobar1"))
85+
requests.get(httpserver.url_for("/foobar2"))
86+
requests.get(httpserver.url_for("/foobar3"))
87+
88+
with pytest.raises(AssertionError):
89+
httpserver.check()
90+
91+
with pytest.raises(ValueError):
92+
httpserver.check()

0 commit comments

Comments
 (0)