Files
wehub-resource-sync adf0d17497
publish / version_or_publish (push) Waiting to run
storybook-build / changes (push) Waiting to run
storybook-build / :storybook-build (push) Blocked by required conditions
Sync Gradio Skills to Hugging Face / sync-skills (push) Waiting to run
functional / changes (push) Waiting to run
functional / build-frontend (push) Blocked by required conditions
functional / functional-test-SSR=false (push) Blocked by required conditions
functional / functional-reload (push) Blocked by required conditions
functional / functional-test-SSR=true (push) Blocked by required conditions
hygiene / hygiene-test (push) Waiting to run
python / changes (push) Waiting to run
python / build (push) Blocked by required conditions
python / test-ubuntu-latest-flaky (push) Blocked by required conditions
python / test-ubuntu-latest-not-flaky (push) Blocked by required conditions
python / test-windows-latest-flaky (push) Blocked by required conditions
python / test-windows-latest-not-flaky (push) Blocked by required conditions
js / changes (push) Waiting to run
js / js-test (push) Blocked by required conditions
docs-build / changes (push) Waiting to run
docs-build / docs-build (push) Blocked by required conditions
docs-build / website-build (push) Blocked by required conditions
chore: import upstream snapshot with attribution
2026-07-13 13:17:32 +08:00

348 lines
11 KiB
Python

import asyncio
import json
import sys
import time
from unittest.mock import patch
import gradio_client as grc
import pytest
from fastapi.testclient import TestClient
import gradio as gr
from gradio.route_utils import API_PREFIX
class TestQueueing:
def test_single_request(self, connect):
with gr.Blocks() as demo:
name = gr.Textbox()
output = gr.Textbox()
def greet(x):
return f"Hello, {x}!"
name.submit(greet, name, output)
with connect(demo) as client:
job = client.submit("x", fn_index=0)
assert job.result() == "Hello, x!"
def test_all_status_messages(self, connect):
with gr.Blocks() as demo:
name = gr.Textbox()
output = gr.Textbox()
def greet(x):
time.sleep(2)
return f"Hello, {x}!"
name.submit(greet, name, output, concurrency_limit=2)
app, local_url, _ = demo.launch(prevent_thread_lock=True)
test_client = TestClient(app)
client = grc.Client(local_url)
client.submit("a", fn_index=0)
job2 = client.submit("b", fn_index=0)
client.submit("c", fn_index=0)
job4 = client.submit("d", fn_index=0)
sizes = []
while job4.status().code.value != "FINISHED":
queue_status = test_client.get(f"{API_PREFIX}/queue/status").json()
queue_size = queue_status["queue_size"]
if len(sizes) == 0 or queue_size != sizes[-1]:
sizes.append(queue_size)
time.sleep(0.01)
time.sleep(0.1)
queue_status = test_client.get(f"{API_PREFIX}/queue/status").json()
queue_size = queue_status["queue_size"]
if queue_size != sizes[-1]:
sizes.append(queue_size)
assert (
max(sizes)
in [
2,
3,
4,
]
) # Can be 2 - 4, depending on if the workers have picked up jobs before the queue status is checked
assert min(sizes) == 0
assert sizes[-1] == 0
assert job2.result() == "Hello, b!"
assert job4.result() == "Hello, d!"
def test_cached_generator_finishes_on_queue_cache_hit(self, connect):
call_count = 0
@gr.cache
def stream_text(text):
nonlocal call_count
call_count += 1
for i in range(len(text)):
yield text[: i + 1]
with gr.Blocks() as demo:
name = gr.Textbox()
output = gr.Textbox()
name.submit(stream_text, name, output)
demo.queue()
with connect(demo) as client:
first = client.submit("hello", fn_index=0)
assert first.result(timeout=5) == "hello"
assert first.outputs() == ["h", "he", "hel", "hell", "hello"]
second = client.submit("hello", fn_index=0)
assert second.result(timeout=5) == "hello"
assert second.outputs() == ["h", "he", "hel", "hell", "hello"]
assert call_count == 1
def test_queue_average_excludes_manual_cache_hits(self, connect):
def greet(x, c=gr.Cache()):
hit = c.get(x)
if hit is not None:
return hit["value"]
time.sleep(0.02)
value = f"Hello, {x}!"
c.set(x, value=value)
return value
with gr.Blocks() as demo:
name = gr.Textbox()
output = gr.Textbox()
name.submit(greet, name, output)
demo.queue()
with connect(demo) as client:
first = client.submit("x", fn_index=0)
assert first.result(timeout=5) == "Hello, x!"
second = client.submit("x", fn_index=0)
assert second.result(timeout=5) == "Hello, x!"
process_time = demo._queue.process_time_per_fn[demo.fns[0]]
assert process_time.count == 1
assert process_time.avg_time >= 0.02
@pytest.mark.flaky
@pytest.mark.parametrize(
"default_concurrency_limit, statuses",
[
("not_set", ["IN_QUEUE", "IN_QUEUE", "PROCESSING"]),
(None, ["PROCESSING", "PROCESSING", "PROCESSING"]),
(1, ["IN_QUEUE", "IN_QUEUE", "PROCESSING"]),
(2, ["IN_QUEUE", "PROCESSING", "PROCESSING"]),
],
)
def test_default_concurrency_limits(self, default_concurrency_limit, statuses):
with gr.Blocks() as demo:
a = gr.Number()
b = gr.Number()
output = gr.Number()
add_btn = gr.Button("Add")
@add_btn.click(inputs=[a, b], outputs=output)
def add(x, y):
time.sleep(4)
return x + y
demo.queue(default_concurrency_limit=default_concurrency_limit)
_, local_url, _ = demo.launch(
prevent_thread_lock=True,
)
client = grc.Client(local_url)
add_job_1 = client.submit(1, 1, fn_index=0)
add_job_2 = client.submit(1, 1, fn_index=0)
add_job_3 = client.submit(1, 1, fn_index=0)
time.sleep(2)
add_job_statuses = [add_job_1.status(), add_job_2.status(), add_job_3.status()]
assert sorted([s.code.value for s in add_job_statuses]) == statuses
@pytest.mark.skipif(
sys.platform == "win32",
reason="Heartbeat task is not reliably cancelled by the time the SSE stream "
"loop returns on Windows CI (cancellation does not propagate within a "
"reasonable wait). Passes on Linux/macOS.",
)
def test_heartbeat_task_cancelled_after_stream_completes():
"""Verify the heartbeat task is cancelled when the SSE stream ends normally."""
with gr.Blocks() as demo:
name = gr.Textbox()
output = gr.Textbox()
def greet(x):
return f"Hello, {x}!"
name.submit(greet, name, output)
app, local_url, _ = demo.launch(prevent_thread_lock=True)
heartbeat_tasks = []
original_create_task = asyncio.create_task
def tracking_create_task(coro, **kwargs):
task = original_create_task(coro, **kwargs)
heartbeat_tasks.append(task)
return task
with patch("gradio.routes.asyncio.create_task", side_effect=tracking_create_task):
test_client = TestClient(app)
r = test_client.post(
f"{API_PREFIX}/queue/join",
json={
"data": ["hello"],
"fn_index": 0,
"event_data": None,
"session_hash": "test_heartbeat",
"trigger_id": None,
},
)
assert r.status_code == 200
r = test_client.get(f"{API_PREFIX}/queue/data?session_hash=test_heartbeat")
# Verify we got a process_completed message
got_completed = False
for line in r.iter_lines():
if "data" in line:
data = json.loads(line[5:])
if data["msg"] == "process_completed":
got_completed = True
assert got_completed
assert len(heartbeat_tasks) > 0, "No heartbeat tasks were created"
for task in heartbeat_tasks:
assert task.cancelled() or task.done(), (
"Heartbeat task was not cancelled after stream completed"
)
demo.close()
def test_cancel_removes_pending_event_from_queue():
"""Cancelling a queued (not yet running) event should remove it from the queue."""
with gr.Blocks() as demo:
start = gr.Button()
output = gr.Textbox()
def slow():
time.sleep(2)
return "done"
start.click(slow, None, output)
demo.queue(default_concurrency_limit=1)
app, _, _ = demo.launch(prevent_thread_lock=True)
test_client = TestClient(app)
join_payload = {
"data": [],
"fn_index": 0,
"event_data": None,
"session_hash": "sess1",
"trigger_id": None,
}
try:
first = test_client.post(f"{API_PREFIX}/queue/join", json=join_payload)
second = test_client.post(f"{API_PREFIX}/queue/join", json=join_payload)
third = test_client.post(f"{API_PREFIX}/queue/join", json=join_payload)
assert first.status_code == 200
assert second.status_code == 200
assert third.status_code == 200
second_event_id = second.json()["event_id"]
third_event_id = third.json()["event_id"]
# First event gets picked up by the worker; second and third are queued.
# The worker dequeues asynchronously, so wait for it to settle before
# asserting (avoids a race on slower CI runners where all three are
# momentarily still in the queue).
for _ in range(50):
if len(demo._queue) == 2:
break
time.sleep(0.1)
assert len(demo._queue) == 2
assert second_event_id in demo._queue.event_ids_to_events
assert second_event_id in demo._queue.pending_event_ids_session["sess1"]
# Cancel the second (pending/queued) event
resp = test_client.post(
f"{API_PREFIX}/cancel",
json={
"session_hash": "sess1",
"fn_index": 0,
"event_id": second_event_id,
},
)
assert resp.status_code == 200
assert third_event_id in demo._queue.event_ids_to_events
assert len(demo._queue) == 1
r = test_client.get(f"{API_PREFIX}/queue/data?session_hash=sess1")
# Verify we got a process_completed message
got_completed = False
for line in r.iter_lines():
if "data" in line:
data = json.loads(line[5:])
if data["msg"] == "process_completed":
got_completed = True
assert got_completed
assert second_event_id not in demo._queue.pending_event_ids_session["sess1"]
assert second_event_id not in demo._queue.event_ids_to_events
finally:
demo.close()
def test_analytics_summary(monkeypatch):
"""Test that the analytics summary endpoint is correctly being computed every N requests,
where N is set by the GRADIO_ANALYTICS_CACHE_FREQUENCY environment variable."""
monkeypatch.setenv("GRADIO_ANALYTICS_CACHE_FREQUENCY", 2)
with gr.Blocks() as demo:
name = gr.Textbox()
output = gr.Textbox()
def greet(x):
return f"Hello, {x}!"
name.submit(greet, name, output, api_name="predict")
_, local_url, _ = demo.launch(prevent_thread_lock=True)
test_client = TestClient(demo.app)
client = grc.Client(local_url)
with test_client as tc:
event_analytics = tc.get("/monitoring/summary").json()
assert event_analytics == {"functions": {}}
client.predict(
"a",
api_name="/predict",
)
client.predict(
"a",
api_name="/predict",
)
event_analytics = tc.get("/monitoring/summary").json()
assert "predict" in event_analytics["functions"]
assert event_analytics["functions"]["predict"]["total_requests"] == 2
client.predict("a", api_name="/predict")
event_analytics = tc.get("/monitoring/summary").json()
assert "predict" in event_analytics["functions"]
assert event_analytics["functions"]["predict"]["total_requests"] == 2
client.predict("a", api_name="/predict")
event_analytics = tc.get("/monitoring/summary").json()
assert "predict" in event_analytics["functions"]
assert event_analytics["functions"]["predict"]["total_requests"] == 4