unity-mcp/Server/tests/integration/test_refresh_unity_retry_re...

47 lines
1.8 KiB
Python
Raw Normal View History

Async Test Infrastructure & Editor Readiness Status + new refresh_unity tool (#507) * Add editor readiness v2, refresh tool, and preflight guards * Detect external package changes and harden refresh retry * feat: add TestRunnerNoThrottle and async test running with background stall prevention - Add TestRunnerNoThrottle.cs: Sets editor to 'No Throttling' mode during test runs with SessionState persistence across domain reload - Add run_tests_async and get_test_job tools for non-blocking test execution - Add TestJobManager for async test job tracking with progress monitoring - Add ForceSynchronousImport to all AssetDatabase.Refresh() calls to prevent stalls - Mark DomainReloadResilienceTests as [Explicit] with documentation explaining the test infrastructure limitation (internal coroutine waits vs MCP socket polling) - MCP workflow is unaffected - socket messages provide external stimulus that keeps Unity responsive even when backgrounded * refactor: simplify and clean up code - Remove unused Newtonsoft.Json.Linq import from TestJobManager - Add throttling to SessionState persistence (once per second) to reduce overhead - Critical job state changes (start/finish) still persist immediately - Fix duplicate XML summary tag in DomainReloadResilienceTests * docs: add async test tools to README, document domain reload limitation - Add run_tests_async and get_test_job to main README tools list - Document background stall limitation for domain reload tests in DEV readme * ci: add separate job for domain reload tests Run [Explicit] domain_reload tests in their own job using -testCategory * ci: run domain reload tests in same job as regular tests Combines into single job with two test steps to reuse cached Library * fix: address coderabbit review issues - Fix TOCTOU race in TestJobManager.StartJob (single lock scope for check-and-set) - Store TestRunnerApi reference with HideAndDontSave to prevent GC/serialization issues * docs: update tool descriptions to prefer run_tests_async - run_tests_async is now marked as preferred for long-running suites - run_tests description notes it blocks and suggests async alternative * docs: update README screenshot to v8.6 UI * docs: add v8.6 UI screenshot * Update README for MCP version and instructions for v8.7 * fix: handle preflight busy signals and derive job status from test results - manage_asset, manage_gameobject, manage_scene now check preflight return value and propagate busy/retry signals to clients (fixes Sourcery #1) - TestJobManager.FinalizeCurrentJobFromRunFinished now sets job status to Failed when resultPayload.Failed > 0, not always Succeeded (fixes Sourcery #2) * fix: increase HTTP server startup timeout for dev mode When 'Force fresh server install' is enabled, uvx uses --no-cache --refresh which rebuilds the package and takes significantly longer to start. - Increase timeout from 10s to 45s when dev mode is enabled - Add informative log message explaining the longer startup time - Show actual timeout value in warning message * fix: derive job status from test results in FinalizeFromTask fallback Apply same logic as FinalizeCurrentJobFromRunFinished: check result.Failed > 0 to correctly mark jobs as Failed when tests fail, even in the fallback path when RunFinished callback is not delivered.
2026-01-04 04:42:32 +08:00
import pytest
from models import MCPResponse
from services.state.external_changes_scanner import external_changes_scanner
from services.state.external_changes_scanner import ExternalChangesState
from .test_helpers import DummyContext
@pytest.mark.asyncio
async def test_refresh_unity_recovers_from_retry_disconnect(monkeypatch):
"""
Option A: if Unity disconnects and the transport returns hint=retry, refresh_unity(wait_for_ready=true)
should poll readiness and then return success + clear external dirty.
"""
from services.tools.refresh_unity import refresh_unity
ctx = DummyContext()
ctx.set_state("unity_instance", "UnityMCPTests@cc8756d4cce0805a")
# Seed dirty state
inst = "UnityMCPTests@cc8756d4cce0805a"
external_changes_scanner._states[inst] = ExternalChangesState(dirty=True, dirty_since_unix_ms=1)
async def fake_send_with_unity_instance(send_fn, unity_instance, command_type, params, **kwargs):
assert command_type == "refresh_unity"
return {"success": False, "error": "disconnected", "hint": "retry"}
async def fake_get_editor_state_v2(_ctx):
return MCPResponse(success=True, data={"advice": {"ready_for_tools": True}})
import services.tools.refresh_unity as refresh_mod
monkeypatch.setattr(refresh_mod.unity_transport, "send_with_unity_instance", fake_send_with_unity_instance)
import services.resources.editor_state_v2 as esv2_mod
monkeypatch.setattr(esv2_mod, "get_editor_state_v2", fake_get_editor_state_v2)
resp = await refresh_unity(ctx, wait_for_ready=True)
payload = resp.model_dump() if hasattr(resp, "model_dump") else resp
assert payload["success"] is True
assert payload.get("data", {}).get("recovered_from_disconnect") is True
# Dirty should be cleared
assert external_changes_scanner._states[inst].dirty is False