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; | ||
|  |         } | ||
|  |     } | ||
|  | } |