Files
2026-07-13 12:49:17 +08:00

297 lines
9.6 KiB
Python

from __future__ import annotations
import asyncio
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
from services.tools.manage_profiler import (
manage_profiler,
ALL_ACTIONS,
SESSION_ACTIONS,
COUNTER_ACTIONS,
MEMORY_SNAPSHOT_ACTIONS,
FRAME_DEBUGGER_ACTIONS,
UTILITY_ACTIONS,
)
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def mock_unity(monkeypatch):
"""Patch Unity transport layer and return captured call dict."""
captured: dict[str, object] = {}
async def fake_send(send_fn, unity_instance, tool_name, params):
captured["unity_instance"] = unity_instance
captured["tool_name"] = tool_name
captured["params"] = params
return {"success": True, "message": "ok"}
monkeypatch.setattr(
"services.tools.manage_profiler.get_unity_instance_from_context",
AsyncMock(return_value="unity-instance-1"),
)
monkeypatch.setattr(
"services.tools.manage_profiler.send_with_unity_instance",
fake_send,
)
return captured
# ---------------------------------------------------------------------------
# Action list completeness
# ---------------------------------------------------------------------------
def test_profiler_actions_count():
assert len(ALL_ACTIONS) == 14
def test_no_duplicate_actions():
assert len(ALL_ACTIONS) == len(set(ALL_ACTIONS))
def test_session_actions():
expected = {"profiler_start", "profiler_stop", "profiler_status", "profiler_set_areas"}
assert set(SESSION_ACTIONS) == expected
def test_counter_actions():
expected = {"get_frame_timing", "get_counters", "get_object_memory"}
assert set(COUNTER_ACTIONS) == expected
def test_memory_snapshot_actions():
expected = {"memory_take_snapshot", "memory_list_snapshots", "memory_compare_snapshots"}
assert set(MEMORY_SNAPSHOT_ACTIONS) == expected
def test_frame_debugger_actions():
expected = {"frame_debugger_enable", "frame_debugger_disable", "frame_debugger_get_events"}
assert set(FRAME_DEBUGGER_ACTIONS) == expected
def test_utility_actions():
assert UTILITY_ACTIONS == ["ping"]
def test_all_actions_is_union():
expected = set(UTILITY_ACTIONS + SESSION_ACTIONS + COUNTER_ACTIONS + MEMORY_SNAPSHOT_ACTIONS + FRAME_DEBUGGER_ACTIONS)
assert set(ALL_ACTIONS) == expected
# ---------------------------------------------------------------------------
# Invalid / missing action
# ---------------------------------------------------------------------------
def test_unknown_action_returns_error(mock_unity):
result = asyncio.run(
manage_profiler(SimpleNamespace(), action="nonexistent_action")
)
assert result["success"] is False
assert "Unknown action" in result["message"]
assert "tool_name" not in mock_unity
def test_empty_action_returns_error(mock_unity):
result = asyncio.run(
manage_profiler(SimpleNamespace(), action="")
)
assert result["success"] is False
assert "Unknown action" in result["message"]
assert "tool_name" not in mock_unity
# ---------------------------------------------------------------------------
# Each action forwards correctly
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("action_name", [
"ping",
"profiler_start", "profiler_stop", "profiler_status", "profiler_set_areas",
"get_frame_timing", "get_counters", "get_object_memory",
"memory_take_snapshot", "memory_list_snapshots", "memory_compare_snapshots",
"frame_debugger_enable", "frame_debugger_disable", "frame_debugger_get_events",
])
def test_every_action_forwards_to_unity(mock_unity, action_name):
result = asyncio.run(
manage_profiler(SimpleNamespace(), action=action_name)
)
assert result["success"] is True
assert mock_unity["tool_name"] == "manage_profiler"
assert mock_unity["params"]["action"] == action_name
def test_uses_unity_instance_from_context(mock_unity):
asyncio.run(
manage_profiler(SimpleNamespace(), action="get_frame_timing")
)
assert mock_unity["unity_instance"] == "unity-instance-1"
# ---------------------------------------------------------------------------
# Param forwarding
# ---------------------------------------------------------------------------
def test_get_counters_forwards_category(mock_unity):
result = asyncio.run(
manage_profiler(SimpleNamespace(), action="get_counters", category="Render")
)
assert result["success"] is True
assert mock_unity["params"]["category"] == "Render"
def test_get_counters_forwards_counter_names(mock_unity):
result = asyncio.run(
manage_profiler(
SimpleNamespace(), action="get_counters",
category="Render", counters=["Draw Calls Count", "Batches Count"],
)
)
assert result["success"] is True
assert mock_unity["params"]["counters"] == ["Draw Calls Count", "Batches Count"]
def test_get_counters_omits_none_counters(mock_unity):
result = asyncio.run(
manage_profiler(SimpleNamespace(), action="get_counters", category="Memory")
)
assert result["success"] is True
assert "counters" not in mock_unity["params"]
def test_profiler_start_forwards_log_file(mock_unity):
result = asyncio.run(
manage_profiler(SimpleNamespace(), action="profiler_start", log_file="/tmp/profile.raw")
)
assert result["success"] is True
assert mock_unity["params"]["log_file"] == "/tmp/profile.raw"
def test_profiler_start_forwards_callstacks(mock_unity):
result = asyncio.run(
manage_profiler(SimpleNamespace(), action="profiler_start", enable_callstacks=True)
)
assert result["success"] is True
assert mock_unity["params"]["enable_callstacks"] is True
def test_profiler_set_areas_forwards_areas(mock_unity):
areas = {"CPU": True, "Audio": False}
result = asyncio.run(
manage_profiler(SimpleNamespace(), action="profiler_set_areas", areas=areas)
)
assert result["success"] is True
assert mock_unity["params"]["areas"] == areas
def test_get_object_memory_forwards_path(mock_unity):
result = asyncio.run(
manage_profiler(SimpleNamespace(), action="get_object_memory", object_path="/Player/Mesh")
)
assert result["success"] is True
assert mock_unity["params"]["object_path"] == "/Player/Mesh"
def test_memory_take_snapshot_forwards_path(mock_unity):
result = asyncio.run(
manage_profiler(SimpleNamespace(), action="memory_take_snapshot", snapshot_path="/tmp/snap.snap")
)
assert result["success"] is True
assert mock_unity["params"]["snapshot_path"] == "/tmp/snap.snap"
def test_memory_compare_forwards_both_paths(mock_unity):
result = asyncio.run(
manage_profiler(
SimpleNamespace(), action="memory_compare_snapshots",
snapshot_a="/tmp/a.snap", snapshot_b="/tmp/b.snap",
)
)
assert result["success"] is True
assert mock_unity["params"]["snapshot_a"] == "/tmp/a.snap"
assert mock_unity["params"]["snapshot_b"] == "/tmp/b.snap"
def test_frame_debugger_get_events_forwards_paging(mock_unity):
result = asyncio.run(
manage_profiler(
SimpleNamespace(), action="frame_debugger_get_events",
page_size=25, cursor=50,
)
)
assert result["success"] is True
assert mock_unity["params"]["page_size"] == 25
assert mock_unity["params"]["cursor"] == 50
def test_action_only_params_no_extras(mock_unity):
result = asyncio.run(
manage_profiler(SimpleNamespace(), action="profiler_stop")
)
assert result["success"] is True
assert mock_unity["params"] == {"action": "profiler_stop"}
# ---------------------------------------------------------------------------
# Case insensitivity
# ---------------------------------------------------------------------------
def test_action_case_insensitive(mock_unity):
result = asyncio.run(
manage_profiler(SimpleNamespace(), action="Get_Frame_Timing")
)
assert result["success"] is True
assert mock_unity["params"]["action"] == "get_frame_timing"
def test_action_uppercase(mock_unity):
result = asyncio.run(
manage_profiler(SimpleNamespace(), action="PROFILER_STATUS")
)
assert result["success"] is True
assert mock_unity["params"]["action"] == "profiler_status"
# ---------------------------------------------------------------------------
# Non-dict response wrapped
# ---------------------------------------------------------------------------
def test_non_dict_response_wrapped(monkeypatch):
monkeypatch.setattr(
"services.tools.manage_profiler.get_unity_instance_from_context",
AsyncMock(return_value="unity-1"),
)
async def fake_send(send_fn, unity_instance, tool_name, params):
return "unexpected string response"
monkeypatch.setattr(
"services.tools.manage_profiler.send_with_unity_instance",
fake_send,
)
result = asyncio.run(
manage_profiler(SimpleNamespace(), action="get_frame_timing")
)
assert result["success"] is False
assert "unexpected string response" in result["message"]
# ---------------------------------------------------------------------------
# Tool registration
# ---------------------------------------------------------------------------
def test_tool_registered_with_profiling_group():
from services.registry.tool_registry import _tool_registry
profiler_tools = [
t for t in _tool_registry if t.get("name") == "manage_profiler"
]
assert len(profiler_tools) == 1
assert profiler_tools[0]["group"] == "profiling"