@mflrevan/ucp 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +36 -5
- package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs +137 -1
- package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs +80 -5
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +24 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs +82 -23
- package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +409 -63
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +68 -6
- package/bridge/com.ucp.bridge/Tests/Editor/MiniJsonSerializerTests.cs +221 -0
- package/bridge/com.ucp.bridge/Tests/Editor/MiniJsonSerializerTests.cs.meta +2 -0
- package/bridge/com.ucp.bridge/package.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using NUnit.Framework;
|
|
4
|
+
using UnityEngine;
|
|
5
|
+
|
|
6
|
+
namespace UCP.Bridge.Tests
|
|
7
|
+
{
|
|
8
|
+
/// <summary>
|
|
9
|
+
/// Regression coverage for the serializer that turns arbitrary <see cref="IUCPScript"/> return
|
|
10
|
+
/// values into JSON. The reflection walk used to be unbounded, so returning anything holding a
|
|
11
|
+
/// UnityEngine math struct (Vector3.normalized returns another Vector3, forever) overflowed the
|
|
12
|
+
/// stack -- which .NET cannot catch and which killed the editor process outright.
|
|
13
|
+
/// </summary>
|
|
14
|
+
public class MiniJsonSerializerTests
|
|
15
|
+
{
|
|
16
|
+
private static Dictionary<string, object> Roundtrip(object value)
|
|
17
|
+
{
|
|
18
|
+
var json = MiniJson.Serialize(new { value });
|
|
19
|
+
var parsed = MiniJson.Deserialize(json) as Dictionary<string, object>;
|
|
20
|
+
Assert.IsNotNull(parsed, $"Serializer produced unparseable JSON: {json}");
|
|
21
|
+
return parsed;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
[Test]
|
|
25
|
+
public void SerializesVector3WithoutStackOverflow()
|
|
26
|
+
{
|
|
27
|
+
// The original crash repro: `return new { pos = Vector3.zero }` from a UCP script.
|
|
28
|
+
var json = MiniJson.Serialize(new { pos = Vector3.zero });
|
|
29
|
+
|
|
30
|
+
Assert.AreEqual("{\"pos\":{\"x\":0,\"y\":0,\"z\":0}}", json);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
[Test]
|
|
34
|
+
public void SerializesUnityMathStructsAsPlainShapes()
|
|
35
|
+
{
|
|
36
|
+
Assert.AreEqual("{\"x\":1,\"y\":2}", MiniJson.Serialize(new Vector2(1f, 2f)));
|
|
37
|
+
Assert.AreEqual("{\"x\":1,\"y\":2,\"z\":3,\"w\":4}", MiniJson.Serialize(new Vector4(1f, 2f, 3f, 4f)));
|
|
38
|
+
Assert.AreEqual("{\"x\":0,\"y\":0,\"z\":0,\"w\":1}", MiniJson.Serialize(Quaternion.identity));
|
|
39
|
+
Assert.AreEqual("{\"r\":1,\"g\":0,\"b\":0,\"a\":1}", MiniJson.Serialize(Color.red));
|
|
40
|
+
Assert.AreEqual("{\"x\":1,\"y\":2,\"z\":3}", MiniJson.Serialize(new Vector3Int(1, 2, 3)));
|
|
41
|
+
Assert.AreEqual(
|
|
42
|
+
"{\"x\":1,\"y\":2,\"width\":3,\"height\":4}",
|
|
43
|
+
MiniJson.Serialize(new Rect(1f, 2f, 3f, 4f)));
|
|
44
|
+
Assert.AreEqual(
|
|
45
|
+
"{\"center\":{\"x\":0,\"y\":0,\"z\":0},\"size\":{\"x\":2,\"y\":2,\"z\":2}}",
|
|
46
|
+
MiniJson.Serialize(new Bounds(Vector3.zero, Vector3.one * 2f)));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
[Test]
|
|
50
|
+
public void SerializesQuaternionNestedInAnonymousResult()
|
|
51
|
+
{
|
|
52
|
+
var parsed = Roundtrip(new { rot = Quaternion.Euler(0f, 90f, 0f), pos = Vector3.one });
|
|
53
|
+
Assert.IsInstanceOf<Dictionary<string, object>>(parsed["value"]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
[Test]
|
|
57
|
+
public void SerializesUnityObjectAsIdentityInsteadOfWalkingTheSceneGraph()
|
|
58
|
+
{
|
|
59
|
+
var go = new GameObject("MiniJsonProbe");
|
|
60
|
+
try
|
|
61
|
+
{
|
|
62
|
+
// GameObject.transform.gameObject is a cycle; walking it never terminates.
|
|
63
|
+
var parsed = Roundtrip(go);
|
|
64
|
+
var identity = (Dictionary<string, object>)parsed["value"];
|
|
65
|
+
|
|
66
|
+
Assert.AreEqual("MiniJsonProbe", identity["name"]);
|
|
67
|
+
Assert.AreEqual("GameObject", identity["type"]);
|
|
68
|
+
Assert.IsTrue(identity.ContainsKey("instanceId"));
|
|
69
|
+
|
|
70
|
+
// Components are the same story via Component.gameObject.
|
|
71
|
+
var componentJson = MiniJson.Serialize(go.transform);
|
|
72
|
+
Assert.IsTrue(componentJson.Contains("\"type\":\"Transform\""), componentJson);
|
|
73
|
+
}
|
|
74
|
+
finally
|
|
75
|
+
{
|
|
76
|
+
UnityEngine.Object.DestroyImmediate(go);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
[Test]
|
|
81
|
+
public void DestroyedUnityObjectSerializesAsNull()
|
|
82
|
+
{
|
|
83
|
+
var go = new GameObject("MiniJsonDestroyed");
|
|
84
|
+
UnityEngine.Object.DestroyImmediate(go);
|
|
85
|
+
|
|
86
|
+
Assert.AreEqual("{\"value\":null}", MiniJson.Serialize(new { value = go }));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
[Test]
|
|
90
|
+
public void ReferenceCyclesAreBrokenInsteadOfRecursingForever()
|
|
91
|
+
{
|
|
92
|
+
var a = new Node { Label = "a" };
|
|
93
|
+
var b = new Node { Label = "b", Next = a };
|
|
94
|
+
a.Next = b;
|
|
95
|
+
|
|
96
|
+
var json = MiniJson.Serialize(a);
|
|
97
|
+
|
|
98
|
+
Assert.IsTrue(json.Contains("<ucp:cycle>"), json);
|
|
99
|
+
Assert.IsNotNull(MiniJson.Deserialize(json));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
[Test]
|
|
103
|
+
public void SelfReferencingDictionaryIsBroken()
|
|
104
|
+
{
|
|
105
|
+
var dict = new Dictionary<string, object> { ["name"] = "root" };
|
|
106
|
+
dict["self"] = dict;
|
|
107
|
+
|
|
108
|
+
var json = MiniJson.Serialize(dict);
|
|
109
|
+
|
|
110
|
+
Assert.IsTrue(json.Contains("<ucp:cycle>"), json);
|
|
111
|
+
Assert.IsNotNull(MiniJson.Deserialize(json));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
[Test]
|
|
115
|
+
public void UnboundedComputedPropertyRecursionIsDepthCapped()
|
|
116
|
+
{
|
|
117
|
+
// Mirrors the Vector3.normalized shape for a type the serializer has no special case
|
|
118
|
+
// for: every read allocates a fresh instance, so reference tracking cannot help and
|
|
119
|
+
// only the depth cap prevents a stack overflow.
|
|
120
|
+
var json = MiniJson.Serialize(new Fractal());
|
|
121
|
+
|
|
122
|
+
Assert.IsTrue(json.Contains("<ucp:max-depth>"), json);
|
|
123
|
+
Assert.IsNotNull(MiniJson.Deserialize(json));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
[Test]
|
|
127
|
+
public void ThrowingGetterDoesNotCorruptTheDocument()
|
|
128
|
+
{
|
|
129
|
+
var json = MiniJson.Serialize(new Explosive());
|
|
130
|
+
|
|
131
|
+
Assert.IsFalse(json.Contains("boom"), json);
|
|
132
|
+
var parsed = MiniJson.Deserialize(json) as Dictionary<string, object>;
|
|
133
|
+
Assert.IsNotNull(parsed, json);
|
|
134
|
+
Assert.AreEqual("ok", parsed["safe"]);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
[Test]
|
|
138
|
+
public void NonFiniteFloatsSerializeAsNullRatherThanInvalidJson()
|
|
139
|
+
{
|
|
140
|
+
// Degenerate bounds and zero-length normalize hand out NaN routinely; raw NaN/Infinity
|
|
141
|
+
// are not valid JSON and made the whole response unparseable on the CLI side.
|
|
142
|
+
var json = MiniJson.Serialize(new { a = float.NaN, b = float.PositiveInfinity, c = double.NaN });
|
|
143
|
+
|
|
144
|
+
Assert.AreEqual("{\"a\":null,\"b\":null,\"c\":null}", json);
|
|
145
|
+
Assert.IsNotNull(MiniJson.Deserialize(json));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
[Test]
|
|
149
|
+
public void UnsignedAndWideIntegersSerializeAsNumbers()
|
|
150
|
+
{
|
|
151
|
+
var json = MiniJson.Serialize(new { a = (uint)7, b = (ushort)8, c = (byte)9, d = 10UL });
|
|
152
|
+
|
|
153
|
+
Assert.AreEqual("{\"a\":7,\"b\":8,\"c\":9,\"d\":10}", json);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
[Test]
|
|
157
|
+
public void NonListEnumerablesSerializeAsArrays()
|
|
158
|
+
{
|
|
159
|
+
var json = MiniJson.Serialize(new { items = new HashSet<int> { 1 } });
|
|
160
|
+
|
|
161
|
+
Assert.AreEqual("{\"items\":[1]}", json);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
[Test]
|
|
165
|
+
public void EnumsStillSerializeAsIntegers()
|
|
166
|
+
{
|
|
167
|
+
Assert.AreEqual("{\"value\":2}", MiniJson.Serialize(new { value = SampleEnum.Two }));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
[Test]
|
|
171
|
+
public void ParserRejectsTruncatedInputInsteadOfHanging()
|
|
172
|
+
{
|
|
173
|
+
// A truncated string literal used to spin the reader on end-of-input forever, wedging
|
|
174
|
+
// the editor's main thread.
|
|
175
|
+
Assert.Throws<FormatException>(() => MiniJson.Deserialize("{\"a\": \"unterminated"));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
[Test]
|
|
179
|
+
public void ParserRejectsPathologicallyNestedInput()
|
|
180
|
+
{
|
|
181
|
+
var deep = new string('[', 1000);
|
|
182
|
+
Assert.Throws<FormatException>(() => MiniJson.Deserialize(deep));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
[Test]
|
|
186
|
+
public void OrdinaryPayloadsAreUnchanged()
|
|
187
|
+
{
|
|
188
|
+
var json = MiniJson.Serialize(new Dictionary<string, object>
|
|
189
|
+
{
|
|
190
|
+
["name"] = "cube",
|
|
191
|
+
["active"] = true,
|
|
192
|
+
["children"] = new List<object> { 1L, 2.5, null },
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
Assert.AreEqual("{\"name\":\"cube\",\"active\":true,\"children\":[1,2.5,null]}", json);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private enum SampleEnum
|
|
199
|
+
{
|
|
200
|
+
One = 1,
|
|
201
|
+
Two = 2,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private sealed class Node
|
|
205
|
+
{
|
|
206
|
+
public string Label;
|
|
207
|
+
public Node Next;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private sealed class Fractal
|
|
211
|
+
{
|
|
212
|
+
public Fractal Child => new Fractal();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private sealed class Explosive
|
|
216
|
+
{
|
|
217
|
+
public string Safe => "ok";
|
|
218
|
+
public string Boom => throw new InvalidOperationException("boom");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|