No Description
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.

TestJobRunner.cs 11KB

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