Skip to content

Commit af975f2

Browse files
committed
Add more tests
1 parent 51afae6 commit af975f2

1 file changed

Lines changed: 76 additions & 2 deletions

File tree

tests/test_server_startup.py

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import time
66

77
import pytest
8+
import requests
9+
from requests.exceptions import Timeout
810

911
from pytest_httpserver import HTTPServer
1012

@@ -58,9 +60,9 @@ def test_slow_start_server_waits_for_ready():
5860
server = SlowStartServer(host="localhost", port=0)
5961
server.expect_request("/").respond_with_data("ok")
6062

61-
start_time = time.time()
63+
start_time = time.monotonic()
6264
server.start()
63-
elapsed = time.time() - start_time
65+
elapsed = time.monotonic() - start_time
6466

6567
try:
6668
# Should have waited at least 0.5 seconds
@@ -112,3 +114,75 @@ def test_warns_when_ready_event_not_set():
112114
raise AssertionError("Server did not accept connections within 1 second")
113115
finally:
114116
server.stop()
117+
118+
119+
class SlowServeServer(HTTPServer):
120+
"""A server that delays serve_forever() but does not set ready event early.
121+
122+
This simulates the scenario where:
123+
- bind() and listen() complete (TCP connections queue in backlog)
124+
- But serve_forever() hasn't started yet (no HTTP responses)
125+
"""
126+
127+
def thread_target(self):
128+
assert self.server is not None
129+
# Delay before serve_forever - connections will queue but not be processed
130+
time.sleep(3.0)
131+
self._server_ready_event.set()
132+
self.server.serve_forever()
133+
134+
135+
def test_http_request_fails_before_serve_forever_without_wait():
136+
"""
137+
Demonstrate the race condition: TCP connects but HTTP times out.
138+
139+
This test shows why waiting for server readiness matters:
140+
- After start(), TCP connections succeed (queued in backlog)
141+
- But HTTP requests timeout because serve_forever() hasn't started
142+
- With short client timeouts (common in production), this causes failures
143+
"""
144+
# Use startup_timeout=0 to NOT wait for ready event (old behavior)
145+
server = SlowServeServer(host="localhost", port=0, startup_timeout=0.0)
146+
server.expect_request("/ping").respond_with_data("pong")
147+
148+
with pytest.warns(UserWarning, match="ready event was not set"):
149+
server.start()
150+
151+
try:
152+
# TCP connection succeeds (proves Zsolt's point about backlog)
153+
sock = socket.create_connection((server.host, server.port), timeout=1)
154+
sock.close()
155+
156+
# But HTTP request with short timeout fails!
157+
# This is the actual problem in containerized environments
158+
with pytest.raises(Timeout):
159+
requests.get(server.url_for("/ping"), timeout=(0.5, 0.5))
160+
finally:
161+
server.stop()
162+
163+
164+
def test_http_request_succeeds_when_waiting_for_ready():
165+
"""
166+
Demonstrate that waiting for ready event fixes the race condition.
167+
168+
With startup_timeout enabled (default), start() waits until
169+
serve_forever() begins, so HTTP requests succeed immediately.
170+
"""
171+
# Use default startup_timeout to wait for ready event
172+
server = SlowServeServer(host="localhost", port=0) # default startup_timeout=10.0
173+
server.expect_request("/ping").respond_with_data("pong")
174+
175+
start_time = time.monotonic()
176+
server.start()
177+
elapsed = time.monotonic() - start_time
178+
179+
try:
180+
# Should have waited for the slow startup
181+
assert elapsed >= 3.0, f"Expected to wait >= 3.0s, but only waited {elapsed}s"
182+
183+
# HTTP request succeeds because serve_forever() has started
184+
response = requests.get(server.url_for("/ping"), timeout=(0.5, 0.5))
185+
assert response.status_code == 200
186+
assert response.text == "pong"
187+
finally:
188+
server.stop()

0 commit comments

Comments
 (0)