chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using MCPForUnity.Editor.Services.Transport;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MCPForUnityTests.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Shared test utilities for EditMode tests across the MCP for Unity test suite.
|
||||
/// Consolidates common patterns to avoid duplication across test files.
|
||||
/// </summary>
|
||||
public static class TestUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Safely converts a command result to JObject, handling both JSON objects and other types.
|
||||
/// Returns an empty JObject if result is null.
|
||||
/// </summary>
|
||||
public static JObject ToJObject(object result)
|
||||
{
|
||||
if (result == null) return new JObject();
|
||||
return result as JObject ?? JObject.FromObject(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates all parent directories for the given asset path if they don't exist.
|
||||
/// Handles normalization and validates against dangerous patterns.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">An Assets-relative folder path (e.g., "Assets/Temp/MyFolder")</param>
|
||||
public static void EnsureFolder(string folderPath)
|
||||
{
|
||||
if (AssetDatabase.IsValidFolder(folderPath))
|
||||
return;
|
||||
|
||||
var sanitized = MCPForUnity.Editor.Helpers.AssetPathUtility.SanitizeAssetPath(folderPath);
|
||||
if (string.Equals(sanitized, "Assets", StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
var parts = sanitized.Split('/');
|
||||
string current = "Assets";
|
||||
for (int i = 1; i < parts.Length; i++)
|
||||
{
|
||||
var next = current + "/" + parts[i];
|
||||
if (!AssetDatabase.IsValidFolder(next))
|
||||
{
|
||||
AssetDatabase.CreateFolder(current, parts[i]);
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for Unity to finish compiling and updating, with a configurable timeout.
|
||||
/// Some EditMode tests trigger script compilation/domain reload.
|
||||
/// Tools intentionally return "compiling_or_reloading" during these windows.
|
||||
/// </summary>
|
||||
/// <param name="timeoutSeconds">Maximum time to wait before failing the test.</param>
|
||||
public static IEnumerator WaitForUnityReady(double timeoutSeconds = 30.0)
|
||||
{
|
||||
double start = EditorApplication.timeSinceStartup;
|
||||
while (EditorApplication.isCompiling || EditorApplication.isUpdating)
|
||||
{
|
||||
if (EditorApplication.timeSinceStartup - start > timeoutSeconds)
|
||||
{
|
||||
Assert.Fail($"Timed out waiting for Unity to finish compiling/updating (>{timeoutSeconds:0.0}s).");
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a fallback shader for creating materials in tests.
|
||||
/// Tries modern pipelines first, then falls back to Standard/Unlit.
|
||||
/// </summary>
|
||||
/// <returns>A shader suitable for test materials, or null if none found.</returns>
|
||||
public static Shader FindFallbackShader()
|
||||
{
|
||||
return Shader.Find("Universal Render Pipeline/Lit")
|
||||
?? Shader.Find("HDRP/Lit")
|
||||
?? Shader.Find("Standard")
|
||||
?? Shader.Find("Unlit/Color");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safely deletes an asset if it exists.
|
||||
/// </summary>
|
||||
/// <param name="path">The asset path to delete.</param>
|
||||
public static void SafeDeleteAsset(string path)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(path) && AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path) != null)
|
||||
{
|
||||
AssetDatabase.DeleteAsset(path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up empty parent folders recursively up to but not including "Assets".
|
||||
/// Useful in TearDown to avoid leaving folder debris.
|
||||
/// </summary>
|
||||
/// <param name="folderPath">The starting folder path to check.</param>
|
||||
public static void CleanupEmptyParentFolders(string folderPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(folderPath))
|
||||
return;
|
||||
|
||||
var parent = Path.GetDirectoryName(folderPath)?.Replace('\\', '/');
|
||||
while (!string.IsNullOrEmpty(parent) && parent != "Assets")
|
||||
{
|
||||
if (AssetDatabase.IsValidFolder(parent))
|
||||
{
|
||||
try
|
||||
{
|
||||
var dirs = Directory.GetDirectories(parent);
|
||||
var files = Directory.GetFiles(parent);
|
||||
if (dirs.Length == 0 && files.Length == 0)
|
||||
{
|
||||
AssetDatabase.DeleteAsset(parent);
|
||||
parent = Path.GetDirectoryName(parent)?.Replace('\\', '/');
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronous IMcpTransportClient fake for transport-lifecycle tests: StartAsync
|
||||
/// completes immediately with StartResult so retry loops run without awaits
|
||||
/// (the test framework floor cannot run async tests).
|
||||
/// </summary>
|
||||
public sealed class FakeTransportClient : IMcpTransportClient
|
||||
{
|
||||
public bool StartResult = true;
|
||||
public int StartCalls;
|
||||
public Action OnStart;
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
public string TransportName => "http";
|
||||
public TransportState State { get; private set; }
|
||||
= TransportState.Disconnected("http");
|
||||
|
||||
public Task<bool> StartAsync()
|
||||
{
|
||||
StartCalls++;
|
||||
OnStart?.Invoke();
|
||||
IsConnected = StartResult;
|
||||
State = StartResult
|
||||
? TransportState.Connected("http")
|
||||
: TransportState.Disconnected("http", "fake start failure");
|
||||
return Task.FromResult(StartResult);
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
IsConnected = false;
|
||||
State = TransportState.Disconnected("http");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<bool> VerifyAsync()
|
||||
=> Task.FromResult(IsConnected);
|
||||
|
||||
public Task ReregisterToolsAsync()
|
||||
=> Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user