chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
import pytest
|
||||
|
||||
import ray.dashboard.modules # noqa
|
||||
from ray.tests.conftest import * # noqa
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def enable_test_module():
|
||||
os.environ["RAY_DASHBOARD_MODULE_TEST"] = "true"
|
||||
import ray.dashboard.tests
|
||||
|
||||
p = pathlib.Path(ray.dashboard.modules.__path__[0]) / "tests" / "__init__.py"
|
||||
p.touch()
|
||||
yield
|
||||
os.environ.pop("RAY_DASHBOARD_MODULE_TEST", None)
|
||||
p.unlink()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def disable_aiohttp_cache():
|
||||
os.environ["RAY_DASHBOARD_NO_CACHE"] = "true"
|
||||
yield
|
||||
os.environ.pop("RAY_DASHBOARD_NO_CACHE", None)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def small_event_line_limit():
|
||||
os.environ["EVENT_READ_LINE_LENGTH_LIMIT"] = "1024"
|
||||
yield 1024
|
||||
os.environ.pop("EVENT_READ_LINE_LENGTH_LIMIT", None)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fast_gcs_failure_detection(monkeypatch):
|
||||
monkeypatch.setenv("RAY_gcs_rpc_server_reconnect_timeout_s", "2")
|
||||
monkeypatch.setenv("GCS_CHECK_ALIVE_INTERVAL_SECONDS", "1")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def reduce_actor_cache():
|
||||
os.environ["RAY_maximum_gcs_destroyed_actor_cached_count"] = "3"
|
||||
yield
|
||||
os.environ.pop("RAY_maximum_gcs_destroyed_actor_cached_count", None)
|
||||
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
video: false,
|
||||
screenshotOnRunFailure: false,
|
||||
retries: {
|
||||
runMode: 2,
|
||||
},
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
videos
|
||||
plugins
|
||||
screenshots
|
||||
@@ -0,0 +1,9 @@
|
||||
describe("Ray Dashboard Test", () => {
|
||||
it("opens a new Ray dashboard", () => {
|
||||
cy.visit("localhost:8653");
|
||||
cy.contains("Overview");
|
||||
cy.contains("Jobs");
|
||||
cy.contains("Cluster");
|
||||
cy.contains("Logs");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
Cypress.on(`window:before:load`, win => {
|
||||
cy.stub( win.console, `error`, msg => {
|
||||
// Abort the test on any error in console,
|
||||
// this is a hack to show full stack trace.
|
||||
throw new Error( msg.stack );
|
||||
});
|
||||
});
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
set -x
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE:-$0}")"; pwd)
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
clean_up() {
|
||||
ray stop --force
|
||||
}
|
||||
trap clean_up EXIT
|
||||
|
||||
CYPRESS_VERSION=14.2.1
|
||||
|
||||
(
|
||||
cd ../client
|
||||
npm ci
|
||||
)
|
||||
|
||||
echo "Installing cypress"
|
||||
if [[ -n "$BUILDKITE" ]]; then
|
||||
apt-get update -qq
|
||||
apt install -y libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
|
||||
sudo npm install "cypress@$CYPRESS_VERSION"
|
||||
else
|
||||
which cypress || npm install "cypress@$CYPRESS_VERSION" -g
|
||||
fi
|
||||
|
||||
ray stop --force
|
||||
ray start --head --dashboard-port=8653 --dashboard-host=0.0.0.0
|
||||
|
||||
sleep 5 # Wait for Ray dashboard to become ready
|
||||
curl localhost:8653 || cat /tmp/ray/session_latest/logs/dashboard.log
|
||||
|
||||
node_modules/.bin/cypress run --project . --headless
|
||||
|
||||
# Run frontend UI tests
|
||||
(
|
||||
cd ../client
|
||||
CI=true npm test
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,155 @@
|
||||
"""Tests for dashboard token authentication."""
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
|
||||
def test_dashboard_request_requires_auth_with_valid_token(
|
||||
setup_cluster_with_token_auth,
|
||||
):
|
||||
"""Test that requests succeed with valid token when auth is enabled."""
|
||||
|
||||
cluster_info = setup_cluster_with_token_auth
|
||||
headers = {"Authorization": f"Bearer {cluster_info['token']}"}
|
||||
|
||||
response = requests.get(
|
||||
f"{cluster_info['dashboard_url']}/api/component_activities",
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_dashboard_request_requires_auth_missing_token(setup_cluster_with_token_auth):
|
||||
"""Test that requests fail without token when auth is enabled."""
|
||||
|
||||
cluster_info = setup_cluster_with_token_auth
|
||||
|
||||
response = requests.get(
|
||||
f"{cluster_info['dashboard_url']}/api/component_activities",
|
||||
json={"test": "data"},
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_dashboard_request_requires_auth_invalid_token(setup_cluster_with_token_auth):
|
||||
"""Test that requests fail with invalid token when auth is enabled."""
|
||||
|
||||
cluster_info = setup_cluster_with_token_auth
|
||||
headers = {"Authorization": "Bearer wrong_token_00000000000000000000000000000000"}
|
||||
|
||||
response = requests.get(
|
||||
f"{cluster_info['dashboard_url']}/api/component_activities",
|
||||
json={"test": "data"},
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_dashboard_request_with_ray_auth_header(setup_cluster_with_token_auth):
|
||||
"""Test that requests succeed with valid token in X-Ray-Authorization header."""
|
||||
|
||||
cluster_info = setup_cluster_with_token_auth
|
||||
headers = {"X-Ray-Authorization": f"Bearer {cluster_info['token']}"}
|
||||
|
||||
response = requests.get(
|
||||
f"{cluster_info['dashboard_url']}/api/component_activities",
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_authorization_header_takes_precedence(setup_cluster_with_token_auth):
|
||||
"""Test that standard Authorization header takes precedence over X-Ray-Authorization."""
|
||||
|
||||
cluster_info = setup_cluster_with_token_auth
|
||||
|
||||
# Provide both headers: valid token in Authorization, invalid in X-Ray-Authorization
|
||||
headers = {
|
||||
"Authorization": f"Bearer {cluster_info['token']}",
|
||||
"X-Ray-Authorization": "Bearer invalid_token_000000000000000000000000",
|
||||
}
|
||||
|
||||
# Should succeed because Authorization header takes precedence
|
||||
response = requests.get(
|
||||
f"{cluster_info['dashboard_url']}/api/component_activities",
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
# Now test with invalid Authorization but valid X-Ray-Authorization
|
||||
headers = {
|
||||
"Authorization": "Bearer invalid_token_000000000000000000000000",
|
||||
"X-Ray-Authorization": f"Bearer {cluster_info['token']}",
|
||||
}
|
||||
|
||||
# Should fail because Authorization header takes precedence (even though it's invalid)
|
||||
response = requests.get(
|
||||
f"{cluster_info['dashboard_url']}/api/component_activities",
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_dashboard_auth_disabled(setup_cluster_without_token_auth):
|
||||
"""Test that auth is not enforced when AUTH_MODE is disabled."""
|
||||
|
||||
cluster_info = setup_cluster_without_token_auth
|
||||
|
||||
response = requests.get(
|
||||
f"{cluster_info['dashboard_url']}/api/component_activities",
|
||||
json={"test": "data"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_authentication_mode_endpoint_with_token_auth(setup_cluster_with_token_auth):
|
||||
"""Test authentication_mode endpoint returns 'token' when auth is enabled."""
|
||||
|
||||
cluster_info = setup_cluster_with_token_auth
|
||||
|
||||
# This endpoint should be accessible WITHOUT authentication
|
||||
response = requests.get(f"{cluster_info['dashboard_url']}/api/authentication_mode")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"authentication_mode": "token"}
|
||||
|
||||
|
||||
def test_authentication_mode_endpoint_without_auth(setup_cluster_without_token_auth):
|
||||
"""Test authentication_mode endpoint returns 'disabled' when auth is off."""
|
||||
|
||||
cluster_info = setup_cluster_without_token_auth
|
||||
|
||||
response = requests.get(f"{cluster_info['dashboard_url']}/api/authentication_mode")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"authentication_mode": "disabled"}
|
||||
|
||||
|
||||
def test_authentication_mode_endpoint_is_public(setup_cluster_with_token_auth):
|
||||
"""Test authentication_mode endpoint works without Authorization header."""
|
||||
|
||||
cluster_info = setup_cluster_with_token_auth
|
||||
|
||||
# Call WITHOUT any authorization header - should still succeed
|
||||
response = requests.get(
|
||||
f"{cluster_info['dashboard_url']}/api/authentication_mode",
|
||||
headers={}, # Explicitly no auth
|
||||
)
|
||||
|
||||
# Should succeed even with token auth enabled
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"authentication_mode": "token"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
sys.exit(pytest.main(["-vv", __file__]))
|
||||
@@ -0,0 +1,273 @@
|
||||
import ray
|
||||
from ray.dashboard.memory_utils import (
|
||||
MemoryTable,
|
||||
MemoryTableEntry,
|
||||
ReferenceType,
|
||||
SortingType,
|
||||
decode_object_ref_if_needed,
|
||||
)
|
||||
|
||||
"""Memory Table Unit Test"""
|
||||
|
||||
NODE_ADDRESS = "127.0.0.1"
|
||||
IS_DRIVER = True
|
||||
PID = 1
|
||||
|
||||
OBJECT_ID = "ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZg=="
|
||||
ACTOR_ID = "fffffffffffffffffffffffffffffffff66d17ba010000c801000000"
|
||||
DECODED_ID = decode_object_ref_if_needed(OBJECT_ID)
|
||||
OBJECT_SIZE = 100
|
||||
|
||||
|
||||
def build_memory_entry(
|
||||
*,
|
||||
local_ref_count,
|
||||
pinned_in_memory,
|
||||
submitted_task_reference_count,
|
||||
contained_in_owned,
|
||||
object_size,
|
||||
pid,
|
||||
object_id=OBJECT_ID,
|
||||
node_address=NODE_ADDRESS
|
||||
):
|
||||
object_ref = {
|
||||
"objectId": object_id,
|
||||
"callSite": "(task call) /Users:458",
|
||||
"objectSize": object_size,
|
||||
"localRefCount": local_ref_count,
|
||||
"pinnedInMemory": pinned_in_memory,
|
||||
"submittedTaskRefCount": submitted_task_reference_count,
|
||||
"containedInOwned": contained_in_owned,
|
||||
}
|
||||
return MemoryTableEntry(
|
||||
object_ref=object_ref, node_address=node_address, is_driver=IS_DRIVER, pid=pid
|
||||
)
|
||||
|
||||
|
||||
def build_local_reference_entry(
|
||||
object_size=OBJECT_SIZE, pid=PID, node_address=NODE_ADDRESS
|
||||
):
|
||||
return build_memory_entry(
|
||||
local_ref_count=1,
|
||||
pinned_in_memory=False,
|
||||
submitted_task_reference_count=0,
|
||||
contained_in_owned=[],
|
||||
object_size=object_size,
|
||||
pid=pid,
|
||||
node_address=node_address,
|
||||
)
|
||||
|
||||
|
||||
def build_used_by_pending_task_entry(
|
||||
object_size=OBJECT_SIZE, pid=PID, node_address=NODE_ADDRESS
|
||||
):
|
||||
return build_memory_entry(
|
||||
local_ref_count=0,
|
||||
pinned_in_memory=False,
|
||||
submitted_task_reference_count=2,
|
||||
contained_in_owned=[],
|
||||
object_size=object_size,
|
||||
pid=pid,
|
||||
node_address=node_address,
|
||||
)
|
||||
|
||||
|
||||
def build_captured_in_object_entry(
|
||||
object_size=OBJECT_SIZE, pid=PID, node_address=NODE_ADDRESS
|
||||
):
|
||||
return build_memory_entry(
|
||||
local_ref_count=0,
|
||||
pinned_in_memory=False,
|
||||
submitted_task_reference_count=0,
|
||||
contained_in_owned=[OBJECT_ID],
|
||||
object_size=object_size,
|
||||
pid=pid,
|
||||
node_address=node_address,
|
||||
)
|
||||
|
||||
|
||||
def build_actor_handle_entry(
|
||||
object_size=OBJECT_SIZE, pid=PID, node_address=NODE_ADDRESS
|
||||
):
|
||||
return build_memory_entry(
|
||||
local_ref_count=1,
|
||||
pinned_in_memory=False,
|
||||
submitted_task_reference_count=0,
|
||||
contained_in_owned=[],
|
||||
object_size=object_size,
|
||||
pid=pid,
|
||||
node_address=node_address,
|
||||
object_id=ACTOR_ID,
|
||||
)
|
||||
|
||||
|
||||
def build_pinned_in_memory_entry(
|
||||
object_size=OBJECT_SIZE, pid=PID, node_address=NODE_ADDRESS
|
||||
):
|
||||
return build_memory_entry(
|
||||
local_ref_count=0,
|
||||
pinned_in_memory=True,
|
||||
submitted_task_reference_count=0,
|
||||
contained_in_owned=[],
|
||||
object_size=object_size,
|
||||
pid=pid,
|
||||
node_address=node_address,
|
||||
)
|
||||
|
||||
|
||||
def build_entry(
|
||||
object_size=OBJECT_SIZE,
|
||||
pid=PID,
|
||||
node_address=NODE_ADDRESS,
|
||||
reference_type=ReferenceType.PINNED_IN_MEMORY,
|
||||
):
|
||||
if reference_type == ReferenceType.USED_BY_PENDING_TASK:
|
||||
return build_used_by_pending_task_entry(
|
||||
pid=pid, object_size=object_size, node_address=node_address
|
||||
)
|
||||
elif reference_type == ReferenceType.LOCAL_REFERENCE:
|
||||
return build_local_reference_entry(
|
||||
pid=pid, object_size=object_size, node_address=node_address
|
||||
)
|
||||
elif reference_type == ReferenceType.PINNED_IN_MEMORY:
|
||||
return build_pinned_in_memory_entry(
|
||||
pid=pid, object_size=object_size, node_address=node_address
|
||||
)
|
||||
elif reference_type == ReferenceType.ACTOR_HANDLE:
|
||||
return build_actor_handle_entry(
|
||||
pid=pid, object_size=object_size, node_address=node_address
|
||||
)
|
||||
elif reference_type == ReferenceType.CAPTURED_IN_OBJECT:
|
||||
return build_captured_in_object_entry(
|
||||
pid=pid, object_size=object_size, node_address=node_address
|
||||
)
|
||||
|
||||
|
||||
def test_invalid_memory_entry():
|
||||
memory_entry = build_memory_entry(
|
||||
local_ref_count=0,
|
||||
pinned_in_memory=False,
|
||||
submitted_task_reference_count=0,
|
||||
contained_in_owned=[],
|
||||
object_size=OBJECT_SIZE,
|
||||
pid=PID,
|
||||
)
|
||||
assert memory_entry.is_valid() is False
|
||||
memory_entry = build_memory_entry(
|
||||
local_ref_count=0,
|
||||
pinned_in_memory=False,
|
||||
submitted_task_reference_count=0,
|
||||
contained_in_owned=[],
|
||||
object_size=-1,
|
||||
pid=PID,
|
||||
)
|
||||
assert memory_entry.is_valid() is False
|
||||
|
||||
|
||||
def test_valid_reference_memory_entry():
|
||||
memory_entry = build_local_reference_entry()
|
||||
assert memory_entry.reference_type == ReferenceType.LOCAL_REFERENCE.value
|
||||
assert memory_entry.object_ref == ray.ObjectRef(
|
||||
decode_object_ref_if_needed(OBJECT_ID)
|
||||
)
|
||||
assert memory_entry.is_valid() is True
|
||||
|
||||
|
||||
def test_reference_type():
|
||||
# pinned in memory
|
||||
memory_entry = build_pinned_in_memory_entry()
|
||||
assert memory_entry.reference_type == ReferenceType.PINNED_IN_MEMORY.value
|
||||
|
||||
# used by pending task
|
||||
memory_entry = build_used_by_pending_task_entry()
|
||||
assert memory_entry.reference_type == ReferenceType.USED_BY_PENDING_TASK.value
|
||||
|
||||
# captued in object
|
||||
memory_entry = build_captured_in_object_entry()
|
||||
assert memory_entry.reference_type == ReferenceType.CAPTURED_IN_OBJECT.value
|
||||
|
||||
# actor handle
|
||||
memory_entry = build_actor_handle_entry()
|
||||
assert memory_entry.reference_type == ReferenceType.ACTOR_HANDLE.value
|
||||
|
||||
|
||||
def test_memory_table_summary():
|
||||
entries = [
|
||||
build_pinned_in_memory_entry(),
|
||||
build_used_by_pending_task_entry(),
|
||||
build_captured_in_object_entry(),
|
||||
build_actor_handle_entry(),
|
||||
build_local_reference_entry(),
|
||||
build_local_reference_entry(),
|
||||
]
|
||||
memory_table = MemoryTable(entries)
|
||||
assert len(memory_table.group) == 1
|
||||
assert memory_table.summary["total_actor_handles"] == 1
|
||||
assert memory_table.summary["total_captured_in_objects"] == 1
|
||||
assert memory_table.summary["total_local_ref_count"] == 2
|
||||
assert memory_table.summary["total_object_size"] == len(entries) * OBJECT_SIZE
|
||||
assert memory_table.summary["total_pinned_in_memory"] == 1
|
||||
assert memory_table.summary["total_used_by_pending_task"] == 1
|
||||
|
||||
|
||||
def test_memory_table_sort_by_pid():
|
||||
unsort = [1, 3, 2]
|
||||
entries = [build_entry(pid=pid) for pid in unsort]
|
||||
memory_table = MemoryTable(entries, sort_by_type=SortingType.PID)
|
||||
sort = sorted(unsort)
|
||||
for pid, entry in zip(sort, memory_table.table):
|
||||
assert pid == entry.pid
|
||||
|
||||
|
||||
def test_memory_table_sort_by_reference_type():
|
||||
unsort = [
|
||||
ReferenceType.USED_BY_PENDING_TASK,
|
||||
ReferenceType.LOCAL_REFERENCE,
|
||||
ReferenceType.LOCAL_REFERENCE,
|
||||
ReferenceType.PINNED_IN_MEMORY,
|
||||
]
|
||||
entries = [build_entry(reference_type=reference_type) for reference_type in unsort]
|
||||
memory_table = MemoryTable(entries, sort_by_type=SortingType.REFERENCE_TYPE)
|
||||
sort = sorted([entry.value for entry in unsort])
|
||||
for reference_type, entry in zip(sort, memory_table.table):
|
||||
assert reference_type == entry.reference_type
|
||||
|
||||
|
||||
def test_memory_table_sort_by_object_size():
|
||||
unsort = [312, 214, -1, 1244, 642]
|
||||
entries = [build_entry(object_size=object_size) for object_size in unsort]
|
||||
memory_table = MemoryTable(entries, sort_by_type=SortingType.OBJECT_SIZE)
|
||||
sort = sorted(unsort)
|
||||
for object_size, entry in zip(sort, memory_table.table):
|
||||
assert object_size == entry.object_size
|
||||
|
||||
|
||||
def test_group_by():
|
||||
node_second = "127.0.0.2"
|
||||
node_first = "127.0.0.1"
|
||||
entries = [
|
||||
build_entry(node_address=node_second, pid=2),
|
||||
build_entry(node_address=node_second, pid=1),
|
||||
build_entry(node_address=node_first, pid=2),
|
||||
build_entry(node_address=node_first, pid=1),
|
||||
]
|
||||
memory_table = MemoryTable(entries)
|
||||
|
||||
# Make sure it is correctly grouped
|
||||
assert node_first in memory_table.group
|
||||
assert node_second in memory_table.group
|
||||
|
||||
# make sure pid is sorted in the right order.
|
||||
for group_key, group_memory_table in memory_table.group.items():
|
||||
pid = 1
|
||||
for entry in group_memory_table.table:
|
||||
assert pid == entry.pid
|
||||
pid += 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
sys.exit(pytest.main(["-v", __file__]))
|
||||
@@ -0,0 +1,94 @@
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from ray.dashboard.modules.state.state_head import RateLimitedModule
|
||||
|
||||
|
||||
class FailedCallError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class A(RateLimitedModule):
|
||||
def __init__(self, max_num_call: int):
|
||||
import logging
|
||||
|
||||
super().__init__(max_num_call, logging.getLogger(__name__))
|
||||
|
||||
@RateLimitedModule.enforce_max_concurrent_calls
|
||||
async def fn1(self, err: bool = False):
|
||||
if err:
|
||||
raise FailedCallError
|
||||
|
||||
await asyncio.sleep(3)
|
||||
return True
|
||||
|
||||
@RateLimitedModule.enforce_max_concurrent_calls
|
||||
async def fn2(self):
|
||||
await asyncio.sleep(3)
|
||||
return True
|
||||
|
||||
async def limit_handler_(self):
|
||||
return False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("extra_req_num", [-5, -3, -1, 0, 1, 3, 5])
|
||||
async def test_max_concurrent_in_progress_functions(extra_req_num):
|
||||
"""Test rate limiting for concurrent in-progress requests on StateHead"""
|
||||
max_req = 10
|
||||
a = A(max_num_call=max_req)
|
||||
|
||||
# Run more than allowed concurrent async functions should trigger rate limiting
|
||||
res_arr = await asyncio.gather(
|
||||
*[a.fn1() if i % 2 == 0 else a.fn2() for i in range(max_req + extra_req_num)]
|
||||
)
|
||||
fail_cnt = 0
|
||||
for ok in res_arr:
|
||||
fail_cnt += 0 if ok else 1
|
||||
|
||||
expected_fail_cnt = max(0, extra_req_num)
|
||||
assert fail_cnt == expected_fail_cnt, (
|
||||
f"{expected_fail_cnt} out of {max_req + extra_req_num} "
|
||||
f"concurrent runs should fail with max={max_req} but {fail_cnt}."
|
||||
)
|
||||
|
||||
assert a.num_call_ == 0, "All requests should be done"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"failures",
|
||||
[
|
||||
[True, True, True, True, True],
|
||||
[False, False, False, False, False],
|
||||
[False, True, False, True, False],
|
||||
[False, False, False, True, True],
|
||||
[True, True, False, False, False],
|
||||
],
|
||||
)
|
||||
async def test_max_concurrent_with_exceptions(failures):
|
||||
|
||||
max_req = 10
|
||||
a = A(max_num_call=max_req)
|
||||
|
||||
# Run more than allowed concurrent async functions should trigger rate limiting
|
||||
res_arr = await asyncio.gather(
|
||||
*[a.fn1(err=should_throw_err) for should_throw_err in failures],
|
||||
return_exceptions=True,
|
||||
)
|
||||
|
||||
expected_num_failure = sum(failures)
|
||||
|
||||
actual_num_failure = 0
|
||||
for res in res_arr:
|
||||
if isinstance(res, FailedCallError):
|
||||
actual_num_failure += 1
|
||||
|
||||
assert expected_num_failure == actual_num_failure, "All failures should be captured"
|
||||
assert a.num_call_ == 0, "Failure should decrement the counter correctly"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main(["-v", __file__]))
|
||||
@@ -0,0 +1,25 @@
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from ray.dashboard.utils import close_logger_file_descriptor
|
||||
|
||||
|
||||
def test_close_logger_file_descriptor():
|
||||
logger_format = "%(message)s"
|
||||
logger = logging.getLogger("test_job_id")
|
||||
|
||||
job_driver_log_path = "/tmp/ray.log"
|
||||
job_driver_handler = logging.FileHandler(job_driver_log_path)
|
||||
job_driver_formatter = logging.Formatter(logger_format)
|
||||
job_driver_handler.setFormatter(job_driver_formatter)
|
||||
logger.addHandler(job_driver_handler)
|
||||
|
||||
assert job_driver_handler.stream.closed is False
|
||||
close_logger_file_descriptor(logger)
|
||||
assert job_driver_handler.stream is None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main(["-v", __file__]))
|
||||
Reference in New Issue
Block a user