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); private IEnumerator SelectJobAwaitLoad(string assemblyName) { EditorWindow.GetWindow()._treeView.TrySelectByDisplayName(assemblyName); return new WaitUntil(() => EditorWindow.GetWindow()._textArea.IsTextSet(assemblyName) ); } [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() { const string jobName2 = "BurstReflectionTests.MyJob - (IJob)"; const string jobName = "BurstInspectorGUITests.MyJob - (IJob)"; // Selecting a specific assembly. yield return SelectJobAwaitLoad(jobName); var window = EditorWindow.GetWindow(); try { // 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); yield return SelectJobAwaitLoad(jobName2); window = EditorWindow.GetWindow(); 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"); } finally { window.Close(); } } [UnityTest] public IEnumerator InspectorStallingLoadTest() { // Error was triggered by selecting a display name, filtering it out, and then doing a script recompilation. yield return SelectJobAwaitLoad("BurstInspectorGUITests.MyJob - (IJob)"); var win = EditorWindow.GetWindow(); 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() { const string jobName = "BurstInspectorGUITests.MyJob - (IJob)"; yield return SelectJobAwaitLoad(jobName); 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() { const string jobName = "BurstInspectorGUITests.MyJob - (IJob)"; yield return SelectJobAwaitLoad(jobName); 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(); // 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 - 0.5f*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(); yield return SelectJobAwaitLoad(job); // 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(); } public static IEnumerable FocusCodeNotBranchesSource { get { yield return (1000, false); yield return (563, true); } } [UnityTest] public IEnumerator FocusCodeNotBranchesTest([ValueSource(nameof(FocusCodeNotBranchesSource))] (int, bool) input) { var (width, doFocus) = input; const string case1 = "BurstInspectorGUITests.BranchArrows - (IJob)"; const string case2 = "BurstInspectorGUITests.BranchArrows2 - (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, width, 405); // Test one where it should focus. yield return SelectJobAwaitLoad(case1); var val1 = win._inspectorView.x; var result1 = Mathf.Approximately(val1, 0f); // Test two with no focus. win._assemblyKind = BurstInspectorGUI.AssemblyOptions.PlainWithDebugInformation; yield return SelectJobAwaitLoad(case2); var val2 = win._inspectorView.x; var result2 = Mathf.Approximately(val2, 0f); // Cleanup and test assertions. win.Close(); Assert.AreEqual(doFocus, result1 == doFocus, $"Inspector view wrong."); //Assert.IsFalse(result1, $"Inspector view did not change (Is {val1})."); Assert.IsTrue(result2, $"Inspector view changed unexpectedly (Is {val2})."); } [UnityTest] 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(); yield return SelectJobAwaitLoad("BurstInspectorGUITests.MyJob - (IJob)"); 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(); } } [UnityTest] public IEnumerator SelectionInAssemblySearchBarTest() { yield return SelectJobAwaitLoad("BurstInspectorGUITests.MyJob - (IJob)"); var win = EditorWindow.GetWindow(); win._searchFieldAssembly.SetFocus(); yield return null; // Send events to input some text. win.SendEvent(Event.KeyboardEvent("a")); win.SendEvent(Event.KeyboardEvent("b")); yield return null; // Move select some using keyboard input win.SendEvent(Event.KeyboardEvent("left")); win.SendEvent(Event.KeyboardEvent("#right")); yield return null; // Do a copy var savedClipBoard = EditorGUIUtility.systemCopyBuffer; win.SendEvent(SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX ? Event.KeyboardEvent("%c") : Event.KeyboardEvent("^c")); yield return null; var copiedText = EditorGUIUtility.systemCopyBuffer; EditorGUIUtility.systemCopyBuffer = savedClipBoard; // Check that all is good win.Close(); Assert.AreEqual("b", copiedText, "Copied text did not match expectation."); } [UnityTest] public IEnumerator GoToNextSearchTargetTest() { var active = -1; var nextActive = -1; yield return SelectJobAwaitLoad("BurstInspectorGUITests.MyJob - (IJob)"); var win = EditorWindow.GetWindow(); try { win._searchFieldAssembly.SetFocus(); yield return null; // Do a search in the text. win.SendEvent(Event.KeyboardEvent("p")); win.SendEvent(Event.KeyboardEvent("u")); win.SendEvent(Event.KeyboardEvent("return")); yield return null; active = win._textArea._activeSearchHitIdx; // Select next search target. win.SendEvent(Event.KeyboardEvent("return")); yield return null; nextActive = win._textArea._activeSearchHitIdx; } finally { win.Close(); } Assert.AreNotEqual(active, nextActive, "Active search target was not changed."); } [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 BranchArrows : 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++) { if (Inpút[i] < 10) { result += 1; } else if (Inpút[i] < 20) { result += 2; } else if (Inpút[i] < 30) { result += 3; } else if (Inpút[i] < 40) { result += 4; } else if (Inpút[i] < 50) { result += 5; } else if (Inpút[i] < 60) { result += 6; } else if (Inpút[i] < 70) { result += 7; } else if (Inpút[i] < 80) { result += 8; } else if (Inpút[i] < 90) { result += 9; } result += Inpút[i]; } Output[0] = result; } } [BurstCompile] private struct BranchArrows2 : 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++) { if (Inpút[i] < 10) { result += 1; } else if (Inpút[i] < 20) { result += 2; } else if (Inpút[i] < 30) { result += 3; } else if (Inpút[i] < 40) { result += 4; } else if (Inpút[i] < 50) { result += 5; } else if (Inpút[i] < 60) { result += 6; } else if (Inpút[i] < 70) { result += 7; } else if (Inpút[i] < 80) { result += 8; } else if (Inpút[i] < 90) { result += 9; } 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); } } }