|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# Copyright 2026 Bytedance Ltd. and/or its affiliates |
| 3 | +# |
| 4 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +# you may not use this file except in compliance with the License. |
| 6 | +# You may obtain a copy of the License at |
| 7 | +# |
| 8 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +# |
| 10 | +# Unless required by applicable law or agreed to in writing, software |
| 11 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +# See the License for the specific language governing permissions and |
| 14 | +# limitations under the License. |
| 15 | + |
| 16 | +from __future__ import annotations |
| 17 | + |
| 18 | +from dataclasses import dataclass |
| 19 | + |
| 20 | +import pytest |
| 21 | + |
| 22 | +import verl.tools.utils.tool_registry as tool_registry |
| 23 | +from verl.tools.utils.tool_registry import ToolListCache |
| 24 | + |
| 25 | + |
| 26 | +@dataclass(frozen=True) |
| 27 | +class _DummyTool: |
| 28 | + name: str |
| 29 | + |
| 30 | + |
| 31 | +def test_tool_list_cache_returns_empty_for_none(): |
| 32 | + ToolListCache._cache.clear() |
| 33 | + assert ToolListCache.get_tool_list(None) == [] |
| 34 | + |
| 35 | + |
| 36 | +def test_tool_list_cache_caches_and_deepcopies(monkeypatch: pytest.MonkeyPatch): |
| 37 | + ToolListCache._cache.clear() |
| 38 | + |
| 39 | + calls: list[tuple[str, float | None]] = [] |
| 40 | + |
| 41 | + def _fake_init(tools_config_file: str, mcp_init_timeout_s: float | None = None): |
| 42 | + calls.append((tools_config_file, mcp_init_timeout_s)) |
| 43 | + return [_DummyTool(name="t1")] |
| 44 | + |
| 45 | + monkeypatch.setattr(tool_registry, "initialize_tools_from_config", _fake_init) |
| 46 | + monkeypatch.setenv("VERL_TOOL_CACHE_INIT_TIMEOUT_S", "12") |
| 47 | + |
| 48 | + out1 = ToolListCache.get_tool_list("dummy.yaml") |
| 49 | + out2 = ToolListCache.get_tool_list("dummy.yaml") |
| 50 | + |
| 51 | + assert calls == [("dummy.yaml", 12.0)] |
| 52 | + assert out1 == out2 |
| 53 | + assert out1 is not out2 |
| 54 | + assert out1[0] is not out2[0] |
| 55 | + |
| 56 | + |
| 57 | +def test_tool_list_cache_retries_before_failing(monkeypatch: pytest.MonkeyPatch): |
| 58 | + ToolListCache._cache.clear() |
| 59 | + |
| 60 | + attempts = {"n": 0} |
| 61 | + |
| 62 | + def _fake_init(tools_config_file: str, mcp_init_timeout_s: float | None = None): |
| 63 | + attempts["n"] += 1 |
| 64 | + if attempts["n"] == 1: |
| 65 | + raise RuntimeError("boom") |
| 66 | + return [_DummyTool(name="t1")] |
| 67 | + |
| 68 | + sleeps: list[float] = [] |
| 69 | + |
| 70 | + monkeypatch.setattr(tool_registry, "initialize_tools_from_config", _fake_init) |
| 71 | + monkeypatch.setattr(tool_registry.time, "sleep", lambda s: sleeps.append(float(s))) |
| 72 | + monkeypatch.setenv("VERL_TOOL_CACHE_INIT_RETRIES", "2") |
| 73 | + monkeypatch.setenv("VERL_TOOL_CACHE_INIT_TIMEOUT_S", "0.5") |
| 74 | + monkeypatch.setenv("VERL_TOOL_CACHE_INIT_BACKOFF_BASE_S", "0.01") |
| 75 | + monkeypatch.setenv("VERL_TOOL_CACHE_INIT_BACKOFF_MAX_S", "0.01") |
| 76 | + |
| 77 | + out = ToolListCache.get_tool_list("dummy.yaml") |
| 78 | + |
| 79 | + assert out == [_DummyTool(name="t1")] |
| 80 | + assert attempts["n"] == 2 |
| 81 | + assert sleeps == [0.01] |
0 commit comments