314 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			314 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections;
 | |
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using Unity.Profiling;
 | |
| using UnityEditor.TestTools.TestRunner.Api;
 | |
| using UnityEditor.TestTools.TestRunner.TestRun.Tasks;
 | |
| using UnityEngine;
 | |
| using UnityEngine.TestTools;
 | |
| 
 | |
| namespace UnityEditor.TestTools.TestRunner.TestRun
 | |
| {
 | |
|     internal class TestJobRunner : ITestJobRunner
 | |
|     {
 | |
|         internal ITestJobDataHolder testJobDataHolder = TestJobDataHolder.instance;
 | |
| 
 | |
|         internal Action<EditorApplication.CallbackFunction> SubscribeCallback =
 | |
|             callback => EditorApplication.update += callback;
 | |
| 
 | |
|         // ReSharper disable once DelegateSubtraction
 | |
|         internal Action<EditorApplication.CallbackFunction> UnsubscribeCallback =
 | |
|             callback => EditorApplication.update -= callback;
 | |
| 
 | |
|         internal TestCommandPcHelper PcHelper = new EditModePcHelper();
 | |
|         internal Func<ExecutionSettings, IEnumerable<TestTaskBase>> GetTasks = TaskList.GetTaskList;
 | |
|         internal Action<Exception> LogException = Debug.LogException;
 | |
|         internal Action<string> LogError = Debug.LogError;
 | |
|         internal Action<string> ReportRunFailed = CallbacksDelegator.instance.RunFailed;
 | |
|         internal Func<TestRunnerApi.RunProgressChangedEvent> RunProgressChanged = () => TestRunnerApi.runProgressChanged;
 | |
| 
 | |
|         private TestJobData m_JobData;
 | |
|         private IEnumerator m_Enumerator;
 | |
|         private string m_CurrentTaskName;
 | |
| 
 | |
|         public string RunJob(TestJobData data)
 | |
|         {
 | |
|             if (data == null)
 | |
|             {
 | |
|                 throw new ArgumentException(null, nameof(data));
 | |
|             }
 | |
| 
 | |
|             if (data.taskInfoStack == null)
 | |
|             {
 | |
|                 throw new ArgumentException($"{nameof(data.taskInfoStack)} on {nameof(TestJobData)} is null.",
 | |
|                     nameof(data));
 | |
|             }
 | |
| 
 | |
|             if (IsRunningJob())
 | |
|             {
 | |
|                 throw new Exception("TestJobRunner is already running a job.");
 | |
|             }
 | |
| 
 | |
|             if (data.isHandledByRunner)
 | |
|             {
 | |
|                 throw new Exception("Test job is already being handled.");
 | |
|             }
 | |
| 
 | |
|             m_JobData = data;
 | |
|             m_JobData.isHandledByRunner = true;
 | |
| 
 | |
|             if (!IsRunningJob())
 | |
|             {
 | |
|                 m_JobData.isRunning = true;
 | |
|                 m_JobData.taskInfoStack.Push(new TaskInfo());
 | |
|                 testJobDataHolder.RegisterRun(this, m_JobData);
 | |
|             }
 | |
|             else // Is resuming run
 | |
|             {
 | |
|                 var taskInfoBeforeResuming = m_JobData.taskInfoStack.Peek();
 | |
|                 if (taskInfoBeforeResuming.taskMode != TaskMode.Resume)
 | |
|                 {
 | |
|                     m_JobData.taskInfoStack.Push(new TaskInfo
 | |
|                     {
 | |
|                         taskMode = TaskMode.Resume,
 | |
|                         index = 0,
 | |
|                         stopBeforeIndex = taskInfoBeforeResuming.index + (taskInfoBeforeResuming.pc > 0 ? 1 : 0)
 | |
|                     });
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     taskInfoBeforeResuming.index = 0;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             m_JobData.Tasks = GetTasks(data.executionSettings).ToArray();
 | |
|             if (m_JobData.Tasks.Length == 0)
 | |
|             {
 | |
|                 throw new Exception($"No tasks founds for {data.executionSettings}");
 | |
|             }
 | |
| 
 | |
|             if (!data.executionSettings.runSynchronously)
 | |
|             {
 | |
|                 SubscribeCallback(ExecuteCallback);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 while (data.isRunning)
 | |
|                 {
 | |
|                     ExecuteStep();
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return data.guid;
 | |
|         }
 | |
| 
 | |
|         private void ExecuteCallback()
 | |
|         {
 | |
|             ExecuteStep();
 | |
|             var c = 0;
 | |
|             while (ShouldExecuteInstantly())
 | |
|             {
 | |
|                 ExecuteStep();
 | |
|                 c++;
 | |
| 
 | |
|                 if (c > 500)
 | |
|                 {
 | |
|                     var taskInfo = m_JobData.taskInfoStack.Peek();
 | |
|                     var taskName = taskInfo != null ? m_JobData.Tasks[taskInfo.index].GetType().Name : "null";
 | |
|                     Debug.LogError(
 | |
|                         $"Too many instant steps in test execution mode: {taskInfo?.taskMode}. Current task {taskName}.");
 | |
|                     StopRun();
 | |
|                     return;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private void ExecuteStep()
 | |
|         {
 | |
|             using (new ProfilerMarker(nameof(TestJobRunner) + "." + nameof(ExecuteStep)).Auto())
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     if (m_JobData.taskInfoStack.Count == 0)
 | |
|                     {
 | |
|                         StopRun();
 | |
|                         return;
 | |
|                     }
 | |
| 
 | |
|                     var taskInfo = m_JobData.taskInfoStack.Peek();
 | |
| 
 | |
|                     if (m_Enumerator == null)
 | |
|                     {
 | |
|                         if (taskInfo.index >= m_JobData.Tasks.Length || (taskInfo.stopBeforeIndex > 0 &&
 | |
|                                                                          taskInfo.index >= taskInfo.stopBeforeIndex))
 | |
|                         {
 | |
|                             m_JobData.taskInfoStack.Pop();
 | |
|                             return;
 | |
|                         }
 | |
| 
 | |
|                         var task = m_JobData.Tasks[taskInfo.index];
 | |
|                         if (!task.ShouldExecute(taskInfo))
 | |
|                         {
 | |
|                             taskInfo.index++;
 | |
|                             return;
 | |
|                         }
 | |
| 
 | |
|                         m_JobData.runProgress.stepName = task.GetTitle();
 | |
|                         m_CurrentTaskName = task.GetName();
 | |
|                         using (new ProfilerMarker(m_CurrentTaskName + ".Setup").Auto())
 | |
|                         {
 | |
|                             m_Enumerator = task.Execute(m_JobData);
 | |
|                         }
 | |
| 
 | |
|                         if (task.SupportsResumingEnumerator)
 | |
|                         {
 | |
|                             m_Enumerator.MoveNext(); // Execute the first step, to set the job data.
 | |
|                             PcHelper.SetEnumeratorPC(m_Enumerator, taskInfo.pc);
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     using (new ProfilerMarker(m_CurrentTaskName + ".Progress").Auto())
 | |
|                     {
 | |
|                         var taskIsDone = !m_Enumerator.MoveNext();
 | |
|                         if (!m_JobData.executionSettings.runSynchronously && taskInfo.taskMode == TaskMode.Normal)
 | |
|                         {
 | |
|                             if (taskIsDone)
 | |
|                             {
 | |
|                                 m_JobData.runProgress.progress += RunProgress.progressPrTask;
 | |
|                             }
 | |
|                             ReportRunProgress(false);
 | |
|                         }
 | |
| 
 | |
|                         if (taskIsDone)
 | |
|                         {
 | |
|                             taskInfo.index++;
 | |
|                             taskInfo.pc = 0;
 | |
|                             m_Enumerator = null;
 | |
| 
 | |
|                             return;
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     if (m_JobData.Tasks[taskInfo.index].SupportsResumingEnumerator)
 | |
|                     {
 | |
|                         taskInfo.pc = PcHelper.GetEnumeratorPC(m_Enumerator);
 | |
|                     }
 | |
|                 }
 | |
|                 catch (TestRunCanceledException)
 | |
|                 {
 | |
|                     StopRun();
 | |
|                 }
 | |
|                 catch (AggregateException ex)
 | |
|                 {
 | |
|                     MarkJobAsError();
 | |
|                     LogError(ex.Message);
 | |
|                     foreach (var innerException in ex.InnerExceptions)
 | |
|                     {
 | |
|                         LogException(innerException);
 | |
|                     }
 | |
| 
 | |
|                     ReportRunFailed("Multiple unexpected errors happened while running tests.");
 | |
|                 }
 | |
|                 catch (Exception ex)
 | |
|                 {
 | |
|                     MarkJobAsError();
 | |
|                     LogException(ex);
 | |
|                     ReportRunFailed("An unexpected error happened while running tests.");
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public bool CancelRun()
 | |
|         {
 | |
|             if (m_JobData == null || m_JobData.taskInfoStack.Count == 0 ||
 | |
|                 m_JobData.taskInfoStack.Peek().taskMode == TaskMode.Canceled)
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             var lastIndex = m_JobData.taskInfoStack.Last().index;
 | |
|             m_JobData.taskInfoStack.Clear();
 | |
|             m_JobData.taskInfoStack.Push(new TaskInfo
 | |
|             {
 | |
|                 index = lastIndex,
 | |
|                 taskMode = TaskMode.Canceled
 | |
|             });
 | |
|             m_Enumerator = null;
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         private bool ShouldExecuteInstantly()
 | |
|         {
 | |
|             if (m_JobData.taskInfoStack.Count == 0)
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             var taskInfo = m_JobData.taskInfoStack.Peek();
 | |
|             var canRunInstantly =
 | |
|                 m_JobData.Tasks.Length <= taskInfo.index || m_JobData.Tasks[taskInfo.index].CanRunInstantly;
 | |
|             return taskInfo.taskMode != TaskMode.Normal && taskInfo.taskMode != TaskMode.Canceled && canRunInstantly;
 | |
|         }
 | |
| 
 | |
|         public bool IsRunningJob()
 | |
|         {
 | |
|             return m_JobData != null && m_JobData.taskInfoStack != null && m_JobData.taskInfoStack.Count > 0;
 | |
|         }
 | |
| 
 | |
|         public TestJobData GetData()
 | |
|         {
 | |
|             return m_JobData;
 | |
|         }
 | |
| 
 | |
|         private void StopRun()
 | |
|         {
 | |
|             m_JobData.isRunning = false;
 | |
|             UnsubscribeCallback(ExecuteCallback);
 | |
|             testJobDataHolder.UnregisterRun(this, m_JobData);
 | |
| 
 | |
|             foreach (var task in m_JobData.Tasks)
 | |
|             {
 | |
|                 if (task is IDisposable disposableTask)
 | |
|                 {
 | |
|                     try
 | |
|                     {
 | |
|                         disposableTask.Dispose();
 | |
|                     }
 | |
|                     catch (Exception e)
 | |
|                     {
 | |
|                         Debug.LogException(e);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (!m_JobData.executionSettings.runSynchronously)
 | |
|             {
 | |
|                 ReportRunProgress(true);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private void ReportRunProgress(bool runHasFinished)
 | |
|         {
 | |
|             RunProgressChanged().Invoke(new TestRunProgress
 | |
|             {
 | |
|                 CurrentStageName = m_JobData.runProgress.stageName ?? "",
 | |
|                 CurrentStepName = m_JobData.runProgress.stepName ?? "",
 | |
|                 Progress = m_JobData.runProgress.progress,
 | |
|                 ExecutionSettings = m_JobData.executionSettings,
 | |
|                 RunGuid = m_JobData.guid ?? "",
 | |
|                 HasFinished = runHasFinished,
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         private void MarkJobAsError()
 | |
|         {
 | |
|             var currentTaskInfo = m_JobData.taskInfoStack.Peek();
 | |
|             currentTaskInfo.taskMode = TaskMode.Error;
 | |
|             currentTaskInfo.index++;
 | |
|             currentTaskInfo.pc = 0;
 | |
|             m_Enumerator = null;
 | |
|         }
 | |
|     }
 | |
| }
 |