unity-mcp/tests/test_telemetry_queue_worker.py

104 lines
3.4 KiB
Python
Raw Normal View History

import sys
import pathlib
import importlib.util
test: Consolidate pytest suite to MCPForUnity and improve test infrastructure (#332) * Update github-repo-stats.yml * pytest: make harness MCPForUnity-only; remove UnityMcpBridge paths from tests; route tools.manage_script via unity_connection for reliable monkeypatching; fix ctx usage; all tests green (39 pass, 5 skip, 7 xpass) * Add missing meta for MaterialMeshInstantiationTests.cs (Assets) * bridge/tools/manage_script: fix missing unity_connection prefix in validate_script; tests: tidy manage_script_uri unused symbols and arg names * tests: rename to script_apply_edits_module; extract DummyContext to tests/test_helpers and import; add telemetry stubs in tests to avoid pyproject I/O * tests: import cleanup and helper extraction; telemetry: prefer plain config and opt-in env override; test stubs and CWD fixes; exclude Bridge from pytest discovery * chore: remove unintended .wt-origin-main gitlink and ignore folder * tests: nit fixes (unused-arg stubs, import order, path-normalized ignore hook); telemetry: validate config endpoint; read_console: action optional * Add development dependencies to pyproject.toml - Add [project.optional-dependencies] section with dev group - Include pytest>=8.0.0 and pytest-anyio>=0.6.0 - Add Development Setup section to README-DEV.md with installation and testing instructions * Revert "Update github-repo-stats.yml" This reverts commit 8ae595d2f4f2525b0e44ece948883ea37138add4. * test: improve test clarity and modernize asyncio usage - Add explanation for 200ms timeout in backpressure test - Replace manual event loop creation with asyncio.run() - Add assertion message with actual elapsed time for easier debugging * refactor: remove duplicate DummyContext definitions across test files Replace 7 duplicate DummyContext class definitions with imports from tests.test_helpers. This follows DRY principles and ensures consistency across the test suite. * chore: remove unused _load function from test_edit_strict_and_warnings.py Dead code cleanup - function was no longer used after refactoring to dynamic tool registration. * docs: add comment explaining CWD manipulation in telemetry test Clarify why os.chdir() is necessary: telemetry.py calls get_package_version() at module load time, which reads pyproject.toml using a relative path. Acknowledges the fragility while explaining why it's currently required.
2025-10-22 01:42:55 +08:00
import os
import types
import threading
import time
import queue as q
ROOT = pathlib.Path(__file__).resolve().parents[1]
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
sys.path.insert(0, str(SRC))
# Stub mcp.server.fastmcp to satisfy imports without the full dependency
mcp_pkg = types.ModuleType("mcp")
server_pkg = types.ModuleType("mcp.server")
fastmcp_pkg = types.ModuleType("mcp.server.fastmcp")
2025-10-01 04:25:33 +08:00
class _Dummy:
pass
2025-10-01 04:25:33 +08:00
fastmcp_pkg.FastMCP = _Dummy
fastmcp_pkg.Context = _Dummy
server_pkg.fastmcp = fastmcp_pkg
mcp_pkg.server = server_pkg
sys.modules.setdefault("mcp", mcp_pkg)
sys.modules.setdefault("mcp.server", server_pkg)
sys.modules.setdefault("mcp.server.fastmcp", fastmcp_pkg)
test: Consolidate pytest suite to MCPForUnity and improve test infrastructure (#332) * Update github-repo-stats.yml * pytest: make harness MCPForUnity-only; remove UnityMcpBridge paths from tests; route tools.manage_script via unity_connection for reliable monkeypatching; fix ctx usage; all tests green (39 pass, 5 skip, 7 xpass) * Add missing meta for MaterialMeshInstantiationTests.cs (Assets) * bridge/tools/manage_script: fix missing unity_connection prefix in validate_script; tests: tidy manage_script_uri unused symbols and arg names * tests: rename to script_apply_edits_module; extract DummyContext to tests/test_helpers and import; add telemetry stubs in tests to avoid pyproject I/O * tests: import cleanup and helper extraction; telemetry: prefer plain config and opt-in env override; test stubs and CWD fixes; exclude Bridge from pytest discovery * chore: remove unintended .wt-origin-main gitlink and ignore folder * tests: nit fixes (unused-arg stubs, import order, path-normalized ignore hook); telemetry: validate config endpoint; read_console: action optional * Add development dependencies to pyproject.toml - Add [project.optional-dependencies] section with dev group - Include pytest>=8.0.0 and pytest-anyio>=0.6.0 - Add Development Setup section to README-DEV.md with installation and testing instructions * Revert "Update github-repo-stats.yml" This reverts commit 8ae595d2f4f2525b0e44ece948883ea37138add4. * test: improve test clarity and modernize asyncio usage - Add explanation for 200ms timeout in backpressure test - Replace manual event loop creation with asyncio.run() - Add assertion message with actual elapsed time for easier debugging * refactor: remove duplicate DummyContext definitions across test files Replace 7 duplicate DummyContext class definitions with imports from tests.test_helpers. This follows DRY principles and ensures consistency across the test suite. * chore: remove unused _load function from test_edit_strict_and_warnings.py Dead code cleanup - function was no longer used after refactoring to dynamic tool registration. * docs: add comment explaining CWD manipulation in telemetry test Clarify why os.chdir() is necessary: telemetry.py calls get_package_version() at module load time, which reads pyproject.toml using a relative path. Acknowledges the fragility while explaining why it's currently required.
2025-10-22 01:42:55 +08:00
# Ensure telemetry module has get_package_version stub before importing
telemetry_stub = types.ModuleType("telemetry")
telemetry_stub.get_package_version = lambda: "0.0.0"
sys.modules.setdefault("telemetry", telemetry_stub)
def _load_module(path: pathlib.Path, name: str):
spec = importlib.util.spec_from_file_location(name, path)
Harden MCP tool parameter handling to eliminate “invalid param” errors (#339) * feat: migrate to FastMCP 2.0 (v2.12.5) - Update pyproject.toml to use fastmcp>=2.12.5 instead of mcp[cli] - Replace all imports from mcp.server.fastmcp to fastmcp - Maintain MCP protocol compliance with mcp>=1.16.0 - All 15 files updated with new import statements - Server and tools registration working with FastMCP 2.0 * chore: bump MCP for Unity to 6.2.2 and widen numeric tool params (asset search/read_resource/run_tests) for better LLM compatibility * chore: bump installed server_version.txt to 6.2.2 so Unity installer logs correct version * fix(parameters): apply parameter hardening to read_console, manage_editor, and manage_gameobject - read_console: accept int|str for count parameter with coercion - manage_editor: accept bool|str for wait_for_completion with coercion - manage_gameobject: accept bool|str for all boolean parameters with coercion - All tools now handle string parameters gracefully and convert to proper types internally * chore(deps): drop fastmcp, use mcp>=1.18.0; update imports to mcp.server.fastmcp * chore(deps): re-add fastmcp>=2.12.5, relax mcp to >=1.16.0 Adds fastmcp as explicit dependency for FastMCP 2.0 migration. Relaxes mcp version constraint to support broader compatibility. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * test: remove obsolete mcp stubs for FastMCP 2.0 compatibility Removes stub mcp modules from test files that were conflicting with the real mcp and fastmcp packages now installed as dependencies. Adds tests/__init__.py to make tests a proper Python package. This fixes test collection errors after migrating to FastMCP 2.0. Test results: 40 passed, 7 xpassed, 5 skipped, 1 failed (pre-existing) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: complete FastMCP 2.0 migration with correct import paths Updates all remaining files to use `from fastmcp import` instead of the old `from mcp.server.fastmcp import` path. Changes: - server.py: Update FastMCP import - tools/__init__.py: Update FastMCP import - resources/__init__.py: Update FastMCP import - tools/manage_script.py, read_console.py, resource_tools.py: Update imports - test stubs: Update to stub `fastmcp` instead of `mcp.server.fastmcp` Addresses PR review feedback about incomplete migration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: harden parameter type handling and resolve PR feedback Parameter Type Improvements: - Broaden count in read_console.py to accept int | str - Broaden build_index in manage_scene.py to accept int | str - Harden vector parsing in manage_gameobject.py with NaN/Inf checks - Add whitespace-delimited vector support (e.g., "1 2 3") - Narrow exception handling from Exception to (ValueError, TypeError) Test Improvements: - Harden _load_module in test files with spec/loader validation - Fix test_manage_gameobject_boolean_and_tag_mapping by mapping tag→search_term Bug Fixes: - Fix syntax error in manage_shader.py (remove stray 'x') Version: Bump to 6.2.3 All tests pass: 41 passed, 5 skipped, 7 xpassed --------- Co-authored-by: Claude <noreply@anthropic.com>
2025-10-23 09:42:46 +08:00
if spec is None or spec.loader is None:
raise ImportError(f"Cannot load module {name} from {path}")
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
test: Consolidate pytest suite to MCPForUnity and improve test infrastructure (#332) * Update github-repo-stats.yml * pytest: make harness MCPForUnity-only; remove UnityMcpBridge paths from tests; route tools.manage_script via unity_connection for reliable monkeypatching; fix ctx usage; all tests green (39 pass, 5 skip, 7 xpass) * Add missing meta for MaterialMeshInstantiationTests.cs (Assets) * bridge/tools/manage_script: fix missing unity_connection prefix in validate_script; tests: tidy manage_script_uri unused symbols and arg names * tests: rename to script_apply_edits_module; extract DummyContext to tests/test_helpers and import; add telemetry stubs in tests to avoid pyproject I/O * tests: import cleanup and helper extraction; telemetry: prefer plain config and opt-in env override; test stubs and CWD fixes; exclude Bridge from pytest discovery * chore: remove unintended .wt-origin-main gitlink and ignore folder * tests: nit fixes (unused-arg stubs, import order, path-normalized ignore hook); telemetry: validate config endpoint; read_console: action optional * Add development dependencies to pyproject.toml - Add [project.optional-dependencies] section with dev group - Include pytest>=8.0.0 and pytest-anyio>=0.6.0 - Add Development Setup section to README-DEV.md with installation and testing instructions * Revert "Update github-repo-stats.yml" This reverts commit 8ae595d2f4f2525b0e44ece948883ea37138add4. * test: improve test clarity and modernize asyncio usage - Add explanation for 200ms timeout in backpressure test - Replace manual event loop creation with asyncio.run() - Add assertion message with actual elapsed time for easier debugging * refactor: remove duplicate DummyContext definitions across test files Replace 7 duplicate DummyContext class definitions with imports from tests.test_helpers. This follows DRY principles and ensures consistency across the test suite. * chore: remove unused _load function from test_edit_strict_and_warnings.py Dead code cleanup - function was no longer used after refactoring to dynamic tool registration. * docs: add comment explaining CWD manipulation in telemetry test Clarify why os.chdir() is necessary: telemetry.py calls get_package_version() at module load time, which reads pyproject.toml using a relative path. Acknowledges the fragility while explaining why it's currently required.
2025-10-22 01:42:55 +08:00
# Load real telemetry on top of stub (it will reuse stubbed helpers)
# Note: CWD change required because telemetry.py calls get_package_version()
# at module load time, which reads pyproject.toml using a relative path.
# This is fragile but necessary given current telemetry module design.
_prev_cwd = os.getcwd()
os.chdir(str(SRC))
try:
telemetry = _load_module(SRC / "telemetry.py", "telemetry_mod")
finally:
os.chdir(_prev_cwd)
def test_telemetry_queue_backpressure_and_single_worker(monkeypatch, caplog):
caplog.set_level("DEBUG")
collector = telemetry.TelemetryCollector()
# Force-enable telemetry regardless of env settings from conftest
collector.config.enabled = True
# Wake existing worker once so it observes the new queue on the next loop
collector.record(telemetry.RecordType.TOOL_EXECUTION, {"i": -1})
# Replace queue with tiny one to trigger backpressure quickly
small_q = q.Queue(maxsize=2)
collector._queue = small_q
# Give the worker a moment to switch queues
time.sleep(0.02)
# Make sends slow to build backlog and exercise worker
def slow_send(self, rec):
time.sleep(0.05)
collector._send_telemetry = types.MethodType(slow_send, collector)
# Fire many events quickly; record() should not block even when queue fills
start = time.perf_counter()
for i in range(50):
collector.record(telemetry.RecordType.TOOL_EXECUTION, {"i": i})
elapsed_ms = (time.perf_counter() - start) * 1000.0
# Should be fast despite backpressure (non-blocking enqueue or drop)
test: Consolidate pytest suite to MCPForUnity and improve test infrastructure (#332) * Update github-repo-stats.yml * pytest: make harness MCPForUnity-only; remove UnityMcpBridge paths from tests; route tools.manage_script via unity_connection for reliable monkeypatching; fix ctx usage; all tests green (39 pass, 5 skip, 7 xpass) * Add missing meta for MaterialMeshInstantiationTests.cs (Assets) * bridge/tools/manage_script: fix missing unity_connection prefix in validate_script; tests: tidy manage_script_uri unused symbols and arg names * tests: rename to script_apply_edits_module; extract DummyContext to tests/test_helpers and import; add telemetry stubs in tests to avoid pyproject I/O * tests: import cleanup and helper extraction; telemetry: prefer plain config and opt-in env override; test stubs and CWD fixes; exclude Bridge from pytest discovery * chore: remove unintended .wt-origin-main gitlink and ignore folder * tests: nit fixes (unused-arg stubs, import order, path-normalized ignore hook); telemetry: validate config endpoint; read_console: action optional * Add development dependencies to pyproject.toml - Add [project.optional-dependencies] section with dev group - Include pytest>=8.0.0 and pytest-anyio>=0.6.0 - Add Development Setup section to README-DEV.md with installation and testing instructions * Revert "Update github-repo-stats.yml" This reverts commit 8ae595d2f4f2525b0e44ece948883ea37138add4. * test: improve test clarity and modernize asyncio usage - Add explanation for 200ms timeout in backpressure test - Replace manual event loop creation with asyncio.run() - Add assertion message with actual elapsed time for easier debugging * refactor: remove duplicate DummyContext definitions across test files Replace 7 duplicate DummyContext class definitions with imports from tests.test_helpers. This follows DRY principles and ensures consistency across the test suite. * chore: remove unused _load function from test_edit_strict_and_warnings.py Dead code cleanup - function was no longer used after refactoring to dynamic tool registration. * docs: add comment explaining CWD manipulation in telemetry test Clarify why os.chdir() is necessary: telemetry.py calls get_package_version() at module load time, which reads pyproject.toml using a relative path. Acknowledges the fragility while explaining why it's currently required.
2025-10-22 01:42:55 +08:00
# Timeout relaxed to 200ms to handle thread scheduling variance in CI/local environments
assert elapsed_ms < 200.0, f"Took {elapsed_ms:.1f}ms (expected <200ms)"
# Allow worker to process some
time.sleep(0.3)
# Verify drops were logged (queue full backpressure)
2025-10-01 04:25:33 +08:00
dropped_logs = [
m for m in caplog.messages if "Telemetry queue full; dropping" in m]
assert len(dropped_logs) >= 1
# Ensure only one worker thread exists and is alive
assert collector._worker.is_alive()
2025-10-01 04:25:33 +08:00
worker_threads = [
t for t in threading.enumerate() if t is collector._worker]
assert len(worker_threads) == 1