An easy to use Unity 3D library for creating illustrated Interactive Fiction games and more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

605 lines
24 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using UnityTest.IntegrationTestRunner;
#if UNITY_5_3_OR_NEWER
using UnityEngine.SceneManagement;
#endif
namespace UnityTest
{
[Serializable]
public class IntegrationTestsRunnerWindow : EditorWindow, IHasCustomMenu
{
#region GUI Contents
private readonly GUIContent m_GUICreateNewTest = new GUIContent("Create", "Create new test");
private readonly GUIContent m_GUIRunSelectedTests = new GUIContent("Run Selected", "Run selected test(s)");
private readonly GUIContent m_GUIRunAllTests = new GUIContent("Run All", "Run all tests");
private readonly GUIContent m_GUIBlockUI = new GUIContent("Block UI when running", "Block UI when running tests");
private readonly GUIContent m_GUIPauseOnFailure = new GUIContent("Pause on test failure");
#endregion
#region runner steerign vars
private static IntegrationTestsRunnerWindow s_Instance;
[SerializeField] private List<GameObject> m_TestsToRun;
[SerializeField] private List<string> m_DynamicTestsToRun;
[SerializeField] private bool m_ReadyToRun;
private bool m_IsBuilding;
public static bool selectedInHierarchy;
private float m_HorizontalSplitBarPosition = 200;
private Vector2 m_TestInfoScroll, m_TestListScroll;
private IntegrationTestRendererBase[] m_TestLines;
private string m_CurrectSceneName;
private TestFilterSettings m_FilterSettings;
Vector2 m_resultTextSize;
string m_resultText;
GameObject m_lastSelectedGO;
int m_resultTestMaxLength = 15000;
[SerializeField] private GameObject m_SelectedLine;
[SerializeField] private List<TestResult> m_ResultList = new List<TestResult>();
[SerializeField] private List<GameObject> m_FoldMarkers = new List<GameObject>();
private IntegrationTestsRunnerSettings m_Settings;
#endregion
static IntegrationTestsRunnerWindow()
{
InitBackgroundRunners();
}
private static void InitBackgroundRunners()
{
EditorApplication.hierarchyWindowItemOnGUI -= OnHierarchyWindowItemDraw;
EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyWindowItemDraw;
EditorApplication.hierarchyWindowChanged -= OnHierarchyChangeUpdate;
EditorApplication.hierarchyWindowChanged += OnHierarchyChangeUpdate;
EditorApplication.update -= BackgroundSceneChangeWatch;
EditorApplication.update += BackgroundSceneChangeWatch;
EditorApplication.playmodeStateChanged -= OnPlaymodeStateChanged;
EditorApplication.playmodeStateChanged += OnPlaymodeStateChanged;
}
private static void OnPlaymodeStateChanged()
{
if (s_Instance && EditorApplication.isPlaying == EditorApplication.isPlayingOrWillChangePlaymode)
s_Instance.RebuildTestList();
}
public void OnDestroy()
{
EditorApplication.hierarchyWindowItemOnGUI -= OnHierarchyWindowItemDraw;
EditorApplication.update -= BackgroundSceneChangeWatch;
EditorApplication.hierarchyWindowChanged -= OnHierarchyChangeUpdate;
EditorApplication.playmodeStateChanged -= OnPlaymodeStateChanged;
TestComponent.DestroyAllDynamicTests();
}
private static void BackgroundSceneChangeWatch()
{
if (!s_Instance) return;
#if UNITY_5_3_OR_NEWER
var currentScene = SceneManager.GetActiveScene().path;
#else
var currentScene = Application.loadedLevelName;
#endif
if (s_Instance.m_CurrectSceneName != null && s_Instance.m_CurrectSceneName == currentScene) return;
if (EditorApplication.isPlayingOrWillChangePlaymode) return;
TestComponent.DestroyAllDynamicTests();
s_Instance.m_CurrectSceneName = currentScene;
s_Instance.m_ResultList.Clear();
s_Instance.RebuildTestList();
}
public void OnEnable()
{
#if UNITY_5_3_OR_NEWER
titleContent = new GUIContent("Integration Tests");
#else
title = "Integration Tests";
#endif
s_Instance = this;
m_Settings = ProjectSettingsBase.Load<IntegrationTestsRunnerSettings>();
m_FilterSettings = new TestFilterSettings("UnityTest.IntegrationTestsRunnerWindow");
InitBackgroundRunners();
if (!EditorApplication.isPlayingOrWillChangePlaymode && !m_ReadyToRun) RebuildTestList();
}
public void OnSelectionChange()
{
if (EditorApplication.isPlayingOrWillChangePlaymode
|| Selection.objects == null
|| Selection.objects.Length == 0) return;
if (Selection.gameObjects.Length == 1)
{
var go = Selection.gameObjects.Single();
var temp = go.transform;
while (temp != null)
{
var tc = temp.GetComponent<TestComponent>();
if (tc != null) break;
temp = temp.parent;
}
if (temp != null)
{
SelectInHierarchy(temp.gameObject);
Selection.activeGameObject = temp.gameObject;
m_SelectedLine = temp.gameObject;
}
}
}
public static void OnHierarchyChangeUpdate()
{
if (!s_Instance || s_Instance.m_TestLines == null || EditorApplication.isPlayingOrWillChangePlaymode) return;
// create a test runner if it doesn't exist
TestRunner.GetTestRunner();
// make tests are not places under a go that is not a test itself
foreach (var test in TestComponent.FindAllTestsOnScene())
{
if (test.gameObject.transform.parent != null && test.gameObject.transform.parent.gameObject.GetComponent<TestComponent>() == null)
{
test.gameObject.transform.parent = null;
Debug.LogWarning("Tests need to be on top of the hierarchy or directly under another test.");
}
}
if (selectedInHierarchy) selectedInHierarchy = false;
else s_Instance.RebuildTestList();
}
public static TestResult GetResultForTest(TestComponent tc)
{
if(!s_Instance) return new TestResult(tc);
return s_Instance.m_ResultList.FirstOrDefault(r => r.GameObject == tc.gameObject);
}
public static void OnHierarchyWindowItemDraw(int id, Rect rect)
{
var o = EditorUtility.InstanceIDToObject(id);
if (o is GameObject)
{
var go = o as GameObject;
if (Event.current.type == EventType.MouseDown
&& Event.current.button == 0
&& rect.Contains(Event.current.mousePosition))
{
var temp = go.transform;
while (temp != null)
{
var c = temp.GetComponent<TestComponent>();
if (c != null) break;
temp = temp.parent;
}
if (temp != null) SelectInHierarchy(temp.gameObject);
}
}
}
private static void SelectInHierarchy(GameObject gameObject)
{
if (!s_Instance) return;
if (gameObject == s_Instance.m_SelectedLine && gameObject.activeInHierarchy) return;
if (EditorApplication.isPlayingOrWillChangePlaymode) return;
if (!gameObject.activeSelf)
{
selectedInHierarchy = true;
gameObject.SetActive(true);
}
var tests = TestComponent.FindAllTestsOnScene();
var skipList = gameObject.GetComponentsInChildren(typeof(TestComponent), true).ToList();
tests.RemoveAll(skipList.Contains);
foreach (var test in tests)
{
var enable = test.GetComponentsInChildren(typeof(TestComponent), true).Any(c => c.gameObject == gameObject);
if (test.gameObject.activeSelf != enable) test.gameObject.SetActive(enable);
}
}
private void RunTests(IList<ITestComponent> tests)
{
if (!tests.Any() || EditorApplication.isCompiling || EditorApplication.isPlayingOrWillChangePlaymode)
return;
FocusWindowIfItsOpen(GetType());
var testComponents = tests.Where(t => t is TestComponent).Cast<TestComponent>().ToList();
var dynaminTests = testComponents.Where(t => t.dynamic).ToList();
m_DynamicTestsToRun = dynaminTests.Select(c => c.dynamicTypeName).ToList();
testComponents.RemoveAll(dynaminTests.Contains);
m_TestsToRun = testComponents.Select( tc => tc.gameObject ).ToList();
m_ReadyToRun = true;
TestComponent.DisableAllTests();
EditorApplication.isPlaying = true;
}
public void Update()
{
if (m_ReadyToRun && EditorApplication.isPlaying)
{
m_ReadyToRun = false;
var testRunner = TestRunner.GetTestRunner();
testRunner.TestRunnerCallback.Add(new RunnerCallback(this));
var testComponents = m_TestsToRun.Select(go => go.GetComponent<TestComponent>()).ToList();
testRunner.InitRunner(testComponents, m_DynamicTestsToRun);
}
}
private void RebuildTestList()
{
m_TestLines = null;
if (!TestComponent.AnyTestsOnScene()
&& !TestComponent.AnyDynamicTestForCurrentScene()) return;
if (!EditorApplication.isPlayingOrWillChangePlaymode)
{
var dynamicTestsOnScene = TestComponent.FindAllDynamicTestsOnScene();
#if UNITY_5_3_OR_NEWER
var dynamicTestTypes = TestComponent.GetTypesWithHelpAttribute(SceneManager.GetActiveScene().path);
#else
var dynamicTestTypes = TestComponent.GetTypesWithHelpAttribute(Application.loadedLevelName);
#endif
foreach (var dynamicTestType in dynamicTestTypes)
{
var existingTests = dynamicTestsOnScene.Where(component => component.dynamicTypeName == dynamicTestType.AssemblyQualifiedName);
if (existingTests.Any())
{
var testComponent = existingTests.Single();
foreach (var c in testComponent.gameObject.GetComponents<Component>())
{
var type = Type.GetType(testComponent.dynamicTypeName);
if (c is TestComponent || c is Transform || type.IsInstanceOfType(c)) continue;
DestroyImmediate(c);
}
dynamicTestsOnScene.Remove(existingTests.Single());
continue;
}
TestComponent.CreateDynamicTest(dynamicTestType);
}
foreach (var testComponent in dynamicTestsOnScene)
DestroyImmediate(testComponent.gameObject);
}
var topTestList = TestComponent.FindAllTopTestsOnScene();
var newResultList = new List<TestResult>();
m_TestLines = ParseTestList(topTestList, newResultList);
var oldDynamicResults = m_ResultList.Where(result => result.dynamicTest);
foreach (var oldResult in m_ResultList)
{
var result = newResultList.Find(r => r.Id == oldResult.Id);
if (result == null) continue;
result.Update(oldResult);
}
newResultList.AddRange(oldDynamicResults.Where(r => !newResultList.Contains(r)));
m_ResultList = newResultList;
IntegrationTestRendererBase.RunTest = RunTests;
IntegrationTestGroupLine.FoldMarkers = m_FoldMarkers;
IntegrationTestLine.Results = m_ResultList;
m_FilterSettings.UpdateCounters(m_ResultList.Cast<ITestResult>());
m_FoldMarkers.RemoveAll(o => o == null);
selectedInHierarchy = true;
Repaint();
}
private IntegrationTestRendererBase[] ParseTestList(List<TestComponent> testList, List<TestResult> results)
{
var tempList = new List<IntegrationTestRendererBase>();
foreach (var testObject in testList)
{
if (!testObject.IsTestGroup())
{
var result = new TestResult(testObject);
if (results != null)
results.Add(result);
tempList.Add(new IntegrationTestLine(testObject.gameObject, result));
continue;
}
var group = new IntegrationTestGroupLine(testObject.gameObject);
var children = testObject.gameObject.GetComponentsInChildren(typeof(TestComponent), true).Cast<TestComponent>().ToList();
children = children.Where(c => c.gameObject.transform.parent == testObject.gameObject.transform).ToList();
group.AddChildren(ParseTestList(children, results));
tempList.Add(group);
}
tempList.Sort();
return tempList.ToArray();
}
public void OnGUI()
{
if (BuildPipeline.isBuildingPlayer)
{
m_IsBuilding = true;
}
else if (m_IsBuilding)
{
m_IsBuilding = false;
Repaint();
}
PrintHeadPanel();
EditorGUILayout.BeginVertical(Styles.testList);
m_TestListScroll = EditorGUILayout.BeginScrollView(m_TestListScroll);
bool repaint = PrintTestList(m_TestLines);
GUILayout.FlexibleSpace();
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
RenderDetails();
if (repaint) Repaint();
}
public void PrintHeadPanel()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
if (GUILayout.Button(m_GUIRunAllTests, EditorStyles.toolbarButton))
{
RunTests(TestComponent.FindAllTestsOnScene().Cast<ITestComponent>().ToList());
}
EditorGUI.BeginDisabledGroup(!Selection.gameObjects.Any (t => t.GetComponent(typeof(ITestComponent))));
if (GUILayout.Button(m_GUIRunSelectedTests, EditorStyles.toolbarButton))
{
RunTests(Selection.gameObjects.Select(t => t.GetComponent(typeof(TestComponent))).Cast<ITestComponent>().ToList());
}
EditorGUI.EndDisabledGroup();
if (GUILayout.Button(m_GUICreateNewTest, EditorStyles.toolbarButton))
{
var test = TestComponent.CreateTest();
if (Selection.gameObjects.Length == 1
&& Selection.activeGameObject != null
&& Selection.activeGameObject.GetComponent<TestComponent>())
{
test.transform.parent = Selection.activeGameObject.transform.parent;
}
Selection.activeGameObject = test;
RebuildTestList();
}
EditorGUI.EndDisabledGroup();
GUILayout.FlexibleSpace ();
m_FilterSettings.OnGUI ();
EditorGUILayout.EndHorizontal ();
}
public void AddItemsToMenu(GenericMenu menu)
{
menu.AddItem(m_GUIBlockUI, m_Settings.blockUIWhenRunning, m_Settings.ToggleBlockUIWhenRunning);
menu.AddItem(m_GUIPauseOnFailure, m_Settings.pauseOnTestFailure, m_Settings.TogglePauseOnTestFailure);
}
private bool PrintTestList(IntegrationTestRendererBase[] renderedLines)
{
if (renderedLines == null) return false;
var filter = m_FilterSettings.BuildRenderingOptions();
bool repaint = false;
foreach (var renderedLine in renderedLines)
{
repaint |= renderedLine.Render(filter);
}
return repaint;
}
private void RenderDetails()
{
var ctrlId = GUIUtility.GetControlID(FocusType.Passive);
Rect rect = GUILayoutUtility.GetLastRect();
rect.y = rect.height + rect.y - 1;
rect.height = 3;
EditorGUIUtility.AddCursorRect(rect, MouseCursor.ResizeVertical);
var e = Event.current;
switch (e.type)
{
case EventType.MouseDown:
if (GUIUtility.hotControl == 0 && rect.Contains(e.mousePosition))
GUIUtility.hotControl = ctrlId;
break;
case EventType.MouseDrag:
if (GUIUtility.hotControl == ctrlId)
{
m_HorizontalSplitBarPosition -= e.delta.y;
if (m_HorizontalSplitBarPosition < 20) m_HorizontalSplitBarPosition = 20;
Repaint();
}
break;
case EventType.MouseUp:
if (GUIUtility.hotControl == ctrlId)
GUIUtility.hotControl = 0;
break;
}
m_TestInfoScroll = EditorGUILayout.BeginScrollView(m_TestInfoScroll, GUILayout.MinHeight(m_HorizontalSplitBarPosition));
if (m_SelectedLine != null)
UpdateResultText(m_SelectedLine);
EditorGUILayout.SelectableLabel(m_resultText, Styles.info,
GUILayout.ExpandHeight(true),
GUILayout.ExpandWidth(true),
GUILayout.MinWidth(m_resultTextSize.x),
GUILayout.MinHeight(m_resultTextSize.y));
EditorGUILayout.EndScrollView();
}
private void UpdateResultText(GameObject go)
{
if(go == m_lastSelectedGO) return;
m_lastSelectedGO = go;
var result = m_ResultList.Find(r => r.GameObject == go);
if (result == null)
{
m_resultText = string.Empty;
m_resultTextSize = Styles.info.CalcSize(new GUIContent(string.Empty));
return;
}
var sb = new StringBuilder(result.Name.Trim());
if (!string.IsNullOrEmpty(result.messages))
{
sb.Append("\n---\n");
sb.Append(result.messages.Trim());
}
if (!string.IsNullOrEmpty(result.stacktrace))
{
sb.Append("\n---\n");
sb.Append(result.stacktrace.Trim());
}
if(sb.Length>m_resultTestMaxLength)
{
sb.Length = m_resultTestMaxLength;
sb.AppendFormat("...\n\n---MESSAGE TRUNCATED AT {0} CHARACTERS---", m_resultTestMaxLength);
}
m_resultText = sb.ToString().Trim();
m_resultTextSize = Styles.info.CalcSize(new GUIContent(m_resultText));
}
public void OnInspectorUpdate()
{
if (focusedWindow != this) Repaint();
}
private void SetCurrentTest(TestComponent tc)
{
foreach (var line in m_TestLines)
line.SetCurrentTest(tc);
}
class RunnerCallback : ITestRunnerCallback
{
private readonly IntegrationTestsRunnerWindow m_Window;
private int m_TestNumber;
private int m_CurrentTestNumber;
private readonly bool m_ConsoleErrorOnPauseValue;
private readonly bool m_RunInBackground;
private TestComponent m_CurrentTest;
public RunnerCallback(IntegrationTestsRunnerWindow window)
{
m_Window = window;
m_ConsoleErrorOnPauseValue = GuiHelper.GetConsoleErrorPause();
GuiHelper.SetConsoleErrorPause(false);
m_RunInBackground = PlayerSettings.runInBackground;
PlayerSettings.runInBackground = true;
}
public void RunStarted(string platform, List<TestComponent> testsToRun)
{
EditorApplication.update += OnEditorUpdate;
m_TestNumber = testsToRun.Count;
foreach (var test in testsToRun)
{
var result = m_Window.m_ResultList.Find(r => r.TestComponent == test);
if (result != null) result.Reset();
}
}
public void RunFinished(List<TestResult> testResults)
{
m_Window.SetCurrentTest(null);
m_CurrentTest = null;
EditorApplication.update -= OnEditorUpdate;
EditorApplication.isPlaying = false;
EditorUtility.ClearProgressBar();
GuiHelper.SetConsoleErrorPause(m_ConsoleErrorOnPauseValue);
PlayerSettings.runInBackground = m_RunInBackground;
}
public void AllScenesFinished()
{
}
public void TestStarted(TestResult test)
{
m_Window.SetCurrentTest(test.TestComponent);
m_CurrentTest = test.TestComponent;
}
public void TestFinished(TestResult test)
{
m_CurrentTestNumber++;
var result = m_Window.m_ResultList.Find(r => r.Id == test.Id);
if (result != null)
result.Update(test);
else
m_Window.m_ResultList.Add(test);
if(test.IsFailure && m_Window.m_Settings.pauseOnTestFailure)
{
EditorUtility.ClearProgressBar();
EditorApplication.isPaused = true;
}
}
public void TestRunInterrupted(List<ITestComponent> testsNotRun)
{
Debug.Log("Test run interrupted");
RunFinished(new List<TestResult>());
}
private void OnEditorUpdate()
{
if(!EditorApplication.isPlaying)
{
TestRunInterrupted(null);
return;
}
if (m_Window.m_Settings.blockUIWhenRunning
&& m_CurrentTest != null
&& !EditorApplication.isPaused
&& EditorUtility.DisplayCancelableProgressBar("Integration Test Runner",
"Running " + m_CurrentTest.Name,
(float)m_CurrentTestNumber / m_TestNumber))
{
TestRunInterrupted(null);
}
}
}
[MenuItem("Unity Test Tools/Integration Test Runner %#&t")]
public static IntegrationTestsRunnerWindow ShowWindow()
{
var w = GetWindow(typeof(IntegrationTestsRunnerWindow));
w.Show();
return w as IntegrationTestsRunnerWindow;
}
}
}