A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections; 3using System.Collections.Generic; 4using System.Linq; 5using Unity.Profiling; 6using UnityEditor.TestTools.TestRunner.Api; 7using UnityEditor.TestTools.TestRunner.TestRun.Tasks; 8using UnityEngine; 9using UnityEngine.TestTools; 10 11namespace UnityEditor.TestTools.TestRunner.TestRun 12{ 13 internal class TestJobRunner : ITestJobRunner 14 { 15 internal ITestJobDataHolder testJobDataHolder = TestJobDataHolder.instance; 16 17 internal Action<EditorApplication.CallbackFunction> SubscribeCallback = 18 callback => EditorApplication.update += callback; 19 20 // ReSharper disable once DelegateSubtraction 21 internal Action<EditorApplication.CallbackFunction> UnsubscribeCallback = 22 callback => EditorApplication.update -= callback; 23 24 internal TestCommandPcHelper PcHelper = new EditModePcHelper(); 25 internal Func<ExecutionSettings, IEnumerable<TestTaskBase>> GetTasks = TaskList.GetTaskList; 26 internal Action<Exception> LogException = Debug.LogException; 27 internal Action<string> LogError = Debug.LogError; 28 internal Action<string> ReportRunFailed = CallbacksDelegator.instance.RunFailed; 29 internal Func<TestRunnerApi.RunProgressChangedEvent> RunProgressChanged = () => TestRunnerApi.runProgressChanged; 30 31 private TestJobData m_JobData; 32 private IEnumerator m_Enumerator; 33 private string m_CurrentTaskName; 34 35 public string RunJob(TestJobData data) 36 { 37 if (data == null) 38 { 39 throw new ArgumentException(null, nameof(data)); 40 } 41 42 if (data.taskInfoStack == null) 43 { 44 throw new ArgumentException($"{nameof(data.taskInfoStack)} on {nameof(TestJobData)} is null.", 45 nameof(data)); 46 } 47 48 if (IsRunningJob()) 49 { 50 throw new Exception("TestJobRunner is already running a job."); 51 } 52 53 if (data.isHandledByRunner) 54 { 55 throw new Exception("Test job is already being handled."); 56 } 57 58 m_JobData = data; 59 m_JobData.isHandledByRunner = true; 60 61 if (!IsRunningJob()) 62 { 63 m_JobData.isRunning = true; 64 m_JobData.taskInfoStack.Push(new TaskInfo()); 65 testJobDataHolder.RegisterRun(this, m_JobData); 66 } 67 else // Is resuming run 68 { 69 var taskInfoBeforeResuming = m_JobData.taskInfoStack.Peek(); 70 if (taskInfoBeforeResuming.taskMode != TaskMode.Resume) 71 { 72 m_JobData.taskInfoStack.Push(new TaskInfo 73 { 74 taskMode = TaskMode.Resume, 75 index = 0, 76 stopBeforeIndex = taskInfoBeforeResuming.index + (taskInfoBeforeResuming.pc > 0 ? 1 : 0) 77 }); 78 } 79 else 80 { 81 taskInfoBeforeResuming.index = 0; 82 } 83 } 84 85 m_JobData.Tasks = GetTasks(data.executionSettings).ToArray(); 86 if (m_JobData.Tasks.Length == 0) 87 { 88 throw new Exception($"No tasks founds for {data.executionSettings}"); 89 } 90 91 if (!data.executionSettings.runSynchronously) 92 { 93 SubscribeCallback(ExecuteCallback); 94 } 95 else 96 { 97 while (data.isRunning) 98 { 99 ExecuteStep(); 100 } 101 } 102 103 return data.guid; 104 } 105 106 private void ExecuteCallback() 107 { 108 ExecuteStep(); 109 var c = 0; 110 while (ShouldExecuteInstantly()) 111 { 112 ExecuteStep(); 113 c++; 114 115 if (c > 500) 116 { 117 var taskInfo = m_JobData.taskInfoStack.Peek(); 118 var taskName = taskInfo != null ? m_JobData.Tasks[taskInfo.index].GetType().Name : "null"; 119 Debug.LogError( 120 $"Too many instant steps in test execution mode: {taskInfo?.taskMode}. Current task {taskName}."); 121 StopRun(); 122 return; 123 } 124 } 125 } 126 127 private void ExecuteStep() 128 { 129 using (new ProfilerMarker(nameof(TestJobRunner) + "." + nameof(ExecuteStep)).Auto()) 130 { 131 try 132 { 133 if (m_JobData.taskInfoStack.Count == 0) 134 { 135 StopRun(); 136 return; 137 } 138 139 var taskInfo = m_JobData.taskInfoStack.Peek(); 140 141 if (m_Enumerator == null) 142 { 143 if (taskInfo.index >= m_JobData.Tasks.Length || (taskInfo.stopBeforeIndex > 0 && 144 taskInfo.index >= taskInfo.stopBeforeIndex)) 145 { 146 m_JobData.taskInfoStack.Pop(); 147 return; 148 } 149 150 var task = m_JobData.Tasks[taskInfo.index]; 151 if (!task.ShouldExecute(taskInfo)) 152 { 153 taskInfo.index++; 154 return; 155 } 156 157 m_JobData.runProgress.stepName = task.GetTitle(); 158 m_CurrentTaskName = task.GetName(); 159 using (new ProfilerMarker(m_CurrentTaskName + ".Setup").Auto()) 160 { 161 m_Enumerator = task.Execute(m_JobData); 162 } 163 164 if (task.SupportsResumingEnumerator) 165 { 166 m_Enumerator.MoveNext(); // Execute the first step, to set the job data. 167 PcHelper.SetEnumeratorPC(m_Enumerator, taskInfo.pc); 168 } 169 } 170 171 using (new ProfilerMarker(m_CurrentTaskName + ".Progress").Auto()) 172 { 173 var taskIsDone = !m_Enumerator.MoveNext(); 174 if (!m_JobData.executionSettings.runSynchronously && taskInfo.taskMode == TaskMode.Normal) 175 { 176 if (taskIsDone) 177 { 178 m_JobData.runProgress.progress += RunProgress.progressPrTask; 179 } 180 ReportRunProgress(false); 181 } 182 183 if (taskIsDone) 184 { 185 taskInfo.index++; 186 taskInfo.pc = 0; 187 m_Enumerator = null; 188 189 return; 190 } 191 } 192 193 if (m_JobData.Tasks[taskInfo.index].SupportsResumingEnumerator) 194 { 195 taskInfo.pc = PcHelper.GetEnumeratorPC(m_Enumerator); 196 } 197 } 198 catch (TestRunCanceledException) 199 { 200 StopRun(); 201 } 202 catch (AggregateException ex) 203 { 204 MarkJobAsError(); 205 LogError(ex.Message); 206 foreach (var innerException in ex.InnerExceptions) 207 { 208 LogException(innerException); 209 } 210 211 ReportRunFailed("Multiple unexpected errors happened while running tests."); 212 } 213 catch (Exception ex) 214 { 215 MarkJobAsError(); 216 LogException(ex); 217 ReportRunFailed("An unexpected error happened while running tests."); 218 } 219 } 220 } 221 222 public bool CancelRun() 223 { 224 if (m_JobData == null || m_JobData.taskInfoStack.Count == 0 || 225 m_JobData.taskInfoStack.Peek().taskMode == TaskMode.Canceled) 226 { 227 return false; 228 } 229 230 var lastIndex = m_JobData.taskInfoStack.Last().index; 231 m_JobData.taskInfoStack.Clear(); 232 m_JobData.taskInfoStack.Push(new TaskInfo 233 { 234 index = lastIndex, 235 taskMode = TaskMode.Canceled 236 }); 237 m_Enumerator = null; 238 return true; 239 } 240 241 private bool ShouldExecuteInstantly() 242 { 243 if (m_JobData.taskInfoStack.Count == 0) 244 { 245 return false; 246 } 247 248 var taskInfo = m_JobData.taskInfoStack.Peek(); 249 var canRunInstantly = 250 m_JobData.Tasks.Length <= taskInfo.index || m_JobData.Tasks[taskInfo.index].CanRunInstantly; 251 return taskInfo.taskMode != TaskMode.Normal && taskInfo.taskMode != TaskMode.Canceled && canRunInstantly; 252 } 253 254 public bool IsRunningJob() 255 { 256 return m_JobData != null && m_JobData.taskInfoStack != null && m_JobData.taskInfoStack.Count > 0; 257 } 258 259 public TestJobData GetData() 260 { 261 return m_JobData; 262 } 263 264 private void StopRun() 265 { 266 m_JobData.isRunning = false; 267 UnsubscribeCallback(ExecuteCallback); 268 testJobDataHolder.UnregisterRun(this, m_JobData); 269 270 foreach (var task in m_JobData.Tasks) 271 { 272 if (task is IDisposable disposableTask) 273 { 274 try 275 { 276 disposableTask.Dispose(); 277 } 278 catch (Exception e) 279 { 280 Debug.LogException(e); 281 } 282 } 283 } 284 285 if (!m_JobData.executionSettings.runSynchronously) 286 { 287 ReportRunProgress(true); 288 } 289 } 290 291 private void ReportRunProgress(bool runHasFinished) 292 { 293 RunProgressChanged().Invoke(new TestRunProgress 294 { 295 CurrentStageName = m_JobData.runProgress.stageName ?? "", 296 CurrentStepName = m_JobData.runProgress.stepName ?? "", 297 Progress = m_JobData.runProgress.progress, 298 ExecutionSettings = m_JobData.executionSettings, 299 RunGuid = m_JobData.guid ?? "", 300 HasFinished = runHasFinished, 301 }); 302 } 303 304 private void MarkJobAsError() 305 { 306 var currentTaskInfo = m_JobData.taskInfoStack.Peek(); 307 currentTaskInfo.taskMode = TaskMode.Error; 308 currentTaskInfo.index++; 309 currentTaskInfo.pc = 0; 310 m_Enumerator = null; 311 } 312 } 313}