451 lines
16 KiB
C#
451 lines
16 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using NUnit.Framework.Interfaces;
|
|
using NUnit.Framework.Internal;
|
|
using NUnit.Framework.Internal.Filters;
|
|
using UnityEngine;
|
|
using UnityEngine.TestTools.NUnitExtensions;
|
|
using UnityEngine.TestTools.TestRunner;
|
|
using UnityEngine.TestTools;
|
|
using UnityEngine.TestTools.TestRunner.GUI;
|
|
using UnityEditor.Callbacks;
|
|
using UnityEditor.TestTools.TestRunner.Api;
|
|
using UnityEngine.TestRunner.NUnitExtensions;
|
|
using UnityEngine.TestRunner.NUnitExtensions.Runner;
|
|
|
|
namespace UnityEditor.TestTools.TestRunner
|
|
{
|
|
internal interface IUnityTestAssemblyRunnerFactory
|
|
{
|
|
IUnityTestAssemblyRunner Create(TestPlatform testPlatform, string[] orderedTestNames, WorkItemFactory factory);
|
|
}
|
|
|
|
internal class UnityTestAssemblyRunnerFactory : IUnityTestAssemblyRunnerFactory
|
|
{
|
|
public IUnityTestAssemblyRunner Create(TestPlatform testPlatform, string[] orderedTestNames, WorkItemFactory factory)
|
|
{
|
|
return new UnityTestAssemblyRunner(new UnityTestAssemblyBuilder(orderedTestNames), factory);
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
internal class EditModeRunner : ScriptableObject, IDisposable
|
|
{
|
|
[SerializeField]
|
|
private Filter[] m_Filters;
|
|
|
|
//The counter from the IEnumerator object
|
|
[SerializeField]
|
|
private int m_CurrentPC;
|
|
|
|
[SerializeField]
|
|
private bool m_ExecuteOnEnable;
|
|
|
|
[SerializeField]
|
|
private List<string> m_AlreadyStartedTests;
|
|
|
|
[SerializeField]
|
|
private List<TestResultSerializer> m_ExecutedTests;
|
|
|
|
[SerializeField]
|
|
private List<ScriptableObject> m_CallbackObjects = new List<ScriptableObject>();
|
|
|
|
[SerializeField]
|
|
private TestStartedEvent m_TestStartedEvent = new TestStartedEvent();
|
|
|
|
[SerializeField]
|
|
private TestFinishedEvent m_TestFinishedEvent = new TestFinishedEvent();
|
|
|
|
[SerializeField]
|
|
private RunStartedEvent m_RunStartedEvent = new RunStartedEvent();
|
|
|
|
[SerializeField]
|
|
private RunFinishedEvent m_RunFinishedEvent = new RunFinishedEvent();
|
|
|
|
[SerializeField]
|
|
private TestRunnerStateSerializer m_TestRunnerStateSerializer = new TestRunnerStateSerializer();
|
|
|
|
[SerializeField]
|
|
private bool m_RunningTests;
|
|
|
|
[SerializeField]
|
|
private TestPlatform m_TestPlatform;
|
|
|
|
[SerializeField]
|
|
private object m_CurrentYieldObject;
|
|
|
|
[SerializeField]
|
|
private BeforeAfterTestCommandState m_SetUpTearDownState;
|
|
[SerializeField]
|
|
private BeforeAfterTestCommandState m_OuterUnityTestActionState;
|
|
|
|
[SerializeField]
|
|
private EnumerableTestState m_EnumerableTestState;
|
|
|
|
[SerializeField]
|
|
private string[] m_OrderedTestNames;
|
|
|
|
[SerializeField]
|
|
public bool RunFinished = false;
|
|
|
|
public bool RunningSynchronously { get; private set; }
|
|
|
|
internal IUnityTestAssemblyRunner m_Runner;
|
|
|
|
private ConstructDelegator m_ConstructDelegator;
|
|
|
|
private IEnumerator m_RunStep;
|
|
|
|
public IUnityTestAssemblyRunnerFactory UnityTestAssemblyRunnerFactory { get; set; }
|
|
|
|
public void Init(Filter[] filters, TestPlatform platform, bool runningSynchronously, string[] orderedTestNames)
|
|
{
|
|
m_Filters = filters;
|
|
m_TestPlatform = platform;
|
|
m_AlreadyStartedTests = new List<string>();
|
|
m_ExecutedTests = new List<TestResultSerializer>();
|
|
m_OrderedTestNames = orderedTestNames;
|
|
RunningSynchronously = runningSynchronously;
|
|
InitRunner();
|
|
}
|
|
|
|
private void InitRunner()
|
|
{
|
|
//We give the EditMode platform here so we dont suddenly create Playmode work items in the test Runner.
|
|
m_Runner = (UnityTestAssemblyRunnerFactory ?? new UnityTestAssemblyRunnerFactory()).Create(TestPlatform.EditMode, m_OrderedTestNames, new EditmodeWorkItemFactory());
|
|
var testAssemblyProvider = new EditorLoadedTestAssemblyProvider(new EditorCompilationInterfaceProxy(), new EditorAssembliesProxy());
|
|
var assemblies = testAssemblyProvider.GetAssembliesGroupedByType(m_TestPlatform).Select(x => x.Assembly).ToArray();
|
|
var loadedTests = m_Runner.Load(assemblies, TestPlatform.EditMode,
|
|
UnityTestAssemblyBuilder.GetNUnitTestBuilderSettings(m_TestPlatform));
|
|
CallbacksDelegator.instance.TestTreeRebuild(loadedTests);
|
|
hideFlags |= HideFlags.DontSave;
|
|
EnumerableSetUpTearDownCommand.ActivePcHelper = new EditModePcHelper();
|
|
OuterUnityTestActionCommand.ActivePcHelper = new EditModePcHelper();
|
|
}
|
|
|
|
public void OnEnable()
|
|
{
|
|
if (m_ExecuteOnEnable)
|
|
{
|
|
InitRunner();
|
|
m_ExecuteOnEnable = false;
|
|
foreach (var callback in m_CallbackObjects)
|
|
{
|
|
AddListeners(callback as ITestRunnerListener);
|
|
}
|
|
m_ConstructDelegator = new ConstructDelegator(m_TestRunnerStateSerializer);
|
|
|
|
EnumeratorStepHelper.SetEnumeratorPC(m_CurrentPC);
|
|
|
|
UnityWorkItemDataHolder.alreadyExecutedTests = m_ExecutedTests.Select(x => x.uniqueName).ToList();
|
|
UnityWorkItemDataHolder.alreadyStartedTests = m_AlreadyStartedTests;
|
|
Run();
|
|
}
|
|
}
|
|
|
|
public void TestStartedEvent(ITest test)
|
|
{
|
|
m_AlreadyStartedTests.Add(test.GetUniqueName());
|
|
}
|
|
|
|
public void TestFinishedEvent(ITestResult testResult)
|
|
{
|
|
m_AlreadyStartedTests.Remove(testResult.Test.GetUniqueName());
|
|
m_ExecutedTests.Add(TestResultSerializer.MakeFromTestResult(testResult));
|
|
}
|
|
|
|
public void Run()
|
|
{
|
|
EditModeTestCallbacks.RestoringTestContext += OnRestoringTest;
|
|
var context = m_Runner.GetCurrentContext();
|
|
if (m_SetUpTearDownState == null)
|
|
{
|
|
m_SetUpTearDownState = CreateInstance<BeforeAfterTestCommandState>();
|
|
}
|
|
context.SetUpTearDownState = m_SetUpTearDownState;
|
|
|
|
if (m_OuterUnityTestActionState == null)
|
|
{
|
|
m_OuterUnityTestActionState = CreateInstance<BeforeAfterTestCommandState>();
|
|
}
|
|
context.OuterUnityTestActionState = m_OuterUnityTestActionState;
|
|
|
|
if (m_EnumerableTestState == null)
|
|
{
|
|
m_EnumerableTestState = new EnumerableTestState();
|
|
}
|
|
context.EnumerableTestState = m_EnumerableTestState;
|
|
|
|
if (!m_RunningTests)
|
|
{
|
|
m_RunStartedEvent.Invoke(m_Runner.LoadedTest);
|
|
}
|
|
|
|
if (m_ConstructDelegator == null)
|
|
m_ConstructDelegator = new ConstructDelegator(m_TestRunnerStateSerializer);
|
|
|
|
Reflect.ConstructorCallWrapper = m_ConstructDelegator.Delegate;
|
|
m_TestStartedEvent.AddListener(TestStartedEvent);
|
|
m_TestFinishedEvent.AddListener(TestFinishedEvent);
|
|
|
|
AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload;
|
|
|
|
RunningTests = true;
|
|
|
|
EditorApplication.LockReloadAssemblies();
|
|
|
|
var testListenerWrapper = new TestListenerWrapper(m_TestStartedEvent, m_TestFinishedEvent);
|
|
m_RunStep = m_Runner.Run(testListenerWrapper, GetFilter()).GetEnumerator();
|
|
m_RunningTests = true;
|
|
|
|
if (!RunningSynchronously)
|
|
EditorApplication.update += TestConsumer;
|
|
}
|
|
|
|
public void CompleteSynchronously()
|
|
{
|
|
while (!m_Runner.IsTestComplete)
|
|
TestConsumer();
|
|
}
|
|
|
|
private void OnBeforeAssemblyReload()
|
|
{
|
|
EditorApplication.update -= TestConsumer;
|
|
|
|
if (m_ExecuteOnEnable)
|
|
{
|
|
AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload;
|
|
return;
|
|
}
|
|
|
|
if (m_Runner != null && m_Runner.TopLevelWorkItem != null)
|
|
m_Runner.TopLevelWorkItem.ResultedInDomainReload = true;
|
|
|
|
if (RunningTests)
|
|
{
|
|
Debug.LogError("TestRunner: Unexpected assembly reload happened while running tests");
|
|
|
|
EditorUtility.ClearProgressBar();
|
|
|
|
if (m_Runner.GetCurrentContext() != null && m_Runner.GetCurrentContext().CurrentResult != null)
|
|
{
|
|
m_Runner.GetCurrentContext().CurrentResult.SetResult(ResultState.Cancelled, "Unexpected assembly reload happened");
|
|
}
|
|
OnRunCancel();
|
|
}
|
|
}
|
|
|
|
private bool RunningTests;
|
|
|
|
private Stack<IEnumerator> StepStack = new Stack<IEnumerator>();
|
|
|
|
private bool MoveNextAndUpdateYieldObject()
|
|
{
|
|
var result = m_RunStep.MoveNext();
|
|
|
|
if (result)
|
|
{
|
|
m_CurrentYieldObject = m_RunStep.Current;
|
|
while (m_CurrentYieldObject is IEnumerator) // going deeper
|
|
{
|
|
var currentEnumerator = (IEnumerator)m_CurrentYieldObject;
|
|
|
|
// go deeper and add parent to stack
|
|
StepStack.Push(m_RunStep);
|
|
|
|
m_RunStep = currentEnumerator;
|
|
m_CurrentYieldObject = m_RunStep.Current;
|
|
}
|
|
|
|
if (StepStack.Count > 0 && m_CurrentYieldObject != null) // not null and not IEnumerator, nested
|
|
{
|
|
Debug.LogError("EditMode test can only yield null, but not <" + m_CurrentYieldObject.GetType().Name + ">");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (StepStack.Count == 0) // done
|
|
return false;
|
|
|
|
m_RunStep = StepStack.Pop(); // going up
|
|
return MoveNextAndUpdateYieldObject();
|
|
}
|
|
|
|
private void TestConsumer()
|
|
{
|
|
var moveNext = MoveNextAndUpdateYieldObject();
|
|
|
|
if (m_CurrentYieldObject != null)
|
|
{
|
|
InvokeDelegator();
|
|
}
|
|
|
|
if (!moveNext && !m_Runner.IsTestComplete)
|
|
{
|
|
CompleteTestRun();
|
|
throw new IndexOutOfRangeException("There are no more elements to process and IsTestComplete is false");
|
|
}
|
|
|
|
if (m_Runner.IsTestComplete)
|
|
{
|
|
CompleteTestRun();
|
|
}
|
|
}
|
|
|
|
private void CompleteTestRun()
|
|
{
|
|
if (!RunningSynchronously)
|
|
EditorApplication.update -= TestConsumer;
|
|
|
|
TestLauncherBase.ExecutePostBuildCleanupMethods(this.GetLoadedTests(), this.GetFilter(), Application.platform);
|
|
|
|
m_RunFinishedEvent.Invoke(m_Runner.Result);
|
|
RunFinished = true;
|
|
|
|
if (m_ConstructDelegator != null)
|
|
m_ConstructDelegator.DestroyCurrentTestObjectIfExists();
|
|
Dispose();
|
|
UnityWorkItemDataHolder.alreadyExecutedTests = null;
|
|
}
|
|
|
|
private void OnRestoringTest()
|
|
{
|
|
var item = m_ExecutedTests.Find(t => t.fullName == UnityTestExecutionContext.CurrentContext.CurrentTest.FullName);
|
|
if (item != null)
|
|
{
|
|
item.RestoreTestResult(UnityTestExecutionContext.CurrentContext.CurrentResult);
|
|
}
|
|
}
|
|
|
|
private static bool IsCancelled()
|
|
{
|
|
return UnityTestExecutionContext.CurrentContext.ExecutionStatus == TestExecutionStatus.AbortRequested || UnityTestExecutionContext.CurrentContext.ExecutionStatus == TestExecutionStatus.StopRequested;
|
|
}
|
|
|
|
private void InvokeDelegator()
|
|
{
|
|
if (m_CurrentYieldObject == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (IsCancelled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_CurrentYieldObject is RestoreTestContextAfterDomainReload)
|
|
{
|
|
if (m_TestRunnerStateSerializer.ShouldRestore())
|
|
{
|
|
m_TestRunnerStateSerializer.RestoreContext();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (m_CurrentYieldObject is IEditModeTestYieldInstruction)
|
|
{
|
|
var editModeTestYieldInstruction = (IEditModeTestYieldInstruction)m_CurrentYieldObject;
|
|
if (editModeTestYieldInstruction.ExpectDomainReload)
|
|
{
|
|
PrepareForDomainReload();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UnityTestExecutionContext.CurrentContext.CurrentResult.RecordException(e);
|
|
return;
|
|
}
|
|
|
|
Debug.LogError("EditMode test can only yield null");
|
|
}
|
|
|
|
private void CompilationFailureWatch()
|
|
{
|
|
if (EditorApplication.isCompiling)
|
|
return;
|
|
|
|
EditorApplication.update -= CompilationFailureWatch;
|
|
|
|
if (EditorUtility.scriptCompilationFailed)
|
|
{
|
|
EditorUtility.ClearProgressBar();
|
|
OnRunCancel();
|
|
}
|
|
}
|
|
|
|
private void PrepareForDomainReload()
|
|
{
|
|
m_TestRunnerStateSerializer.SaveContext();
|
|
m_CurrentPC = EnumeratorStepHelper.GetEnumeratorPC(TestEnumerator.Enumerator);
|
|
m_ExecuteOnEnable = true;
|
|
|
|
RunningTests = false;
|
|
}
|
|
|
|
public T AddEventHandler<T>() where T : ScriptableObject, ITestRunnerListener
|
|
{
|
|
var eventHandler = CreateInstance<T>();
|
|
eventHandler.hideFlags |= HideFlags.DontSave;
|
|
m_CallbackObjects.Add(eventHandler);
|
|
|
|
AddListeners(eventHandler);
|
|
|
|
return eventHandler;
|
|
}
|
|
|
|
private void AddListeners(ITestRunnerListener eventHandler)
|
|
{
|
|
m_TestStartedEvent.AddListener(eventHandler.TestStarted);
|
|
m_TestFinishedEvent.AddListener(eventHandler.TestFinished);
|
|
m_RunStartedEvent.AddListener(eventHandler.RunStarted);
|
|
m_RunFinishedEvent.AddListener(eventHandler.RunFinished);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Reflect.MethodCallWrapper = null;
|
|
EditorApplication.update -= TestConsumer;
|
|
|
|
DestroyImmediate(this);
|
|
|
|
if (m_CallbackObjects != null)
|
|
{
|
|
foreach (var obj in m_CallbackObjects)
|
|
{
|
|
DestroyImmediate(obj);
|
|
}
|
|
m_CallbackObjects.Clear();
|
|
}
|
|
RunningTests = false;
|
|
EditorApplication.UnlockReloadAssemblies();
|
|
}
|
|
|
|
public void OnRunCancel()
|
|
{
|
|
UnityWorkItemDataHolder.alreadyExecutedTests = null;
|
|
m_ExecuteOnEnable = false;
|
|
m_Runner.StopRun();
|
|
RunFinished = true;
|
|
}
|
|
|
|
public ITest GetLoadedTests()
|
|
{
|
|
return m_Runner.LoadedTest;
|
|
}
|
|
|
|
public ITestFilter GetFilter()
|
|
{
|
|
return new OrFilter(m_Filters.Select(filter => filter.ToRuntimeTestRunnerFilter(RunningSynchronously).BuildNUnitFilter()).ToArray());
|
|
}
|
|
}
|
|
}
|