Initial commit: AIGame Unity client with URP, HybridCLR, YooAsset and tooling.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+87
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using NUnit.Framework;
|
||||
using Unity.Pipeline.Console;
|
||||
using Unity.Pipeline.Runtime.Commands;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace Unity.Pipeline.Tests.Editor.Console
|
||||
{
|
||||
class ConsoleCaptureResubscribeTests
|
||||
{
|
||||
[UnityTearDown]
|
||||
public IEnumerator TearDown()
|
||||
{
|
||||
if (EditorApplication.isPlaying)
|
||||
yield return new ExitPlayMode();
|
||||
|
||||
RearmConsoleLogCapture();
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator ConsoleCapture_SurvivesPlayModeExit()
|
||||
{
|
||||
yield return new EnterPlayMode();
|
||||
yield return new ExitPlayMode();
|
||||
|
||||
var marker = NewMarker("survives_exit");
|
||||
EmitError(marker);
|
||||
|
||||
var consoleResponse = ConsoleCommand.GetConsole(level: "error");
|
||||
Assert.IsTrue(consoleResponse.Entries.Any(e => e.Message != null && e.Message.Contains(marker)),
|
||||
"console must still capture entries logged after exiting play mode");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CaptureSurvivesLostSubscription_WhenRearmed()
|
||||
{
|
||||
StripConsoleLogCaptureSubscription();
|
||||
|
||||
var deadMarker = NewMarker("dead");
|
||||
EmitError(deadMarker);
|
||||
Assert.AreEqual(0, CountCaptures(deadMarker),
|
||||
"Stripping the subscription should stop capture (sanity check on the strip step)");
|
||||
|
||||
RearmConsoleLogCapture();
|
||||
|
||||
var liveMarker = NewMarker("live");
|
||||
EmitError(liveMarker);
|
||||
Assert.AreEqual(1, CountCaptures(liveMarker),
|
||||
"Re-arming the subscription must restore capture");
|
||||
|
||||
RearmConsoleLogCapture();
|
||||
|
||||
var idempotentMarker = NewMarker("idempotent");
|
||||
EmitError(idempotentMarker);
|
||||
Assert.AreEqual(1, CountCaptures(idempotentMarker),
|
||||
"Re-arming an already-live subscription must not double-record a single log");
|
||||
}
|
||||
|
||||
private static string NewMarker(string tag) => $"consolecapture_{tag}_{Guid.NewGuid():N}";
|
||||
|
||||
private static void EmitError(string marker)
|
||||
{
|
||||
LogAssert.Expect(LogType.Error, new Regex(".*" + Regex.Escape(marker) + ".*"));
|
||||
Debug.LogError(marker);
|
||||
}
|
||||
|
||||
private static void StripConsoleLogCaptureSubscription()
|
||||
{
|
||||
var method = typeof(ConsoleLogCapture).GetMethod("OnLogMessage", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
Assert.IsNotNull(method, "ConsoleLogCapture.OnLogMessage must exist for this test to strip its subscription");
|
||||
var handler = (Application.LogCallback)Delegate.CreateDelegate(typeof(Application.LogCallback), method);
|
||||
Application.logMessageReceivedThreaded -= handler;
|
||||
}
|
||||
|
||||
private static void RearmConsoleLogCapture() => ConsoleLogCapture.EnsureCapturing();
|
||||
|
||||
private static int CountCaptures(string marker) =>
|
||||
ConsoleLogCapture.Buffer.Query(-1, 0, ConsoleLogBuffer.SeverityLog)
|
||||
.Entries.Count(e => e.Message != null && e.Message.Contains(marker));
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d89fccd0e0213ab4bbde1251233cd766
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using NUnit.Framework;
|
||||
using Unity.Pipeline.Console;
|
||||
using Unity.Pipeline.Editor.Console;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace Unity.Pipeline.Tests.Editor.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for the Editor console ground-truth sampler: what it publishes, that seeding leaves the
|
||||
/// user's console filtering exactly as it found it, and that it re-arms capture when the console
|
||||
/// moves and the buffer's cursor does not.
|
||||
/// </summary>
|
||||
class EditorConsoleGroundTruthTests
|
||||
{
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
// A failed watchdog test would otherwise leave capture dead for the rest of the run.
|
||||
ConsoleLogCapture.EnsureCapturing();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Sample_PublishesConsoleCountsAndCompilationFlag()
|
||||
{
|
||||
var marker = NewMarker("groundtruth");
|
||||
EmitWarning(marker);
|
||||
|
||||
EditorConsoleGroundTruth.SampleForTests();
|
||||
|
||||
var truth = ConsoleLogCapture.GroundTruth;
|
||||
Assert.IsNotNull(truth, "Sampling must publish a snapshot");
|
||||
Assert.Greater(truth.ConsoleWarnings, 0, "The warning just logged is in the Editor console");
|
||||
Assert.Less((DateTime.UtcNow - truth.SampledUtc).TotalSeconds, 30, "The snapshot should be the one just taken");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Seed_RestoresConsoleFlagsAndFilterText()
|
||||
{
|
||||
if (!EditorConsoleEntries.Available)
|
||||
Assert.Ignore("UnityEditor.LogEntries is not reachable on this Editor version");
|
||||
|
||||
var originalFlags = GetConsoleFlags();
|
||||
var originalFilter = GetFilteringText();
|
||||
try
|
||||
{
|
||||
// Restrict the console the way a user might: hide errors and set a search filter.
|
||||
// Seeding has to force both open to see every entry, then put them back.
|
||||
SetConsoleFlags(originalFlags & ~LogLevelError);
|
||||
SetFilteringText("pipeline_seed_filter_probe");
|
||||
|
||||
var restrictedFlags = GetConsoleFlags();
|
||||
var restrictedFilter = GetFilteringText();
|
||||
|
||||
EditorConsoleGroundTruth.ResetForTests();
|
||||
EditorConsoleGroundTruth.SampleForTests();
|
||||
|
||||
Assert.AreEqual(restrictedFlags, GetConsoleFlags(), "Seeding must leave the console's level toggles as it found them");
|
||||
Assert.AreEqual(restrictedFilter, GetFilteringText(), "Seeding must leave the console's search text as it found it");
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetFilteringText(originalFilter);
|
||||
SetConsoleFlags(originalFlags);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Seed_DoesNotDuplicateEntriesAlreadyCaptured()
|
||||
{
|
||||
if (!EditorConsoleEntries.Available)
|
||||
Assert.Ignore("UnityEditor.LogEntries is not reachable on this Editor version");
|
||||
|
||||
var marker = NewMarker("dedupe");
|
||||
EmitError(marker);
|
||||
Assert.AreEqual(1, CountCaptures(marker), "The live callback captured it once");
|
||||
|
||||
EditorConsoleGroundTruth.ResetForTests();
|
||||
EditorConsoleGroundTruth.SampleForTests();
|
||||
|
||||
Assert.AreEqual(1, CountCaptures(marker), "Seeding must not re-add an entry the buffer already holds");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Seed_RecoversEntriesLoggedWhileCaptureWasDead()
|
||||
{
|
||||
if (!EditorConsoleEntries.Available)
|
||||
Assert.Ignore("UnityEditor.LogEntries is not reachable on this Editor version");
|
||||
|
||||
// The repair path, and the shape of the reported symptom: the Editor console holds an
|
||||
// error the buffer never received, because capture was not listening when it was logged.
|
||||
// Editor compile errors are the real case — they are sticky, so they stand in the console
|
||||
// long after the callback missed them.
|
||||
StripSubscription();
|
||||
|
||||
var marker = NewMarker("missed");
|
||||
EmitError(marker);
|
||||
Assert.AreEqual(0, CountCaptures(marker),
|
||||
"Precondition: the dead subscription must have missed it");
|
||||
|
||||
ConsoleLogCapture.EnsureCapturing();
|
||||
EditorConsoleGroundTruth.ResetForTests();
|
||||
EditorConsoleGroundTruth.SampleForTests();
|
||||
|
||||
Assert.AreEqual(1, CountCaptures(marker),
|
||||
"Seeding must recover the entry the console holds and the buffer missed");
|
||||
|
||||
var seeded = ConsoleLogCapture.Buffer.Query(-1, 0, ConsoleLogBuffer.SeverityLog)
|
||||
.Entries.First(e => e.Message != null && e.Message.Contains(marker));
|
||||
Assert.IsTrue(seeded.Seeded,
|
||||
"A recovered entry must be marked, since its timestamp is the read time");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Seed_RecoversARepeatedEntryTheBufferMissed()
|
||||
{
|
||||
if (!EditorConsoleEntries.Available)
|
||||
Assert.Ignore("UnityEditor.LogEntries is not reachable on this Editor version");
|
||||
|
||||
// Console messages repeat by design, so matching on presence alone is not enough: the
|
||||
// copy the buffer already holds would cancel out the identical copy it missed, and that
|
||||
// one would never be recovered.
|
||||
var marker = NewMarker("repeated");
|
||||
EmitError(marker);
|
||||
Assert.AreEqual(1, CountCaptures(marker), "The live callback captured the first one");
|
||||
|
||||
StripSubscription();
|
||||
EmitError(marker);
|
||||
Assert.AreEqual(1, CountCaptures(marker),
|
||||
"Precondition: the second, identical entry was missed while capture was dead");
|
||||
|
||||
ConsoleLogCapture.EnsureCapturing();
|
||||
EditorConsoleGroundTruth.ResetForTests();
|
||||
EditorConsoleGroundTruth.SampleForTests();
|
||||
|
||||
Assert.AreEqual(2, CountCaptures(marker),
|
||||
"Both occurrences must be held after seeding, not just one");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Seed_RecoversEveryOccurrenceOfAPreCaptureRepeat()
|
||||
{
|
||||
if (!EditorConsoleEntries.Available)
|
||||
Assert.Ignore("UnityEditor.LogEntries is not reachable on this Editor version");
|
||||
|
||||
// A fresh seed with nothing held: all three identical rows have to come across.
|
||||
StripSubscription();
|
||||
var marker = NewMarker("triple");
|
||||
EmitError(marker);
|
||||
EmitError(marker);
|
||||
EmitError(marker);
|
||||
Assert.AreEqual(0, CountCaptures(marker), "Precondition: capture was dead for all three");
|
||||
|
||||
ConsoleLogCapture.EnsureCapturing();
|
||||
EditorConsoleGroundTruth.ResetForTests();
|
||||
EditorConsoleGroundTruth.SampleForTests();
|
||||
|
||||
Assert.AreEqual(3, CountCaptures(marker), "Every occurrence must be recovered");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Watchdog_ReArmsCaptureWhenConsoleMovedAndBufferDidNot()
|
||||
{
|
||||
StripSubscription();
|
||||
|
||||
// Baseline, then a log the dead subscription cannot deliver: the console's count moves
|
||||
// while the buffer's cursor stands still, which is the only symptom available.
|
||||
EditorConsoleGroundTruth.SampleForTests();
|
||||
EmitError(NewMarker("gap"));
|
||||
EditorConsoleGroundTruth.SampleForTests();
|
||||
EditorConsoleGroundTruth.SampleForTests();
|
||||
|
||||
var marker = NewMarker("recovered");
|
||||
EmitError(marker);
|
||||
Assert.AreEqual(1, CountCaptures(marker), "The watchdog must have re-armed capture");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EditorConsoleEntries_ReadsRecentEntries()
|
||||
{
|
||||
if (!EditorConsoleEntries.Available)
|
||||
Assert.Ignore("UnityEditor.LogEntries is not reachable on this Editor version");
|
||||
|
||||
var marker = NewMarker("read");
|
||||
EmitError(marker);
|
||||
|
||||
var entries = new List<EditorConsoleEntries.Entry>();
|
||||
Assert.IsTrue(EditorConsoleEntries.TryReadRecent(200, entries));
|
||||
Assert.IsTrue(entries.Any(e => e.Message != null && e.Message.Contains(marker)),
|
||||
"The entry just logged must be readable from the Editor console store");
|
||||
}
|
||||
|
||||
// ConsoleMode.kLogLevelError, as mirrored by the internal ConsoleWindow.ConsoleFlags.
|
||||
const int LogLevelError = 1 << 9;
|
||||
|
||||
static readonly Type k_LogEntries = Type.GetType("UnityEditor.LogEntries,UnityEditor");
|
||||
|
||||
static int GetConsoleFlags() => (int)Property("consoleFlags").GetGetMethod(true).Invoke(null, null);
|
||||
|
||||
static void SetConsoleFlags(int flags) => Property("consoleFlags").GetSetMethod(true).Invoke(null, new object[] { flags });
|
||||
|
||||
static string GetFilteringText() => (string)Method("GetFilteringText").Invoke(null, null) ?? string.Empty;
|
||||
|
||||
static void SetFilteringText(string text) => Method("SetFilteringText").Invoke(null, new object[] { text });
|
||||
|
||||
static PropertyInfo Property(string name) =>
|
||||
k_LogEntries.GetProperty(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
static MethodInfo Method(string name) =>
|
||||
k_LogEntries.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
static string NewMarker(string tag) => $"groundtruth_{tag}_{Guid.NewGuid():N}";
|
||||
|
||||
static void EmitError(string marker)
|
||||
{
|
||||
LogAssert.Expect(LogType.Error, new Regex(".*" + Regex.Escape(marker) + ".*"));
|
||||
Debug.LogError(marker);
|
||||
}
|
||||
|
||||
static void EmitWarning(string marker)
|
||||
{
|
||||
LogAssert.Expect(LogType.Warning, new Regex(".*" + Regex.Escape(marker) + ".*"));
|
||||
Debug.LogWarning(marker);
|
||||
}
|
||||
|
||||
static void StripSubscription()
|
||||
{
|
||||
var method = typeof(ConsoleLogCapture).GetMethod("OnLogMessage", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
Assert.IsNotNull(method, "ConsoleLogCapture.OnLogMessage must exist for this test to strip its subscription");
|
||||
var handler = (Application.LogCallback)Delegate.CreateDelegate(typeof(Application.LogCallback), method);
|
||||
Application.logMessageReceivedThreaded -= handler;
|
||||
}
|
||||
|
||||
static int CountCaptures(string marker) =>
|
||||
ConsoleLogCapture.Buffer.Query(-1, 0, ConsoleLogBuffer.SeverityLog)
|
||||
.Entries.Count(e => e.Message != null && e.Message.Contains(marker));
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b4a37e6cb5686043949dcba6e8f64ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user