Initial commit
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* Unity Test Framework Tests for GameObject Caching
|
||||
*
|
||||
* Performance optimization testing for BaseHandler's WeakReference caching.
|
||||
*/
|
||||
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditorToolkit.Handlers;
|
||||
|
||||
namespace UnityEditorToolkit.Tests
|
||||
{
|
||||
public class GameObjectCachingTests
|
||||
{
|
||||
private GameObject testGameObject;
|
||||
private GameObjectHandler handler;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
handler = new GameObjectHandler();
|
||||
testGameObject = new GameObject("TestCacheObject");
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
{
|
||||
// Clean up all test GameObjects
|
||||
var allTestObjects = Object.FindObjectsOfType<GameObject>()
|
||||
.Where(go => go.name.StartsWith("TestCache") || go.name.StartsWith("CacheTest"));
|
||||
|
||||
foreach (var obj in allTestObjects)
|
||||
{
|
||||
Object.DestroyImmediate(obj);
|
||||
}
|
||||
|
||||
testGameObject = null;
|
||||
handler = null;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FindGameObject_Should_FindExistingObject()
|
||||
{
|
||||
// Arrange
|
||||
var expectedName = "TestCacheObject";
|
||||
|
||||
// Act
|
||||
var result = handler.FindGameObject(expectedName);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result, "Should find existing GameObject");
|
||||
Assert.AreEqual(expectedName, result.name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FindGameObject_Should_ReturnNull_WhenNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var nonExistentName = "NonExistent_GameObject_12345";
|
||||
|
||||
// Act
|
||||
var result = handler.FindGameObject(nonExistentName);
|
||||
|
||||
// Assert
|
||||
Assert.IsNull(result, "Should return null for non-existent GameObject");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FindGameObject_Should_UseCaching_OnSecondCall()
|
||||
{
|
||||
// Arrange
|
||||
var objectName = "TestCacheObject";
|
||||
|
||||
// Act: First call (cache miss)
|
||||
var start1 = System.DateTime.Now;
|
||||
var result1 = handler.FindGameObject(objectName);
|
||||
var time1 = (System.DateTime.Now - start1).TotalMilliseconds;
|
||||
|
||||
// Second call (cache hit)
|
||||
var start2 = System.DateTime.Now;
|
||||
var result2 = handler.FindGameObject(objectName);
|
||||
var time2 = (System.DateTime.Now - start2).TotalMilliseconds;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(result1, result2, "Should return same GameObject instance");
|
||||
Assert.Less(time2, time1 * 0.5, "Second call should be significantly faster (cache hit)");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FindGameObject_Should_HandleMultipleObjects()
|
||||
{
|
||||
// Arrange
|
||||
var objects = new List<GameObject>();
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
objects.Add(new GameObject($"CacheTest_{i}"));
|
||||
}
|
||||
|
||||
// Act: Find all objects
|
||||
var results = new List<GameObject>();
|
||||
foreach (var obj in objects)
|
||||
{
|
||||
results.Add(handler.FindGameObject(obj.name));
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(5, results.Count, "Should find all 5 objects");
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
Assert.IsNotNull(results[i], $"Object {i} should be found");
|
||||
Assert.AreEqual(objects[i], results[i], $"Object {i} should match");
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
foreach (var obj in objects)
|
||||
{
|
||||
Object.DestroyImmediate(obj);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cache_Should_Invalidate_WhenGameObjectDestroyed()
|
||||
{
|
||||
// Arrange
|
||||
var objectName = "TestCacheObject";
|
||||
handler.FindGameObject(objectName); // Cache it
|
||||
|
||||
// Act: Destroy GameObject
|
||||
Object.DestroyImmediate(testGameObject);
|
||||
testGameObject = null;
|
||||
|
||||
// Create new GameObject with same name
|
||||
testGameObject = new GameObject(objectName);
|
||||
var result = handler.FindGameObject(objectName);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result, "Should find newly created GameObject");
|
||||
Assert.AreEqual(objectName, result.name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cache_Should_HandleInactiveGameObjects()
|
||||
{
|
||||
// Arrange
|
||||
testGameObject.SetActive(false);
|
||||
var objectName = testGameObject.name;
|
||||
|
||||
// Act
|
||||
var result = handler.FindGameObject(objectName);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result, "Should find inactive GameObject");
|
||||
Assert.AreEqual(objectName, result.name);
|
||||
Assert.IsFalse(result.activeSelf, "GameObject should be inactive");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cache_Should_HandleNestedGameObjects()
|
||||
{
|
||||
// Arrange
|
||||
var parent = new GameObject("CacheTest_Parent");
|
||||
var child = new GameObject("CacheTest_Child");
|
||||
child.transform.SetParent(parent.transform);
|
||||
|
||||
// Act
|
||||
var foundParent = handler.FindGameObject("CacheTest_Parent");
|
||||
var foundChild = handler.FindGameObject("CacheTest_Child");
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(foundParent, "Should find parent");
|
||||
Assert.IsNotNull(foundChild, "Should find child");
|
||||
Assert.AreEqual(parent, foundParent);
|
||||
Assert.AreEqual(child, foundChild);
|
||||
|
||||
// Cleanup
|
||||
Object.DestroyImmediate(parent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cache_Should_HandleDuplicateNames()
|
||||
{
|
||||
// Arrange
|
||||
var obj1 = new GameObject("CacheTest_Duplicate");
|
||||
var obj2 = new GameObject("CacheTest_Duplicate");
|
||||
|
||||
// Act
|
||||
var result = handler.FindGameObject("CacheTest_Duplicate");
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result, "Should find one of the duplicate objects");
|
||||
Assert.AreEqual("CacheTest_Duplicate", result.name);
|
||||
|
||||
// Cleanup
|
||||
Object.DestroyImmediate(obj1);
|
||||
Object.DestroyImmediate(obj2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FindGameObject_Should_HandleEmptyString()
|
||||
{
|
||||
// Arrange
|
||||
var emptyName = "";
|
||||
|
||||
// Act
|
||||
var result = handler.FindGameObject(emptyName);
|
||||
|
||||
// Assert
|
||||
Assert.IsNull(result, "Should return null for empty name");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FindGameObject_Should_HandleNullString()
|
||||
{
|
||||
// Arrange
|
||||
string nullName = null;
|
||||
|
||||
// Act
|
||||
var result = handler.FindGameObject(nullName);
|
||||
|
||||
// Assert
|
||||
Assert.IsNull(result, "Should return null for null name");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cache_Should_WorkAcrossMultipleCalls()
|
||||
{
|
||||
// Arrange
|
||||
var obj1 = new GameObject("CacheTest_Multi_1");
|
||||
var obj2 = new GameObject("CacheTest_Multi_2");
|
||||
var obj3 = new GameObject("CacheTest_Multi_3");
|
||||
|
||||
// Act: Interleave calls to different objects
|
||||
var result1a = handler.FindGameObject("CacheTest_Multi_1");
|
||||
var result2a = handler.FindGameObject("CacheTest_Multi_2");
|
||||
var result1b = handler.FindGameObject("CacheTest_Multi_1"); // From cache
|
||||
var result3a = handler.FindGameObject("CacheTest_Multi_3");
|
||||
var result2b = handler.FindGameObject("CacheTest_Multi_2"); // From cache
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(obj1, result1a);
|
||||
Assert.AreEqual(obj1, result1b);
|
||||
Assert.AreEqual(obj2, result2a);
|
||||
Assert.AreEqual(obj2, result2b);
|
||||
Assert.AreEqual(obj3, result3a);
|
||||
|
||||
// Cleanup
|
||||
Object.DestroyImmediate(obj1);
|
||||
Object.DestroyImmediate(obj2);
|
||||
Object.DestroyImmediate(obj3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cache_Should_HandleLargeNumberOfObjects()
|
||||
{
|
||||
// Arrange: Create 100 GameObjects
|
||||
var objects = new List<GameObject>();
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
objects.Add(new GameObject($"CacheTest_Large_{i}"));
|
||||
}
|
||||
|
||||
// Act: Find all objects (should populate cache)
|
||||
var results = new List<GameObject>();
|
||||
foreach (var obj in objects)
|
||||
{
|
||||
results.Add(handler.FindGameObject(obj.name));
|
||||
}
|
||||
|
||||
// Assert: All objects found
|
||||
Assert.AreEqual(100, results.Count);
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
Assert.IsNotNull(results[i], $"Object {i} should be found");
|
||||
}
|
||||
|
||||
// Act: Find all again (should use cache)
|
||||
var cachedResults = new List<GameObject>();
|
||||
var start = System.DateTime.Now;
|
||||
foreach (var obj in objects)
|
||||
{
|
||||
cachedResults.Add(handler.FindGameObject(obj.name));
|
||||
}
|
||||
var cacheTime = (System.DateTime.Now - start).TotalMilliseconds;
|
||||
|
||||
// Assert: Cached access should be fast
|
||||
Assert.Less(cacheTime, 50, "Cached access for 100 objects should be < 50ms");
|
||||
|
||||
// Cleanup
|
||||
foreach (var obj in objects)
|
||||
{
|
||||
Object.DestroyImmediate(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5412e9c9d2c86740a38bd913dd0efca
|
||||
363
skills/assets/unity-package/Tests/Editor/JsonRpcProtocolTests.cs
Normal file
363
skills/assets/unity-package/Tests/Editor/JsonRpcProtocolTests.cs
Normal file
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* Unity Test Framework Tests for JSON-RPC Protocol
|
||||
*
|
||||
* Protocol compliance and serialization testing.
|
||||
*/
|
||||
|
||||
using NUnit.Framework;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace UnityEditorToolkit.Tests
|
||||
{
|
||||
public class JsonRpcProtocolTests
|
||||
{
|
||||
[Test]
|
||||
public void JsonRpcRequest_Should_SerializeCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var request = new JsonRpcRequest
|
||||
{
|
||||
JsonRpc = "2.0",
|
||||
Id = "test_123",
|
||||
Method = "GameObject.Find",
|
||||
Params = JToken.FromObject(new { name = "Player" })
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = request.ToJson();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotEmpty(json);
|
||||
Assert.IsTrue(json.Contains("\"jsonrpc\":\"2.0\""));
|
||||
Assert.IsTrue(json.Contains("\"id\":\"test_123\""));
|
||||
Assert.IsTrue(json.Contains("\"method\":\"GameObject.Find\""));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void JsonRpcRequest_Should_DeserializeCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var json = @"{
|
||||
""jsonrpc"": ""2.0"",
|
||||
""id"": ""req_456"",
|
||||
""method"": ""Transform.SetPosition"",
|
||||
""params"": { ""name"": ""Cube"", ""position"": { ""x"": 1, ""y"": 2, ""z"": 3 } }
|
||||
}";
|
||||
|
||||
// Act
|
||||
var request = JsonRpcRequest.FromJson(json);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(request);
|
||||
Assert.AreEqual("2.0", request.JsonRpc);
|
||||
Assert.AreEqual("req_456", request.Id);
|
||||
Assert.AreEqual("Transform.SetPosition", request.Method);
|
||||
Assert.IsNotNull(request.Params);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void JsonRpcResponse_Should_SerializeSuccessResponse()
|
||||
{
|
||||
// Arrange
|
||||
var response = new JsonRpcResponse("req_789", new
|
||||
{
|
||||
success = true,
|
||||
name = "Player",
|
||||
active = true
|
||||
});
|
||||
|
||||
// Act
|
||||
var json = response.ToJson();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotEmpty(json);
|
||||
Assert.IsTrue(json.Contains("\"jsonrpc\":\"2.0\""));
|
||||
Assert.IsTrue(json.Contains("\"id\":\"req_789\""));
|
||||
Assert.IsTrue(json.Contains("\"result\""));
|
||||
Assert.IsFalse(json.Contains("\"error\""));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void JsonRpcErrorResponse_Should_SerializeErrorResponse()
|
||||
{
|
||||
// Arrange
|
||||
var error = JsonRpcError.InternalError("Test error message");
|
||||
var response = new JsonRpcErrorResponse("req_error", error);
|
||||
|
||||
// Act
|
||||
var json = response.ToJson();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotEmpty(json);
|
||||
Assert.IsTrue(json.Contains("\"jsonrpc\":\"2.0\""));
|
||||
Assert.IsTrue(json.Contains("\"id\":\"req_error\""));
|
||||
Assert.IsTrue(json.Contains("\"error\""));
|
||||
Assert.IsTrue(json.Contains("\"code\":-32603"));
|
||||
Assert.IsTrue(json.Contains("Test error message"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void JsonRpcErrorResponse_Should_PreserveRequestId()
|
||||
{
|
||||
// Arrange
|
||||
var requestId = "important_request_123";
|
||||
var error = JsonRpcError.MethodNotFound("GameObject.InvalidMethod");
|
||||
|
||||
// Act
|
||||
var response = new JsonRpcErrorResponse(requestId, error);
|
||||
var json = response.ToJson();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(json.Contains($"\"id\":\"{requestId}\""));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void JsonRpcErrorResponse_Should_HandleNullRequestId()
|
||||
{
|
||||
// Arrange
|
||||
var error = JsonRpcError.ParseError();
|
||||
|
||||
// Act
|
||||
var response = new JsonRpcErrorResponse(null, error);
|
||||
var json = response.ToJson();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(json.Contains("\"id\":null"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void JsonRpcError_InternalError_Should_HaveCorrectCode()
|
||||
{
|
||||
// Arrange & Act
|
||||
var error = JsonRpcError.InternalError("Test message");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(-32603, error.Code);
|
||||
Assert.AreEqual("Internal error", error.Message);
|
||||
Assert.IsNotNull(error.Data);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void JsonRpcError_MethodNotFound_Should_HaveCorrectCode()
|
||||
{
|
||||
// Arrange & Act
|
||||
var error = JsonRpcError.MethodNotFound("Unknown.Method");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(-32601, error.Code);
|
||||
Assert.AreEqual("Method not found", error.Message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void JsonRpcError_InvalidParams_Should_HaveCorrectCode()
|
||||
{
|
||||
// Arrange & Act
|
||||
var error = JsonRpcError.InvalidParams("Missing 'name' parameter");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(-32602, error.Code);
|
||||
Assert.AreEqual("Invalid params", error.Message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void JsonRpcError_ParseError_Should_HaveCorrectCode()
|
||||
{
|
||||
// Arrange & Act
|
||||
var error = JsonRpcError.ParseError();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(-32700, error.Code);
|
||||
Assert.AreEqual("Parse error", error.Message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void JsonRpcError_InvalidRequest_Should_HaveCorrectCode()
|
||||
{
|
||||
// Arrange & Act
|
||||
var error = JsonRpcError.InvalidRequest();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(-32600, error.Code);
|
||||
Assert.AreEqual("Invalid Request", error.Message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetParams_Should_DeserializeCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var json = @"{
|
||||
""jsonrpc"": ""2.0"",
|
||||
""id"": 1,
|
||||
""method"": ""GameObject.Find"",
|
||||
""params"": { ""name"": ""TestObject"" }
|
||||
}";
|
||||
var request = JsonRpcRequest.FromJson(json);
|
||||
|
||||
// Act
|
||||
var param = request.GetParams<FindParams>();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(param);
|
||||
Assert.AreEqual("TestObject", param.name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetParams_Should_HandleUnknownFields()
|
||||
{
|
||||
// Arrange
|
||||
var json = @"{
|
||||
""jsonrpc"": ""2.0"",
|
||||
""id"": 1,
|
||||
""method"": ""GameObject.Find"",
|
||||
""params"": { ""wrongField"": ""value"" }
|
||||
}";
|
||||
var request = JsonRpcRequest.FromJson(json);
|
||||
|
||||
// Act
|
||||
var param = request.GetParams<FindParams>();
|
||||
|
||||
// Assert: JsonConvert ignores unknown fields, so param is not null but name is null
|
||||
Assert.IsNotNull(param);
|
||||
Assert.IsNull(param.name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetParams_Should_ReturnNullForNullParams()
|
||||
{
|
||||
// Arrange
|
||||
var request = new JsonRpcRequest
|
||||
{
|
||||
JsonRpc = "2.0",
|
||||
Id = 1,
|
||||
Method = "ping",
|
||||
Params = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var param = request.GetParams<FindParams>();
|
||||
|
||||
// Assert
|
||||
Assert.IsNull(param);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Request_Should_HandleComplexParameters()
|
||||
{
|
||||
// Arrange
|
||||
var json = @"{
|
||||
""jsonrpc"": ""2.0"",
|
||||
""id"": ""complex_1"",
|
||||
""method"": ""Transform.SetPosition"",
|
||||
""params"": {
|
||||
""name"": ""Player"",
|
||||
""position"": { ""x"": 1.5, ""y"": 2.7, ""z"": -3.9 }
|
||||
}
|
||||
}";
|
||||
|
||||
// Act
|
||||
var request = JsonRpcRequest.FromJson(json);
|
||||
var param = request.GetParams<SetPositionParams>();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(param);
|
||||
Assert.AreEqual("Player", param.name);
|
||||
Assert.IsNotNull(param.position);
|
||||
Assert.AreEqual(1.5f, param.position.x, 0.0001f);
|
||||
Assert.AreEqual(2.7f, param.position.y, 0.0001f);
|
||||
Assert.AreEqual(-3.9f, param.position.z, 0.0001f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Request_Should_HandleNumericId()
|
||||
{
|
||||
// Arrange
|
||||
var request = new JsonRpcRequest
|
||||
{
|
||||
JsonRpc = "2.0",
|
||||
Id = 42,
|
||||
Method = "GameObject.Find"
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = request.ToJson();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(json.Contains("\"id\":42"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Request_Should_HandleStringId()
|
||||
{
|
||||
// Arrange
|
||||
var request = new JsonRpcRequest
|
||||
{
|
||||
JsonRpc = "2.0",
|
||||
Id = "string_id_123",
|
||||
Method = "GameObject.Find"
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = request.ToJson();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(json.Contains("\"id\":\"string_id_123\""));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Response_Should_MatchRequestId()
|
||||
{
|
||||
// Arrange
|
||||
var requestId = "match_test_789";
|
||||
var result = new { success = true };
|
||||
|
||||
// Act
|
||||
var response = new JsonRpcResponse(requestId, result);
|
||||
var json = response.ToJson();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(json.Contains($"\"id\":\"{requestId}\""));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Protocol_Should_Comply_WithJsonRpc20Spec()
|
||||
{
|
||||
// Arrange
|
||||
var request = new JsonRpcRequest
|
||||
{
|
||||
JsonRpc = "2.0",
|
||||
Id = "spec_test",
|
||||
Method = "GameObject.Find",
|
||||
Params = JToken.FromObject(new { name = "Test" })
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = request.ToJson();
|
||||
var parsed = JObject.Parse(json);
|
||||
|
||||
// Assert: JSON-RPC 2.0 required fields
|
||||
Assert.IsTrue(parsed.ContainsKey("jsonrpc"));
|
||||
Assert.IsTrue(parsed.ContainsKey("method"));
|
||||
Assert.IsTrue(parsed.ContainsKey("id"));
|
||||
Assert.AreEqual("2.0", parsed["jsonrpc"].ToString());
|
||||
}
|
||||
|
||||
// Helper classes for testing
|
||||
private class FindParams
|
||||
{
|
||||
public string name { get; set; }
|
||||
}
|
||||
|
||||
private class SetPositionParams
|
||||
{
|
||||
public string name { get; set; }
|
||||
public Vector3Param position { get; set; }
|
||||
}
|
||||
|
||||
private class Vector3Param
|
||||
{
|
||||
public float x { get; set; }
|
||||
public float y { get; set; }
|
||||
public float z { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7d883732abd0f24c86ce9c7e3a7480d
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "UnityEditorToolkit.Editor.Tests",
|
||||
"rootNamespace": "UnityEditorToolkit.Tests",
|
||||
"references": [
|
||||
"UnityEditorToolkit",
|
||||
"UnityEditorToolkit.Editor",
|
||||
"Unity.Nuget.Newtonsoft-Json",
|
||||
"UnityEngine.TestRunner",
|
||||
"UnityEditor.TestRunner"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"nunit.framework.dll"
|
||||
],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 085f6286143df3442b7938a709f17559
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* Unity Test Framework Tests for UnityMainThreadDispatcher
|
||||
*
|
||||
* Critical thread safety component testing.
|
||||
*/
|
||||
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using System.Collections;
|
||||
using System.Threading;
|
||||
using UnityEditorToolkit.Utils;
|
||||
|
||||
namespace UnityEditorToolkit.Tests
|
||||
{
|
||||
public class UnityMainThreadDispatcherTests
|
||||
{
|
||||
private UnityMainThreadDispatcher dispatcher;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
// Ensure fresh dispatcher instance for each test
|
||||
var existingDispatcher = Object.FindObjectOfType<UnityMainThreadDispatcher>();
|
||||
if (existingDispatcher != null)
|
||||
{
|
||||
Object.DestroyImmediate(existingDispatcher.gameObject);
|
||||
}
|
||||
|
||||
dispatcher = UnityMainThreadDispatcher.Instance();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
{
|
||||
if (dispatcher != null && dispatcher.gameObject != null)
|
||||
{
|
||||
Object.DestroyImmediate(dispatcher.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Instance_Should_CreateSingleton()
|
||||
{
|
||||
// Arrange & Act
|
||||
var instance1 = UnityMainThreadDispatcher.Instance();
|
||||
var instance2 = UnityMainThreadDispatcher.Instance();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(instance1);
|
||||
Assert.IsNotNull(instance2);
|
||||
Assert.AreEqual(instance1, instance2, "Instance should be singleton");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Instance_Should_CreateGameObjectWithCorrectName()
|
||||
{
|
||||
// Arrange & Act
|
||||
var instance = UnityMainThreadDispatcher.Instance();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("UnityMainThreadDispatcher", instance.gameObject.name);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator Enqueue_Should_ExecuteAction_OnMainThread()
|
||||
{
|
||||
// Arrange
|
||||
bool actionExecuted = false;
|
||||
int executionThreadId = 0;
|
||||
int mainThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
|
||||
// Act: Enqueue from background thread
|
||||
var backgroundThread = new Thread(() =>
|
||||
{
|
||||
dispatcher.Enqueue(() =>
|
||||
{
|
||||
executionThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
actionExecuted = true;
|
||||
});
|
||||
});
|
||||
|
||||
backgroundThread.Start();
|
||||
backgroundThread.Join(); // Wait for thread to complete
|
||||
|
||||
// Wait one frame for Update to process queue
|
||||
yield return null;
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(actionExecuted, "Action should have been executed");
|
||||
Assert.AreEqual(mainThreadId, executionThreadId, "Action should execute on main thread");
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator Enqueue_Should_ExecuteMultipleActions_InOrder()
|
||||
{
|
||||
// Arrange
|
||||
var executionOrder = new System.Collections.Generic.List<int>();
|
||||
|
||||
// Act
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
int index = i; // Capture loop variable
|
||||
dispatcher.Enqueue(() => executionOrder.Add(index));
|
||||
}
|
||||
|
||||
// Wait one frame for Update to process queue
|
||||
yield return null;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(10, executionOrder.Count, "All actions should be executed");
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
Assert.AreEqual(i, executionOrder[i], $"Action {i} should execute in order");
|
||||
}
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator Enqueue_Should_HandleExceptions_Gracefully()
|
||||
{
|
||||
// Arrange
|
||||
bool firstActionExecuted = false;
|
||||
bool secondActionExecuted = false;
|
||||
bool thirdActionExecuted = false;
|
||||
|
||||
// Act: First action executes, second throws exception, third should still execute
|
||||
dispatcher.Enqueue(() => firstActionExecuted = true);
|
||||
dispatcher.Enqueue(() => throw new System.Exception("Test exception"));
|
||||
dispatcher.Enqueue(() => thirdActionExecuted = true);
|
||||
|
||||
// Expect error log from exception
|
||||
LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex(".*Test exception.*"));
|
||||
|
||||
// Wait one frame for Update to process queue
|
||||
yield return null;
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(firstActionExecuted, "First action should execute");
|
||||
Assert.IsTrue(thirdActionExecuted, "Third action should execute despite exception in second");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Enqueue_Should_ThrowException_WhenActionIsNull()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
Assert.Throws<System.ArgumentNullException>(() =>
|
||||
{
|
||||
dispatcher.Enqueue(null);
|
||||
});
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator Enqueue_Should_HandleConcurrentAccess()
|
||||
{
|
||||
// Arrange
|
||||
int executionCount = 0;
|
||||
var threads = new Thread[5];
|
||||
|
||||
// Act: Multiple threads enqueueing simultaneously
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
threads[i] = new Thread(() =>
|
||||
{
|
||||
for (int j = 0; j < 10; j++)
|
||||
{
|
||||
dispatcher.Enqueue(() => Interlocked.Increment(ref executionCount));
|
||||
}
|
||||
});
|
||||
threads[i].Start();
|
||||
}
|
||||
|
||||
// Wait for all threads to complete
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
threads[i].Join();
|
||||
}
|
||||
|
||||
// Wait one frame for Update to process queue
|
||||
yield return null;
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(50, executionCount, "All 50 actions should execute (5 threads × 10 actions)");
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator Update_Should_ClearQueue_AfterExecution()
|
||||
{
|
||||
// Arrange
|
||||
int executionCount = 0;
|
||||
dispatcher.Enqueue(() => executionCount++);
|
||||
|
||||
// Act: Wait for first Update
|
||||
yield return null;
|
||||
Assert.AreEqual(1, executionCount);
|
||||
|
||||
// Wait for second Update (queue should be empty)
|
||||
yield return null;
|
||||
|
||||
// Assert: No additional executions
|
||||
Assert.AreEqual(1, executionCount, "Queue should be cleared after execution");
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator Dispatcher_Should_SurviveSceneLoad()
|
||||
{
|
||||
// Arrange
|
||||
var instance = UnityMainThreadDispatcher.Instance();
|
||||
bool actionExecuted = false;
|
||||
|
||||
// Act: Enqueue action
|
||||
dispatcher.Enqueue(() => actionExecuted = true);
|
||||
|
||||
// Wait one frame
|
||||
yield return null;
|
||||
|
||||
// Assert: Dispatcher still exists and works
|
||||
Assert.IsNotNull(Object.FindObjectOfType<UnityMainThreadDispatcher>());
|
||||
Assert.IsTrue(actionExecuted);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator Enqueue_Should_WorkWith_UnityAPIcalls()
|
||||
{
|
||||
// Arrange
|
||||
GameObject testObject = null;
|
||||
string objectName = "TestObject_FromBackgroundThread";
|
||||
|
||||
// Act: Create GameObject from background thread via dispatcher
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
dispatcher.Enqueue(() =>
|
||||
{
|
||||
testObject = new GameObject(objectName);
|
||||
});
|
||||
});
|
||||
|
||||
thread.Start();
|
||||
thread.Join();
|
||||
|
||||
// Wait for Update to process
|
||||
yield return null;
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(testObject);
|
||||
Assert.AreEqual(objectName, testObject.name);
|
||||
|
||||
// Cleanup
|
||||
Object.DestroyImmediate(testObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2404c3c8ae4d1804b9344e65740fa83f
|
||||
@@ -0,0 +1,336 @@
|
||||
/**
|
||||
* Unity Test Framework Tests for Vector3 Validation
|
||||
*
|
||||
* Security testing for Vector3Data's NaN/Infinity validation.
|
||||
*/
|
||||
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditorToolkit.Tests
|
||||
{
|
||||
public class Vector3ValidationTests
|
||||
{
|
||||
[Test]
|
||||
public void ToVector3_Should_Throw_On_NaN_X()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = float.NaN,
|
||||
y = 0f,
|
||||
z = 0f
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<System.ArgumentException>(() => data.ToVector3());
|
||||
Assert.IsTrue(exception.Message.Contains("x"), "Error message should mention x coordinate");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Throw_On_NaN_Y()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = 0f,
|
||||
y = float.NaN,
|
||||
z = 0f
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<System.ArgumentException>(() => data.ToVector3());
|
||||
Assert.IsTrue(exception.Message.Contains("y"), "Error message should mention y coordinate");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Throw_On_NaN_Z()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = 0f,
|
||||
y = 0f,
|
||||
z = float.NaN
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<System.ArgumentException>(() => data.ToVector3());
|
||||
Assert.IsTrue(exception.Message.Contains("z"), "Error message should mention z coordinate");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Throw_On_PositiveInfinity_X()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = float.PositiveInfinity,
|
||||
y = 0f,
|
||||
z = 0f
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<System.ArgumentException>(() => data.ToVector3());
|
||||
Assert.IsTrue(exception.Message.Contains("x"), "Error message should mention x coordinate");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Throw_On_PositiveInfinity_Y()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = 0f,
|
||||
y = float.PositiveInfinity,
|
||||
z = 0f
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<System.ArgumentException>(() => data.ToVector3());
|
||||
Assert.IsTrue(exception.Message.Contains("y"), "Error message should mention y coordinate");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Throw_On_PositiveInfinity_Z()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = 0f,
|
||||
y = 0f,
|
||||
z = float.PositiveInfinity
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<System.ArgumentException>(() => data.ToVector3());
|
||||
Assert.IsTrue(exception.Message.Contains("z"), "Error message should mention z coordinate");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Throw_On_NegativeInfinity_X()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = float.NegativeInfinity,
|
||||
y = 0f,
|
||||
z = 0f
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<System.ArgumentException>(() => data.ToVector3());
|
||||
Assert.IsTrue(exception.Message.Contains("x"), "Error message should mention x coordinate");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Throw_On_NegativeInfinity_Y()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = 0f,
|
||||
y = float.NegativeInfinity,
|
||||
z = 0f
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<System.ArgumentException>(() => data.ToVector3());
|
||||
Assert.IsTrue(exception.Message.Contains("y"), "Error message should mention y coordinate");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Throw_On_NegativeInfinity_Z()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = 0f,
|
||||
y = 0f,
|
||||
z = float.NegativeInfinity
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<System.ArgumentException>(() => data.ToVector3());
|
||||
Assert.IsTrue(exception.Message.Contains("z"), "Error message should mention z coordinate");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Accept_Zero()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = 0f,
|
||||
y = 0f,
|
||||
z = 0f
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = data.ToVector3();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(Vector3.zero, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Accept_PositiveValues()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = 1.5f,
|
||||
y = 2.7f,
|
||||
z = 3.9f
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = data.ToVector3();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1.5f, result.x, 0.0001f);
|
||||
Assert.AreEqual(2.7f, result.y, 0.0001f);
|
||||
Assert.AreEqual(3.9f, result.z, 0.0001f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Accept_NegativeValues()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = -1.5f,
|
||||
y = -2.7f,
|
||||
z = -3.9f
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = data.ToVector3();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(-1.5f, result.x, 0.0001f);
|
||||
Assert.AreEqual(-2.7f, result.y, 0.0001f);
|
||||
Assert.AreEqual(-3.9f, result.z, 0.0001f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Accept_MixedValues()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = -5.5f,
|
||||
y = 0f,
|
||||
z = 10.25f
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = data.ToVector3();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(-5.5f, result.x, 0.0001f);
|
||||
Assert.AreEqual(0f, result.y, 0.0001f);
|
||||
Assert.AreEqual(10.25f, result.z, 0.0001f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Accept_VerySmallValues()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = 0.0001f,
|
||||
y = -0.0001f,
|
||||
z = 0.00001f
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = data.ToVector3();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0.0001f, result.x, 0.000001f);
|
||||
Assert.AreEqual(-0.0001f, result.y, 0.000001f);
|
||||
Assert.AreEqual(0.00001f, result.z, 0.000001f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Accept_VeryLargeValues()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = 1000000f,
|
||||
y = -1000000f,
|
||||
z = 999999.99f
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = data.ToVector3();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1000000f, result.x, 0.01f);
|
||||
Assert.AreEqual(-1000000f, result.y, 0.01f);
|
||||
Assert.AreEqual(999999.99f, result.z, 0.01f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Accept_MaxValue()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = float.MaxValue,
|
||||
y = float.MaxValue,
|
||||
z = float.MaxValue
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = data.ToVector3();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(float.MaxValue, result.x);
|
||||
Assert.AreEqual(float.MaxValue, result.y);
|
||||
Assert.AreEqual(float.MaxValue, result.z);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_Accept_MinValue()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = float.MinValue,
|
||||
y = float.MinValue,
|
||||
z = float.MinValue
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = data.ToVector3();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(float.MinValue, result.x);
|
||||
Assert.AreEqual(float.MinValue, result.y);
|
||||
Assert.AreEqual(float.MinValue, result.z);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ToVector3_Should_PreserveFloatPrecision()
|
||||
{
|
||||
// Arrange
|
||||
var data = new TransformHandler.Vector3Data
|
||||
{
|
||||
x = 1.23456789f,
|
||||
y = -9.87654321f,
|
||||
z = 0.11111111f
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = data.ToVector3();
|
||||
|
||||
// Assert: Float precision is ~7 significant digits
|
||||
Assert.AreEqual(1.23456789f, result.x, 0.0000001f);
|
||||
Assert.AreEqual(-9.87654321f, result.y, 0.0000001f);
|
||||
Assert.AreEqual(0.11111111f, result.z, 0.0000001f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c991ed74ecfc7749872d96a419169bb
|
||||
Reference in New Issue
Block a user