using System; using System.Collections; using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Unity.Burst.Editor; using UnityEditor; using UnityEngine; using UnityEngine.TestTools; using Unity.Collections; using Unity.Burst; using Unity.Jobs; [TestFixture] [UnityPlatform(RuntimePlatform.WindowsEditor, RuntimePlatform.OSXEditor)] public class BurstInspectorGUITests { private readonly WaitUntil _waitForInitialized = new WaitUntil(() => EditorWindow.GetWindow()._initialized); [UnitySetUp] public IEnumerator SetUp() { // Close down window if it's open, to start with a fresh inspector. EditorWindow.GetWindow().Close(); EditorWindow.GetWindow().Show(); // Make sure window is actually initialized before continuing. yield return _waitForInitialized; } [UnityTest] public IEnumerator TestInspectorOpenDuringDomainReloadDoesNotLogErrors() { // Show Inspector window EditorWindow.GetWindow().Show(); Assert.IsTrue(EditorWindow.HasOpenInstances()); // Ask for domain reload EditorUtility.RequestScriptReload(); // Wait for the domain reload to be completed yield return new WaitForDomainReload(); Assert.IsTrue(EditorWindow.HasOpenInstances()); // Hide Inspector window EditorWindow.GetWindow().Close(); Assert.IsFalse(EditorWindow.HasOpenInstances()); } [UnityTest] public IEnumerator DisassemblerNotChangingUnexpectedlyTest() { var window = EditorWindow.GetWindow(); // Selecting a specific assembly. window._treeView.TrySelectByDisplayName("BurstInspectorGUITests.MyJob - (IJob)"); // Sending event to set the displayname, to avoid it resetting _scrollPos because of target change. window.SendEvent(new Event() { type = EventType.Repaint, mousePosition = new Vector2(window.position.width / 2f, window.position.height / 2f) }); yield return null; // Doing actual test work: var prev = new BurstDisassemblerWithCopy(window._burstDisassembler); window.SendEvent(new Event() { type = EventType.Repaint, mousePosition = new Vector2(window.position.width / 2f, window.position.height / 2f) }); yield return null; Assert.IsTrue(prev.Equals(window._burstDisassembler), "Public fields changed in burstDisassembler even though they shouldn't"); prev = new BurstDisassemblerWithCopy(window._burstDisassembler); window.SendEvent(new Event() { type = EventType.MouseUp, mousePosition = Vector2.zero }); yield return null; Assert.IsTrue(prev.Equals(window._burstDisassembler), "Public fields changed in burstDisassembler even though they shouldn't"); prev = new BurstDisassemblerWithCopy(window._burstDisassembler); window._treeView.TrySelectByDisplayName("BurstReflectionTests.MyJob - (IJob)"); // Changing job, meaning SetText(.) should be called during next event. window.SendEvent(new Event() { type = EventType.Repaint, mousePosition = new Vector2(window.position.width / 2f, window.position.height / 2f) }); yield return null; Assert.IsFalse(prev.Equals(window._burstDisassembler), "Public fields of burstDisassembler did not change"); window.Close(); } [UnityTest] public IEnumerator InspectorStallingLoadTest() { var win = EditorWindow.GetWindow(); // Error was triggered by selecting a display name, filtering it out, and then doing a script recompilation. win._treeView.TrySelectByDisplayName("BurstInspectorGUITests.MyJob - (IJob)"); win._searchFieldJobs.SetFocus(); yield return null; // Simulate event for sending "a" as it will filter out the chosen job. win.SendEvent(Event.KeyboardEvent("a")); yield return null; // Send RequestScriptReload to try and trigger the bug // and wait for it to return EditorUtility.RequestScriptReload(); yield return new WaitForDomainReload(); win = EditorWindow.GetWindow(); // Wait for it to actually initialize. yield return _waitForInitialized; Assert.IsTrue(win._initialized, "BurstInspector did not initialize properly after script reload"); win.Close(); } [UnityTest] public IEnumerator FontStyleDuringDomainReloadTest() { // Enter play mod yield return new EnterPlayMode(); // Exit play mode yield return new ExitPlayMode(); // Wait for the inspector to actually reload yield return _waitForInitialized; var inspectorWindow = EditorWindow.GetWindow(); #if UNITY_2023_1_OR_NEWER Assert.AreEqual("RobotoMono-Regular", inspectorWindow._font.name); #else if (Application.platform == RuntimePlatform.WindowsEditor) { Assert.AreEqual("Consolas", inspectorWindow._font.name); } else { Assert.AreEqual("Courier", inspectorWindow._font.name); } #endif inspectorWindow.Close(); } [UnityTest] public IEnumerator BranchHoverTest() { var info = SetupBranchTest(); var window = EditorWindow.GetWindow(); window.SendEvent(new Event() { type = EventType.MouseUp, mousePosition = info.mousePos }); var branch = window._textArea.hoveredBranch; yield return null; // Close window to avoid it sending more events window.Close(); Assert.AreNotEqual(branch, default(LongTextArea.Branch), "Mouse is not hovering any branch."); Assert.AreEqual(info.blockIdx.src, branch.Edge.OriginRef.BlockIndex); Assert.AreEqual(info.blockIdx.dst, branch.Edge.LineRef.BlockIndex); } [UnityTest] public IEnumerator ClickBranchTest() { var info = SetupBranchTest(); var window = EditorWindow.GetWindow(); // Seeing if clicking the branch takes us to a spot where branch is still hovered. window.SendEvent(new Event() { type = EventType.MouseDown, mousePosition = info.mousePos }); var branch = window._textArea.hoveredBranch; yield return null; Assert.AreNotEqual(branch, default(LongTextArea.Branch), "Mouse is not hovering any branch."); Assert.AreEqual(info.blockIdx.src, branch.Edge.OriginRef.BlockIndex); Assert.AreEqual(info.blockIdx.dst, branch.Edge.LineRef.BlockIndex); // Going back again. window.SendEvent(new Event() { type = EventType.MouseDown, mousePosition = info.mousePos }); var branch2 = window._textArea.hoveredBranch; yield return null; Assert.AreNotEqual(branch2, default(LongTextArea.Branch), "Mouse is not hovering any branch."); Assert.AreEqual(info.blockIdx.src, branch2.Edge.OriginRef.BlockIndex); Assert.AreEqual(info.blockIdx.dst, branch2.Edge.LineRef.BlockIndex); // Close window to avoid it sending more events. window.Close(); } private struct InfoThingy { public (int src, int dst) blockIdx; public Vector2 mousePos; } private InfoThingy SetupBranchTest() { var window = EditorWindow.GetWindow(); // Selecting a specific assembly. window._treeView.TrySelectByDisplayName("BurstInspectorGUITests.MyJob - (IJob)"); // Make sure we use fontSize 12: window.fontSizeIndex = 4; window._textArea.Invalidate(); window.fixedFontStyle = null; // Force window size to actually show branch arrows. window.position = new Rect(window.position.x, window.position.y, 390, 405); // Sending event to set the displayname, to avoid it resetting _scrollPos because of target change. // Sending two events as initial guess for buttonbar width might be off, and it will be a precise calculation after second event. window.SendEvent(new Event() { type = EventType.Repaint, mousePosition = new Vector2(window.position.width / 2f, window.position.height / 2f) }); window.SendEvent(new Event() { type = EventType.Repaint, mousePosition = new Vector2(window.position.width / 2f, window.position.height / 2f) }); // Setting up for the test. // Finding an edge: int dstBlockIdx = -1; int srcBlockIdx = -1; int line = -1; for (int idx = 0; idx < window._burstDisassembler.Blocks.Count; idx++) { var block = window._burstDisassembler.Blocks[idx]; if (block.Edges != null) { foreach (var edge in block.Edges) { if (edge.Kind == BurstDisassembler.AsmEdgeKind.OutBound) { dstBlockIdx = edge.LineRef.BlockIndex; line = window._textArea.blockLine[dstBlockIdx]; if ((dstBlockIdx == idx + 1 && edge.LineRef.LineIndex == 0)) // pointing to next line { continue; } srcBlockIdx = idx; break; } } if (srcBlockIdx != -1) { break; } } } if (srcBlockIdx == -1) { window.Close(); throw new System.Exception("No edges present in assembly for \"BurstInspectorGUITests.MyJob - (IJob)\""); } float dist = window._textArea.fontHeight * line; float x = (window.position.width - (window._inspectorView.width + BurstInspectorGUI._scrollbarThickness)) + window._textArea.horizontalPad - (2*window._textArea.fontWidth); // setting _ScrollPos so end of arrow is at bottom of screen, to make sure there is actually room for the scrolling. window._scrollPos = new Vector2(0, dist - window._inspectorView.height * 0.93f); // Setting mousePos to bottom of inspector view. float topOfInspectorToBranchArrow = window._buttonOverlapInspectorView + 66.5f;//66.5f is the size of space over the treeview of different jobs. var mousePos = new Vector2(x, topOfInspectorToBranchArrow + window._inspectorView.height - window._textArea.fontHeight); return new InfoThingy() { blockIdx = (srcBlockIdx, dstBlockIdx), mousePos = mousePos}; } public static IEnumerable ValueSource { get { yield return "BurstInspectorGUITests.MyJob - (IJob)"; yield return "BurstReflectionTests.GenericType`1.NestedGeneric`1[System.Int32,System.Single].TestMethod3()"; yield return "BurstReflectionTests.GenericType`1.NestedNonGeneric[System.Int32].TestMethod2()"; yield return "BurstReflectionTests.GenericParallelForJob`1[System.Int32] - (IJobParallelFor)"; } } [UnityTest] public IEnumerator FocusCodeTest([ValueSource(nameof(ValueSource))] string job) { var win = EditorWindow.GetWindow(); win._treeView.TrySelectByDisplayName(job); yield return null; // Doesn't check that it's at the right spot, simply that it actually moves Assert.IsFalse(Mathf.Approximately(win._inspectorView.y, 0f), "Inspector view did not change"); win.Close(); } [UnityTest] public IEnumerator FocusCodeNotBranchesTest() { const string case1 = "BurstInspectorGUITests.MyJob - (IJob)"; const string case2 = "BurstInspectorGUITests.MyJob2 - (IJob)"; var win = EditorWindow.GetWindow(); // Force window size to be small enough for it to position it more to the right. win.position = new Rect(win.position.x, win.position.y, 390, 405); // Test one where it should focus. win._treeView.TrySelectByDisplayName(case1); yield return null; var val1 = win._inspectorView.x; var result1 = Mathf.Approximately(val1, 0f); // Test two with no focus. win._assemblyKind = BurstInspectorGUI.AssemblyOptions.PlainWithDebugInformation; win._treeView.TrySelectByDisplayName(case2); yield return null; var val2 = win._inspectorView.x; var result2 = Mathf.Approximately(val2, 0f); // Cleanup and test assertions. win.Close(); Assert.IsFalse(result1, $"Inspector view did not change (Is {val1})."); Assert.IsTrue(result2, $"Inspector view changed unexpectedly (Is {val2})."); } public IEnumerator SelectionNotOutsideBoundsTest() { void MoveSelection(BurstInspectorGUI gui, LongTextArea.Direction dir) { switch (dir) { case LongTextArea.Direction.Down: gui._textArea.SelectAll(); gui._textArea.MoveSelectionDown(gui._inspectorView, true); break; case LongTextArea.Direction.Right: gui._textArea.SelectAll(); gui._textArea.MoveSelectionRight(gui._inspectorView, true); break; case LongTextArea.Direction.Left: gui._textArea.selectDragPos = Vector2.zero; gui._textArea.MoveSelectionLeft(gui._inspectorView, true); break; case LongTextArea.Direction.Up: gui._textArea.selectDragPos = Vector2.zero; gui._textArea.MoveSelectionUp(gui._inspectorView, true); break; } } var win = EditorWindow.GetWindow(); win._treeView.TrySelectByDisplayName("BurstInspectorGUITests.MyJob - (IJob)"); yield return null; try { foreach (var dir in Enum.GetValues(typeof(LongTextArea.Direction))) { MoveSelection(win, (LongTextArea.Direction)dir); yield return null; // Check that no errors have happened. LogAssert.NoUnexpectedReceived(); } } finally { win.Close(); } } [BurstCompile] private struct MyJob : IJob { [ReadOnly] public NativeArray Inpút; [WriteOnly] public NativeArray Output; public void Execute() { float result = 0.0f; for (int i = 0; i < Inpút.Length; i++) { result += Inpút[i]; } Output[0] = result; } } [BurstCompile] private struct MyJob2 : IJob { [ReadOnly] public NativeArray Inpút; [WriteOnly] public NativeArray Output; public void Execute() { float result = 0.0f; for (int i = 0; i < Inpút.Length; i++) { result += Inpút[i]; } Output[0] = result; } } private class BurstDisassemblerWithCopy : BurstDisassembler { public List BlocksCopy; public bool IsColoredCopy; public List LinesCopy; public List TokensCopy; public BurstDisassemblerWithCopy(BurstDisassembler disassembler) : base() { IsColoredCopy = disassembler.IsColored; BlocksCopy = new List(disassembler.Blocks); LinesCopy = new List(disassembler.Lines); TokensCopy = new List(disassembler.Tokens); } public bool Equals(BurstDisassembler other) { return IsColoredCopy == other.IsColored && BlocksCopy.SequenceEqual(other.Blocks) && LinesCopy.SequenceEqual(other.Lines) && TokensCopy.SequenceEqual(other.Tokens); } } }