com.valectric.mooserunner 2.1.16 → 2.1.18
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/Samples~/Demos/MooseRunner.Demo.CancellationTokens.Tests/SetupTeardownCancellationTests.cs +1 -0
- package/Samples~/Demos/MooseRunner.Demo.ErrorHandling.Tests/ErrorHandelingTest.cs +1 -0
- package/Samples~/Demos/MooseRunner.Demo.RealUseCase/RealUseCaseTest.cs +74 -72
- package/Samples~/Demos/MooseRunner.Demo.Support/MooseRunnerSampleVersionCheck.cs +57 -0
- package/Samples~/Demos/MooseRunner.Demo.Support/MooseRunnerSampleVersionCheck.cs.meta +2 -0
- package/Samples~/Demos/MooseRunner.Demo.Tests/DummyTestBase1.cs +1 -0
- package/Samples~/Demos/MooseRunner.Demo.Tests/DummyTestClass1.cs +1 -0
- package/Samples~/Demos/MooseRunner.Demo.Tests/DummyTestClass2.cs +1 -0
- package/Samples~/Demos/MooseRunner.Demo.Tests/InstanceHandlingVerificationTest.cs +1 -0
- package/package.json +1 -1
package/Samples~/Demos/MooseRunner.Demo.CancellationTokens.Tests/SetupTeardownCancellationTests.cs
CHANGED
|
@@ -21,6 +21,7 @@ namespace MooseRunner.Internal.Tests
|
|
|
21
21
|
[OneTimeSetUp]
|
|
22
22
|
public async Task OneTimeSetupMethod(CancellationToken ct)
|
|
23
23
|
{
|
|
24
|
+
MooseRunnerSampleVersionCheck.EnsureSingleVersion();
|
|
24
25
|
Debug.Log("[OneTimeSetUp] OneTimeSetUp started - 5 second delay");
|
|
25
26
|
Debug.Log("[OneTimeSetUp] Click STOP during OneTimeSetUp to test cancellation");
|
|
26
27
|
await Task.Delay(5000, ct);
|
|
@@ -27,6 +27,7 @@ namespace MooseRunner.Internal.Tests
|
|
|
27
27
|
[OneTimeSetUp]
|
|
28
28
|
public async Task SetUpOnce()
|
|
29
29
|
{
|
|
30
|
+
MooseRunnerSampleVersionCheck.EnsureSingleVersion();
|
|
30
31
|
await MooseRunnerFacade.Instance.LoadSceneFromNameAsync("RealUseCaseScene");
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -210,14 +211,17 @@ namespace MooseRunner.Internal.Tests
|
|
|
210
211
|
/// Also demonstrates SessionRecorder usage: records the test, then asks Gemini to describe what happened.
|
|
211
212
|
/// When Unity Recorder isn't installed every facade call throws InvalidOperationException with install
|
|
212
213
|
/// instructions; the catches log the message so all three throw sites surface in the test output.
|
|
214
|
+
/// The Stop and Analyze calls run in a finally block so they fire even when the ghost-cat scenario
|
|
215
|
+
/// fails — important for demonstrating all three throw sites in a customer install.
|
|
213
216
|
/// </summary>
|
|
214
217
|
[Test]
|
|
215
218
|
public async Task TestDying()
|
|
216
219
|
{
|
|
217
|
-
// SessionRecorder soft-dep: start a recording. Throws with install instructions if
|
|
218
|
-
// com.unity.recorder isn't installed.
|
|
219
220
|
SessionInfo recordingSession = null;
|
|
220
221
|
string outputPath = Path.Combine(Application.dataPath, "../.mooserunner/Recordings/TestDying");
|
|
222
|
+
|
|
223
|
+
// SessionRecorder soft-dep: start a recording. Throws with install instructions if
|
|
224
|
+
// com.unity.recorder isn't installed.
|
|
221
225
|
try
|
|
222
226
|
{
|
|
223
227
|
var cfg = new SessionRecordingConfig(Camera.main, outputPath);
|
|
@@ -229,92 +233,90 @@ namespace MooseRunner.Internal.Tests
|
|
|
229
233
|
Debug.LogWarning("[SessionRecorder] StartRecordingAsync threw: " + ex.Message);
|
|
230
234
|
}
|
|
231
235
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
GameObject ghostCatInstance =
|
|
238
|
-
Object.Instantiate(ghostCatPrefab, new Vector3(0f, 2f, 0f), Quaternion.identity);
|
|
239
|
-
Assert.IsNotNull(ghostCatInstance, "Failed to instantiate the Ghost_Cat prefab.");
|
|
236
|
+
try
|
|
237
|
+
{
|
|
238
|
+
// Load the "Ghost_Cat" prefab from the Resources folder
|
|
239
|
+
GameObject ghostCatPrefab = Resources.Load<GameObject>("Ghost_Cat");
|
|
240
|
+
Assert.IsNotNull(ghostCatPrefab, "The Ghost_Cat prefab could not be found in the Resources folder.");
|
|
240
241
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
// Spawn the "Ghost_Cat" prefab into the scene
|
|
243
|
+
GameObject ghostCatInstance =
|
|
244
|
+
Object.Instantiate(ghostCatPrefab, new Vector3(0f, 2f, 0f), Quaternion.identity);
|
|
245
|
+
Assert.IsNotNull(ghostCatInstance, "Failed to instantiate the Ghost_Cat prefab.");
|
|
244
246
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
new Vector3(ghostCatInstance.transform.position.x, 0.5f,
|
|
249
|
-
0.5f); // Position the wall directly in front of the Ghost_Cat
|
|
247
|
+
// Get the Enemy component
|
|
248
|
+
Enemy enemyComponent = ghostCatInstance.GetComponent<Enemy>();
|
|
249
|
+
Assert.IsNotNull(enemyComponent, "The Ghost_Cat instance does not have an Enemy component.");
|
|
250
250
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
251
|
+
// Create a box object
|
|
252
|
+
GameObject box = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
253
|
+
box.transform.position =
|
|
254
|
+
new Vector3(ghostCatInstance.transform.position.x, 0.5f,
|
|
255
|
+
0.5f); // Position the wall directly in front of the Ghost_Cat
|
|
255
256
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
new Vector3(ghostCatInstance.transform.position.x, 0f, 2.5f); // Position the block appropriately
|
|
261
|
-
fire.transform.localScale = new Vector3(1f, 1f, 3f); // Scale the box to 2 along the x-axis
|
|
257
|
+
// Add a Rigidbody and Collider component to ensure collision detection works
|
|
258
|
+
box.AddComponent<BoxCollider>();
|
|
259
|
+
Rigidbody wallRigidbody = box.AddComponent<Rigidbody>();
|
|
260
|
+
wallRigidbody.isKinematic = true;
|
|
262
261
|
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
// Create a red block simulating fire
|
|
263
|
+
GameObject fire = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
264
|
+
fire.name = "Fire"; // Rename the block to "Fire"
|
|
265
|
+
fire.transform.position =
|
|
266
|
+
new Vector3(ghostCatInstance.transform.position.x, 0f, 2.5f);
|
|
267
|
+
fire.transform.localScale = new Vector3(1f, 1f, 3f);
|
|
265
268
|
|
|
266
|
-
|
|
267
|
-
BoxCollider collider = fire.AddComponent<BoxCollider>();
|
|
268
|
-
collider.isTrigger = true; // Set the collider to be a trigger
|
|
269
|
+
fire.GetComponent<Renderer>().material.color = Color.red;
|
|
269
270
|
|
|
270
|
-
|
|
271
|
-
|
|
271
|
+
BoxCollider collider = fire.AddComponent<BoxCollider>();
|
|
272
|
+
collider.isTrigger = true;
|
|
272
273
|
|
|
273
|
-
|
|
274
|
-
enemyComponent.moveForward = true;
|
|
274
|
+
fire.AddComponent<FireComponent>();
|
|
275
275
|
|
|
276
|
-
|
|
277
|
-
await Task.Yield();
|
|
276
|
+
enemyComponent.moveForward = true;
|
|
278
277
|
|
|
279
|
-
|
|
280
|
-
float initialY = ghostCatInstance.transform.position.y;
|
|
281
|
-
await WaitForSecondsUnity(7f);
|
|
278
|
+
await Task.Yield();
|
|
282
279
|
|
|
283
|
-
|
|
284
|
-
|
|
280
|
+
float initialY = ghostCatInstance.transform.position.y;
|
|
281
|
+
await WaitForSecondsUnity(7f);
|
|
285
282
|
|
|
286
|
-
|
|
287
|
-
// message when Recorder isn't installed.
|
|
288
|
-
try
|
|
289
|
-
{
|
|
290
|
-
SessionRecorderFacade.Instance.StopRecording();
|
|
291
|
-
Debug.Log("[SessionRecorder] Stopped recording");
|
|
292
|
-
}
|
|
293
|
-
catch (InvalidOperationException ex)
|
|
294
|
-
{
|
|
295
|
-
Debug.LogWarning("[SessionRecorder] StopRecording threw: " + ex.Message);
|
|
296
|
-
}
|
|
283
|
+
enemyComponent.moveForward = false;
|
|
297
284
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
// — that one only surfaces once we get past the Recorder gate).
|
|
301
|
-
try
|
|
302
|
-
{
|
|
303
|
-
string sessionPath = recordingSession?.SessionPath ?? outputPath;
|
|
304
|
-
var result = await SessionRecorderFacade.Instance.AnalyzeVideoSegmentAsync(
|
|
305
|
-
sessionPath, 0.0, 5.0,
|
|
306
|
-
"Did the Ghost_Cat catch fire and die?",
|
|
307
|
-
CancellationToken.None);
|
|
308
|
-
Debug.Log("[SessionRecorder] Gemini analysis: " + result.Summary);
|
|
285
|
+
Assert.IsTrue(ghostCatInstance == null || !ghostCatInstance.activeSelf,
|
|
286
|
+
"The Ghost_Cat was not destroyed or deactivated.");
|
|
309
287
|
}
|
|
310
|
-
|
|
288
|
+
finally
|
|
311
289
|
{
|
|
312
|
-
|
|
290
|
+
// SessionRecorder soft-dep: stop recording. Throws the same install-instruction
|
|
291
|
+
// message when Recorder isn't installed. In finally so it fires even when the
|
|
292
|
+
// scenario above failed (e.g. prefab/component resolution issues).
|
|
293
|
+
try
|
|
294
|
+
{
|
|
295
|
+
SessionRecorderFacade.Instance.StopRecording();
|
|
296
|
+
Debug.Log("[SessionRecorder] Stopped recording");
|
|
297
|
+
}
|
|
298
|
+
catch (InvalidOperationException ex)
|
|
299
|
+
{
|
|
300
|
+
Debug.LogWarning("[SessionRecorder] StopRecording threw: " + ex.Message);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// SessionRecorder soft-dep: video analysis via Gemini. Same install-instruction
|
|
304
|
+
// message when Recorder isn't installed (different from a Gemini-not-configured
|
|
305
|
+
// error — that one only surfaces once we get past the Recorder gate).
|
|
306
|
+
try
|
|
307
|
+
{
|
|
308
|
+
string sessionPath = recordingSession?.SessionPath ?? outputPath;
|
|
309
|
+
var result = await SessionRecorderFacade.Instance.AnalyzeVideoSegmentAsync(
|
|
310
|
+
sessionPath, 0.0, 5.0,
|
|
311
|
+
"Did the Ghost_Cat catch fire and die?",
|
|
312
|
+
CancellationToken.None);
|
|
313
|
+
Debug.Log("[SessionRecorder] Gemini analysis: " + result.Summary);
|
|
314
|
+
}
|
|
315
|
+
catch (InvalidOperationException ex)
|
|
316
|
+
{
|
|
317
|
+
Debug.LogWarning("[SessionRecorder] AnalyzeVideoSegmentAsync threw: " + ex.Message);
|
|
318
|
+
}
|
|
313
319
|
}
|
|
314
|
-
|
|
315
|
-
// Check if the entity is destroyed or marked as "dead"
|
|
316
|
-
Assert.IsTrue(ghostCatInstance == null || !ghostCatInstance.activeSelf,
|
|
317
|
-
"The Ghost_Cat was not destroyed or deactivated.");
|
|
318
320
|
}
|
|
319
321
|
|
|
320
322
|
/// <summary>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.IO;
|
|
3
|
+
using System.Linq;
|
|
4
|
+
using UnityEngine;
|
|
5
|
+
|
|
6
|
+
namespace MooseRunner.Internal.Tests
|
|
7
|
+
{
|
|
8
|
+
/// <summary>
|
|
9
|
+
/// Detects duplicate MooseRunner sample-version folders in Assets/Samples/.
|
|
10
|
+
/// Unity's UPM Sample import creates a new <c>Assets/Samples/MooseRunner/<version>/...</c>
|
|
11
|
+
/// folder on every upgrade and never deletes the previous one. The duplicate .cs files
|
|
12
|
+
/// share class FQNs (e.g. <c>MooseRunner.Internal.Tests.Enemy</c>) and stable .meta GUIDs,
|
|
13
|
+
/// which produces a "is not derived from MonoBehaviour or ScriptableObject!" runtime
|
|
14
|
+
/// error when prefabs instantiate. Called from each sample test's OneTimeSetUp so the
|
|
15
|
+
/// check fires only when the customer actually uses the samples.
|
|
16
|
+
/// </summary>
|
|
17
|
+
public static class MooseRunnerSampleVersionCheck
|
|
18
|
+
{
|
|
19
|
+
private static bool _alreadyChecked;
|
|
20
|
+
private const string SamplesRoot = "Assets/Samples/MooseRunner";
|
|
21
|
+
|
|
22
|
+
/// <summary>
|
|
23
|
+
/// Throws <see cref="InvalidOperationException"/> if multiple MooseRunner sample-version
|
|
24
|
+
/// folders are present. First call performs the scan; subsequent calls within the same
|
|
25
|
+
/// domain are no-ops (the flag resets on domain reload, which is fine — the check is
|
|
26
|
+
/// idempotent and cheap).
|
|
27
|
+
/// </summary>
|
|
28
|
+
public static void EnsureSingleVersion()
|
|
29
|
+
{
|
|
30
|
+
if (_alreadyChecked) return;
|
|
31
|
+
_alreadyChecked = true;
|
|
32
|
+
|
|
33
|
+
if (!Directory.Exists(SamplesRoot)) return;
|
|
34
|
+
|
|
35
|
+
var versionDirs = Directory.GetDirectories(SamplesRoot);
|
|
36
|
+
if (versionDirs.Length <= 1) return;
|
|
37
|
+
|
|
38
|
+
var sortedFolders = versionDirs
|
|
39
|
+
.Select(Path.GetFileName)
|
|
40
|
+
.OrderBy(n => n)
|
|
41
|
+
.Select(n => " " + SamplesRoot + "/" + n);
|
|
42
|
+
|
|
43
|
+
string msg =
|
|
44
|
+
"Multiple MooseRunner sample versions detected. Unity's UPM 'Reimport Sample' " +
|
|
45
|
+
"creates a new versioned folder on every package upgrade and never deletes the " +
|
|
46
|
+
"previous one. Duplicate .cs files with the same class FQN and stable .meta GUIDs " +
|
|
47
|
+
"produce the runtime error \"... is not derived from MonoBehaviour or ScriptableObject!\" " +
|
|
48
|
+
"when prefabs instantiate.\n" +
|
|
49
|
+
"\n" +
|
|
50
|
+
"Found:\n" + string.Join("\n", sortedFolders) + "\n" +
|
|
51
|
+
"\n" +
|
|
52
|
+
"Fix: delete all but the newest folder under Assets/Samples/MooseRunner/, then re-run.";
|
|
53
|
+
Debug.LogError(msg);
|
|
54
|
+
throw new InvalidOperationException(msg);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -27,6 +27,7 @@ namespace MooseRunner.Internal.Tests
|
|
|
27
27
|
[OneTimeSetUp]
|
|
28
28
|
public void OneTimeSetUpMethod()
|
|
29
29
|
{
|
|
30
|
+
MooseRunnerSampleVersionCheck.EnsureSingleVersion();
|
|
30
31
|
// OneTimeSetUp should get a fresh instance, so _instanceId should be 99 (default)
|
|
31
32
|
Assert.AreEqual(99, _instanceId, "OneTimeSetUp should start with default _instanceId = 99 (fresh instance)");
|
|
32
33
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "com.valectric.mooserunner",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.18",
|
|
4
4
|
"displayName": "MooseRunner",
|
|
5
5
|
"description": "MooseRunner boosts PlayMode testing with Hot Reload, MCP for AIs, timescale control, and Task support. It enables rapid iteration, auto test reruns, full documentation and dedicated support.",
|
|
6
6
|
"unity": "6000.2",
|