UnityGame/Library/PackageCache/com.unity.test-framework/UnityEditor.TestRunner/TestRun/TestJobRunner.cs
2024-10-27 10:53:47 +03:00

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