-
Notifications
You must be signed in to change notification settings - Fork 32
Implement http readiness checking #462
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| from collections.abc import Generator | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wrote a test. from collections.abc import Generator
import pytest
import requests
from pytest_httpserver.httpserver import HTTPServer
@pytest.fixture
def httpserver_with_readiness() -> Generator[HTTPServer, None, None]:
with HTTPServer(startup_timeout=10) as server:
yield server
@pytest.fixture
def httpserver_without_readiness() -> Generator[HTTPServer, None, None]:
with HTTPServer() as server:
yield server
@pytest.mark.parametrize(
("startup_timeout", "expected_timeout"),
[
(10, 10),
(None, None),
],
ids=["with_timeout", "without_timeout"],
)
def test_httpserver_startup_timeout_attribute(startup_timeout: float | None, expected_timeout: float | None):
with HTTPServer(startup_timeout=startup_timeout) as server:
# Arrange
server.expect_request("/test").respond_with_data("OK")
# Act
resp = requests.get(server.url_for("/test"))
# Assert
assert server.startup_timeout == expected_timeout
assert resp.status_code == 200
assert resp.text == "OK"
def test_httpserver_readiness_with_context_manager():
with HTTPServer(startup_timeout=10) as server:
# Arrange
server.expect_request("/ctx").respond_with_data("context manager works")
# Act
resp = requests.get(server.url_for("/ctx"))
# Assert
assert resp.status_code == 200
assert resp.text == "context manager works"
@pytest.mark.parametrize("num_cycles", [1, 3])
def test_httpserver_multiple_start_stop_cycles(num_cycles: int):
# Arrange
server = HTTPServer(startup_timeout=5)
for i in range(num_cycles):
# Arrange
server.start()
server.expect_request(f"/cycle{i}").respond_with_data(f"cycle {i}")
# Act
resp = requests.get(server.url_for(f"/cycle{i}"))
# Assert
assert resp.status_code == 200
assert resp.text == f"cycle {i}"
# Cleanup
server.clear()
server.stop()
@pytest.mark.parametrize(
("handler_type", "path", "expected_text"),
[
("permanent", "/perm", "permanent handler"),
("oneshot", "/oneshot", "oneshot handler"),
],
ids=["permanent", "oneshot"],
)
def test_httpserver_readiness_with_handler_types(
httpserver_with_readiness: HTTPServer, handler_type: str, path: str, expected_text: str
):
# Arrange
if handler_type == "permanent":
httpserver_with_readiness.expect_request(path).respond_with_data(expected_text)
else:
httpserver_with_readiness.expect_oneshot_request(path).respond_with_data(expected_text)
# Act
resp = requests.get(httpserver_with_readiness.url_for(path))
# Assert
assert resp.status_code == 200
assert resp.text == expected_text
def test_httpserver_readiness_ordered_handlers(httpserver_with_readiness: HTTPServer):
# Arrange
httpserver_with_readiness.expect_ordered_request("/first").respond_with_data("1")
httpserver_with_readiness.expect_ordered_request("/second").respond_with_data("2")
# Act
resp1 = requests.get(httpserver_with_readiness.url_for("/first"))
resp2 = requests.get(httpserver_with_readiness.url_for("/second"))
# Assert
assert resp1.status_code == 200
assert resp1.text == "1"
assert resp2.status_code == 200
assert resp2.text == "2"
def test_httpserver_readiness_does_not_interfere_with_handlers(httpserver_with_readiness: HTTPServer):
# Arrange
httpserver_with_readiness.expect_request("/").respond_with_data("user handler")
# Act
resp = requests.get(httpserver_with_readiness.url_for("/"))
# Assert
assert resp.status_code == 200
assert resp.text == "user handler"
def test_httpserver_wait_for_server_ready_noop_when_no_timeout(httpserver_without_readiness: HTTPServer):
# Act & Assert (no exception raised)
httpserver_without_readiness.wait_for_server_ready()
@pytest.mark.parametrize("startup_timeout", [10, None], ids=["with_timeout", "without_timeout"])
def test_httpserver_readiness_check_pending_flag(startup_timeout: float | None):
# Arrange
server = HTTPServer(startup_timeout=startup_timeout)
# Assert (before start)
assert server._readiness_check_pending is False # noqa: SLF001
# Act
with server:
# Assert (after start)
assert server._readiness_check_pending is False # noqa: SLF001
def test_httpserver_oneshot_consumed_after_readiness(httpserver_with_readiness: HTTPServer):
# Arrange
httpserver_with_readiness.expect_oneshot_request("/oneshot").respond_with_data("once")
# Act
resp1 = requests.get(httpserver_with_readiness.url_for("/oneshot"))
resp2 = requests.get(httpserver_with_readiness.url_for("/oneshot"))
# Assert
assert resp1.status_code == 200
assert resp1.text == "once"
assert resp2.status_code == 500
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hi @HayaoSuzuki , Thank you! I'll take a closer look. For the first sight I think some of your tests overlap with existing tests. For example oneshot test at the end is very similar to the already defined oneshot test(s) in test/test_oneshot.py. I'm thinking adding tests which records the calls for
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for the feedback. class RecordingHTTPServer(HTTPServer):
"""HTTPServer subclass that records wait_for_server_ready() calls."""
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.wait_for_ready_call_count = 0
self.readiness_pending_before_wait: list[bool] = []
self.readiness_pending_after_wait: list[bool] = []
def wait_for_server_ready(self) -> None:
self.wait_for_ready_call_count += 1
self.readiness_pending_before_wait.append(self._readiness_check_pending)
super().wait_for_server_ready()
self.readiness_pending_after_wait.append(self._readiness_check_pending)
@pytest.fixture
def recording_server_with_timeout() -> Generator[RecordingHTTPServer]:
with RecordingHTTPServer(startup_timeout=10) as server:
yield server
@pytest.fixture
def recording_server_without_timeout() -> Generator[RecordingHTTPServer]:
with RecordingHTTPServer() as server:
yield server
def test_wait_for_server_ready_called_with_timeout(
recording_server_with_timeout: RecordingHTTPServer,
) -> None:
assert recording_server_with_timeout.wait_for_ready_call_count == 1
assert recording_server_with_timeout.readiness_pending_before_wait == [True]
assert recording_server_with_timeout.readiness_pending_after_wait == [False]
def test_wait_for_server_ready_called_without_timeout(
recording_server_without_timeout: RecordingHTTPServer,
) -> None:
assert recording_server_without_timeout.wait_for_ready_call_count == 1
assert recording_server_without_timeout.readiness_pending_before_wait == [False]
assert recording_server_without_timeout.readiness_pending_after_wait == [False]
def test_wait_for_server_ready_called_each_start_stop_cycle() -> None:
server = RecordingHTTPServer(startup_timeout=5)
try:
for i in range(3):
server.start()
assert server.wait_for_ready_call_count == i + 1
server.clear()
server.stop()
finally:
if server.is_running():
server.clear()
server.stop()
assert server.readiness_pending_before_wait == [True, True, True]
assert server.readiness_pending_after_wait == [False, False, False]
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks good for the first sight. Should I copy your code or do you want to make a commit? A commit with your name would be better I think as you deserve the credit. Hopefully I'll have more time to work on this on the weekend. Zsolt
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| from typing import Any | ||
|
|
||
| import pytest | ||
| import requests | ||
|
|
||
| from pytest_httpserver.httpserver import HTTPServer | ||
| from pytest_httpserver.httpserver import HTTPServerError | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def httpserver() -> Generator[HTTPServer, None, None]: | ||
| server = HTTPServer(startup_timeout=10) | ||
| server.start() | ||
| yield server | ||
| server.clear() | ||
| if server.is_running(): | ||
| server.stop() | ||
|
|
||
|
|
||
| def test_httpserver_readiness(httpserver: HTTPServer): | ||
| assert httpserver.startup_timeout == 10 | ||
| httpserver.expect_request("/").respond_with_data("Hello, world!") | ||
| resp = requests.get(httpserver.url_for("/")) | ||
| assert resp.status_code == 200 | ||
| assert resp.text == "Hello, world!" | ||
|
|
||
|
|
||
| class RecordingHTTPServer(HTTPServer): | ||
| """HTTPServer subclass that records wait_for_server_ready() calls.""" | ||
|
|
||
| def __init__(self, **kwargs: Any) -> None: | ||
| super().__init__(**kwargs) | ||
| self.wait_for_ready_call_count = 0 | ||
| self.readiness_pending_before_wait: list[bool] = [] | ||
| self.readiness_pending_after_wait: list[bool] = [] | ||
|
|
||
| def wait_for_server_ready(self) -> None: | ||
| self.wait_for_ready_call_count += 1 | ||
| self.readiness_pending_before_wait.append(self._readiness_check_pending) | ||
| super().wait_for_server_ready() | ||
| self.readiness_pending_after_wait.append(self._readiness_check_pending) | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def recording_server_with_timeout() -> Generator[RecordingHTTPServer]: | ||
| with RecordingHTTPServer(startup_timeout=10) as server: | ||
| yield server | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def recording_server_without_timeout() -> Generator[RecordingHTTPServer]: | ||
| with RecordingHTTPServer() as server: | ||
| yield server | ||
|
|
||
|
|
||
| def test_wait_for_server_ready_called_with_timeout( | ||
| recording_server_with_timeout: RecordingHTTPServer, | ||
| ) -> None: | ||
| assert recording_server_with_timeout.wait_for_ready_call_count == 1 | ||
| assert recording_server_with_timeout.readiness_pending_before_wait == [True] | ||
| assert recording_server_with_timeout.readiness_pending_after_wait == [False] | ||
|
|
||
|
|
||
| def test_wait_for_server_ready_called_without_timeout( | ||
| recording_server_without_timeout: RecordingHTTPServer, | ||
| ) -> None: | ||
| assert recording_server_without_timeout.wait_for_ready_call_count == 1 | ||
| assert recording_server_without_timeout.readiness_pending_before_wait == [False] | ||
| assert recording_server_without_timeout.readiness_pending_after_wait == [False] | ||
|
|
||
|
|
||
| def test_wait_for_server_ready_called_each_start_stop_cycle() -> None: | ||
| server = RecordingHTTPServer(startup_timeout=5) | ||
| try: | ||
| for i in range(3): | ||
| server.start() | ||
| assert server.wait_for_ready_call_count == i + 1 | ||
| server.clear() | ||
| server.stop() | ||
| finally: | ||
| if server.is_running(): | ||
| server.clear() | ||
| server.stop() | ||
|
|
||
| assert server.readiness_pending_before_wait == [True, True, True] | ||
| assert server.readiness_pending_after_wait == [False, False, False] | ||
|
|
||
|
|
||
| def test_double_start_does_not_poison_readiness_flag() -> None: | ||
| server = HTTPServer(startup_timeout=5) | ||
| server.start() | ||
| try: | ||
| with pytest.raises(HTTPServerError, match="already running"): | ||
| server.start() | ||
|
|
||
| assert server._readiness_check_pending is False # noqa: SLF001 | ||
|
|
||
| server.expect_request("/test").respond_with_data("normal response") | ||
| resp = requests.get(server.url_for("/test")) | ||
| assert resp.status_code == 200 | ||
| assert resp.text == "normal response" | ||
| finally: | ||
| server.clear() | ||
| if server.is_running(): | ||
| server.stop() | ||
|
|
||
|
|
||
| class FailingReadinessServer(HTTPServer): | ||
| """HTTPServer subclass whose readiness check always fails.""" | ||
|
|
||
| def __init__(self, **kwargs: Any) -> None: | ||
| super().__init__(**kwargs) | ||
|
|
||
| def wait_for_server_ready(self) -> None: | ||
| raise HTTPServerError("Simulated readiness failure") | ||
|
|
||
|
|
||
| def test_readiness_failure_stops_server() -> None: | ||
| server = FailingReadinessServer(startup_timeout=5) | ||
| with pytest.raises(HTTPServerError, match="Simulated readiness failure"): | ||
| server.start() | ||
|
|
||
| assert not server.is_running() | ||
Uh oh!
There was an error while loading. Please reload this page.