Files
2026-07-13 13:17:40 +08:00

225 lines
5.0 KiB
Python

import random
import subprocess
import sys
import time
import httpx
import pytest
import ray
from ray import serve
from ray._common.test_utils import run_string_as_driver
from ray.serve._private.utils import inside_ray_client_context
# https://tools.ietf.org/html/rfc6335#section-6
MIN_DYNAMIC_PORT = 49152
MAX_DYNAMIC_PORT = 65535
@pytest.fixture
def ray_client_instance(scope="module"):
port = random.randint(MIN_DYNAMIC_PORT, MAX_DYNAMIC_PORT)
subprocess.check_output(
[
"ray",
"start",
"--head",
"--num-cpus",
"16",
"--ray-client-server-port",
f"{port}",
]
)
try:
yield f"localhost:{port}"
finally:
subprocess.check_output(["ray", "stop", "--force"])
@pytest.fixture
def serve_with_client(ray_client_instance, ray_init_kwargs=None):
if ray_init_kwargs is None:
ray_init_kwargs = dict()
ray.init(
f"ray://{ray_client_instance}",
namespace="default_test_namespace",
**ray_init_kwargs,
)
assert inside_ray_client_context()
yield
serve.shutdown()
ray.util.disconnect()
@pytest.mark.skipif(
sys.platform != "linux" and sys.platform != "win32", reason="Buggy on MacOS"
)
def test_ray_client(ray_client_instance):
ray.util.connect(ray_client_instance, namespace="default_test_namespace")
start = """
import ray
ray.util.connect("{}", namespace="default_test_namespace")
from ray import serve
serve.start()
""".format(
ray_client_instance
)
run_string_as_driver(start)
deploy = """
import ray
ray.util.connect("{}", namespace="default_test_namespace")
from ray import serve
@serve.deployment
def f(*args):
return "hello"
serve.run(f.bind(), name="test1", route_prefix="/hello")
""".format(
ray_client_instance
)
run_string_as_driver(deploy)
assert "test1" in serve.status().applications
assert httpx.get("http://localhost:8000/hello").text == "hello"
delete = """
import ray
ray.util.connect("{}", namespace="default_test_namespace")
from ray import serve
serve.delete("test1")
""".format(
ray_client_instance
)
run_string_as_driver(delete)
assert "test1" not in serve.status().applications
fastapi = """
import ray
ray.util.connect("{}", namespace="default_test_namespace")
from ray import serve
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def hello():
return "hello"
@serve.deployment
@serve.ingress(app)
class A:
pass
serve.run(A.bind(), route_prefix="/A")
""".format(
ray_client_instance
)
run_string_as_driver(fastapi)
assert httpx.get("http://localhost:8000/A", follow_redirects=True).json() == "hello"
serve.shutdown()
ray.util.disconnect()
def test_quickstart_class(serve_with_client):
@serve.deployment
def hello(request):
name = request.query_params["name"]
return f"Hello {name}!"
serve.run(hello.bind())
# Query our endpoint over HTTP.
response = httpx.get("http://127.0.0.1:8000/hello?name=serve").text
assert response == "Hello serve!"
def test_quickstart_counter(serve_with_client):
@serve.deployment
class Counter:
def __init__(self):
self.count = 0
def __call__(self, *args):
self.count += 1
return {"count": self.count}
# Deploy our class.
handle = serve.run(Counter.bind())
print("deploy finished")
# Query our endpoint in two different ways: from HTTP and from Python.
assert httpx.get("http://127.0.0.1:8000/Counter").json() == {"count": 1}
print("query 1 finished")
assert handle.remote().result() == {"count": 2}
print("query 2 finished")
@pytest.mark.parametrize(
"serve_with_client",
[
{
"runtime_env": {
"env_vars": {
"LISTEN_FOR_CHANGE_REQUEST_TIMEOUT_S_LOWER_BOUND": "1",
"LISTEN_FOR_CHANGE_REQUEST_TIMEOUT_S_UPPER_BOUND": "2",
}
}
}
],
indirect=True,
)
def test_handle_hanging(serve_with_client):
# With https://github.com/ray-project/ray/issues/20971
# the following will hang forever.
@serve.deployment
def f():
return 1
handle = serve.run(f.bind())
for _ in range(5):
assert handle.remote().result() == 1
time.sleep(0.5)
ray.shutdown()
def test_streaming_handle(serve_with_client):
"""stream=True is not supported, check that there's a good error message."""
@serve.deployment
def f():
return 1
h = serve.run(f.bind())
h = h.options(stream=False)
with pytest.raises(
RuntimeError,
match=(
"Streaming DeploymentHandles are not currently supported when "
"connected to a remote Ray cluster using Ray Client."
),
):
h = h.options(stream=True)
assert h.remote().result() == 1
if __name__ == "__main__":
sys.exit(pytest.main(["-v", "-s", __file__]))