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.
447 lines
15 KiB
447 lines
15 KiB
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, WorkItemFactory factory); |
|
} |
|
|
|
internal class UnityTestAssemblyRunnerFactory : IUnityTestAssemblyRunnerFactory |
|
{ |
|
public IUnityTestAssemblyRunner Create(TestPlatform testPlatform, WorkItemFactory factory) |
|
{ |
|
return new UnityTestAssemblyRunner(new UnityTestAssemblyBuilder(), 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] |
|
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) |
|
{ |
|
m_Filters = filters; |
|
m_TestPlatform = platform; |
|
m_AlreadyStartedTests = new List<string>(); |
|
m_ExecutedTests = new List<TestResultSerializer>(); |
|
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, 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)); |
|
loadedTests.ParseForNameDuplicates(); |
|
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()); |
|
} |
|
} |
|
}
|
|
|