-
Notifications
You must be signed in to change notification settings - Fork 693
Harden manage_scriptable_object Tool
#522
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0c3b79a
82c28e7
f480199
25781d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,140 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Collections; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.IO; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+113
to
+130
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Empty catch block silently swallows exceptions. Consider logging or at minimum breaking out with a comment explaining why the exception is intentionally ignored. Silent exception swallowing can mask real issues during test cleanup. 🔎 Proposed fix catch
{
+ // Cleanup is best-effort; ignore errors (e.g., file locks, permissions)
break;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| using UnityEngine; | ||
|
|
||
| [CreateAssetMenu(fileName = "ArrayStressSO", menuName = "StressTests/ArrayStressSO")] | ||
| public class ArrayStressSO : ScriptableObject | ||
| { | ||
| public float[] floatArray = new float[3]; | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| using UnityEngine; | ||
| using System.Collections.Generic; | ||
|
|
||
| [System.Serializable] | ||
| public struct NestedData | ||
| { | ||
| public string id; | ||
| public float value; | ||
| public Vector3 position; | ||
| } | ||
|
|
||
| [System.Serializable] | ||
| public class ComplexSubClass | ||
| { | ||
| public string name; | ||
| public int level; | ||
| public List<float> scores; | ||
| } | ||
|
|
||
| public enum TestEnum | ||
| { | ||
| Alpha, | ||
| Beta, | ||
| Gamma | ||
| } | ||
|
|
||
| [CreateAssetMenu(fileName = "ComplexStressSO", menuName = "StressTests/ComplexStressSO")] | ||
| public class ComplexStressSO : ScriptableObject | ||
| { | ||
| [Header("Basic Types")] | ||
| public int intValue; | ||
| public float floatValue; | ||
| public string stringValue; | ||
| public bool boolValue; | ||
| public Vector3 vectorValue; | ||
| public Color colorValue; | ||
| public TestEnum enumValue; | ||
|
|
||
| [Header("Arrays & Lists")] | ||
| public int[] intArray; | ||
| public List<string> stringList; | ||
| public Vector3[] vectorArray; | ||
|
|
||
| [Header("Complex Types")] | ||
| public NestedData nestedStruct; | ||
| public ComplexSubClass nestedClass; | ||
| public List<NestedData> nestedDataList; | ||
|
|
||
| [Header("Extended Types (Phase 6)")] | ||
| public AnimationCurve animCurve; | ||
| public Quaternion rotation; | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (testing): Returning an empty JObject on null results may hide bugs that earlier per-test helpers caught; consider providing an assertion-based variant or using this helper carefully in tests that expect non-null results.
Previously, local
ToJObjecthelpers in tests (e.g.,ReadConsoleTests) wouldAssert.Failon null results. The sharedTestUtilities.ToJObjectnow returns an emptyJObjectinstead, which can mask cases where a non-null response is required.For tests where a null result should fail, consider either:
ToNonNullJObjecthelper that asserts on null and using it there, orresult is not nullbefore callingToJObject.This preserves the convenience of the shared helper while keeping tests from treating null results as valid empty payloads.
Suggested implementation:
TestUtilities.cshasusing NUnit.Framework;(or the appropriate assertion library) at the top of the file so thatAssert.IsNotNullis available.ToJObjecthelpers withAssert.Fail) to callTestUtilities.ToNonNullJObject(...)instead ofToJObject(...)where a non-null result is required.