chore: import upstream snapshot with attribution
This commit is contained in:
+289
@@ -0,0 +1,289 @@
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using MCPForUnity.Editor.Tools;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace MCPForUnityTests.Editor.Tools
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for domain reload resilience - ensuring MCP requests succeed even during Unity domain reloads.
|
||||
///
|
||||
/// These tests trigger script compilation which can cause test timing issues when Unity is
|
||||
/// backgrounded, but the MCP workflow itself is unaffected - socket messages provide external
|
||||
/// stimulus that keeps Unity responsive.
|
||||
///
|
||||
/// Note: Focus nudge improvements (P2-9) should help with background test reliability.
|
||||
/// </summary>
|
||||
[Category("domain_reload")]
|
||||
[Explicit("Domain reload stress tests; run manually when needed.")]
|
||||
public class DomainReloadResilienceTests
|
||||
{
|
||||
private const string TempDir = "Assets/Temp/DomainReloadTests";
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
// Ensure temp directory exists
|
||||
if (!AssetDatabase.IsValidFolder(TempDir))
|
||||
{
|
||||
Directory.CreateDirectory(TempDir);
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
|
||||
}
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
// Clean up temp directory - this deletes any scripts we created
|
||||
if (AssetDatabase.IsValidFolder(TempDir))
|
||||
{
|
||||
AssetDatabase.DeleteAsset(TempDir);
|
||||
}
|
||||
|
||||
// Remove parent temp folder if nothing else is inside
|
||||
if (AssetDatabase.IsValidFolder("Assets/Temp"))
|
||||
{
|
||||
var remainingDirs = Directory.GetDirectories("Assets/Temp");
|
||||
var remainingFiles = Directory.GetFiles("Assets/Temp");
|
||||
if (remainingDirs.Length == 0 && remainingFiles.Length == 0)
|
||||
{
|
||||
AssetDatabase.DeleteAsset("Assets/Temp");
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL: Force a synchronous refresh and wait for any pending compilation to finish.
|
||||
// This prevents leaving compilation running that could stall subsequent tests.
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This test simulates the stress test scenario:
|
||||
/// 1. Create a script (triggers domain reload)
|
||||
/// 2. Make multiple rapid read_console calls
|
||||
/// 3. Verify all calls succeed (no "No Unity plugins are currently connected" errors)
|
||||
///
|
||||
/// Note: This test uses UnityTest coroutine to handle the async nature of domain reloads.
|
||||
/// </summary>
|
||||
[UnityTest]
|
||||
public IEnumerator StressTest_CreateScriptAndReadConsoleMultipleTimes()
|
||||
{
|
||||
// Step 1: Create a script to trigger domain reload
|
||||
var scriptPath = Path.Combine(TempDir, "StressTestScript.cs").Replace("\\", "/");
|
||||
var scriptContent = @"using UnityEngine;
|
||||
|
||||
public class StressTestScript : MonoBehaviour
|
||||
{
|
||||
void Start() { }
|
||||
}";
|
||||
|
||||
// Write script file
|
||||
File.WriteAllText(scriptPath, scriptContent);
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
|
||||
|
||||
Debug.Log("[DomainReloadTest] Script created, domain reload triggered");
|
||||
|
||||
// Wait a frame for the domain reload to start
|
||||
yield return null;
|
||||
|
||||
// Step 2: Make multiple rapid read_console calls
|
||||
// These should succeed even during the reload window
|
||||
int successCount = 0;
|
||||
int totalCalls = 5;
|
||||
|
||||
for (int i = 0; i < totalCalls; i++)
|
||||
{
|
||||
var request = new JObject
|
||||
{
|
||||
["action"] = "get",
|
||||
["types"] = new JArray { "all" },
|
||||
["count"] = 50,
|
||||
["format"] = "plain",
|
||||
["includeStacktrace"] = false
|
||||
};
|
||||
|
||||
var result = ExecuteReadConsole(request);
|
||||
|
||||
// Check if the call succeeded
|
||||
if (result != null && result["success"]?.Value<bool>() == true)
|
||||
{
|
||||
successCount++;
|
||||
Debug.Log($"[DomainReloadTest] read_console call {i+1}/{totalCalls} succeeded");
|
||||
}
|
||||
else
|
||||
{
|
||||
var error = result?["error"]?.ToString() ?? "Unknown error";
|
||||
Debug.LogError($"[DomainReloadTest] read_console call {i+1}/{totalCalls} failed: {error}");
|
||||
}
|
||||
|
||||
// Small delay between calls to simulate rapid-fire scenario
|
||||
yield return WaitFrames(6);
|
||||
}
|
||||
|
||||
// Step 3: Verify all calls succeeded
|
||||
Debug.Log($"[DomainReloadTest] {successCount}/{totalCalls} read_console calls succeeded");
|
||||
Assert.AreEqual(totalCalls, successCount,
|
||||
$"Expected all {totalCalls} read_console calls to succeed during domain reload, but only {successCount} succeeded");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test that read_console works reliably after a domain reload completes.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ReadConsole_AfterDomainReload_Succeeds()
|
||||
{
|
||||
// This test assumes domain reload has already completed
|
||||
// (Unity tests run after domain reload)
|
||||
|
||||
var request = new JObject
|
||||
{
|
||||
["action"] = "get",
|
||||
["types"] = new JArray { "error", "warning", "log" },
|
||||
["count"] = 10,
|
||||
["format"] = "plain"
|
||||
};
|
||||
|
||||
var result = ExecuteReadConsole(request);
|
||||
|
||||
Assert.IsNotNull(result, "read_console should return a result");
|
||||
Assert.IsTrue(result["success"]?.Value<bool>() ?? false,
|
||||
$"read_console should succeed: {result["error"]}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test creating a script and immediately querying console logs.
|
||||
/// This simulates a common AI workflow pattern.
|
||||
/// </summary>
|
||||
[UnityTest]
|
||||
public IEnumerator CreateScript_ThenQueryConsole_Succeeds()
|
||||
{
|
||||
// Create a simple script
|
||||
var scriptPath = Path.Combine(TempDir, "TestScript1.cs").Replace("\\", "/");
|
||||
var scriptContent = @"using UnityEngine;
|
||||
|
||||
public class TestScript1 : MonoBehaviour
|
||||
{
|
||||
void Start()
|
||||
{
|
||||
Debug.Log(""TestScript1 initialized"");
|
||||
}
|
||||
}";
|
||||
|
||||
File.WriteAllText(scriptPath, scriptContent);
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
|
||||
|
||||
Debug.Log("[DomainReloadTest] Script created");
|
||||
|
||||
// Wait a frame
|
||||
yield return null;
|
||||
|
||||
// Immediately try to read console
|
||||
var request = new JObject
|
||||
{
|
||||
["action"] = "get",
|
||||
["types"] = new JArray { "all" },
|
||||
["count"] = 50,
|
||||
["format"] = "plain"
|
||||
};
|
||||
|
||||
var result = ExecuteReadConsole(request);
|
||||
|
||||
// Should succeed even if domain reload is happening
|
||||
Assert.IsNotNull(result, "read_console should return a result");
|
||||
Assert.IsTrue(result["success"]?.Value<bool>() ?? false,
|
||||
$"read_console should succeed even during/after script creation: {result["error"]}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test rapid script creation followed by console reads.
|
||||
/// This is an even more aggressive stress test.
|
||||
/// </summary>
|
||||
[UnityTest]
|
||||
public IEnumerator RapidScriptCreation_WithConsoleReads_AllSucceed()
|
||||
{
|
||||
int scriptCount = 3;
|
||||
int consoleReadsPerScript = 2;
|
||||
int successCount = 0;
|
||||
int totalExpectedReads = scriptCount * consoleReadsPerScript;
|
||||
|
||||
for (int i = 0; i < scriptCount; i++)
|
||||
{
|
||||
// Create script
|
||||
var scriptPath = Path.Combine(TempDir, $"RapidScript{i}.cs").Replace("\\", "/");
|
||||
var scriptContent = $@"using UnityEngine;
|
||||
|
||||
public class RapidScript{i} : MonoBehaviour
|
||||
{{
|
||||
void Start() {{ Debug.Log(""RapidScript{i}""); }}
|
||||
}}";
|
||||
|
||||
File.WriteAllText(scriptPath, scriptContent);
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
|
||||
|
||||
Debug.Log($"[DomainReloadTest] Created script {i+1}/{scriptCount}");
|
||||
|
||||
// Immediately try console reads
|
||||
for (int j = 0; j < consoleReadsPerScript; j++)
|
||||
{
|
||||
var request = new JObject
|
||||
{
|
||||
["action"] = "get",
|
||||
["types"] = new JArray { "all" },
|
||||
["count"] = 20,
|
||||
["format"] = "plain"
|
||||
};
|
||||
|
||||
var result = ExecuteReadConsole(request);
|
||||
|
||||
if (result != null && result["success"]?.Value<bool>() == true)
|
||||
{
|
||||
successCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
var error = result?["error"]?.ToString() ?? "Unknown error";
|
||||
Debug.LogError($"[DomainReloadTest] Console read failed: {error}");
|
||||
}
|
||||
|
||||
yield return WaitFrames(3);
|
||||
}
|
||||
|
||||
// Brief wait between script creations
|
||||
yield return WaitFrames(12);
|
||||
}
|
||||
|
||||
Debug.Log($"[DomainReloadTest] {successCount}/{totalExpectedReads} console reads succeeded");
|
||||
|
||||
// We expect at least 80% success rate (some may fail due to timing, but resilience should help most)
|
||||
int minExpectedSuccess = (int)(totalExpectedReads * 0.8f);
|
||||
Assert.GreaterOrEqual(successCount, minExpectedSuccess,
|
||||
$"Expected at least {minExpectedSuccess} console reads to succeed, but only {successCount} succeeded");
|
||||
}
|
||||
|
||||
private static JObject ExecuteReadConsole(JObject request)
|
||||
{
|
||||
var raw = ReadConsole.HandleCommand(request);
|
||||
if (raw == null)
|
||||
{
|
||||
return new JObject
|
||||
{
|
||||
["success"] = false,
|
||||
["error"] = "ReadConsole returned null"
|
||||
};
|
||||
}
|
||||
|
||||
return JObject.FromObject(raw);
|
||||
}
|
||||
|
||||
private static IEnumerator WaitFrames(int frameCount)
|
||||
{
|
||||
for (int i = 0; i < frameCount; i++)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user