This commit is contained in:
2025-01-17 13:10:42 +01:00
commit 4536213c91
15115 changed files with 1442174 additions and 0 deletions

View File

@@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.TestRunner.TestLaunchers;
using UnityEditor.TestTools.TestRunner.Api;
using UnityEngine;
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
internal class Executer : IExecuter
{
internal IRunData runData = RunData.instance;
private ITestRunnerApi m_TestRunnerApi;
private ISettingsBuilder m_SettingsBuilder;
private Action<string, object[]> m_LogErrorFormat;
private Action<Exception> m_LogException;
private Action<string> m_LogMessage;
private Action<int> m_ExitEditorApplication;
private Func<bool> m_ScriptCompilationFailedCheck;
private Func<bool> m_IsRunActive;
public Executer(ITestRunnerApi testRunnerApi, ISettingsBuilder settingsBuilder, Action<string, object[]> logErrorFormat, Action<Exception> logException, Action<string> logMessage, Action<int> exitEditorApplication, Func<bool> scriptCompilationFailedCheck, Func<bool> isRunActive)
{
m_TestRunnerApi = testRunnerApi;
m_SettingsBuilder = settingsBuilder;
m_LogErrorFormat = logErrorFormat;
m_LogException = logException;
m_LogMessage = logMessage;
m_ExitEditorApplication = exitEditorApplication;
m_ScriptCompilationFailedCheck = scriptCompilationFailedCheck;
m_IsRunActive = isRunActive;
}
public string InitializeAndExecuteRun(string[] commandLineArgs)
{
Api.ExecutionSettings executionSettings;
try
{
executionSettings = m_SettingsBuilder.BuildApiExecutionSettings(commandLineArgs);
if (executionSettings.targetPlatform.HasValue)
RemotePlayerLogController.instance.SetBuildTarget(executionSettings.targetPlatform.Value);
}
catch (SetupException exception)
{
HandleSetupException(exception);
return string.Empty;
}
try
{
// It is important that the message starts with "Running tests for ", otherwise TestCleanConsole will fail.
m_LogMessage($"Running tests for {executionSettings}");
return m_TestRunnerApi.Execute(executionSettings);
}
catch (Exception exception)
{
m_LogException(exception);
ExitApplication(ReturnCodes.RunError, "Exception when starting test run.");
return string.Empty;
}
}
public void ExitIfRunIsCompleted()
{
if (m_IsRunActive())
{
return;
}
var runState = runData.RunState;
var returnCode = s_StateReturnCodes[runState];
var reason = s_StateMessages[runState] ?? runData.RunErrorMessage;
ExitApplication(returnCode, reason);
}
private void ExitApplication(ReturnCodes returnCode, string reason)
{
var returnCodeInt = (int)returnCode;
m_LogMessage($"Test run completed. Exiting with code {returnCodeInt} ({returnCode}). {reason}");
m_ExitEditorApplication(returnCodeInt);
}
public ExecutionSettings BuildExecutionSettings(string[] commandLineArgs)
{
return m_SettingsBuilder.BuildExecutionSettings(commandLineArgs);
}
internal enum ReturnCodes
{
Ok = 0,
Failed = 2,
RunError = 3,
PlatformNotFoundReturnCode = 4
}
public void SetUpCallbacks(ExecutionSettings executionSettings)
{
RemotePlayerLogController.instance.SetLogsDirectory(executionSettings.DeviceLogsDirectory);
var resultSavingCallback = ScriptableObject.CreateInstance<ResultsSavingCallbacks>();
resultSavingCallback.m_ResultFilePath = executionSettings.TestResultsFile;
var logSavingCallback = ScriptableObject.CreateInstance<LogSavingCallbacks>();
TestRunnerApi.RegisterTestCallback(resultSavingCallback);
TestRunnerApi.RegisterTestCallback(logSavingCallback);
TestRunnerApi.RegisterTestCallback(new RunStateCallbacks());
}
public void ExitOnCompileErrors()
{
if (m_ScriptCompilationFailedCheck())
{
var handling = s_ExceptionHandlingMapping.First(h => h.m_ExceptionType == SetupException.ExceptionType.ScriptCompilationFailed);
m_LogErrorFormat(handling.m_Message, new object[0]);
ExitApplication(handling.m_ReturnCode, handling.m_Message);
}
}
private void HandleSetupException(SetupException exception)
{
ExceptionHandling handling = s_ExceptionHandlingMapping.FirstOrDefault(h => h.m_ExceptionType == exception.Type) ?? new ExceptionHandling(exception.Type, "Unknown command line test run error. " + exception.Type, ReturnCodes.RunError);
m_LogErrorFormat(handling.m_Message, exception.Details);
ExitApplication(handling.m_ReturnCode, handling.m_Message);
}
private class ExceptionHandling
{
internal SetupException.ExceptionType m_ExceptionType;
internal string m_Message;
internal ReturnCodes m_ReturnCode;
public ExceptionHandling(SetupException.ExceptionType exceptionType, string message, ReturnCodes returnCode)
{
m_ExceptionType = exceptionType;
m_Message = message;
m_ReturnCode = returnCode;
}
}
private static ExceptionHandling[] s_ExceptionHandlingMapping = {
new ExceptionHandling(SetupException.ExceptionType.ScriptCompilationFailed, "Scripts had compilation errors.", ReturnCodes.RunError),
new ExceptionHandling(SetupException.ExceptionType.PlatformNotFound, "Test platform not found ({0}).", ReturnCodes.PlatformNotFoundReturnCode),
new ExceptionHandling(SetupException.ExceptionType.TestSettingsFileNotFound, "Test settings file not found at {0}.", ReturnCodes.RunError),
new ExceptionHandling(SetupException.ExceptionType.OrderedTestListFileNotFound, "Ordered test list file not found at {0}.", ReturnCodes.RunError)
};
private static IDictionary<TestRunState, string> s_StateMessages = new Dictionary<TestRunState, string>()
{
{TestRunState.NoCallbacksReceived, "No callbacks received."},
{TestRunState.OneOrMoreTestsExecutedWithNoFailures, "Run completed."},
{TestRunState.OneOrMoreTestsExecutedWithOneOrMoreFailed, "One or more tests failed."},
{TestRunState.CompletedJobWithoutAnyTestsExecuted, "No tests were executed."},
{TestRunState.RunError, null}
};
private static IDictionary<TestRunState, ReturnCodes> s_StateReturnCodes = new Dictionary<TestRunState, ReturnCodes>()
{
{TestRunState.NoCallbacksReceived, ReturnCodes.RunError},
{TestRunState.OneOrMoreTestsExecutedWithNoFailures, ReturnCodes.Ok},
{TestRunState.OneOrMoreTestsExecutedWithOneOrMoreFailed, ReturnCodes.Failed},
{TestRunState.CompletedJobWithoutAnyTestsExecuted, ReturnCodes.Ok},
{TestRunState.RunError, ReturnCodes.RunError}
};
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 083c6a3a5426382449369ddc12b691d8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
using System;
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
[Serializable]
internal class ExecutionSettings
{
public string TestResultsFile;
public string DeviceLogsDirectory;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c3a75354f6ceac94ca15ca9d96593290
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
internal interface IExecuter
{
string InitializeAndExecuteRun(string[] commandLineArgs);
void ExitIfRunIsCompleted();
ExecutionSettings BuildExecutionSettings(string[] commandLineArgs);
void SetUpCallbacks(ExecutionSettings executionSettings);
void ExitOnCompileErrors();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3297738ef11b478a8b7ee802953ae768
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
internal interface IRunData
{
bool IsRunning { get; set; }
ExecutionSettings ExecutionSettings { get; set; }
string RunId { get; set; }
TestRunState RunState { get; set; }
string RunErrorMessage { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d56e34a052c646e39a3317c404303e5f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
using System;
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
internal interface ISettingsBuilder
{
Api.ExecutionSettings BuildApiExecutionSettings(string[] commandLineArgs);
ExecutionSettings BuildExecutionSettings(string[] commandLineArgs);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8a13cbeb2099aca47bb456f49845f86c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
using System;
using UnityEditor.TestRunner.TestLaunchers;
using UnityEditor.TestTools.TestRunner.Api;
using UnityEngine;
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
[Serializable]
internal class LogSavingCallbacks : ScriptableObject, ICallbacks
{
public void RunStarted(ITestAdaptor testsToRun)
{
RemotePlayerLogController.instance.StartLogWriters();
}
public virtual void RunFinished(ITestResultAdaptor testResults)
{
RemotePlayerLogController.instance.StopLogWriters();
}
public void TestStarted(ITestAdaptor test)
{
}
public void TestFinished(ITestResultAdaptor result)
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8d20eedbe40f0ce41a4c4f633f225de8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor.DeploymentTargets;
using UnityEditor.Utils;
using UnityEngine;
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
internal class LogWriter : IDisposable
{
private string m_LogsDirectory;
private string m_DeviceID;
private Dictionary<string, StreamWriter> m_LogStreams;
private DeploymentTargetLogger m_Logger;
internal LogWriter(string logsDirectory, string deviceID, DeploymentTargetLogger logger)
{
m_LogStreams = new Dictionary<string, StreamWriter>();
m_Logger = logger;
m_LogsDirectory = logsDirectory;
m_DeviceID = deviceID;
logger.logMessage += WriteLogToFile;
}
private void WriteLogToFile(string id, string logLine)
{
StreamWriter logStream;
var streamExists = m_LogStreams.TryGetValue(id, out logStream);
if (!streamExists)
{
var filePath = GetLogFilePath(m_LogsDirectory, m_DeviceID, id);
logStream = CreateLogFile(filePath);
m_LogStreams.Add(id, logStream);
}
try
{
if (logLine != null)
logStream.WriteLine(logLine);
}
catch (Exception ex)
{
Debug.LogError($"Writing {id} log failed.");
Debug.LogException(ex);
}
}
public void Stop()
{
m_Logger.Stop();
foreach (var logStream in m_LogStreams)
{
logStream.Value.Close();
}
}
public void Dispose()
{
Stop();
}
private StreamWriter CreateLogFile(string path)
{
Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, "Creating {0} device log: {1}", m_DeviceID, path);
StreamWriter streamWriter = null;
try
{
if (!Directory.Exists(path))
Directory.CreateDirectory(Path.GetDirectoryName(path));
streamWriter = File.CreateText(path);
}
catch (Exception ex)
{
Debug.LogError($"Creating device log {path} file failed.");
Debug.LogException(ex);
}
return streamWriter;
}
private string GetLogFilePath(string lgosDirectory, string deviceID, string logID)
{
var fileName = "Device-" + deviceID + "-" + logID + ".txt";
fileName = string.Join("_", fileName.Split(Path.GetInvalidFileNameChars()));
return Paths.Combine(lgosDirectory, fileName);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 05778dd1de4433d418793b6f3d3c18cf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
using System;
using System.IO;
using UnityEditor.TestTools.TestRunner.Api;
using UnityEditor.Utils;
using UnityEngine;
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
[Serializable]
internal class ResultsSavingCallbacks : ScriptableObject, ICallbacks
{
[SerializeField]
public string m_ResultFilePath;
public ResultsSavingCallbacks()
{
m_ResultFilePath = GetDefaultResultFilePath();
}
public void RunStarted(ITestAdaptor testsToRun)
{
}
public virtual void RunFinished(ITestResultAdaptor testResults)
{
if (string.IsNullOrEmpty(m_ResultFilePath))
{
m_ResultFilePath = GetDefaultResultFilePath();
}
TestRunnerApi.SaveResultToFile(testResults, m_ResultFilePath);
}
public void TestStarted(ITestAdaptor test)
{
}
public void TestFinished(ITestResultAdaptor result)
{
}
private static string GetDefaultResultFilePath()
{
var fileName = "TestResults-" + DateTime.Now.Ticks + ".xml";
var projectPath = Directory.GetCurrentDirectory();
return Paths.Combine(projectPath, fileName);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ef563c5a6ecf64d4193dc144cb7d472a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
using System;
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
internal class RunData : ScriptableSingleton<RunData>, IRunData
{
private bool isRunning;
private ExecutionSettings executionSettings;
private string runId;
private TestRunState runState;
private string runErrorMessage;
public bool IsRunning
{
get { return isRunning; }
set { isRunning = value; }
}
public ExecutionSettings ExecutionSettings
{
get { return executionSettings; }
set { executionSettings = value; }
}
public string RunId
{
get { return runId; }
set { runId = value; }
}
public TestRunState RunState
{
get { return runState; }
set { runState = value; }
}
public string RunErrorMessage
{
get { return runErrorMessage; }
set { runErrorMessage = value; }
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3f8c1075884df0249b80e23a0598f9c1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
using System;
using UnityEditor.TestTools.TestRunner.Api;
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
internal class RunSettings : ITestRunSettings
{
private ITestSettings m_TestSettings;
public RunSettings(ITestSettings testSettings)
{
m_TestSettings = testSettings;
}
public void Apply()
{
if (m_TestSettings != null)
{
m_TestSettings.SetupProjectParameters();
}
}
public void Dispose()
{
if (m_TestSettings != null)
{
m_TestSettings.Dispose();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 59d3f5586b341a74c84c8f72144a4568
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,51 @@
using System;
using UnityEditor.TestTools.TestRunner.Api;
using UnityEngine;
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
internal class RunStateCallbacks : IErrorCallbacks
{
internal IRunData runData = RunData.instance;
internal static bool preventExit;
public void RunFinished(ITestResultAdaptor testResults)
{
if (preventExit)
{
return;
}
if (runData.RunState == TestRunState.NoCallbacksReceived)
{
runData.RunState = TestRunState.CompletedJobWithoutAnyTestsExecuted;
}
}
public void TestStarted(ITestAdaptor test)
{
if (!test.IsSuite && runData.RunState == TestRunState.NoCallbacksReceived)
{
runData.RunState = TestRunState.OneOrMoreTestsExecutedWithNoFailures;
}
}
public void TestFinished(ITestResultAdaptor result)
{
if (!result.Test.IsSuite && (result.TestStatus == TestStatus.Failed || result.TestStatus == TestStatus.Inconclusive))
{
runData.RunState = TestRunState.OneOrMoreTestsExecutedWithOneOrMoreFailed;
}
}
public void RunStarted(ITestAdaptor testsToRun)
{
}
public void OnError(string message)
{
runData.RunState = TestRunState.RunError;
runData.RunErrorMessage = message;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 43db38ca7188900429bf8be95ebbe197
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,179 @@
using System;
using System.IO;
using UnityEditor.TestRunner.CommandLineParser;
using UnityEditor.TestTools.TestRunner.Api;
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
internal class SettingsBuilder : ISettingsBuilder
{
private ITestSettingsDeserializer m_TestSettingsDeserializer;
private Action<string> m_LogAction;
private Action<string> m_LogWarningAction;
internal Func<string, bool> fileExistsCheck = File.Exists;
private Func<bool> m_ScriptCompilationFailedCheck;
internal Func<string, string[]> readAllLines = filePath => File.ReadAllText(filePath).Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
public SettingsBuilder(ITestSettingsDeserializer testSettingsDeserializer, Action<string> logAction, Action<string> logWarningAction, Func<bool> scriptCompilationFailedCheck)
{
m_LogAction = logAction;
m_LogWarningAction = logWarningAction;
m_ScriptCompilationFailedCheck = scriptCompilationFailedCheck;
m_TestSettingsDeserializer = testSettingsDeserializer;
}
public Api.ExecutionSettings BuildApiExecutionSettings(string[] commandLineArgs)
{
var quit = false;
string testPlatform = TestMode.EditMode.ToString();
string[] testFilters = null;
string[] testCategories = null;
string testSettingsFilePath = null;
int? playerHeartbeatTimeout = null;
bool runSynchronously = false;
string[] testAssemblyNames = null;
string buildPlayerPath = string.Empty;
string orderedTestListFilePath = null;
int retry = 0;
int repeat = 0;
int randomOrderSeed = 0;
var optionSet = new CommandLineOptionSet(
new CommandLineOption("quit", () => { quit = true; }),
new CommandLineOption("testPlatform", platform => { testPlatform = platform; }),
new CommandLineOption("editorTestsFilter", filters => { testFilters = filters; }),
new CommandLineOption("testFilter", filters => { testFilters = filters; }),
new CommandLineOption("editorTestsCategories", catagories => { testCategories = catagories; }),
new CommandLineOption("testCategory", catagories => { testCategories = catagories; }),
new CommandLineOption("testSettingsFile", settingsFilePath => { testSettingsFilePath = settingsFilePath; }),
new CommandLineOption("playerHeartbeatTimeout", timeout => { playerHeartbeatTimeout = int.Parse(timeout); }),
new CommandLineOption("runSynchronously", () => { runSynchronously = true; }),
new CommandLineOption("assemblyNames", assemblyNames => { testAssemblyNames = assemblyNames; }),
new CommandLineOption("buildPlayerPath", buildPath => { buildPlayerPath = buildPath; }),
new CommandLineOption("orderedTestListFile", filePath => { orderedTestListFilePath = filePath; }),
new CommandLineOption("randomOrderSeed", seed => { randomOrderSeed = int.Parse(seed);}),
new CommandLineOption("retry", n => { retry = int.Parse(n); }),
new CommandLineOption("repeat", n => { repeat = int.Parse(n); })
);
optionSet.Parse(commandLineArgs);
DisplayQuitWarningIfQuitIsGiven(quit);
CheckForScriptCompilationErrors();
var testSettings = GetTestSettings(testSettingsFilePath);
var filter = new Filter
{
testMode = testPlatform.ToLower() == "editmode" ? TestMode.EditMode : TestMode.PlayMode,
groupNames = testFilters,
categoryNames = testCategories,
assemblyNames = testAssemblyNames
};
var settings = new Api.ExecutionSettings
{
filters = new []{ filter },
overloadTestRunSettings = new RunSettings(testSettings),
ignoreTests = testSettings?.ignoreTests,
featureFlags = testSettings?.featureFlags,
targetPlatform = GetBuildTarget(testPlatform),
runSynchronously = runSynchronously,
playerSavePath = buildPlayerPath,
orderedTestNames = GetOrderedTestList(orderedTestListFilePath),
repeatCount = repeat,
retryCount = retry,
randomOrderSeed = randomOrderSeed
};
if (playerHeartbeatTimeout != null)
{
settings.playerHeartbeatTimeout = playerHeartbeatTimeout.Value;
}
return settings;
}
public ExecutionSettings BuildExecutionSettings(string[] commandLineArgs)
{
string resultFilePath = null;
string deviceLogsDirectory = null;
var optionSet = new CommandLineOptionSet(
new CommandLineOption("editorTestsResultFile", filePath => { resultFilePath = filePath; }),
new CommandLineOption("testResults", filePath => { resultFilePath = filePath; }),
new CommandLineOption("deviceLogs", dirPath => { deviceLogsDirectory = dirPath; })
);
optionSet.Parse(commandLineArgs);
return new ExecutionSettings
{
TestResultsFile = resultFilePath,
DeviceLogsDirectory = deviceLogsDirectory
};
}
private void DisplayQuitWarningIfQuitIsGiven(bool quitIsGiven)
{
if (quitIsGiven)
{
m_LogWarningAction("Running tests from command line arguments will not work when \"quit\" is specified.");
}
}
private void CheckForScriptCompilationErrors()
{
if (m_ScriptCompilationFailedCheck())
{
throw new SetupException(SetupException.ExceptionType.ScriptCompilationFailed);
}
}
private ITestSettings GetTestSettings(string testSettingsFilePath)
{
ITestSettings testSettings = null;
if (!string.IsNullOrEmpty(testSettingsFilePath))
{
if (!fileExistsCheck(testSettingsFilePath))
{
throw new SetupException(SetupException.ExceptionType.TestSettingsFileNotFound, testSettingsFilePath);
}
testSettings = m_TestSettingsDeserializer.GetSettingsFromJsonFile(testSettingsFilePath);
}
return testSettings;
}
private string[] GetOrderedTestList(string orderedTestListFilePath)
{
if (!string.IsNullOrEmpty(orderedTestListFilePath))
{
if (!fileExistsCheck(orderedTestListFilePath))
{
throw new SetupException(SetupException.ExceptionType.OrderedTestListFileNotFound, orderedTestListFilePath);
}
return readAllLines(orderedTestListFilePath);
}
return null;
}
private static BuildTarget? GetBuildTarget(string testPlatform)
{
var testPlatformLower = testPlatform.ToLower();
if (testPlatformLower == "editmode" || testPlatformLower == "playmode" || testPlatformLower == "editor" ||
string.IsNullOrEmpty(testPlatformLower))
{
return null;
}
try
{
return (BuildTarget)Enum.Parse(typeof(BuildTarget), testPlatform, true);
}
catch (ArgumentException)
{
throw new SetupException(SetupException.ExceptionType.PlatformNotFound, testPlatform);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b7468a027a77337478e133b40b42b4f9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
using System;
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
internal class SetupException : Exception
{
public ExceptionType Type { get; }
public object[] Details { get; }
public SetupException(ExceptionType type, params object[] details)
{
Type = type;
Details = details;
}
public enum ExceptionType
{
ScriptCompilationFailed,
PlatformNotFound,
TestSettingsFileNotFound,
OrderedTestListFileNotFound,
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 63572993f2104574099a48392460b211
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,96 @@
using System;
using UnityEditor.TestRunner.CommandLineParser;
using UnityEditor.TestTools.TestRunner.Api;
using UnityEngine;
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
internal class TestStarter
{
[InitializeOnLoadMethod]
internal static void Initialize()
{
new TestStarter().Init();
}
internal Action<EditorApplication.CallbackFunction> registerEditorUpdateCallback = action =>
{
EditorApplication.update += action;
};
internal Action<EditorApplication.CallbackFunction> unregisterEditorUpdateCallback = action =>
{
EditorApplication.update -= action;
};
internal Func<bool> isCompiling = () => EditorApplication.isCompiling;
internal IRunData runData = RunData.instance;
internal Func<string[]> GetCommandLineArgs = Environment.GetCommandLineArgs;
internal void Init()
{
if (!ShouldRunTests())
{
return;
}
if (isCompiling())
{
return;
}
executer.ExitOnCompileErrors();
if (runData.IsRunning)
{
executer.SetUpCallbacks(runData.ExecutionSettings);
registerEditorUpdateCallback(executer.ExitIfRunIsCompleted);
return;
}
// Execute the test run on the next editor update to allow other framework components
// (the TestJobDataHolder.ResumeRunningJobs method in particular) to register themselves
// or modify the test run environment using InitializeOnLoad and InitializeOnLoadMethod calls
registerEditorUpdateCallback(InitializeAndExecuteRun);
}
internal void InitializeAndExecuteRun()
{
unregisterEditorUpdateCallback(InitializeAndExecuteRun);
runData.IsRunning = true;
var commandLineArgs = GetCommandLineArgs();
runData.ExecutionSettings = executer.BuildExecutionSettings(commandLineArgs);
executer.SetUpCallbacks(runData.ExecutionSettings);
runData.RunState = default;
runData.RunId = executer.InitializeAndExecuteRun(commandLineArgs);
registerEditorUpdateCallback(executer.ExitIfRunIsCompleted);
}
private bool ShouldRunTests()
{
var shouldRunTests = false;
var optionSet = new CommandLineOptionSet(
new CommandLineOption("runTests", () => { shouldRunTests = true; }),
new CommandLineOption("runEditorTests", () => { shouldRunTests = true; })
);
optionSet.Parse(GetCommandLineArgs());
return shouldRunTests;
}
internal IExecuter m_Executer;
private IExecuter executer
{
get
{
if (m_Executer == null)
{
Func<bool> compilationCheck = () => EditorUtility.scriptCompilationFailed;
Action<string> actionLogger = msg => { Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, msg); };
var apiSettingsBuilder = new SettingsBuilder(new TestSettingsDeserializer(() => new TestSettings()), actionLogger, Debug.LogWarning, compilationCheck);
m_Executer = new Executer(ScriptableObject.CreateInstance<TestRunnerApi>(), apiSettingsBuilder, Debug.LogErrorFormat, Debug.LogException, Debug.Log, EditorApplication.Exit, compilationCheck, TestRunnerApi.IsRunActive);
}
return m_Executer;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4d616d1a494edd144b262cf6cd5e5fda
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
{
internal enum TestRunState
{
/// <summary>
/// When the test run has started, but no callbacks from the test runner api have yet been received.
/// </summary>
NoCallbacksReceived,
/// <summary>
/// When at least one test started event have been fired for a test node.
/// </summary>
OneOrMoreTestsExecutedWithNoFailures,
/// <summary>
/// When at least one test finished event have been fired for a test node and the status is failed.
/// </summary>
OneOrMoreTestsExecutedWithOneOrMoreFailed,
/// <summary>
/// When the test job in the test runner api have completed, but no test started events for test nodes has happened. E.g. if there are no valid tests in the project.
/// </summary>
CompletedJobWithoutAnyTestsExecuted,
/// <summary>
/// When the test runner api has raised an error during the run.
/// </summary>
RunError
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 317f06562c344e22991ba4117cf7b71c
timeCreated: 1582809279