mirror of
https://github.com/lexmount/moli.git
synced 2026-09-27 16:01:31 +00:00
fix(wpt): stream the Fetch trickle fixture response
Model trickle.py by draining the request body, delaying headers and body chunks, and writing TEST_TRICKLE lines according to ms/count. Preserve the optional Content-Type omission and close-delimited response framing. Exercise supported methods, defaults, duplicate/invalid parameters, progressive delivery, and both fixed-length and chunked uploads. Validation: 221 Python tests passed. With the same Moli binary, 16 WPT cases add two passing cases and eight passing subtests, with no regressions. No Rust source or build metadata changed.
This commit is contained in:
@@ -2345,6 +2345,9 @@ def _make_handler(
|
||||
if path == "/fetch/api/resources/status.py":
|
||||
self._serve_fetch_status(parsed.query, emit_body=emit_body)
|
||||
return
|
||||
if path == "/fetch/api/resources/trickle.py":
|
||||
self._serve_fetch_trickle(parsed.query, emit_body=emit_body)
|
||||
return
|
||||
if path == "/fetch/api/resources/inspect-headers.py":
|
||||
self._send_bytes(
|
||||
"text/plain",
|
||||
@@ -3363,6 +3366,41 @@ def _make_handler(
|
||||
except (BrokenPipeError, ConnectionResetError):
|
||||
return
|
||||
|
||||
def _serve_fetch_trickle(self, query: str, *, emit_body: bool) -> None:
|
||||
params = parse_qs(query, keep_blank_values=True)
|
||||
delay = _wpt_delay_seconds(query)
|
||||
try:
|
||||
count = int(params.get("count", ["50"])[0])
|
||||
except ValueError:
|
||||
self.send_error(500)
|
||||
return
|
||||
if delay is None:
|
||||
self.send_error(500)
|
||||
return
|
||||
# Upstream reads the upload before delaying the response headers.
|
||||
if not self._consume_request_body():
|
||||
return
|
||||
self.close_connection = True
|
||||
try:
|
||||
time.sleep(delay)
|
||||
self.send_response(200)
|
||||
if "notype" not in params:
|
||||
self.send_header("Content-Type", "text/plain")
|
||||
# Like wptserve's explicit writer, delimit the body by EOF.
|
||||
# Flush each chunk so readers can consume it before EOF.
|
||||
self.send_header("Connection", "close")
|
||||
self.end_headers()
|
||||
self.wfile.flush()
|
||||
if not emit_body:
|
||||
return
|
||||
time.sleep(delay)
|
||||
for _ in range(count):
|
||||
self.wfile.write(b"TEST_TRICKLE\n")
|
||||
self.wfile.flush()
|
||||
time.sleep(delay)
|
||||
except (BrokenPipeError, ConnectionResetError, OSError):
|
||||
return
|
||||
|
||||
def _serve_fetch_status(self, query: str, *, emit_body: bool) -> None:
|
||||
try:
|
||||
status, text, content_type, body = _fetch_status_response(query)
|
||||
|
||||
@@ -4391,6 +4391,115 @@ test(() => {}, "ok");
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
def test_fixture_server_models_fetch_trickle_parameters_and_methods(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
root_path = Path(root)
|
||||
(root_path / "resources").mkdir()
|
||||
(root_path / "resources" / "testharness.js").write_text("// testharness")
|
||||
with WptFixtureServer(root_path) as server, patch(
|
||||
"moli_benchmark.wpt_cross.server.time.sleep"
|
||||
) as sleep:
|
||||
cases = [
|
||||
("", 50, "text/plain", 0.5),
|
||||
("count=2&count=9&ms=1.5&ms=900", 2, "text/plain", 0.0015),
|
||||
("count=0&ms=0¬ype", 0, None, 0.0),
|
||||
("count=-1&ms=0¬ype=false", 0, None, 0.0),
|
||||
]
|
||||
for method in ["GET", "HEAD", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", "YO"]:
|
||||
for query, count, mime, delay in cases:
|
||||
with self.subTest(method=method, query=query):
|
||||
sleep.reset_mock()
|
||||
connection = HTTPConnection("127.0.0.1", server.port, timeout=2)
|
||||
try:
|
||||
connection.request(
|
||||
method, "/fetch/api/resources/trickle.py?" + query,
|
||||
body=b"upload" if method not in {"GET", "HEAD"} else None,
|
||||
)
|
||||
response = connection.getresponse()
|
||||
self.assertEqual(response.status, 200)
|
||||
self.assertEqual(response.headers.get("Content-Type"), mime)
|
||||
self.assertIsNone(response.headers.get("Content-Length"))
|
||||
self.assertIsNone(response.headers.get("Transfer-Encoding"))
|
||||
self.assertEqual(response.read(), b"" if method == "HEAD" else b"TEST_TRICKLE\n" * count)
|
||||
expected_sleeps = 1 if method == "HEAD" else count + 2
|
||||
self.assertEqual([args.args[0] for args in sleep.call_args_list], [delay] * expected_sleeps)
|
||||
finally:
|
||||
connection.close()
|
||||
for query in ["count=invalid", "count=", "ms=invalid", "ms=-1", "ms=nan", "ms=inf"]:
|
||||
with self.subTest(query=query):
|
||||
connection = HTTPConnection("127.0.0.1", server.port, timeout=2)
|
||||
try:
|
||||
connection.request("GET", "/fetch/api/resources/trickle.py?" + query)
|
||||
response = connection.getresponse()
|
||||
self.assertEqual(response.status, 500)
|
||||
response.read()
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
def test_fixture_server_delivers_fetch_trickle_headers_and_chunks_before_eof(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
root_path = Path(root)
|
||||
(root_path / "resources").mkdir()
|
||||
(root_path / "resources" / "testharness.js").write_text("// testharness")
|
||||
headers_ready, chunk_ready = threading.Event(), threading.Event()
|
||||
release_first, release_second = threading.Event(), threading.Event()
|
||||
sleeps = []
|
||||
|
||||
def pause(delay: float) -> None:
|
||||
sleeps.append(delay)
|
||||
if len(sleeps) == 2:
|
||||
headers_ready.set()
|
||||
self.assertTrue(release_first.wait(2))
|
||||
elif len(sleeps) == 3:
|
||||
chunk_ready.set()
|
||||
self.assertTrue(release_second.wait(2))
|
||||
|
||||
with WptFixtureServer(root_path) as server, patch(
|
||||
"moli_benchmark.wpt_cross.server.time.sleep", side_effect=pause
|
||||
):
|
||||
connection = HTTPConnection("127.0.0.1", server.port, timeout=2)
|
||||
try:
|
||||
connection.request("GET", "/fetch/api/resources/trickle.py?count=2¬ype")
|
||||
response = connection.getresponse()
|
||||
self.assertTrue(headers_ready.wait(2))
|
||||
self.assertIsNone(response.headers.get("Content-Type"))
|
||||
release_first.set()
|
||||
self.assertEqual(response.read(13), b"TEST_TRICKLE\n")
|
||||
self.assertTrue(chunk_ready.wait(2))
|
||||
release_second.set()
|
||||
self.assertEqual(response.read(), b"TEST_TRICKLE\n")
|
||||
self.assertEqual(sleeps, [0.5] * 4)
|
||||
finally:
|
||||
release_first.set()
|
||||
release_second.set()
|
||||
connection.close()
|
||||
|
||||
def test_fixture_server_reads_upload_before_starting_fetch_trickle(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
root_path = Path(root)
|
||||
(root_path / "resources").mkdir()
|
||||
(root_path / "resources" / "testharness.js").write_text("// testharness")
|
||||
started = threading.Event()
|
||||
with WptFixtureServer(root_path) as server, patch(
|
||||
"moli_benchmark.wpt_cross.server.time.sleep",
|
||||
side_effect=lambda _: started.set(),
|
||||
):
|
||||
for framing, first, last in [
|
||||
(b"Content-Length: 6", b"abc", b"def"),
|
||||
(b"Transfer-Encoding: chunked", b"3\r\nabc\r\n", b"3\r\ndef\r\n0\r\n\r\n"),
|
||||
]:
|
||||
with self.subTest(framing=framing), socket.create_connection(("127.0.0.1", server.port), timeout=2) as connection:
|
||||
started.clear()
|
||||
connection.sendall(
|
||||
b"POST /fetch/api/resources/trickle.py?count=1&ms=0 HTTP/1.1\r\n"
|
||||
b"Host: localhost\r\n" + framing + b"\r\n\r\n" + first
|
||||
)
|
||||
self.assertFalse(started.wait(0.05), "response started before upload completed")
|
||||
connection.sendall(last)
|
||||
self.assertTrue(started.wait(2))
|
||||
response = connection.makefile("rb").read()
|
||||
self.assertEqual(response.split(b"\r\n\r\n", 1)[1], b"TEST_TRICKLE\n")
|
||||
|
||||
def test_fixture_server_models_fetch_inspect_headers_handler(self) -> None:
|
||||
self.assertEqual(
|
||||
_inspect_headers_response_headers(
|
||||
|
||||
@@ -5527,6 +5527,8 @@ event-timing/supported-types-consistent-with-self.html
|
||||
fetch/api/abort/destroyed-context.html
|
||||
fetch/api/basic/response-null-body.any.js?moli-wpt-any=dedicatedworker
|
||||
fetch/api/basic/response-null-body.any.js?moli-wpt-any=window
|
||||
fetch/api/basic/stream-response.any.js?moli-wpt-any=dedicatedworker
|
||||
fetch/api/basic/stream-response.any.js?moli-wpt-any=window
|
||||
fetch/api/basic/text-utf8.any.js?moli-wpt-any=dedicatedworker
|
||||
fetch/api/basic/text-utf8.any.js?moli-wpt-any=window
|
||||
fetch/api/cors/data-url-iframe.html
|
||||
|
||||
Reference in New Issue
Block a user